目录
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
3、#详解(解决函数不能打印传值前的变量名称以及引入1具体作用)
引入
预处理是文本操作,博主认为预处理能够省去很多冗余代码,也能够使用标识符常量来更好的表明代码含义,深度学习预处理能够提高代码玩家们的程序设计能力。
预处理前 预处理后
1、预定义符号
这些预定义符号都是语言内置的
这些预定义符号可以用来写日志。
1、为什么oj的编译器是clang和gcc呐?
使用__STDC__来测试编译器是否遵循C标准
vs不遵循,gcc遵循 clang也遵循等等。
1、vs测试
2、gcc测试
2、#define
1、#define定义标识符
1、#define的花样使用
语法:
#define name stuff
例如:
#define 定义的内容在编译的预处理阶段会进行文本替换。
下面的是因为有的语言switch case 不使用break,代码玩家就设计了这种写法,省事了许多。
2、续行符\的使用
注意:续行符的后面是不能有其他东西的!
3、预处理文件的内容展示和为什么头文件不能重复包含
前一万多行都是包含的头文件展开
这里的#define定义的标识符常量直接进行了文本替换
所以,尽量避免重复包含相同的头文件,时间和空间都会影响!
4、vs下如何生成预处理后的文件?
使用完成后要把选项改回去,不然还是编译不了,只能编译到预处理后的结果。
预处理后的文件在当前项目的Debug文件中后缀为.i。
5、为什么#define定义的标识符常量后不能跟分号;
这个虽然没有报错,但是是因为文本替换后return 0前有两个分号,第二个分号是个空语句,所以不起什么作用。
从上可以看出#define为什么不要加‘;’,存在很大的隐患!
2、#define定义宏
语法:#define 宏名(参数) 宏体
注意:参数的(左括号一定要紧挨着宏名,否则#define SUM (a,b) ((a)+(b))就变成了标识符了。
1、初次使用
上述展示是否有问题呐?向下看解读!
2、为什么要宏体要加括号()?
宏替换,不是先计算后替换,而是先替换后计算
上图解析:宏替换X*X -> 5+1*5+1=11
正确写法:
因为存在优先级的原因,宏体当中的因子如果是一个整体就要加括号(),外层也要加(),为什么呐?再给大家一个案例:
为什么宏体外还要加大括号?
还是优先级的原因导致,正确写法:
3、#define替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归(例如SUM自己调用自己,不要把宏和函数混淆)。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索(字符串里的内容如果出现标识符常量时不会被替换)。
4、#和##
1、引入1
打印内容是什么?
这个字符串是会合并在一起的。
2、引入2
这个代码的目的是为了表明函数不能够把变量名打印出来,只能够吧变量值打印出来,x是固定的。
3、#详解(解决函数不能打印传值前的变量名称以及引入1具体作用)
使用 # ,把一个宏参数变成对应的字符串!
这里我使用上述的问题来解释#的厉害!
#n相当于"n",因为宏替换的特性是文本替换,我变量名时,检测参数后,文本会插入到本身位置
print(a)-->printf("the value of""a"" is %d",n);
基于引入1进行的具体实践
如果类型不同需要打印呐?宏替换!字符串合并!
4、##详解
在宏体中将相邻的两个符号合并成一个符号
5、带副作用的宏参数
大家先算一下下列代码m、a、b打印的值
答案解析:后置++是先使用后++,宏替换后,判断5>4?先使用原来的a和b,然后++,此时a是6,b是5,逻辑上该走?a++:这里的a++,先使用,所以返回值是6,然后++。后面的b++没执行,所以最终a加了两次,b加了一次,m = 6,a =7,b = 5
x+1;//不带副作用
x++;//带有副作用
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
所以我们在设计宏的时候要避免带有副作用的宏参数。
6、宏和函数的对比
宏的封装能够大大减少代码量,更加方便。宏处理小程序比函数快和小。
引入:
比如:比较两个数,求最大值。
从步骤明显看出宏对小程序的easy,并且宏是没有类型检查的,函数有,对于重复操作的逻辑,一个宏就可以搞定不同类型,所以宏比函数在程序的规模和速度方面更胜一筹。
更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反
之这个宏可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。
宏的缺点:
当然和函数相比宏也有劣势的地方:
1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2. 宏是没法调试的。
3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏厉害的表现
方便、简洁!
宏和函数的对比图
在C++的内联函数是把宏和函数合体了。
7、命名约定
一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。
那我们平时的一个习惯是:
把宏名全部大写
函数名不要全部大写
但并不是全都按照这个规则。例如:ofsetof是个宏,getchar有些实现也是宏
3、undef
这条指令用于移除一个宏定义。
#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
4、命令行定义
在命令行定义一个标识符常量
vs不好演示,这里使用Linux环境演示
编译错误,缺少SZ,并且for循环里定义int i仅仅在C99标准模式下使用,所以在for循环外定义int i就行了。
所以直接在命令行添加标识符SZ
编译指令:gcc -D ARRAY_SIZE=10 programe.c
执行结果展示
这样就可以在命令行随意设置标识符常量值。
5、条件编译
满足条件就编译,不满足就不编译
常见的条件编译指令
以endif来结束一个条件编译。
1.if...endif
也能看出来条件为假,VS会将此部分颜色变淡
2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断标识符是否被定义
第一种写法(defined()、!defined())
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
第二种写法(ifdef、ifndef)
一般条件需要执行的内容不用像if那样加大括号{}。
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
6、文件包含
防止头文件多次包含的两种方法
1.本地文件包含
#include "filename"
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
如果找不到就提示编译错误。
2.库文件包含
#include <filename.h>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
这样是不是可以说,对于库文件也可以使用 “” 的形式包含?
答案是肯定的,可以。
但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。
7、其他预处理指令
#error
#pragma
#line
...
#pragma pack()在结构体部分介绍。
参考《C语言深度解剖》学习
-------------------------------------------------本章内容完-----------------------------------------------------------------
欢迎大家在评论区留言讨论,补充知识,让内容更加丰富,让细节更加饱满,让知识海洋不断扩大!