提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
在ANSI C的任何一种实现中,存在两个不同的环境。
- 翻译环境,在这个环境中源代码被转换为可执行的机器指令。
- 执行环境,它用于实际执行代码。
翻译环境又可细分为:预处理(预编译)、编译、汇编、链接。
而预处理做的代码文本的替换工作。
一、预定义符号
以下是几个语言内置的预定义符号:
使用举例:
int main()
{
printf("%s\n", __FILE__);
printf("%s\n", __TIME__);
printf("%s\n", __DATE__);
printf("%d\n", __LINE__);
printf("%d\n", __STDC__);//在vs2019中,该符号是未定义的
return 0;
}
运行结果:
二、#define
1.#define定义标识符
#define定义的标识符是一个常量。
语法:
#define name content
- name是自定义的名字
- content是该名字所表示的内容
举例:
#define MAX 100 //用MAX表示100
#define DOU double //用DOU表示double
#define forever for(;;){printf("1");} //用forever表示这个无限循环输出1的函数
2.#define定义标识符加不加分号?
在敲一个语句的时候,是以分号为结尾的,所以我们在写代码时习惯了写完一句代码就加上分号,但是在用#define定义标识符时最好不要加上分号,因为#define定义的标识符在预编译阶段会进行替换,如果加上了分号,可能导致不必要的错误。
如下:
#define MAX 100;
int main()
{
int max = MAX;
return 0;
}
以上代码就出现了错误,在预编译阶段,代码会变成如下:
int main()
{
int max = 100;;
return 0;
}
预编译阶段会把代码中所有的MAX替换为100;,而语句中原本就有分号了,现在又加了一个分号,就导致了语法错误!
所以,在用#define定义标识符时,建议不要加上分号!
3.#define定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)。
定义宏的语法:
#define name(parameter-list) content
- name表示宏名
- parameter-list表示该宏的参数列表
- content表示该宏的内容
注意:
- 参数列表的左括号必须与name紧邻。
- 如果两者之间有任何空白存在,参数列表就会被解释为content的一部分。
因为#define是进行代码文本替换的,所以用#define定义宏,可能因为代码执行顺序的优先级,导致最终结果和我们预料的不同。
比如:
#define FUNC(x,y) x*y //该宏的功能是将传入的x和y相乘
int main()
{
int ret = FUNC(1+2,2+3); //按照我们以往的经验来说,1+2=3,2+3=5,那么传入的参数就是3和5,最终两数相乘应该就是15
printf("%d\n",ret); //最终输出的到底是不是15呢?
return 0;
}
运行结果:
我们可以看到,运行结果是8!这和我们预料的不一样,为什么呢?在前面也说过了,#define是进行文本替换,所以参数列表里的参数不会像函数的参数那样先进行计算再传给形参,而是直接进行替换,在预编译替换后直接变成如下模样:
int main()
{
int ret = 1+2*2+3; // 1+4+3=8
printf("%d\n",ret);
return 0;
}
先算乘法,再算加法,显然结果是8。
修改一下代码:
#define FUNC(x,y) ((x)*(y)) //该宏的功能是将传入的x和y相乘
int main()
{
int ret = FUNC(1+2,2+3);
printf("%d\n",ret);
return 0;
}
运行结果:
这样结果就对了!
总的来说,只要我们在定义宏时恰当合适的加上括号,就可以避免遇到顺序优先级导致的错误!
4.#define替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤:
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
- 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
- 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
其他都好理解,“当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。” 这句话的意思是:
#define MAX 100
int main()
{
printf("MAX is\n");
return 0;
}
双引号中的MAX并不会被替换为100,因为它是字符串!
5.#和##
5.1#
先看如下代码:
int main()
{
printf("Hello ""World\n");
return 0;
}
这段代码最终会输出怎样的结果呢?
运行结果:
没错,就是输出Hello World这段内容,所以即使是不同分号中的内容,最终输出时也会把它们拼接起来输出
有了这个知识做铺垫,那么假设有一段这样的代码:
int main()
{
int a = 10;
int b = 20;
int c = 30;
printf("This is the value of a:%d\n", a);
printf("This is the value of b:%d\n", b);
printf("This is the value of c:%d\n", c);
return 0;
}
这里我们想输出This is the value of a/b/c:%d就必须写三个语句,能不能实现一个宏,根据参数的不同,而直接输出This is the value of a/b/c:%d呢?
代码如下:
#define PRINT(name,a) printf("This is the value of "#name":%d\n",a);
int main()
{
int a = 10;
int b = 20;
int c = 30;
PRINT(a, a);
PRINT(b, b);
PRINT(c, c);
return 0;
}
运行结果:
运用宏确实可以实现,# 就表示把一个宏参数变成对应的字符串。
也就像如下这样:
#define print(name,a) printf("This is the value of ""name(根据传进来的name输出)"":%d\n",a);
5.2##
##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。
#define PRINT(a,b) a##plusb += (a+b);
int main()
{
int a = 10;
int b = 20;
int aplusb = 0;
PRINT(a, b)
printf("%d\n",aplusb);
return 0;
}
运行结果:
这段代码就相当于:
#define print(a,b) aplusb += (a+b);
int main()
{
int a = 10;
int b = 20;
int aplusb = 0;
print(a, b)
printf("%d\n",aplusb);
return 0;
}
注意:
这样的连接必须产生一个合法的标识符,否则其结果就是未定义的。
6.带有副作用的宏参数
宏的参数是进行代码文本替换,所以可能一个参数会被执行多次,而如果该参数是有副作用的话,那么最终结果可能会出错。
举个例子:
#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{
int a = 5;
int b = 6;
int max = MAX(++a, ++b);
printf("max = %d\n", max);
printf("a = %d b = %d\n", a, b);
return 0;
}
这段代码执行后max、a、b分别是多少呢?
运行结果:
这就是带有副作用的参数,宏并不像函数,如果是函数的话,最多是在传参是++a和++b,函数内部就不会修改a和b了,而宏是代码文本替换,所以这里也就相当于是 int max = ((++a) > (++b) ? (++a) : (++b)),++a被执行了一次,++b被执行了两次,所以a的值变成6,b的值变成8,而max的值是8。
7.宏和函数的对比
8.命名约定
宏名全部大写
总结
谢谢阅读,欢迎指正!