目录
预处理详解
1.预定义符号
2. #define
2.1 #define定义标识符
2.2#define 定义宏
2.3#define 替换规则
注意事项:
2.4#和##
2.5 带副作用的宏参数
2.6 宏和函数对比
3. #undef
4. 条件编译
4.1 单分支条件编译
4.2 多分支条件编译
4.3 判断是否被定义
5. 文件包含
5.1 本地文件包含
5.2 库函数包含
5.3嵌套文件包含
写在最后:
预处理详解
思维导图:
1.预定义符号
例:
#include int main(){printf("%s\n", __FILE__);//进行编译的文件位置printf("%d\n", __LINE__);//文件当前的行号printf("%s\n", __DATE__);//文件被编译的日期printf("%s\n", __TIME__);//文件被编译的时间return 0;}
输出:
输出:F:\my code\c_plus_plus-code-exercise-warehouse\2023_2_8\2023_2_8\test.c24Feb8 202319:49:32
注:输出的第一行是我这个文件的路径。
2. #define
2.1 #define定义标识符
#include #define print printf#define size sizeof#define MAX 1000int main(){print("hello world\n");print("%d\n", size(int));print("%d\n", MAX);return 0;}
这个其实就是:
1. 用print 代替了 printf,
2. 用size 代替了 sizeof,
3. 用MAX 代替了 1000。
输出:
输出:hello world41000
另外,建议不要在#define 后面加分号,容易出事。
2.2#define 定义宏
例:
#include #define mul(x) ((x)*(x))int main(){int x = 3;int ret = mul(x);printf("%d\n", ret);return 0;}
输出:
输出:9
这个的本质其实就是将mul(x) 替换成 ((x)*(x))
当然,x可以是你指定的值。
例:
#include #define mul(x) ((x)*(x))int main(){int x = 3;int ret = mul(x);printf("%d\n", ret);ret = mul(3);printf("%d\n", ret);return 0;}
输出:
输出:99
所以这个也是一样的。
注:
1. 参数列表的左括号必须与mul(你定义的名称)紧邻。
2. 用于对数值表达式进行求值的宏定义建议加上()确保优先级。
2.3#define 替换规则
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号,
如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。
对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号,
如果是,就重复上述处理过程。
注意事项:
1. 宏参数和#define 定义中可以出现其他#define 定义的符号,但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
2.4#和##
例:
#define PRINT(format, x) printf("the value of "#x" is "format"\n", x)int main(){int a = 10;//printf("the value of a is %d\n", a);PRINT("%d", a);//printf("the value of ""a"" is ""%d""\n", x)int b = 20;//printf("the value of b is %d\n", b);PRINT("%d", b);float f = 3.14f;PRINT("%f", f);//printf("the value of ""f"" is ""%f""\n", x)return 0;}
输出:
输出:the value of a is 10the value of b is 20the value of f is 3.140000
总结:这里就是使用 # ,把一个宏参数变成对应的字符串。
例2:
#include #define CAT(x,y) x##yint main(){int helloworld = 2023;printf("%d\n", CAT(hello, world));return 0;}
输出:
输出:2023
总结:##可以把位于它两边的符号合成一个符号。
2.5 带副作用的宏参数
例:
#include #define MAX(x, y)((x)>(y)" />
输出:
输出:64 7
因为宏是直接将代码替换下来,所以在使用有副作用的参数时一定要小心。
使用函数的话,就不会出现这样的问题:
例:
#include int max(int x, int y){return x > y ? x : y;}int main(){int a = 3;int b = 5;int m = max(a++, b++);printf("%d\n", m);printf("%d %d\n", a, b);return 0;}
输出:
输出:54 6
因为函数传参就是传的a和b过去计算。
2.6 宏和函数对比
属 性 | #define定义宏 | 函数 |
代 码 长 度 | 每次使用时,宏代码都会被插入到程序中。 除了非常小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方; 每次使用这个函数时, 都调用那个地方的同一份代码 |
执 行 速 度 | 更快 | 函数调用和返回有额外开销, 所以相对慢一些 |
操 作 符 优 先 级 | 宏参数的求值是: 在所有周围表达式的上下文环境里, 否则邻近操作符的优先级可能会产生 所以建议宏在书写的时候多些括号。 | 函数参数只在函数调用的时候 求值一次, 它的结果值传递给函数。 表达式求值结果更容易预测。 |
带 有 副 作 用 的 参 数 | 参数可能被替换到宏体中的多个位置, 所以带有副作用的参数求值, 可能会产生不可预料的结果。 | 函数参数只在传参的时候 求值一次,结果更容易控制。 |
参 数 类 型 | 宏的参数与类型无关, 只要对参数的操作是合法的, | 函数的参数是与类型有关的, 如果参数的类型不同, 就需要不同的函数, 即使他们执行的任务是 |
调 试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递 归 | 宏是不能递归的 | 函数是可以递归的 |
总结:
宏和函数各有优劣,根据实际场景权衡使用。
2.7 命名约定
把宏名全部大写
函数名不要全部大写
3. #undef
这条指令用于移除一个宏定义。
例:
我们发现,移除宏定义MAX之后,再次使用就报错了。
4. 条件编译
我们可以通过使用条件编译根据设定条件屏蔽掉我们不想要的代码。
4.1 单分支条件编译
例:
#include #define PRINT int main(){#ifdef PRINT //还有一个#ifndef是表示PRINT未定义就执行printf("hehe\n");#endifreturn 0;}
输出 :
输出:hehe
4.2多分支条件编译:
例:
#include #define PRINT 1int main(){#if PRINT == 1printf("1");#elif PRINT == 10printf("10");#else printf("" />
输出:
输出:1
#include #define PRINT 10int main(){#if PRINT == 1printf("1");#elif PRINT == 10printf("10");#else printf("???");#endif return 0;}
输出:
输出:10
#include #define PRINT 100int main(){#if PRINT == 1printf("1");#elif PRINT == 10printf("10");#else printf("???");#endif return 0;}
输出:
输出:???
4.3 判断是否被定义
例:
#include #define PRINT int main(){#if !defined(PRINT)printf("hehe\n");#endif#if defined(PRINT)printf("haha\n");#endifreturn 0;}
输出:
输出:haha
当然,条件编译也支持嵌套。
在实际中,条件编译也有广泛的应用:
例:
我们可以看一个头文件的源码感受一下:
5. 文件包含
5.1 本地文件包含
先在源文件所在目录下查找,如果该头文件未找到,
编译器就像查找库函数头文件一样在标准位置查找头文件。
5.2 库函数包含
就直接去标准路径下去查找,如果找不到就提示编译错误。
5.3嵌套文件包含
如果在包含头文件的时候出现这样的情况:
因为头文件展开后会将所以代码放开,
这样就会造成文件内容的重复。
我们可以用条件编译解决这样的问题:
例:
#ifndef __TEST_H__#define __TEST_H__//头文件具体内容://...//#endif
我们用这个条件编译将头文件的内容包起来,
当再次调用这个头文件的时候,
就会因为 __TEST_H__已经定义过了,而不再编译头文件内容。
当然,如果你嫌麻烦的话,
#pragma once
在头文件中写下这段代码也是同样的效果。
写在最后:
以上就是本篇文章的内容了,感谢你的阅读。
如果喜欢本文的话,欢迎点赞和评论,写下你的见解。
如果想和我一起学习编程,不妨点个关注,我们一起学习,一同成长。
之后我还会输出更多高质量内容,欢迎收看。