目录
三、 预处理详解:
3.1 预定义符号:
3.2 #define:
3.2.1 #define 定义标识符(符号):
3.2.2 #define 定义宏 :
3.2.3 #define 替换规则 :
3.2.4 #和##:
3.2.5 带副作用的宏参数:
3.2.6 宏和函数对比:
所谓预定义符号,即指:在预处理阶段被处理的,已经定义好的符号,可以直接拿来进行使用、
这些预定义符号都是语言内置的。
__FILE__ //打印出正在进行编译的源文件的文件名 ——— %s、 __LINE__ //打印该代码当前所处的行号 ——— %d、 __DATE__ //文件被编译的日期 ——— %s、 __TIME__ //文件被编译的时间 ——— %s、 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义 ——— %d、 //VS编译器不支持该标准,而Linux系统下的gcc编译器支持该标准、
例如:
所以,由此可知,VS编译器不支持循ANSI C标准,而Linux系统下的gcc编译器是支持循ANSI C的,并且,得到的值是1、
作用:当工程特别复杂的时候,在代码运行中记录一些日志信息,当运行出现错误的时候,可以通过记录的日志信息来排查错误,写日志
就是在写文件、
比如:
作用:定义符号(标识符)和宏、
语法: #define name stuff
#define 定义的符号不一定只能是数字,只要满足语法要求即可、
比如:
#define MAX 1000 #define REG register //为 register这个关键字,创建一个简短的名字、 #define DO_FOREVER for(;;) //用更形象的符号来替换一种实现,死循环、 #define CASE break;case //在写case语句的时候自动把 break 写上、 // 如果定义的 stuff 过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。 #define DEBUG_PRINT printf("file:%s\tline:%d\t \ date:%s\ttime:%s\n" ,\ __FILE__,__LINE__ , \ __DATE__,__TIME__ )
提问: 在define 定义 标识符 的时候,要不要在最后加上分号 ; 比如:
#define MAX 1000; #define MAX 1000
部分情况下是可以加上的,加上之后语法也是正确的,比如:
但是在某些情况下,加上分号之后,语法就是错误的,比如:
所以,当 #define 定义标识符的时候,尽量不要带上分号; 在某些情况下容易出现语法错误、
#define name( parament-list ) stuff其中的 parament-list 是一个由 逗号隔开 的 符号表(参数表) ,它们可能出现在内容 stuff 中、 注意: 参数列表的左括号 必须与 name紧邻 ,否则,则会认为是在定义 标识符, 如果两者之间有任何 空白 存在, 参数列表 就会被解释为 内容stuff 的 一部分、
拓展1:
由替换产生的表达式并没有按照预想的次序进行求值、改进:
总结:在写宏的时候,不要吝啬括号,括号的作用非常重要、
所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预
料的相互作用。
拓展2:
改进:
#include<stdio.h> #define M 100 #define MAX(X,Y) (((X)>(Y))?(X):(Y)) int main() { int max = MAX(101, M); //在完成宏的替换之前,先检查宏的参数,如果有#define定义的符号,先替换该符号,把所有的#define定义的符号替换完成后再替换宏,最后再次检查,如果再有#define定义的 //符号的话,重复上述过程、 //由上面可知,宏的参数中有#define定义的符号M,先进行替换,得到: int max = MAX(101, 100); //再进行宏的替换,得到: int max = (((101)>(100))?(101):(100)); //#define定义的符号和宏都是在预处理阶段进行完成的、 return 0; }注意: 1、宏参数 和 #define 定义中 可以出现 其他#define定义的常量, 但是对于 宏 ,不能出现 递归、 2、 当 预处理器 搜索 #define 定义的 符号 的时候, 字符串常量 的内容 并不被搜索 、
# 和 ## 只能在宏内部使用,不是直接拿宏的参数去替换文本,而是把参数中的内容转化成对应的字符串的形式再放在文本中、
当使用 printf 打印两个字符串的时候,处理的方式和一个字符串是一样的,两个字符串天然的会连接在一起,无论其两者中间有多少个空格
都不影响结果、 字符串是有自动连接的特点的、
错误示范:
改正:
如何把参数插入到字符串中?拓展:
## 的作用 :
把两个符号连接成一个符号,也可以把多个符号连接成一个符号,即把两端的符号连接形成一个整体、
##可以把位于它 两边的符号合成一个符号 ,它允许宏定义 从分离的文本片段创建标识符、 这样的连接必须产生一个 合法的标识符 ,否则其结果就是 未定义 的、何谓副作用,此处所讲的副作用即指,比如:
#include<stdio.h> int main() { int a = 1; int b = a + 1; //上述代码结果中,a=1,,b=2,,,就称上述代码没有副作用、 int a = 1; int b = ++a; //上述代码最后的结果中,a=2,,b=2,,此时,a要自增一下,就称之为具有副作用、 return 0; }
如果宏的参数部分带有副作用,即:
//答案应该是多少呢? #include<stdio.h> #define MAX(X,Y) ((X)>(Y)?(X):(Y)) int main() { int a = 5; int b = 8; int m = MAX(a++, b++);//宏的参数带有副作用、 printf("m=%d\n", m);// ? return 0; }
带有副作用的宏参数,尽量避免使用,当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就
可能出现危险,导致不可预测的后果,副作用就是表达式求值的时候出现的永久性效果、
x+1;//不带副作用 x++;//带有副作用
#define MAX(a, b) ((a)>(b)?(a):(b))那为什么 不用 函数 来完成这个 简单的任务 呢? 原因有二: 1、 用于 调用函数以及进入函数内部到达真正的计算部分之前 和调用 函数返回的过程 可能比实际执行这个 小型计算 工作所需要的 时间更多, 所以宏 比函数在程序的规模和速度方面更胜一筹,效率更高、 2、 更为 重要 的是 函数的形参 必须声明为 特定的类型 ,所以函数只能在 类型合适的表达式上使用 ,反之这个 宏 则可以适用于 整形、长整型、浮点型 等 可以用于 > 来比较的类型, 宏是类型无关的。 当然 宏相比函数 也有 劣势 的地方: 1、 每次使用 宏 的时候,一份 宏定义的代码 即 宏体 或者说是 宏的文 本将 插入到程序 中,除非 宏体比较短 ,否则可能 大幅度增加程序的长度 ,但是 函 数不同 ,函数只要定义好一份, 不管怎么调用只有定义好的那一份代码、 2、宏是没法调试的 ,因为 调试 是在生成可执行程序之 后 , 运行环境中 来进行 调试 的,本质上调试的对象是该 可执行程序 ,而 宏的替换在预处理阶 段 就已经完成了,所以在 运行环境中宏就已经不存在了 ,所以没有办法进行调试,虽然 我们看到的 代码中宏没有被替换,但 本质 上,计算机就已 经在预处理阶段把宏替换掉了,此时我们看到的代码和调试时的代码是 不一样的 ,所以 宏是没有办法进行调试的、 3、 宏由于 类型无关 ,也就 不够严谨 ,这也是 宏的一种劣势 ,不够严谨, 宏的参数没有类型检查、
一、使用宏来运算该简单的任务: 其汇编代码只有以下这些:宏的两个参数是完全替换的,替换之后只要能够使用大于号进行比较就行,所以即使是其他类型,只要能够使用 > 进行比较,就满足要求,
比如两个浮点型进行比较,两个整型进行比较,只要替换掉的两个值能够使用大于号进行比较都是可以的,宏的参数是没有类型检查的,所
以用宏进行运算的话,可以比较多种类型的数据,而使用函数运算的话,只能比较一种固定类型的数据,所以在处理一些简单的问题上,使
用宏进行运算比使用函数要具有较多的优点,但是在一些运算比较复杂的情况下,使用函数进行运算比使用宏进行运算会更加方便、
二、使用函数来运算该简单的任务:
其汇编代码有:
由上图可知,为了实现两个数真正的比较过程,要进行大量的准备工作,同时当两个数比较完大小之后还要为调用函数的返回过程做准备,
所以,使用函数的方法来进行比较简单的运算的时候,真正比较大小前的准备工作和调用函数返回过程的工作量可能比进行两个数的比较的
工作所需要花费的时间更多,除此之外,函数的形参部分能够接收到的数据的类型是固定的,在此代码中,只能用来比较两个整型int类型的
数据,不能用来比较两个其他类型数据的大小,由于字符类型也属于整形家族,所以还可以用来比较两个字符的大小,只不过可能会报警
告,类型有所差异、
宏有时候可以做函数做不到的事情,比如:宏的参数可以出现类型,但是函数做不到、 还有就是,宏体内部的#和##的使用,函数也是做不到
的、
#include<stdio.h> //在定义宏的时候,宏名最好写成全部大写,宏的参数可以写成小写、 #define MALLOC(num,type) (type*)malloc(num*sizeof(type)) int main() { int* p = (int*)malloc(10 * sizeof(int)); //这样来动态开辟内存空间时,malloc函数参数中的10*sizeof(int),书写起来比价麻烦,能否写成: int* p = (int*)malloc(10,int); //答案是不行的,函数是不支持这样进行传参的,要知道,malloc是一个函数,函数传参的时候,不能使用类型去传参,可以传值,传变量, //但是不可以传类型,所以就可以使用宏来完成这件事情,这是函数所做不到的、 int* p = MALLOC(10, int); //这就是所谓的宏的参数中可以出现类型的情况,这是函数所做不到的、 //预处理替换后得到的是: int*p=(int*)malloc(10*sizeof(int)) return 0; }
总结:如果一个运算的过程比较简单,则可以使用宏进行运算,因为简单,写宏的时候不容易出现错误,速度更快,效率更高,如果一个运算的
过程比较复杂,则最好写成函数的形式,结果容易把控,在C99之后引入了内联函数(inline)的概念,它结合了宏和函数的优点,在C++中会具
体涉及到、
命名约定: 一般来讲 函数的宏的使用语法很相似, 所以 语言本身没法帮我们区分二 者: 那我们平时的一个 习惯 是: 把 宏名 全部 大写 , 宏参 可以不用全部大写, 函数名不要全部大写 ,可以某一个字母大写、 但 不代表着必须把宏名全部大写 ,小写也是符合语法的,只不过是, 一般常写成大写来与函数作区分 ,在把 宏伪装成函数 的时候,可以不用把宏 名全部大写、 今天的分享到此结束,感谢大家点赞收藏加关注~