文章目次
媒介
一、预界说符号
二、#define
(一)、#define 界说的标识符
(二)、#define 界说的宏
(三)、#define 更换规则
(四)、# 和 ##
1、 # 的作用
2、## 的作用
(五)、带副作用的宏参数
(六)、宏和函数的对比
(七)、定名约定
三、#undef
总结
媒介
路漫漫其修远兮,吾将上下而求索;
一、预界说符号
在C语言自己便预界说了一些符号,这些符号是可以直接利用的;
__FILE__ //举行编译的源文件(文件名:路径+主干名+后缀) %s
__LINE__ //文件当前的行号 %d
__DATE__ //文件被编译的日期 %s
__TIME__ //文件被编译的时间 %s
__STDC__ //如果编译器遵照 ANSI C,其值为1,否则未界说 %d
注:这些预界说符号在预处理惩罚阶段C语言本就界说好了的(这些预界说符号均是语言内置),是可以直接利用;固然,预界说符号出了上述列出来的几个,另有其他的,只不外这几个最常利用;
这些预界说符号有什么用呢?
比方,下面的代码便是在屏幕上打印出了目的数字:
但是,“打印”究竟是在文件的谁人地方、其代码在哪一行、什么时间举行打印的呢?我们是否能记载一下呢?此时便可以用到 __FILE__、__LINE__、__DATE__、__TIME__利用如下:
__STDC__: 如果编译器遵照ANCI C,其返回值为1,否则未界说;故,倘若你想要知道VS编译器是否遵照ANSI C标准便可以用__STDC__ 来测试一下;此预界说符号的详细利用如下:
注:Linux 情况下的 gcc 编译器是遵照ANSI C标准的,这也就是为什么有些语法在测试的时间,在VS下的效果会与在gcc 编译器下的效果差别,当出现这一情况的时间,要以 gcc 编译器的效果为准,由于gcc 编译器才是严格符合ANSI C标准的编译器;
看了上述的注解,你大概会问:DevC++呢?
- DevC++ 对于标准的支持不严谨,故而你会发现倘若在DevC++ 中写的代码好坏常随意的,表现在:你写的语法很糟糕但是该编译器辨认不堕落误;以是在OJ网站上,一样平常就是要么利用gcc ,要么利用 clang (苹果公司所维护的编译器);
想必你会有疑问,这些预界说符号有什么用呢有什么用呢?
- 显然,当我们想知道当前代码在哪个文件哪一行什么时间运行的时间,便可以利用这些预定于符号,故而也不会对获取其行号而发愁;
将来在那边可以用到这些预定于符号?
二、#define
(一)、#define 界说的标识符
语法: #define name stuff
利用如下:
#define MAX 1000 //界说了一个标识符常量MAX
#define reg register //为register 这个关键字创建一个简短的名字 reg
#define do_forever for(;;) //用更加形象的符号来更换一种实现(乃至可以是一段代码)
#define CASE break;case //在写case 语句的时间主动把break 写上
#define DEBUG_PRINT printf("file: %s line=%d \
date:%s time:%s \n", \ __FILE__,__LINE__, \
__DATE__,__TIME__)
注:如果界说的stuff 过长,可以分成几段,除了末了一行外可以在每一行的反面都添一个反斜杠(续行符),而且在此续行符反面不可以再添加其他的东西;
续行符的作用?
- 相当于转义了回车,让回车不再是回车;如果在续行符反面添加了一个空格,那么此续行符转义的便不再是回车,而是厥后的空格 --> 没有转义回车而将一条语句分成了多段--> 报错;
#define 界说的标识符究竟是怎样操纵的?
- #define 界说的标识符是在预处理惩罚阶段被更换掉,同时会删除该符号;
注:在 test.i 文件中不难发现在我们编写的代码前面有很多行代码,显然这是<stdio.h> 中的文件,故而不要频仍多次地包罗头文件;
在#define 界说的标识符反面可不可以添加 ; ?
比方如许:
#define DEBUG_PRINT printf("file: %s line=%d \
date:%s time:%s \n", \ __FILE__,__LINE__, \
__DATE__,__TIME__);
#define MAX 100;
如许写是不保举的,由于容易写出 bug ;
为什么?
纵使 #define 界说的是一条语句,也不要加 ; , 由于当你在调用此条语句的时间还会在其反面加 ; (利于代码的可读性)
(二)、#define 界说的宏
#define 机制包罗了一个规定,允许把参数更换到文本之中,这种实现通常称为宏(macro)大概界说宏(define macro)
宏的阐明方式: #define name(parament-list) stuff
注:
- 此中的parament-list 是一个由逗号隔开的符号表,它们大概出现在 stuff 中;
- 参数左括号必须与name 紧挨;倘若参数左括号与name 之间有任何的空缺存在,均会将该参数列表被表明为stuff 的一部分;
利用如下:
#define SQUARE(X) X*X
#define 界说的宏与#define 界说的标识符有什么区别:
- #define 界说的宏是有参数的,而#define 界说的标识符没有参数;
宏的利用如下:
#define 界说的宏仍然是更换;
正式由于宏的本质是更换,那么就极容易出现操纵符优先级先后的题目,上述代码存在的题目如下图所示:
特别注意,宏的参数不是盘算传入宏体的,而是更换举行的;故而,倘若没有利用 () 来包管宏体中的操纵符的优先级,而宏参数不愿定只是单单的一个数字,如若也是表达式,那么极易出现操纵符优先级带来现实的盘算序次与预期的盘算序次不符合的情况;
以是此处就得将宏中的参数当作一个团体,修改如下:
同理,既然宏参数与宏体之间存在操纵符的优先级关系,那么宏也不但单是单独利用,即宏体与其外貌的数字大概也会存在操纵优先级的题目,比方:
以是也要将宏体当作一个团体;
焦点在于,宏的本质是更换,要思量到宏参数、宏体以及宏加入盘算时与其四周操纵符的优先级关系,以是就要利用括号将宏参数与宏体括起来,以包管其盘算的序次;
(三)、#define 更换规则
在步伐中扩展 #define 界说的符号和宏时,须要涉及以下三个步调:
- 在调用宏时,起重要对其参数举行查抄,看看是否包罗任何由 #define 界说的符号,如果是,它们起首被更换(起首更换#define 界说的符号)
- 所要更换的宏领会被插入到步伐中本来利用宏的位置,而对于宏、参数名被宏体所更换;(然后更换#define 界说的宏)
- 再次对效果文件举行扫描,看看它是否包罗了任何对 #define 界说的符号,如果是便就重复上述操纵;(末了再次查抄)
上述步调图解如下:
注:
1、宏参数和 #define 界说中可以出现其他 #define 界说的符号。但是对于宏,不能出现递归;
2、当预处理惩罚器搜刮 #define 界说的符号的时间,字符串常量的中内容并不会被搜刮;
为什么宏不可以递归?
- 由于宏是完成更换的,它与函数不一样;并不是由于在宏中不能写递归的缘故原由是在于其没有限定条件,而是由于在语法上硬性不支持宏写递归;
(四)、# 和 ##
1、 # 的作用
在陈诉 # 的作用之前,我们先来相识一下字符串的特性;
在C语言中,如若你想打印 "hello world" 可以如许写:,固然你还可以如许写: --> 现实上是在函数printf 中放了两个字符串,但终极会归并为一个字符串,字符串具有主动毗连的特点;
基于此原理,我们便产生了一个想法:
颠末思索,你会发现,此处只能封装成宏,而非函数;
为什么不能封装成函数呢?
- 由于如若你要封装成一个函数,那么此函数的内部功能要同一才行,而上图中是针对差别的变量而输出差别的对象名称,以是此处不能用函数;
既然云云,变量名怎样传入?换句话说,怎样将参数插入字符串中?
大概你起首会想到这么写:,但现实上认真思索会发现,N放在字符串中,而宏无法在字符串中被预处理惩罚器搜刮,故而字符串中的N并不会更换,例子如下:
此时的字符串中的N成了一个普平常通的字符;
接洽到,前面将两条字符串放在printf 函数中但末了归并成了一个字符串,你大概会说将"the value of" 与"is %d\n" 分为两条字符串,然后中央放宏参数,详细实现如下:
毫无疑问,这也是不可的,由于只有字符串相邻放在一起才华合成为一条字符串,N 单独放在两字符串中央肯定会堕落何况函数printf 也不允许这么操纵;
此时便会用到 #
# --> 将一个宏的参数酿成对应的字符串
那么此时 #N 便已然是个字符串,便就可以和字符串相邻放在一起而归并成一条字符串;
利用如下:
还可以利用宏来处理惩罚打印差别范例数据的题目;由于差别范例的数据对应着差别的占位符,以是此时的宏会有两个参数:变量以及此数据所对应的占位符;利用如下:
2、## 的作用
## 可以把位于它双方的符号归并成一个符号,而且允许宏界说从分离的文本片断创建标识符
#define ADD_TO_SUM(num , value) sum##num+=value
ADD_TO_SUM (10,20); --> sum10+=20; 即让 sum10 增长20;
注:如许的毗连必须产生的是一个合法的标识符。否则其效果就是未界说的;
注:在预处理惩罚之中的 ## 可以将两个符号归并成一个符号;而且允许宏界说从分离的文本片断创建标识符;
## 的利用(在现实的写代码中,## 用得非常少):
当我们利用#define 界说了一个函数,
注:上述代码相当于,利用宏写出了一个“函数模具”,函数的参数具有特定范例,与宏相团结便可以生存函数的大要框架,而利于更换函数的参数范例、返回范例,并利用## 实现函数名的“定制”;
(五)、带副作用的宏参数
当宏参数在宏的界说(宏体)中出现高出一次的时间,如若参数带有副作用,那么你在利用这个宏的时间就大概出现伤害,而导致不可推测的效果。副作用就是表达式求值的时间出现有永世性效果;
什么叫做副作用?
- 在现实生存中,以抱病为例子,倘若一个人抱病了,医生给他开的药治疗他的病的同时大概会给他带来副作用(副作用便是产生不良的反应);在代码之中,便表现为,我"资助"了别人,效果改变了自己,比方 int a = 2 ; int b = ++a ; --> 此处的b 确实能得到值3,但是在这过程中 a 的值变了;于是乎此式便带有副作用;存在两个作用:为b 赋值、 更改a 的值;此中更改a 的值便为副作用;
对于宏而言,如下图:
显然,当宏参数有副作用的时间,所得到的效果离开了我们操持该宏的目的;
上述的题目是什么出来的呢?
我们先往返首一下三目操纵符(条件操纵符),盘算规则:从左到右依次盘算, 其详细实现细节如下图所示:
分析上述代码盘算的过程:
从上述代码中,你可以发现,像 a++ , b++ 这种带有副作用的宏参数,并不是单单地只实行一次,当 (a++ > b++ )为真的时间,便会让 a++ 实行两次,而当 (a++ > b++ )为假的时间,便会让 b++ 实行两次;如许的代码时非常伤害的,由于其效果难以推测;
(六)、宏和函数的对比
宏能完成的任务同样函数也可以,二者究竟有何区别?
例,就上图中求取最大值而言,宏与函数哪个更好?
于此例中,从参数范例的角度来看,宏没有参数范例的查抄,可实用于很多的范例,故而显得非常机动;而函数对参数范例的要求很严格;
从实行速率来看,相较于函数,宏的速率更快。为什么呢?
由于宏的本质是更换,还是以上述例子为例;
上述代码利用宏而比函数好的缘故原由:
- 1、用于调用函数(传参、函数栈帧的开发)和从函数返回(函数大概会返回数据)的代码大概比现实实行这个小型盘算工作所须要的时间长,以是宏比函数在步伐的规模和速率方面更胜一筹;
- 2、函数的参数必须声明为特定的范例,而宏的参数与范例无关;函数只能在范例符合的表达式上利用,而宏实用于可用于盘算该式的全部范例;
但是宏也不是全能的,他也存在缺点:
- 每次利用宏的时间,一份宏界说的代码插入到,倘若其代码很长,而又多次利用到该宏,那么便会大幅度地增长代码的长度;
- 宏是不可以举行调试的;
- 宏的参数与范例无关,显得不严谨;
- 弘大概会带来运算符优先级的题目,而导致步伐容易堕落;
- 宏不可以递归
注:宏的处理惩罚是在预处理惩罚阶段举行的,而调试调试的是编译、链接产生的可实行步伐;
看了上文,你大概会有疑问,宏能实现的,函数也能实现,那么有没有宏能实而函数不能实现的情况呢?
- 总所周知,函数的参数不能单单是范例,但是介于宏的本质实现是更换,以是宏参数可以是范例;
在前面学习动态开发的时间,是否有如许的感觉,比方利用malloc 开发空间,那么就得盘算所要开发的空间的字节数,针对差别范例的数据、存放此数据的个数来盘算开发的空间的巨细,其盘算过程便容易出现题目,为了淘汰bug 的出现,那么此时便可以利用宏来实现,代码如下:
- #include<stdio.h>
- #define MALLOC(num,type) (type *)malloc((num)*sizeof(type))
- int main()
- {
- short* p1 = MALLOC(10,short);
- if (p1 == NULL)
- {
- perror("malloc short");
- return;
- }
- int* p2 = MALLOC(20, int);
- if (p2 == NULL)
- {
- perror("malloc int");
- return;
- }
-
- return 0;
- }
复制代码 宏与函数的对比:
| 属性 | #define 界说宏 | 函数 | | 代码长度 | 每次利用宏的时间,宏代码都会被插入到步伐中;倘若宏的代码行很多,多次利用该宏便会使得该步伐的代码行大幅度增长 | 函数详细实现的代码只会出现在一个地方;每次利用这个函数的时间,都会谁人地方的函数;不会因多次调用函数而大幅度地增长代码行 | | 实行速率 | 宏的本质是更换,且在预处理惩罚阶段完成的,只有实行该代码的时间开销,故而其实行速率会更快 | 存在函数的调用和返回效果的时间上的开销,故而相对来说会慢一些 | | 操纵符的优先级 | 宏的本质是更换;宏参数求值是在全部四周表达式的上下文情况里,故而其相近操纵符的优先级大概会影响宏体中的实现,进而导致现实效果会与预期效果不符合的情况;以是在写宏的时间,要为宏参数与宏体加上括号,以包管其盘算的序次; | 函数参数只在函数调用的时间求值一次,它的效果值通报给函数;表达式求值的效果更容易推测; | | 带有副作用的参数 | 宏参数大概会被更换到宏体中的多个位置,倘若此宏参数带有副作用,那么便会举行多次盘算,从而导致所得到的效果会偏离利用的目的,即产生不可推测的效果; | 函数传参只在传参的时间求值一次,即无论是传址还是传值,本质均是函数针对所通报过来的数据举行操纵,以是效果更容易控制; | | 参数范例 | 宏的参数与范例无关,只要对参数的操纵合法,那么此宏便可以实用于任何范例的参数。 | 函数的参数是与范例有关的,如果参数的范例差别,就须要差别的参数,纵然他们实行的任务是雷同的; | | 调试 | 宏不能调试 | 函数可以逐语句举行调试 | | 递归 | 宏不能递归 | 函数可以递归 | 怎么判断在一个条件下该利用宏还是利用函数呢?
- 如果说该代码富足简单,那我们便可利用宏来写;倘若该代码写出来很复杂、行数多而且容易堕落,介于宏不能举行调试,便无法观察实行的细节,以是我们便可以利用函数来办理题目;(注:固然,利用c++ 可以不消纠结于到底利用宏还是函数了; 由于在 c++ 之中提供了概关键字 inline(内联函数),inline 具有了函数的优点和宏的优点;)
(七)、定名约定
云云看来函数与宏在利用的语法上很相似,既然无法利用语言来帮我们区分二者,那么有什么办法能资助我们区分二者呢?
注:但是也不要以为满是小写的肯定是函数,比方: offsetof 满是小写,咋一看以为是函数,着实offsetof 本质上是宏; 函数getchar 中有部分实现大概利用了宏;
此处只是个约定,总有人不按照套路来走,以是“全大写是宏” 这种判断是宏还是函数的方法只是一种参考,详细靠谱地判断还是得团结代码;
三、#undef
#define 可以用来界说标识符,也可以用来界说宏,那么其界说能否被取消呢?
#undef 用于移除一个宏界说;
总结
1、在C语言自己便预界说了一些符号,这些符号是可以直接利用的;
__FILE__ //举行编译的源文件(文件名:路径+主干名+后缀) %s
__LINE__ //文件当前的行号 %d
__DATE__ //文件被编译的日期 %s
__TIME__ //文件被编译的时间 %s
__STDC__ //如果编译器遵照 ANSI C,其值为1,否则未界说 %d
2、#define 界说的标识符
语法: #define name stuff
3、#define 机制包罗了一个规定,允许把参数更换到文本之中,这种实现通常称为宏(macro)大概界说宏(define macro)
宏的阐明方式: #define name(parament-list) stuff
注:
- 此中的parament-list 是一个由逗号隔开的符号表,它们大概出现在 stuff 中;
- 参数左括号必须与name 紧挨;倘若参数左括号与name 之间有任何的空缺存在,均会将该参数列表被表明为stuff 的一部分;
4、# --> 将一个宏的参数酿成对应的字符串
5、## 可以把位于它双方的符号归并成一个符号,而且允许宏界说从分离的文本片断创建标识符
6、#undef 用于移除一个宏界说;
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金 |