凡科建站怎么绑定自己的域名,哪里做网站一套一百,开创集团万网站建设,如何拿到网站后台密码本篇文章目录 1. 预处理指令#define宏2. #define定义标识符或宏#xff0c;要不要最后加上分号#xff1f;3.宏的参数替换后产生的运算符优先级问题3.1 问题产生3.2 不太完美的解决办法3.3 完美的解决办法 4.#define的替换规则5. 有副作用的宏参数6. 宏与函数的优劣对比6.1 宏… 本篇文章目录 1. 预处理指令#define宏2. #define定义标识符或宏要不要最后加上分号3.宏的参数替换后产生的运算符优先级问题3.1 问题产生3.2 不太完美的解决办法3.3 完美的解决办法 4.#define的替换规则5. 有副作用的宏参数6. 宏与函数的优劣对比6.1 宏的优点6.1.1 宏的执行速度更快6.1.2 宏不关心参数类型6.1.2 宏的参数可以出现数据类型 6.2 宏的缺点 7. 总结宏和函数的对比8. 宏的命名约定 1. 预处理指令#define宏
#define除了能定义标识符常量外还允许把参数替换到文本中这种实现通常称为宏macro或定义宏define macro。
下面是宏的申明方式
#define name(parament-list) statement
其中的 parament-list 是一个由逗号隔开的符号表它们可能出现在statement中。
例如实现一个宏求两个数中的最大值
#define MAX(x, y) ((x) (y) ? (x) : (y))参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在参数列表就会被解释为statement的一部分。
如
#define MAX (x, y) ((x) (y) ? (x) : (y))MAX后面加了一个空格这样写的话是没法使用的(x, y)会被误认为是表达式而不是宏的参数。
2. #define定义标识符或宏要不要最后加上分号
比如
#define PI 3.14;
#define MAX(x, y) ((x) (y) ? (x) : (y));建议不要加上分号这样容易导致问题比如下面的场景
int max;
int a 10;
int b 20;
if(condition)max MAX(a, b); // error
elsemax 0;我们都知道#define在预编译后就会完成符号的替换在代码中所有出现过#define定义的标识符或宏都会被替换成所代表的常量或宏参数。
那么上面这么写的话实际上就变成了这样
int max;
int a 10;
int b 20;
if(condition)max MAX((a) (b) ? (a) : (b));;
elsemax 0;那么后面会有两个分号一个分号是#define后面带上的另一个是编码习惯性地带上一个分号。
最关键的是这样替换后的代码在你实际编写的.c源文件中是看不到的所以在你的眼中代码任然长这样
int max;
int a 10;
int b 20;
if(condition)max MAX(a, b);
elsemax 0;这样甚至都不需要去编译运行IDE就能早早发现错误给你报错了if后没接大括号只能有一条语句而实际上宏参数被替换后多了个分号这个分号虽然没有实际的意义但它也是一条语句。 3.宏的参数替换后产生的运算符优先级问题
3.1 问题产生
如果你写了一个这样的宏求一个数的平方
#define SQUARE(n) n * n然后使用这个宏
int ret SQUARE(4);
printf(%d, ret);咋一看肯定没毛病能得出正确的结果
但如果这么使用
int ret SQUARE(4 1);
printf(%d, ret);这时你心里想结果是25实际运行后的结果却是 为啥呢其实表达式预编译后长这样
int ret 4 1 * 4 1;
printf(%d, ret);#define完成的是符号的替换无论是定义的标识符常量还是宏要么就是把标识符替换成常量要么就是将宏表达式的参数替换成你传入的参数。仅仅只是替换工作并不会帮你计算好再传参要知道计算的工作是真正在程序运行后才能执行的预编译阶段才仅仅是编译的第一个阶段呢文章.c源文件从编译到链接生成可执行程序的过程
3.2 不太完美的解决办法
解决的办法就是在宏体表达式中给每个参数加上括号
#define SQUARE(n) (n) * (n)这样就确实能得到正确的结果
3.3 完美的解决办法
但实际上像上面这样加上括号任然存在问题比如有这么一个宏
#define DOUBLE(n) (n) (n)然后我这么使用
int ret 10 * DOUBLE(5);
printf(%d, ret);预测结果是100结果却是 经过前面的分析大伙也不难分析出问题是怎样产生的原因就是预编译后替换成了这样
int ret 10 * (5) (5);
printf(%d, ret);这个问题的解决办法是在宏定义表达式两边加上一对括号就可以了。
// #define DOUBLE(n) (n) (n)
#define DOUBLE(n) ((n) (n))所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
4.#define的替换规则
在程序中扩展#define定义符号和宏时需要涉及几个步骤。
在调用宏时首先对参数进行检查看看是否包含任何由#define定义的符号。如果是它们首先被替换。替换文本随后被插入到程序中原来文本的位置。对于宏参数名被他们的值所替换。最后再次对结果文件进行扫描看看它是否包含任何由#define定义的符号。如果是就重复上述处理过程。
其它
宏参数和#define 定义中可以出现其他#define定义的符号。
#define N 10
#define CAL(x) ((x) N) 当预处理器搜索#define定义的符号的时候字符串常量的内容并不被搜索。
#define N 10
#define STR NOT
printf(No);NOT中的N并不会被替换No中的N也不会被替换。
对于宏不能出现递归。
5. 有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候如果参数带有副作用那么你在使用这个宏的时候就可能出现危险导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
#include stdio.h
#define MAX(x,y) ((x) (y) ? (x) : (y))
int main() {int a 10;int b 20;int max MAX(a, b);printf(%d\n, max);printf(%d\n, a);printf(%d\n, b);
}你可能会认为三个输出的结果分别是20、11和21理由是a和b都是后置那么传入的理应是10和20求出较大值是20然后a和b自增后分别是11和21而实际结果是 将参数替换后倒也很容易发现问题的产生原因
//int max MAX(a, b);
int max ((a) (b) ? (a) : (b));(a) (b)比较后肯定为假但是a和b的值都要被自增为11和21然后整个表达式的值是bb这个表达式的结果也是b所以整个表达式的结果是21但是b后b要自增为22。
所以在使用宏传参时应该这么写更合适
int max MAX(a 1, b 1);6. 宏与函数的优劣对比
先总结宏通常被应用于执行简单的运算比如这种
#define MAX(a, b) ((a)(b)?(a):(b))那为什么不用函数来完成这个任务
6.1 宏的优点
6.1.1 宏的执行速度更快
用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多所以宏比函数在程序的规模和速度方面更胜一筹。
说简单点就是调用函数也需要时间函数返回也需要时间如果计算工作过于简单可能函数调用和函数返回的时间都比计算过程花费的时间要长。
①这里使用反汇编直接看汇编代码的行数进行对比在VS中对下面这段程序进行debug ②然后右击鼠标转到反汇编代码 ③查看执行这个宏的汇编代码: ④查看执行这个函数的汇编代码 ⑤这咋一看你会认为好像代码行数更少啊但仔细看其中有个call指令这是函数调用指令所以这并不是真正的函数内部我们执行到call这一行f11进入函数内部 ⑥调用函数要进行跳转这也是一行汇编代码然后再f11就是真正的函数内部了
⑦在这里你会发现还没执行到真正的计算在计算前就存在着某些操作这些操作实际上是参数传递、栈空间的创建然后才是真正的计算计算的汇编代码才是和宏的汇编代码一样但是这还没完函数还有返回这也是要执行的
那从函数调用的汇编代码开始算4行 函数跳转1行 函数执行的21行总共26行这里实际计算工作其实只有9行。。。。。。
而宏只有十行汇编代码可以算是只有计算不存在其它的工作所以比较高效。
小结一下宏和函数的执行过程
宏的执行函数的执行计算函数调用与跳转-申请创建栈内存空间-参数传递-计算-函数返回
6.1.2 宏不关心参数类型
函数的参数必须声明为特定的类型而宏是类型无关的。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于比较操作符来比较的类型。
6.1.2 宏的参数可以出现数据类型
宏有时候可以做函数做不到的事情比如宏的参数可以出现类型但是函数做不到。
例如stddef.h头文件中的宏offsetof求结构体成员相对于起始点的字节数 offsetof宏的使用
#include stdio.h
#include stddef.hstruct Test {char c;int a;
};int main() {printf(%zd\n, offsetof(struct Test, c));printf(%zd\n, offsetof(struct Test, a));return 0;
}利用宏简化malloc的使用
#include stdio.h
#include stdlib.h#define MALLOC(num, type) (type*)malloc((num) * sizeof(type))int main() {int* pArr1 (int*)malloc(10 * sizeof(int));// 对比一下int* pArr2 MALLOC(10, int);return 0;
}6.2 宏的缺点
每次使用宏的时候一份宏定义的代码将插入到程序中除非宏比较短否则可能大幅度增加程序的长度。宏是没法调试的宏在预处理阶段就完成了不在运行阶段所以从肉眼上根本无法查看替换后的内容。宏由于类型无关也就不够严谨这个既是优点也是缺点。宏可能会带来运算符优先级的问题导致程容易出现错。
7. 总结宏和函数的对比
角度宏函数代码长度每次使用时宏代码都会被插入到程序中除了非常小的宏之外程序的长度会大幅度增长。函数代码只出现于一个地方每次使用这个函数时都调用那个地方的同一份代码。√执行速度更快。√存在函数的调用和返回的额外开销所以相对慢一些操作符优先级宏参数的求值是在所有周围表达式的上下文环境里除非加上括号否则邻近操作符的优先级可能会产生不可预料的后果所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次它的结果值传递给函数表达式的求值结果更容易预测。√参数参数可能被替换到宏体中的多个位置所以带有副作用的参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次结果更容易控制。√参数类型宏的参数与类型无关只要对参数的操作是合法的它就可以使用于任何参数类型更加灵活但不够严谨。√函数的参数是与类型有关的如果参数的类型不同就需要不同的函数即使他们执行的任务是不同的。调试宏是不方便调试的。函数是可以逐语句调试的。√递归宏是不能递归的。函数是可以递归的。√
这么看下来函数的优势更多那么是否是直接无脑使用函数就行了呢还是开头的那句话如果执行的任务简单一行代码就能解决的事还是用宏好一些。
8. 宏的命名约定
一般来讲函数的宏的使用语法很相似所以语言本身没法帮我们区分二者。
把宏名全部大写函数名不要全部大写
函数名怎么命名推荐看《高质量的C/C编程》这本书简单来说就是首字母大写如AddXxxwindows编程风格或全小写下划线_如add_xxxLinux编程风格。