C语言之预处理

  那是201陆年的结尾1篇博客,年底定的布署是写1贰篇博客,每月一篇,1/三转发,2/三原创,看来是促成持续了!

题外话。今天要写的事物是C语言中的预处理器,大家常说的宏定义的用法。为什么要写那么些事物吗,原因很简短:从前对预处理精通不深。假设你对C语言只是摸底照旧是一味在高档高校中学习过C语言,提及预处理推断你只晓得下边那条语句:(因为作者正是那种处境,哈哈!)

1 #define name value

  作者再上学预处理间接的驱重力是看了php的源码,伊始一大推的宏定义器,在此之前’理解’的少数#define的用法太少了,根本看不懂源码中宏的处理逻辑和平运动行的路子。所以再学习预处理器很有须要,里面繁多东西其实并轻便,只是你从未接触到,等你读书了,就感到轻便了。

  1、宏定义和平运动用中的坑

  那小节选拔先给代码再作证的款型,那样你能够看看每种代码的运维结果是或不是和您预期的等同!

  宏是如何,宏正是#define机制把钦定的参数替换的公文中,这样的得以达成情势便是宏。使用宏定义能够挤出频仍调用的函数,加速施行的快慢。定义如下:#define
name(参数)  施行体… 
“参数”能够是使用逗号分隔的参数列表,这个参数能够被利用到执行体中,必供给小心的是“参数”的左括号必须和宏名字紧邻,不然编辑器会报错,只怕被分解成实践体中的一片段。比如您写了一个TEST(a) a * a 调用推行的时候写上 TEST(①) 实际施行的是替换后的 一 * 1。

  凡事都有利弊,宏定义固然使用方便,并且有着函数不可比拟的进行进程,不过宏定义中留存诸多的坑,上面就说1说这一个坑。看上面包车型大巴代码:

 1 #include <stdio.h>
 2 
 3 #define TEST(a) a * a
 4 
 5 int main() {
 6     int b = TEST(2);
 7     int c = TEST(1+2);
 8     printf("b=%d, c=%d", b, c);
 9     printf("\n\n");
10 }

  未有举办的情形下,你感到得到的结果是不怎么呢!好三个人不加思量的说:b=4,c=九。如果真是如此,就不设有坑了,实际打印出来是:b=四,
c=伍,为何c的值和预期的会有过错,其实您把实行体中的值替换一下试跳,就一举成功发现题目了,当输入一+二的时候,宏替换到了
壹+2*一+2,当然正是伍了。好了接头了,那你学会了吗?学会了再看1个:

 1 #include <stdio.h>
 2 
 3 #define TEST(a,b) ((a) > (b) ? (a) : (b))
 4 
 5 int main() {
 6     int zyf = 1;
 7     int abc = 2;
 8     int ret = TEST(zyf++, abc++);
 9     printf("zyf=%d,abc=%d,ret=%d", zyf, abc, ret);
10     printf("\n\n");
11 }

  输出多少吗,倘使是 zyf=贰,abc=三,ret=3就错了,实际结果是:zyf=二,abc=四,ret=3。道理和目前的均等,只看替换后的结果手艺确实看到答案。

  那样的标题防不胜防,怎么着能力消除吧,其实办法很粗大略,错误的案由是进行的种种和大家预料的不均等,那增添小括号应该能够缓解那种主题材料。
比如 (a) * (a)。那样其实也不是最万全的章程,比如您看那一个:ADD(a)  (a) +
(a) ,借使如此调用:ADD(2) * 5 ,那样又万分了,被替换到了 (a) + (a) *
5 实践各个和预期的依旧不平等,所以还要在最外层加上括号:((a) +
(a)),那样就化解了。

 

  二**、预约义符号**

  C语言中有几个预订义的暗号,依然有须要和大家说上1说,先看壹段代码:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #define VAR_DUMP printf( \
 4     "[\n \tfile:%s\n" \
 5     "\tline:%d\n" \
 6     "\ttime:%s %s\n" \
 7     "\tvalue:%d\n]", \
 8     __FILE__, __LINE__, __DATE__, __TIME__, value \
 9 )
10 int main() {
11     int value = 1;
12     VAR_DUMP;
13     printf("\n\n");
14 }

  是否和你在大学学习的有点不壹致,最轻巧易行的宏定义能够选取#define
name value
的方式,当然也能够把值写成1个函数,运转的时候一向交换函数。这一个宏定义是包装了调整方法,是打字与印刷变量内容能像PHP中var_dump()或者print_r()函数同样,打字与印刷出变量的内容。

从那段代码中能学习到几点内容:

1、使用#define能够使任何文件替换来程序中,在主程序中您能够专擅行使VAHummerH贰_DUMP。

二、宏定义不以分号甘休,假使不行长的宏定义,你能够在结尾加上反斜杠来分行,保持代码易读性。

3、你可以定义频仍调用的函数为宏定义,那样能够加快实践的速速,具体原因前边会聊起。

四、C语言有多少个约定的符号必要我们明白,诸多时候特意实用:

  __FILE__ 预编写翻译的文件名

     __LINE__
文件当前行的行号(实行到那一行)

  __DATE__ 文件编译的日期

  __TIME__ 文件编写翻译的切实日子

     __STDC__ 是或不是遵循ANSI C
(不常用)

末尾附上运转结果,如图:

C语言 1

  三**、宏替换的进度**

  在先后的编写翻译阶段,宏先被施行替换,一般要涉及上面包车型客车步骤:

  一、调用宏的地点看是或不是 实行了 #define定义,假诺是就进展交换。

  二、把替换的文件新闻插入到替换的职位,其中参数被替换到了实际的值。

  3、#define能够涵盖别的概念的#define定义的事物,要求小心的是不可能现身递归的情状。

  因为替换存在临近字段自动结合,所以能够动用部分精美绝伦的方案:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 
 4 #define VAR_DUMP(A,B)\
 5    printf("Value of " #B " is " A "\n", B)
 6 
 7 int main(){
 8     int x = 1;
 9     VAR_DUMP("%d", x+2);
10 }

  

  四**、条件编写翻译和其它宏用法**

  在大型的C程序中你能收看众多的尺码编写翻译,比如能够依照当下的环境加载差异的宏配置,大概在编写翻译的时候加多直极预设的编写翻译条件。那么些事物的完毕都离不开条件编译。

  1、条件嵌套,#if   #endif 原型:

1 #if condition
2     执行体
3 #endif

能够依照condition来规定实践体要不要实施,以此来调整在区别的环境下编写翻译成差异的系统。看上面包车型地铁代码,当把DEBUG定义成非0值时,MAX宏定义是存在的,当定义成0时,程序就会报错。

 1 #include <stdio.h>
 2 
 3 #define DEBUG 0
 4 #if DEBUG
 5     #define MAX(a) ((a) * (a))
 6 #endif
 7 
 8 int main() {
 9     int b = MAX(2);
10     int c = MAX(1+2);
11     printf("b=%d, c=%d", b, c);
12     printf("\n\n");
13 }

当然#if 也足以与#elif嵌套使用,那样就和我们在函数里使用if
else同样了,上边是1段php源码中的一段话,你能看出编写翻译php内定分歧的参数,检查分裂的条件等等都足以通过预处理中的条件编写翻译开完毕。

 1 #ifndef PHP_H
 2     #define PHP_H
 3 
 4     #ifdef HAVE_DMALLOC
 5         #include <dmalloc.h>
 6     #endif
 7 
 8     #define PHP_API_VERSION 20100412
 9     #define PHP_HAVE_STREAMS
10     #define YYDEBUG 0
11 
12     #include "php_version.h"
13     #include "zend.h"
14     #include "zend_qsort.h"
15     #include "php_compat.h"
16     #include "zend_API.h"
17 
18     #undef sprintf
19     #define sprintf php_sprintf
20 
21     /* PHP's DEBUG value must match Zend's ZEND_DEBUG value */
22     #undef PHP_DEBUG
23     #define PHP_DEBUG ZEND_DEBUG
24 
25     #ifdef PHP_WIN32
26     #    include "tsrm_win32.h"
27     #    include "win95nt.h"
28     #    ifdef PHP_EXPORTS
29     #        define PHPAPI __declspec(dllexport)
30     #    else
31     #        define PHPAPI __declspec(dllimport)
32     #    endif
33     #    define PHP_DIR_SEPARATOR '\\'
34     #    define PHP_EOL "\r\n"
35     #else
36     #    if defined(__GNUC__) && __GNUC__ >= 4
37     #        define PHPAPI __attribute__ ((visibility("default")))
38     #    else
39     #        define PHPAPI
40     #    endif
41 
42     #    define THREAD_LS
43     #    define PHP_DIR_SEPARATOR '/'
44     #    define PHP_EOL "\n"
45     #endif
46 
47     #ifdef NETWARE
48         /* For php_get_uname() function */
49         #define PHP_UNAME  "NetWare"
50         #define PHP_OS      PHP_UNAME
51     #endif
52 
53     #if HAVE_ASSERT_H
54         #if PHP_DEBUG
55             #undef NDEBUG
56         #else
57             #ifndef NDEBUG
58                 #define NDEBUG
59             #endif
60         #endif
61         #include <assert.h>
62 
63     #else /* HAVE_ASSERT_H */
64         #define assert(expr) ((void) (0))
65     #endif /* HAVE_ASSERT_H */
66 
67     #define APACHE 0
68     #if HAVE_UNIX_H
69         #include <unix.h>
70     #endif
71 
72     #if HAVE_ALLOCA_H
73         #include <alloca.h>
74     #endif
75 
76     #if HAVE_BUILD_DEFS_H
77         #include <build-defs.h>
78     #endif
79     . . . 

  二、是不是早已被定义

      被定义:#if   define() 或者是#ifdef 

  不被定义:#if !define() 或者是#ifndef

  前者的写法固然从未后者精炼,不过前者有越多的运用情状,比如上面那种,可以张开嵌套推行。

1 #if defined(DEBUG)
2      #ifdef DEBUGTWO
3            #define TEST(a) a * a
4      #endif
5 #endif

  3、移除1个宏定义,当不再利用2个宏定义后,能够利用undef来把不要求的宏移除,原型:

1 #undef name

 

  五**、宏命名规则和与函数分歧**

  从后边的应用中大家能够见见,宏的使用规则和函数真是一模一样,不过精神上可能有分其他,在运用中什么分化宏和函数,涉及到代码规范和代码的可读性难点。标准的宏使用相应运用大写字母,那样在先后中私行地方采纳宏都会分晓这是3个宏定义。比如前边用到的
#define TEST(a) ((a) * (a))。

  宏与函数不一样有以下几点:

  壹、施行进程上,宏定义越来越快,函数因为急需调用栈,存在调用,再次回到,保存现场的系统开采,所以比宏要慢。

  二、代码长度上,宏在代码长度上实际是增进的,每壹处的行使宏都会把name替换到宏内容假如大度施用,会是代码明显增进,函数代码唯有1份,比较节省代码空间。

  三、参数类型上,宏未有参数类型,只要能够使用都行。函数不均等,函数有参数类型确定性。正式因为这么,有些宏能神奇的使用那或多或少,落成函数不可能产生的职务,看上面代码(书上看的),奇妙的接纳传递类型Infiniti制的风味自动开采想要的各连串型空间:

1 #include <stdio.h>
2 #include <stdlib.h>
3 
4 #define CREATE_P(nums, type) ((type *) malloc((nums) * sizeof(type)))
5 
6 int main(){
7     int nums = 2;
8     CREATE_P(nums, int);
9 }

  四、宏定义和函数的使用情状,宏定义一般在先后的始发,函数转化成宏定义一定要思索开支难题,短小精炼的函数转化成宏使用时最棒的,功效负责的函数转化成宏就有点进寸退尺了。

 

  六**、文件包罗**

  一、本和姑件包括和库文件包括  

  文件包涵在大型系统中显明会用到,大型系统宏定义巨多无比,不容许把具备的宏定义都复制到每一种文件中,那么文件包涵就能缓解那种问题。

  实际上编辑器支持三种文件包括,壹种是大家平日会用的库文件的带有,比如上面大家看出的:#include
<stdio.h>,还有壹种是当三步跳件包蕴,说白了就是大家分甘共苦写的文书,包蕴的原型如下:

1 #include <filename>
2 #include "filename"

  那二种方法都足以开始展览文件的包罗,不一样的是首先种是库文件的带有,标准的C库函数都会以.h扩张名结尾,第叁种是地方文件包蕴,当编辑器看到第贰种方法时,优先查找本路线下得本地库文件,如若未有找到就会像包罗库文件那样在内定的路子下去找,那时第两种和第一种就多数了。第两种含有格局在编码习惯上也是相比较好的,外人看您的代码很轻便领悟那几个文件是库函数依旧你协调写的。

  1、嵌套文件包涵

  大型系统中不仅有大气的文书包涵,还会有雅量的嵌套文件包括,看上面包车型地铁事例:

  a.h,b.h,c.h,define.c文件,个中a,b,c,define文件的内容如下:

 1 a.h:
 2 #include "c.h"
 3 void var_dumpa(){
 4     test obja;
 5     obja.a[1] = 2;
 6     printf("obja.a[1]: %d\n", obja.a[1]);
 7 }
 8 
 9 b.h:
10 #include "c.h"
11 void var_dumpb(){
12     test objb;
13     objb.a[1] = 2;
14     printf("objb.a[1]: %d\n", objb.a[1]);
15 }
16 
17 c.h:
18 #include <stdlib.h>
19 #include <stdio.h>
20 
21 typedef struct test{
22     int a[10];
23 }test;
24 
25 define.c:
26 #include <stdio.h>
27 #include "a.h"
28 #include "b.h"
29 
30 int main() {
31     var_dumpa();
32     var_dumpb();
33     printf("\n\n");
34 }

C语言,  ab文件包含c文件,define.c文件文件引用a,b文件后会引发1个不当:typedef
struct
test类型错误,因为c.h文件被含有了五次,像那种气象在巨型系统中会平日遇到,也许说,你会发觉重复引用库文件也不会报错,总之,库文件一定是运用了解决办法。其实消除那种漏洞非常多的方案便是行使标准编写翻译,当这么些文件引进到另1个文书中后我们得以设置二个宏定义,比如:

1 #include <stdlib.h>
2 #include <stdio.h>
3 
4 #ifndef PATH_C_H
5     #define PATH_C_H 1
6     typedef struct test{
7         int a[10];
8     }test;
9 #endif

因为每便编写翻译编写翻译器都会读入整个头文件,要是把持有的文本都助长那几个条件编译的话,那交叉引用文件发出的重复宏编写翻译问题就消除了,运维如下:

C语言 2

  好了,就写这样多吗,重新梳理了对宏定义的认识和着力的应用。时间匆忙,出错的地点请大婶们肯定提出,相当多谢!

注意:

一、本博客同步更新到自个儿的个体网址:http://www.zhaoyafei.cn

二、本文属原创内容,为了强调外人劳动,转发请表明本文地址:

http://www.cnblogs.com/zyf-zhaoyafei/p/6237295.html