预处理器

编译一个C程序涉及很多步骤,其中第一个步骤被称为处理阶段(preprocessing)。C预处理器在源代码编译之前对其进行一些文本类性质的操作,它的主要任务包括删除注释、插入被#include包含的文件的内容、定义和替换由替换由#define指令定义的符号以及确定代码的部分内容是否应该根据一些条件编译指令进行编译。

1.预定义符号

下表总结了预处理器定义的符号。他们的值可以是,字符串常量,或者是十进制数字常量。

2.#define

define的简单用法就是为数值明明为一个符号。下面观察一下它更为正式的用途。

#define name stuff

有了这条指令之后,每当有name出现在这条指令后面时,预处理器就会把它替换成功stuff。

替换文本并不仅限于数值字面值常量,使用#define指令可以把任何文本替换到程序中,下面有几个例子:

#define reg  register
#define do_forever for(;;)
#define CASE break;case

第一个定义为关键字register创建了一个更简短的别名,这个较短的名字使各个声明更容易通过制表符进行排列。第二个声明用一个更具有描述性的符号来代替一种用于实现无限循环的for语句类型。最后一个#define**定义了一种简短记法,以便在switch语句中使用**。它自动的把一个break放在每个case之前。

如果定义的stuff非常长,他可以分成几行,除了最后一行之外,每行的末尾都要加一个反斜杠,如下面的例子:

#define DEBUG_PRINT   printf("File %s line %d:"\" x = %d, y = %d, z = %d:,\__FILE__, __LINE__,\x,y,z);

注意,不要在宏定义的尾部加上分号,这样的话就会产生两条语句,而有些场合只能出现一条语句,例如if语句之类的语句。

不要滥用#define指令,如果相同的代码需要出现在程序的几个地方,最好的方法是把它实现为一个函数。

2.1宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或者宏定义,下面是宏的声明方式:

#define nname(parameter-list)  stuff

其中,parameter-list(参数列表)是一个由逗号分号分隔的符号列表,他们可能出现在stuff中,参数列表的左括号必须与name紧邻,如果有空白存在就被解释为stuff的一部分

例子:

#define SQUARE(x)   x * x

上述声明之后,如果有这样一句:SQUARE(5),预处理器就用下面这个表达式替换:5*5

注意,观察下面的代码段:

a = 5;
printf("%d\n",SQUARE( a + 1));

这个代码段打印的值是11,因为参数x被文本a+1替换了:

printf("%d\n", a + 1 * a + 1 );

如果把宏定义改为下=下面这样就解决了:

#define DOUBLE(x) (x) + (x)a = 5;
printf("%d\n", 10 * DOUBLE ( a ));

上面这个代码段,结果是55,替换的代码段是:

printf("%d\n", 10 * (a) + (a));

所以在定义宏的时候,在整个表达式两边加上括号就行:

#define DOUBLE(x)  ((x) + (x))

所有用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时,由于参数中的操作符或邻近的操作符之间不可预料的相互作用

2.2宏替换

在程序中扩展#define定义符号和宏时,需要涉及几个步骤:

1.调用宏时,首先对参数进行检查,看看是否包含了任何由#define定义的符号,如果是,它们首先被替换。

2.替换文本随后被插入到程序中原来文本的位置,对于宏,参数名被它们的值所替代

3.最后,再次对结果文本进行扫描,看看它是否包含了任何由#define定义的符号,如果是,就重复上面的步骤。

注:
当预处理器搜索#define定义可以包含其他#define定义的符号宏,不可以出现递归

##结构执行一种不同的任务,它把位于它两边的符号连接成一个符号。下面这个例子用这种连接把一个值添加到几个变量之一:

#define ADD_TO_SUM(sum_number, value ) \sum##sum_number += valueADD_TO_SUM(5, 25)

上面这条语句把值25加到变量sum5,注意这种连接必须需产生一个合法的标识符,否则其结果是未定义的

2.3宏与函数

宏非常频繁的用于执行简单的计算,比如在两个表达式中寻找其中较大(或较小)的一个:

#define  MAX( a, b )   ((a) > (b) ? (a) : (b))

为什么上述操作不用函数呢,首先函数调用和从函数返回的代码可能比世纪执行这个计算工作的代码更大,所以宏比使用函数在程序的规模和速度方面都更胜一筹。

更重要的是函数的参数声明必须为一种特定的类型,反之宏可以用于整型、长整型、单浮点数、双浮点数以及其他任何可以用操作符比较值大小的类型,宏是与类型无关的

2.4带副作用的宏参数

当宏参数在宏定义中出现的次数超过一次时,如果这个参数具有副作用,那么当你使用这个宏时就可能出现危险。副作用就是在表达式求值时出现永久性效果,例如:

x++;
x+1;

第二个表达式可以执行很多次都没有副作用,而x++就具有副作用,它会增加x的值。

观察下面的代码段:

#define MAX(a, b)  ((a) > (b) ? (a) : (b))x = 5;
y = 8;
z = MAX(x++, y++);
printf("x = %d, y = %d, z = %d\n",x, y, z);

上面这个表达式的最终结果是:
x = 6, y = 10, z = 9;

虽然那个较小的值只自增了一次,但那个较大的值却增值了两次——第一次是在比较时,第二次是在执行?符号后面的表达式时出现。

2.5命名约定

宏一般都用大写字母。

下表示宏和函数的不同之处:

2.6#undef

这条预处理指令用于移除一个宏定义。

#undef name

如果一个现存的名字需要被重新定义,那么它的旧定义I首先必须用#undef移除。

2.7命令行定义

许多C编译器都允许我们在命令行中定义符号,用于启动编译过程,当我们根据同一个源文件编译一个程序的不同版本时,这个特性就很有用。

如果某个数组类似下面的形式进行声明:

int array[ARRAY_SIZE];

那么在编译程序时,ARRAY_SIZE的值可以在命令行中指定

在UNIX中,-D可以玩车工这项任务

-Dname
-Dname = stuff

在需要引用数组长度时,使用符号常量

3.条件编译

使用条件编译,可以在调试程序时,可以选择代码的一部分是被正常编译还是被完全忽略。

用于支持条件编译的基本结构是#if指令和与其匹配的#endif指令,下面是语法形式:

#if constant-expressionstatements
#endif

常量表达式由预处理器进行求值,如果它的值是非0值,那么statements部分就被正常编译,否则预处理器就删除这部分代码。

#if指令还具有可选的#elif和#else字句,完整的语法如下所示:

#if  constant-expressionstatementss
#elif constant-expressionother statements...
#else other statements
#endif

#elif语句可以有许多个。

3.1是否被定义

代码示意:

#if define(symbol)#ifdef symbol#if !defined(symbol)
#define symbol

每对定义的两条语句是等价的但是#if的形式功能更强,因为常量表达式可能包含额外的条件

3.2嵌套指令

前面提到的这些指令可以嵌套于另一个指令内部,如下面的代码所示:

#if define(ps_UNIX)#ifdef OPTION1unix_version_of_option1();#enif#ifdef OPTION2unix_version_of_option2();#enif
#elif define(OS_MSDOS) #define OPTION2msdos_version_of_option2();#endif
#endif

这就是嵌套使用的一个例子

4.文件包含

#include指令使另一个文件的内容被编译,这种替换方式很简单:预处理器删除这条指令,并用于包含文件的内容取而代之。

4.1函数库文件包含和本地文件包含

编译器支持两种不同类型的#include文件包含:函数库文件和本地文件。事实上,它们之间的区别很小

#include<filename.h>
#include"filename.h"

4.2嵌套文件包含

在一个将被其他文件包含的文件中使用#include指令是可能的,标准要求编译器必须支持至少八层的头文件嵌套,但它并没有限定嵌套深度的最大值。

多重包含在绝大多数情况下出现于大型程序中,解决这个问题时往往使用条件编译,如果所有的头文件都像下面这样编写:

#ifndef _HEADERNAME_H
#define _HEADERNAME_H 1#endif

如果可能,应该避免出现多重包含,不管是否由于嵌套头文件导致

5.其他指令

预处理器还支持其他一些指令,例如程序编译之后,#error指令允许生成错误信息,例如:

#error text of error message

还有一种用途较小的#line指令,它的形式如下:

#line number"string"

它通知预处理器number是下一行是输入的行号,如果给出了可选部分“string”,预处理器就把它作为当前文件的名字。这条指令将修改_LINE_ 符号的值。

#pragma指令是另一种机制,用于支持因编译器而异的特性,语法也因为编译器而异。

最后,无效指令(null directive)就是一个#符号开头,但后面的不跟任何内容的一行。这类指令只是被预处理器简单的删除

一些警告

1.不要在一个宏定义的末尾加上分号,使其成为一条完整的语句

2.在宏定义中使用参数,但忘了在他们周围加上括号

3.忘了在整个宏定义的两边加上括号

4.避免用#define指令定义可以用于函数实现的很长序列的代码

5.避免使用#define宏创建一种新语言

6.只要合适就应该使用文件包含,不必担心额外开销

7.头文件应该只包含一组函数或者数据的声明

8.采用命名约定,避免程序员看不出某个标识符是否为#define宏

【C语言总结】C语言预处理器相关推荐

  1. 16道嵌入式C语言面试题(经典) 预处理器(Preprocessor)

    16道嵌入式C语言面试题(经典) 预处理器(Preprocessor) 1. 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题) #define SECONDS_PER_ ...

  2. C语言再学习 -- C 预处理器

    gcc/cc xxx.c  可以编译链接C源程序生成一个可执行文件 a.out 整个过程中可以划分为以下的4步流程: (1)预处理/预编译: 主要用于包含头文件的扩展,以及执行宏替换等 //加上 -E ...

  3. 【C语言篇】C预处理器和C库

    友情链接:C/C++系列系统学习目录 知识总结顺序参考C Primer Plus(第六版)和谭浩强老师的C程序设计(第五版)等,内容以书中为标准,同时参考其它各类书籍以及优质文章,以至减少知识点上的错 ...

  4. css预处理器Less

    css预处理器有:less.sass.stylus等 less是一种动态样式语言,属于css预处理器的范畴,它扩展了 CSS 语言,增加了变量.Mixin.函数等特性,使 CSS 更易维护和扩展. 使 ...

  5. 关于C语言中的预处理器的简单笔记

    在将源代码提交给编译器之前,C语言预处理器将对源代码做出一定修正.预处理器命令有很多如最常用的#include,#define命令. 预处理命令都是以#开头,一般放在代码的最左侧,通常定义的宏全部都是 ...

  6. C 语言编程 — 宏定义与预处理器指令

    目录 文章目录 目录 前文列表 宏 预处理器 预处理器指令 预处理器指令示例 预处理器指令运算符 宏定义 简单宏定义 带参数的宏定义 符号吞噬问题 使用 do{}while(0) 结构 预定义的宏 常 ...

  7. python预处理c语言_详解C语言编程中预处理器的用法

    预处理最大的标志便是大写,虽然这不是标准,但请你在使用的时候大写,为了自己,也为了后人. 预处理器在一般看来,用得最多的还是宏,这里总结一下预处理器的用法. #include #define MACR ...

  8. Sass:一种CSS预处理器语言

    http://sass-lang.com/ Sass是一种CSS预处理器语言,通过编程方式生成CSS代码.因为可编程,所以操控灵活性自由度高,方便实现一些直接编写CSS代码较困难的代码. 同时,因为S ...

  9. C程序设计语言现代方法14:预处理器

    目录 1. 预处理器工作原理 1.1 预处理器性质 1.2 预处理器主要功能 1.3 GCC编译过程及常用选项 1.3.1 GCC编译过程 1.3.2 编译选项实例 1.4 注意事项 2. 预处理指令 ...

  10. CSS预处理器语言:Sass、LESS、Stylus

    CSS预处理器语言:Sass.LESS.Stylus Sass 和 Less 都使用的是标准的 CSS 语法.默认 Sass 使用 .sass 扩展名, Less 使用 .less 扩展名,Stylu ...

最新文章

  1. 程序员如何写出更好的代码
  2. [Splay][线段树] jzoj P5662 尺树寸泓
  3. 430. Flatten a Multilevel Doubly Linked List | 430. 扁平化多级双向链表(DFS)
  4. c++ vector 一部分_为什么现在的手机都采用Type-C接口?它到底好在哪里?看完你就明白了...
  5. java文件重命名失败问题
  6. 58页PPT揭示图神经网络研究最新进展
  7. 这可能是最详细的Python文件操作
  8. Git(5)-- 获取 Git 仓库(git init 和 git clone命令)
  9. eclipse设置黑色主题
  10. steam游戏直连工具
  11. html嵌入百度地图无法显示
  12. DPDK Release 22.07
  13. 魔兽世界服务器维护后稀有会马上刷新么,魔兽世界:“七大稀有物品”最后一个,让无数LR玩家,蹲点等刷新...
  14. 计算机数学基础:斜率与截距、导数、权重的关系
  15. 计算机高考计划,职中高三计算机高考复习计划
  16. HTML短信验证码框,vue实现短信验证码输入框
  17. 多臂赌博机Multi-Armed Bandit(MAB)
  18. 滴滴快车奖励政策,高峰奖励,翻倍奖励,按成交率,指派单数分级(10月17日~10月23日)...
  19. java.lang.IllegalArgumentException: argument type mismatch
  20. Java日历设计思路

热门文章

  1. Android拍照识别身份证SDK
  2. dede常用标签(随时更新)
  3. CAD设置靶心的大小
  4. logback出现大量XXX_IS_UNDEFINED日志文件的问题
  5. Android系统篇(二)——Android编译核心Build系统
  6. 【技术干货】聊聊在大厂推荐场景中embedding都是怎么做的
  7. 进程双向通信c语言代码,进程间通信——管道(示例代码)
  8. 《java并发编程实战》第11章-性能与可伸缩性
  9. openGL贝塞尔曲面细分
  10. python将图片生成视频,和空白视频