📚作者简介:爱编程的小马,正在学习C/C++,Linux及MySQL..
📚以后会将数据结构收录为一个系列,敬请期待
● 本期内容讲解C语言中程序预处理要做的事情
目录
1.1 预处理符号
1.2 #define
1.2.1 #define定义标识符
1.2.2 #define定义宏
1.2.3 #define替换规则
1.3 #和##
1.4 带副作用的宏参数
1.5 宏与函数的区别与联系
1.预处理详解
1.1 预处理符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSIC,其值为一,否则未定义
打印出来看一看,很明显,vs2019不支持ANSIC
1.2 #define
1.2.1 #define定义标识符
语法:#define name stuff
#define MAX 100 //定义MAX的值为100
#define reg register //为register这个关键字创建简短的名字
#define ro for(;;) //打印for这个死循环
#define CASE break;case //在case语句后换成break;case
#define debug_print printf("file:%s\tline:%d\t\date:%s\ttime:%s\t",__FILE__,__LINE__,__DATE__,__TIME__);
#include<stdio.h>
int main()
{printf("%d\n", MAX);debug_print;//ro//{// //printf("hehe\n");//}//int a = 0;//switch (a)//{//case1: //不推荐这种写法,可读性太差//CASE2://CASE3://}return 0;
}
提问:
在定义#define的时候,是否要加上;号
比如:
#define MAX1 100
#define MAX2 100;
#include<stdio.h>
int main()
{printf("%d\n", MAX1);printf("%d\n", MAX2);return 0;
}
究竟是MAX1可以还是MAX2可以呢?看看运行结果
解析:实际上在预处理的时候,会将#define定义的标识符进行替换,直接替换成表达式。比方说,MAX1处会被直接替换为100,MAX2处会被替换为100;在这个地方语句就出问题了,怎么两个分号呢?所以MAX2是不能这么写的。
1.2.2 #define定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种通常被称为宏或者定义宏
宏的定义:
#define name(parament-list) stuff
如果两者之间有任何空白存在,那么参数列表就会解释为stuff的一部分
举个例子:
#define SQUARE(x) x*x
#include<stdio.h>
int main()
{int a = 5;int c = SQUARE(a);printf("%d\n", c);return 0;
}
上述代码输出一定是25,因为在预处理过程中会直接替换,int c = a*a ,那么我再问问大家,如果我传的是a+5呢?
#define SQUARE(x) x*x
#include<stdio.h>
int main()
{int a = 5;int c = SQUARE(a+5);printf("%d\n", c);return 0;
}
解析:为什么我传递的是a+5是10,应该是输出100的,为什么最后打印了一个35?原因是这样的,还是替换 int c = a+5*a+5 ,输出就是35,怎么解决呢,要加括号。
加上括号之后,就对了。
但是呢,我又突发奇想 ,又问大家一个问题,看下面这个代码,会输出什么?
#define DOUBLE(x) (x)+(x)
#include<stdio.h>
int main()
{int a = 5;int c = 10*DOUBLE(a + 5);printf("%d\n", c);return 0;
}
按理来说,调用DOUBLE这个宏应该是2x也就是20,再乘10就是200。那我们来看看是否是输出200呢?
解析:可以看到输出的是110,并不是200,为什么呢?其实上就是替换 int c = 10*(a+5)+(a+5),实际上是输出110而不是我们想要的200,怎么办呢?其实还是加括号,再宏定义处再加括号即可
所以呢在创建宏的时候,一定要不要吝啬括号,一定要多加括号。
1.2.3 #define替换规则
在程序过程中扩展#define定义符号和宏时,需要涉及几个步骤:
1、在调用宏时,首先对参数进行检查,看看是否任何由#define定义的符号。如果是,它们首先被替换。
2、替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换
3、最后,再次对结果文件进行扫描,看看他是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
1、宏函数和#define定义中可以可以出现其他#define定义的符号。但是对于宏,不可以递归
2、当预处理器搜索宏的时候,字符串常量不被搜索。
可以看个例子:
#define DOUBLE(x) ((x)+(x))
#include<stdio.h>
int main()
{printf("DOUBLE(5)");
}
1.3 #和##
(1)首先我们一起来看这样一段代码
#include<stdio.h>
int main()
{char* p = "hello bit\n";printf("%s", p);printf("hello ""bit");return 0;
}
可以看到,字符串是有自动连接的特点的。那么我有个设想,是说我们可以根据数据类型来打印对应的数据,比方说我给一个宏传 3 整形,他就可以给我打印出3;比方说我给一个宏传3.5这个浮点型,就可以给我打印出3.500000,看我如何实现
#define PRINT(FORMAT,VALUE) printf("the value of "#VALUE" is "FORMAT"\n",VALUE);
#include<stdio.h>
int main()
{int a = 10;//printf("the value of a is %d\n", a);PRINT("%d",a);return 0;
}
这个代码是可以这么理解,就是宏参数前面加上#号,可以把一个宏参数变成对应的字符串。
这个代码的输出结果就是,PRINT("%d",a)会被替换为printf("the value of "a" is "%d"\n",10)
(2)##
##的作用是:
1、##可以把位于它两边的符号合成一个符号
2、它允许宏定义从分离的片段创建标识符
举个栗子:
#include<stdio.h>#define ping(num1,num2) num1##num2
int main()
{int chinano1 = 2024;int r = ping(china,no1);printf("%d", r);return 0;
}
解析:宏会直接替换,int r = chinano1,所以最后会打印出2024。
1.4 带副作用的宏参数
当宏参数在宏定义出现超过一次的时候,如果这个参数带有副作用,那么你使用宏参数的时候就会非常的危险,导致不可预测的后果。副作用就是表达式求值后出现永久性的效果。
副作用是什么呢?例如:
int x = 5;
//我想让y变成6,有几种方法
//1、
int y = x+1 //无副作用
//2、
int y = ++x; //有副作用的
举个栗子来证明下如果是带副作用的宏参数,会有什么问题:
#include<stdio.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{//传递有副作用的宏参数int a = 5;int b = 6;int c = MAX(a++,b++);printf("a = %d\n", a);printf("b = %d\n", b);printf("c = %d\n", c);return 0;
}
输出结果:
为什么是这个结果,不是6和7比较返回7吗?其实不是的,因为你调用了宏就是替换,
int c = (a++)>(b++)?(a++):(b++),可以看到,宏参数每往后走一步就加加,如果一直反复调用实际上是很危险的。
1.5 宏与函数的区别与联系
属性 | #define定义的宏 | 函数 |
代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,其他的代码量会大幅增长 | 函数代码只出现在一个地方,每次调用,都去那一个地方找 |
执行速度 | 更快 | 存在函数的调用与返回,要慢一些 |
操作符优先级 | 宏的求值是要在上下文联系中的,除非加上括号,否则会产生不可改变的后果 | 函数参数只在函数调用的时候求值一次,函数表达式结果更好预测 |
带有副作用的参数 | 参数可能被替换到任意位置,所以带有副作用的参数会带来不可预料的结果 | 函数参数只在传参的时候求值一次,因此表达式结果更好预测 |
参数类型 | 宏的参数与类型无关,只要表达式合法,都可以用 | 函数参数对类型要求十分严格,如果函数类型不同,就需要不同的函数参数,即使他们执行的类型是相同的。 |
调试 | 宏是不方便调试 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |
总结
上文就是C语言编程预处理阶段的相关知识了,C语言进阶系列正式完结!!!
如果这份博客对大家有帮助,希望各位给小马一个大大的点赞鼓励一下,如果喜欢,请收藏一下,谢谢大家!!!
制作不易,如果大家有什么疑问或给小马的意见,欢迎评论区留言。