深入理解预处理

1.预定义符号

C语言设置了⼀些预定义符号,可以直接使用,预定义符号也是在预处理期间处理的。

__FILE__
//进⾏编译的源⽂件
__LINE__
//⽂件当前的⾏号
__DATE__
//⽂件被编译的⽇期
__TIME__
//⽂件被编译的时间
__STDC__
//如果编译器遵循ANSI C,其值为1,否则未定义
#include<stdio.h>
int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	printf("%d\n", __STDC__);

	return 0;
}

例如在VS中打印以上预定义符号时就会出现以下报错

这就说明VS没有完全遵循ANSI C 

将_STDC_去除就可以打印出以下信息

2. #define 定义常量 

基本语法:

#define name  stuff

例如以下代码

在预处理阶段就会将代码内#define定义的内容替换为以下形式

再比如说在一些计算机语言中是存在switch语句的,有一些在使用switch时不需要在case语句后加上break,但例如我们学习的c语言就需要在case语句后加上break才能实现switch语句,这时就可以用到#define来在写case语句的时候⾃动把 break写上

#define CASE break;case

例如以下代码就是使用#define后的switch语句 

#define CASE break;case
#include<stdio.h>
int main()
{
	int n = 0;
	scanf("%d", &n);
	switch (n)
	{
	    case 1:
		CASE 2:
	    CASE 3:
			//……
	}

	return 0;
}

在预处理之后就会变为以下形式

#include<stdio.h>
int main()
{
	int n = 0;
	scanf("%d", &n);
	switch (n)
	{
	    case 1:
		break; case 2:
		break; case  3:
			//……
	}
	return 0;
}

如果在使用#define定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加⼀个反斜杠(续行符),这样就可以使得内容表示在同一行代码上。

#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ ,   \
__DATE__,__TIME__ )

思考:在define定义标识符的时候,要不要在最后加上 ; ? 

在一些情况下加上代码不会有什么太大的问题,但在一些情况下就会使代码出现错误
例如:

#define M 100;
#include<stdio.h>
int main()
{
    int a=M;
	printf("%d", a);
	return 0;
}

 在以上代码程序就会出现以下报错

这时因为在预处理之后代码就变为以下形式

 

通过以上代码就可以了解到使用#define后不要在在最后加上 ;

3. #define定义宏 

#define 机制包括了⼀个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)

⼀般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分⼆者。
那我们平时的一个习惯是:

把宏名全部大写
函数名不要全部大写

下面是宏的申明方式:

#define name( parament-list ) stuff

其中的 parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。 

#define SQURE(n) n*n
#include<stdio.h>
int main()
{
	int x= 0;
	scanf("%d", &x);
    int ret=SQURE(x);
	printf("%d", ret);

	return 0;
}

在以上代码在预处理之后代码就变为以下形式

那么你认为在以上我们写的宏存在什么问题吗?
如果你认为没有问题的话再看以下代码

#define SQURE(n) n*n
#include<stdio.h>
int main()
{
    int ret=SQURE(5+1);
	printf("%d", ret);

	return 0;
}

这时再看代码在预处理之后SQURE就变为5+1*5+1结果就为11,当问题是我们想计算的是(5+1)的平方结果为36,因此以上代码就无法满足预期

在此就有一个使用#define时要注意的点:在使用宏时不要吝啬括号,所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
把以上代码改为以下形式就可以实现(5+1)的平方

#define SQURE(n) ((n)*(n))
#include<stdio.h>
int main()
{
    int ret=SQURE(5+1);
	printf("%d", ret);

	return 0;
}

再比如在以下代码中如果在宏中不加上括号就会直接将ret=10*5+5

#define DOUBLE(n) ((n)+(n))
#include<stdio.h>
int main()
{
    int ret=10*DOUBLE(5);
	printf("%d", ret);

	return 0;
}

4. 带有副作用的宏参数

当宏参数在宏的定义中出现超过⼀次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

例如以下#define MAX用于判断两个中大的值:

#define MAX(x,y) ((x>y)?(x):(y))
#include<stdio.h>
int main()
{
	int a= 20;
	int b = 10;
	printf("%d", MAX(a,b));

	return 0;
}

当像以上代码给宏传a和b时,代码能输出我们想要的结果

但如果像以下这样呢?

#define MAX(x,y) ((x>y)?(x):(y))
#include<stdio.h>
int main()
{
	int a= 20;
	int b = 10;
	printf("%d", MAX(a++,b++));

	return 0;
}

这时就是将a++和b++传给了定义的宏,在预处理之后代码就变为以下形式

#include<stdio.h>
int main()
{
	int a= 20;
	int b = 10;
	printf("%d", ((a++>b++)?(a++):(b++)));

	return 0;
}

输出结果就变为:

 因此当在使用宏时,最好不要使得参数带有++或者--,否则可能导致不可预测的后果

5. 宏替换的规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重上述处理过程

例如以下代码:

#define M 100
#define MAX(x,y) ((x>y)?(x):(y))
#include<stdio.h>
int main()
{
	int b = 10;
	printf("%d", MAX(M, b));

	return 0;
}

在调用宏MAX时先将宏内由#define定义的参数替换,这时MAX的参数就变为以下形式

MAX(100,b);

再将调用的宏替换为以下形式 

((100>b)?(100):(b))

 注意:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

例: 

#define M 100
#define MAX(x,y) ((x>y)?(x):(y))
#include<stdio.h>
int main()
{
	int b = 10;
	printf("MAX(M, b)");

	return 0;
}

在以上代码中最终待打印的是字符串MAX(M,100),因此字符串内的常量就不会被搜索,最终打印结果就为MAX(M,100)

6. 宏与函数的对比

例如当我们要输出两个数中大的那个数

#define MAX(x,y) ((x>y)?(x):(y))
#include<stdio.h>
int max(int x, int y)
{
	return x > y ? x : y;
}

int main()
{
	int a = 20;
	int b = 10;
	int M = MAX(a, b);//使用宏
	printf("%d\n", M);
	int m = max(a, b);//使用函数
	printf("%d\n", m);

	return 0;
}

 使用两种方式都可以实现要求,那么在此那种方式更好呢?
在此执行的是一个简单的运算,使用宏更合适。宏通常被应用于执行简单的运算。

那为什么不用函数来完成这个任务?
原因有二:

1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于 > 来比较的类型。宏的参数是类型无关的。

那么函数相比宏又有哪些优点呢?
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2. 宏是没法调试的。
3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。


宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

#define MALLOC(num, type)\
(type*)malloc(num*sizeof(type))
...
//使⽤
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 sizeof(int));

属性#define定义宏函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的程序外,程序的长度都会大幅度增长函数代码只出现于一个地方;每次使用函数时,都会调用那个地方的同一份代码
执行速度更快存在函数调用和返回的额外开销,使用相对慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的结果函数参数只在函数调用的时候求值一次的结果值传递给函数。表达式的求值结果更容易预测
带有副作用的参数参数可能被替换到宏体中的多个位置,如果宏的参数被多次计算,带有副作用的参数求值可能会产生不可预料的结果函数传参只在传参的时候求值一次,结果更容易控制
参数类型宏的参数与类型无关,只要对参数的操作是合法的,他就可以适用于任何参数类型宏的参数与类型无关,只要对参数的操作是合法的,他就可以适用于任何参数类型
调试宏是不方便调试的函数是可以逐语句调试的
递归宏是不能递归的函数是可以递归的

 7. #和##

 7.1 #运算符

#运算符将宏的一个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。
#运算符所执行的操作可以理解为”字符串化“。

例如以下代码

#include<stdio.h>
int main()
{
	int a = 10;
	printf("The value of a is %d\n", a);
	int b = 20;
	printf("The value of b is %d\n", b);
	float c = 5.5;
	printf("The value of c is %f\n", c);

	return 0;
}

在这段代码中我们要打印的内容就只有参数和值不同,用以上这种写法就会显得很繁琐,那么有什么简洁的方法呢?
首先要知道的是在c语言中两个字符串都用双引号引起来时,两个字符串就可和为一个字符串

printf("hello"" world");

像以上这样就可以直接打印出hello world

了解了以上的知识点后可以试着用宏来实现简洁的写法

#define PRINTF(format,n) printf("The value of n is "format"\n", n)
#include<stdio.h>

int main()
{
	int a = 10;
	PRINTF("%d", a);
	int b = 20;
	PRINTF("%d", b);
	float c = 5.5;
	PRINTF("%f", c);

	return 0;
}

当以上代码存在一个问题是偶打印出的of后都是n,无法根据实际改变
 

要改变以上情况就需要用到#,将宏的一个参数转换为字符串字面量,这样就可以实现我们想要的效果了

#define PRINTF(format,n) printf("The value of "#n" is "format"\n", n)
#include<stdio.h>
int main()
{
	int a = 10;
	PRINTF("%d", a);
	PRINTF("%d", b);
	float c = 5.5;
	PRINTF("%f", c);

	return 0;
}

 

 7.2##运算符

## 可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。 ## 被称
为记号粘合

这样的连接必须产生⼀个合法的标识符。否则其结果就是未定义的。

例如当要写两个函数分别实现求整型和浮点型中两个数中较大值
在此就可以用宏来实现函数的模板

#define DENERIC_MAX(type)        \
type type##_max(type x,type y)   \
{                                \
 return x>y?x:y;                 \
}

再使用模板就可以定义不同类型的函数

#define DENERIC_MAX(type)        \
type type##_max(type x,type y)   \
{                                \
 return x>y?x:y;                 \
}
#include<stdio.h>

DENERIC_MAX(int)
DENERIC_MAX(float)

int main()
{
	printf("%d\n", int_max(4, 5));
	printf("%f\n", float_max(5.5, 4.5));
	return 0;
}

 8. #undef

这条指令用于移除一个宏定义

例如定义一个#define M 100

#define M 100
#include<stdio.h>
int main()
{
 #undef M
	printf("%d", M);
	return 0;
}

运行以上代码就会出现报错,原因是#define M 100已经被移除
 

9. 命令行定义 

许多C 的编译器提供了⼀种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出⼀个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外⼀个机器内存大些,我们需要一个数组能够大些。)

#include <stdio.h>
int main()
{
int array [ARRAY_SIZE];
int i = 0;
for(i = 0; i< ARRAY_SIZE; i ++)
{
array[i] = i;
}
for(i = 0; i< ARRAY_SIZE; i ++)
{
printf("%d " ,array[i]);
}
printf("\n" );
return 0;
}

编译指令 :

//linux 环境演⽰
gcc -D ARRAY_SIZE=10 programe.c

10. 条件编译 

条件编译就是满足条件时参与编译,不满足就不参与编译

 

比如说:
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

例如以下代码我们使用一个printf语句来输出程序错误时的信息,这时就可以用到条件编译

#include <stdio.h>
#define __DEBUG__
int main()
{
int i = 0;
int arr[10] = {0};
for(i=0; i<10; i++)
{
arr[i] = i;
#ifdef __DEBUG__
printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
}
return 0;
}

那就来了解一些常用的条件编译指令

1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。

#if后常量表达式结果为真时#if到#endif内的代码内容就保留,否则就被消除 

2.多个分⽀的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol

 #if defined(symbol)和#ifdef symbol的功能是一样的,symbol定义了就参与编译,未定义就不参与

#if !defined(symbol)和#ifndef symbol的功能是一样的,symbol定义了就不参与编译,未定义就参与

4.嵌套指令
#if defined(OS_UNIX)
    #ifdef OPTION1
       unix_version_option1();
    #endif
    #ifdef OPTION2
       unix_version_option2();
    #endif
#elif defined(OS_MSDOS)
    #ifdef OPTION2
      msdos_version_option2();
    #endif
#endif

嵌套指令在头文件中广泛的使用 

11. 头文件的包含 

11.1 头文件被包含的方式:

11.1.1 本地文件包含

1 #include "filename"

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件如果找不到就提示编译错误。 

Linux环境的标准头文件的路径:

/usr/include

VS环境的标准头文件的路径:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//这是VS2013的默认路径

 11.1.2 库文件包含

1 #include <filename.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。 

这样是不是可以说,对于库⽂件也可以使用 “” 的形式包含?
答案是肯定的,可以,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

12.2 嵌套文件包含

我们已经知道, #include 指令可以使另外⼀个文件被编译。就像它实际出现于 #include 指令的
地方⼀样。

test.c

#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{
return 0;
}

test.h

void test();
struct Stu
{
int id;
char name[20];
};

如果直接这样写,test.c⽂件中将test.h包含5次,那么test.h⽂件的内容将会被拷贝5份在test.c中。
如果test.h 文件比较大,这样预处理后代码量会剧增。如果⼯程⽐较大,有公共使用的头件,被⼤家都能使用,⼜不做任何的处理,那么后果真的不堪设想。
如何解决头文件被重复引入的问题?答案:条件编译

#ifndef __TEST_H__
#define __TEST_H__
void test();
struct Stu
{
int id;
char name[20];
};
#endif

在此就可以让test.h在第一次时#define __TEST_H__之后再调用test.h就不会再将内容在test.c内展开

#pragma once

在此使用#pragma once也可以实现以上同样的效果,这样就可以避免头文件的重复引入。

13. 其他预处理指令

#error
#pragma
#line
...

#pragma pack()在结构体部分介绍过

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/725561.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Python/Pytorch 】-- 滑动窗口算法

文章目录 文章目录 00 写在前面01 基于Python版本的滑动窗口代码02 算法效果 00 写在前面 写这个算法原因是&#xff1a;训练了一个时序网络&#xff0c;该网络模型的时序维度为32&#xff0c;而测试数据的时序维度为90。因此需要采用滑动窗口的方法&#xff0c;生成一系列32…

LLM大语言模型应用方案之RAG检索增强生成的实现步骤。

0.我理解的RAG 什么是RAG&#xff1f; RAG的全称是“检索增强生成模型”&#xff08;Retrieval-Augmented Generation&#xff09;。这是一种特别聪明的大语言模型。 RAG是怎么工作的呢&#xff1f; 1.检索&#xff1a;当你问RAG一个问题时&#xff0c;它会先去“图书…

前端 JS 经典:数字变化动画

1. 需求 给你一个数字&#xff0c;当这个数字变化时&#xff0c;有一个动画的过渡效果。 2. 思路 首先我们要知道两个数字变化需要多少秒&#xff0c;然后变化的范围&#xff0c;算出变化的速度。记住开始变化的时间&#xff0c;然后通过 requestAnimationFrame 函数&#x…

深入理解Qt状态机的应用(二)

前文《深入理解Qt状态机的应用&#xff08;一&#xff09;》介绍了状态机的理论知识以及简单的状态机示例。在实际应用场景中&#xff0c;状态机往往会比较复杂&#xff1b;本文将详细介绍分组状态、历史状态、并行状态以及其他技术。 通过分组状态共享转换 还是以交通信号灯…

【计算机毕业设计】211校园约拍微信小程序

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

进入docker容器内部操作mysql数据库

文章目录 1、查询docker容器2、进入mysql容器内部3、连接mysql数据库4、查询mysql所有的数据库5、使用某个数据库6、展示数据库中所有的表7、查询某张表8、断开mysql9、退出mysql容器 1、查询docker容器 [rootlocalhost ~]# docker ps CONTAINER ID IMAGE …

java.lang.ClassNotFoundException: javafx.util.Pair的问题解决与原因详解

先说解决办法: 1、引入依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.12</version> </dependency>2、更换代码依赖地址&#xff1a; 原来依赖地址&#xff1a; import j…

YOLOv10改进 | 注意力篇 | YOLOv10引入EMAttention(EMA)注意力

1. EMA介绍 1.1 摘要:在各种计算机视觉任务中说明了通道或空间注意机制在产生更可辨别的特征表示方面的显着有效性。 然而,通过通道降维来建模跨通道关系可能会给提取深度视觉表示带来副作用。 本文提出了一种新型高效的多尺度注意力(EMA)模块。 着眼于保留每个通道的信息…

springMVC的bug

写SpringMVC时&#xff0c;配置视图解析器路径中少写了个“/”导致url拼接错误&#xff0c;无法返回视图

机器学习参数寻优:方法、实例与分析

机器学习参数寻优:方法、实例与分析 机器学习模型的性能很大程度上依赖于其参数的选择。参数寻优(Hyperparameter Tuning)是提升模型表现的关键步骤之一。本文将详细介绍主流的参数寻优方法,包括网格搜索(Grid Search)、随机搜索(Random Search)、贝叶斯优化(Bayesia…

开发基于Java语言的SaaS(Software-as-a-Service,软件即服务)模式的HIS系统详解 HIS系统源码 支持二开

开发基于Java语言的SaaS&#xff08;Software-as-a-Service&#xff0c;软件即服务&#xff09;模式的HIS系统详解 HIS系统源码 支持二开 开发基于Java语言的SaaS&#xff08;Software-as-a-Service&#xff0c;软件即服务&#xff09;模式的HIS&#xff08;Hospital Informat…

SpringCloud Alibaba Sentinel 流量控制之流控模式实践总结

官网文档&#xff1a;https://sentinelguard.io/zh-cn/docs/flow-control.html wiki地址&#xff1a;https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6 本文版本&#xff1a;spring-cloud-starter-alibaba&#xff1a;2.2.0.RELEASE 如下图所…

STM32单片机I2C通信详解

文章目录 1. I2C通信概述 2. 硬件电路 3. I2C时序基本单元 4. I2C时序 4.1 指定地址写 4.2 当前地址读 4.3 指定地址读 5. I2C外设 6. I2C框图 7. I2C基本结构 8. 主机发送 9. 主机接收 10. 软件和硬件波形对比 11. 代码示例 1. I2C通信概述 I2C(Inter-Integrat…

arm-linux-strip 指令的作用

指令作用 arm-linux-strip 是一个用于从目标文件&#xff08;如可执行文件或对象文件&#xff09;中移除符号信息的工具。这些符号信息&#xff08;如函数名、变量名等&#xff09;在开发过程中很有用&#xff0c;因为它们允许调试器&#xff08;如 GDB&#xff09;确定内存地址…

【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【11】ElasticSearch

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【11】ElasticSearch 简介基本概念ElasticSearch概念-倒排索引安装基本命令ik 分词器SpringBoot整合测试存储数据&#xff1a;测试复杂检索同步与异步调用 参考 简介 Elasticsearch 是一…

华为云与AWS负载均衡服务深度对比:性能、成本与可用性

随着云计算的迅速发展&#xff0c;企业对于云服务提供商的选择变得越来越关键。在选择云服务提供商时&#xff0c;负载均衡服务是企业关注的重点之一。我们九河云将深入比较两大知名云服务提供商华为云和AWS的负载均衡服务&#xff0c;从性能、成本和可用性等方面进行对比。 AW…

数据库并发控制技术

1.数据库中为什么要采用并发控制&#xff1f;并发控制技术能保证事务的哪些特性&#xff1f; 因为多个事务的并发操作会对数据库产生影响&#xff0c;当多个事务同时访问一个数据时就会互相干扰。并发控制技术能保证事务的一致性&#xff0c;隔离性。一致性是指事务要么全部运…

ARM服务器虚拟化手机,云手机推流应用案例

大家都知道&#xff0c;ARM 服务器虚拟化手机和云手机推流技术可算是热门话题&#xff0c;不止是企业&#xff0c;个人卖家也会通过云手机推流来获得更多的客源&#xff0c;实现经济自由&#xff0c;但是针对云手机的推流&#xff0c;很多人还是不知道有哪些应用场景~我们可以展…

三十分钟学会RabbitMQ

1、初识MQ 1.1 MQ是什么&#xff1f; MQ(message queue)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO先入先出&#xff0c;只不过队列中存放的内容是message而已&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游传递消息。在互联网架构中…

SpringCloud Alibaba Sentinel基础入门与安装

GitHub地址&#xff1a;https://github.com/alibaba/Sentinel 中文文档&#xff1a;https://sentinelguard.io/zh-cn/docs/introduction.html 下载地址&#xff1a;https://github.com/alibaba/Sentinel/releases Spring Cloud Alibaba 官方说明文档&#xff1a;Spring Clou…