编译程序之前,先由预处理器检查程序(因此称为预处理器)。根据程序中使用的预处理器指令,预处理用符号缩略语所代表的内容替换程序中的缩略语。

预处理器可以根据你的请求包含其他文件,还可以让编译器处理哪些代码。预处理器不能理解C,它一般是接受一些文本并将其转换成其他文本。-- (C Primer Plus中文第五版)

graph TD;
写好的C文件 --> 编译器翻译,为预处理做准备
编译器翻译,为预处理做准备 --> 预处理器寻找肯能存在的预处理指令,开始预处理

预处理符号

明显常量 #define

#define定义的作用域从定义出现的位置开始直到文件的结尾。

这里表明在函数里使用#define,凡是在它定义的后面的所有函数都是可以用的:

#include <stdio.h>
void pp();
int main() {
#define FF 10
pp();
} void pp(){
printf("FF: %d ",FF);
}

输出:

FF: 10

要时刻记住:#define会把它之后的文件里的所有符合要求的文本替换掉。

每个#define行由三部分组成:

  • 指令自身 #define
  • 缩略语,即宏(macro)

    宏的名字中不允许有空格,必须遵循C变量名规则
  • 替换列表或者主体(body)

预处理器在程序中发现了宏的实例后,总会用实体代替该宏。从宏变成最终的替换文本的过程称为宏展开(macro expansion)。

宏可以分两种:

  • 类对象宏 object-like macro

    宏用来代表值
  • 类函数宏 function-like macro

    外形和作用都与函数相似
#define TWO 2;
#define PX printf("X is: %d\n",x)
#define FMT "X+1 is: %d\n" int main() {
int x =TWO;
PX;
printf(FMT,x+1);
}

输出:

X is: 2
X+1 is: 3

上面的程序其实被预处理器改成了:

int x = 2;
printf("X is: %d\n",x);
printf("X+1 is: %d\n",x+1);
//先变成int y = TWO * TWO;
int y = 2*2;
printf("y is: %d\n",y);

注意,宏展开过程中,是进行替换,并不进行计算。C编译器在编译时对所有常量表达式(只包含常量的表达式)求值,所以实际相乘过程发生在编译阶段,而不是预处理阶段。预处理器不进行计算,它只是按照指令进行文字替换操作。

宏展开过程中,会用宏的等价(即body)来替换文本,如果宏的body本身还含有宏的话,会继续展开这些宏。但是,双引号中的与宏缩略语一样的字符串无法被替换。

重定义常量

假设一个缩略语被定义后又在同文件中被定义,这样被称为重定义(redefinng a constant)。有的编译器会对这样提出警告,但允许重定义存在,有的则直接报错。

#define SIX 3 * 3
#define SIX 3 * 3
//上面这样的重定义会被编译器认为是重复定义,是相同的 #define SIX 3*3
//这样的重定义与上面两种是不同的

#define中使用参数

类函数宏的定义中,用圆括号阔气一个或多个参数,随后这些参数出现在替换部分。

#define SQUARE(X) X*X
//一个参数X //使用
int y = SQUARE(2);

宏调用和函数调用存在着区别:

程序运行时,函数调用把参数的值传递给函数,而编译前,宏调用把参数的语言符号传递给程序,仅仅是替换字符,而不计算。

#define  SQUARE(X) X*X
int main() {
int x = 2;
int y = SQUARE(x);
printf("SQUARE(x) is: %d\n",y);
printf("SQUARE(x+2) is: %d\n",SQUARE(x+2));
}

输出:

SQUARE(x) is: 4
SQUARE(x+2) is: 8

按理说square(2+2)应该是16啊,怎么会是8呢?原来像刚才上面说的,预处理只是替换,因此SQUARE(x+2)中的Xx+2替换,最后成了x+2*x+2*优先级高,,因此程序运行时先计算2*x,再加上x2,也就成了8。要想实现平方的效果,需要重新定义:

#define SQUARE(X) ((X)*(X))

即使这样定义,还是无法避免自增、自减情况下的错误:

int x = 3;
int a = SQUARE(++x);

这里替换成++x字符后,进行了两次增量运算,最后结果肯定不是平方了。因此,在宏中不要使用增量或减量运算符而且一定要充分的使用圆括号来保证正确的运算顺序

在类函数宏中使用#运算符

上面说了在引号表示的字符串无法替换掉宏参数,,但是使用#预处理运算符,可以把传入的参量转化为文本替换到字符串里。

#define SQUARE(X) (X)*(X)

#define PF(X) printf("The square of " #X " is : %d\n",SQUARE(X))
int main() {
int x = 10;
PF(x);
PF(2+4);
}

输出:

The square of x is : 100
The square of 2+4 is : 36
在宏中使用##运算符

##运算符把两个语言符号组合成单个语言符号:

#define XVAR(X) x ## X

int XVAR(2) = 11;//声明了一个标识符为x2的变量
可变宏:...__VA_ARGS__

宏定义中参数列表的最后一个参数为省略号,预定义宏__VA_ARGS_就可以被用在替换部分,以代表省略号省略了什么。

#define PF(X,...) printf("Result " #X " : " __VA_ARGS__)

int main() {
PF(1,"%d\n",10);
PF(2,"%d's power is %d\n",4,16);
}

输出:

Result 1 : 10
Result 2 : 4's power is 16

省略号只能代替最后的宏参数。

#define WRONG(X, ... ,Y) #X #__VA_ARGS__ #Y)//错误

这里有个有趣的现象:一般想要打印字符串,字符串都得用双引号括起来,这里不用:

#define STRING(... ) #__VA_ARGS__

int main() {
printf(STRING(abcdefg));
}

输出:

abcdefg

C Primer Plus--C预处理器和C库(1)的更多相关文章

  1. C Primer Plus之C预处理器和C库

    编译程序前,先由预处理器检查程序(因此称为预处理器).根据程序中使用的预处理器指令,预处理器用符号缩略语所代表的内容替换程序中的缩略语. 预处理器不能理解C,它一般是接受一些文件并将其转换成其他文本. ...

  2. C Primer Plus--C预处理器和C库(2)

    目录 #include指令 头文件 其他指令 #undef 条件编译 内联函数 #include指令 #include <头文件.h>//在标准系统目录中寻找头文件 #include &q ...

  3. C预处理器和C库

    #define #include #undef #ifdef #else #endif #if #elif #else #endif 预处理宏: p463 _ _fun_ _是预定义标识符(函数作用域 ...

  4. 第 16 章 C 预处理器和 C 库(可变参数:stdarg.h)

    /*------------------------------------------------- varargs.c -- use variable number of arguments -- ...

  5. 第 16 章 C 预处理器和 C 库(string.h 库中的 memcpy() 和 memmove())

    /*----------------------------------------- mems.c -- 使用 memcpy() 和 memmove() ---------------------- ...

  6. 第 16 章 C 预处理器和 C 库(qsort() 函数)

    /*---------------------------------------- qsorter.c -- 用 qsort() 排序一组数字 --------------------------- ...

  7. 第 16 章 C 预处理器和 C 库(直角坐标转换极坐标)

    /*------------------------------------- rect_pol.c -- 把直角坐标转换为极坐标 ---------------------------------- ...

  8. 第 16 章 C 预处理器和 C 库(预定义宏)

    /*------------------------------------- predef.c -- 预定义宏和预定义标识符 ------------------------------------ ...

  9. 第 16 章 C 预处理器和 C 库(条件编译)

    /*-------------------------------------- names_st.h -- names_st 结构的头文件 ----------------------------- ...

随机推荐

  1. "Sed" 高级实用功能汇总

    sed命令有两个空间,一个叫pattern space,一个叫hold space.这两个空间能够证明人类的脑瓜容量是非常小的,需要经过大量的训练和烧脑的理解,才能适应一些非常简单的操作. 不信看下面 ...

  2. Python进阶----类的结构(公有成员 , 私有成员(私有属性,私有方法),类方法,静态方法,属性) ,isinstance 和issubcalss ,元类(type())

    Python进阶----类的结构(公有成员 , 私有成员(私有属性,私有方法),类方法,静态方法,属性) ,isinstance 和issubcalss ,元类(type()) 一丶类的结构细分    ...

  3. JAVA项目之注册

    public class RegisterServlet extends HttpServlet { private UsersService usersService = new UsersServ ...

  4. 在eclipse下给android应用添加jar包

    右键工程,Build path,java build path,选择libraries在右边的按钮中点击“Add Library”选择“User library”,点击“下一步”点击“User lib ...

  5. Celery:Daemonization

    参考文档:http://docs.celeryproject.org/en/latest/userguide/daemonizing.html#daemonizing

  6. CNN原理

    卷积神经网络(Convolutional Neural Network)的结构类似于神经网络,可以看做是对其的改进.它利用局部连接.权值共享.多核卷积.池化四个手段大大降低了参数的数目,使得网络的层数 ...

  7. sqlserver 备份集中的数据库备份与现有的 'XXX' 数据库不同。

    需求是将Ecology复制一份出来用于发布测试程序. 方案一 正确步骤 1.新建数据库Ecology2 2.选中Ecology2 点击右键:任务--还原--文件和文件组 选中源设备--点击右侧...按 ...

  8. 【python】文件操作

    基本语法 open("文件名","访问方式") # 1. 打开文件 file = open("README.txt") # 2. 读取文件内 ...

  9. ansible运维工具,dhcp,cobbler

    1.借助Ansible Playbook自动化搭建LNMP环境(可借助yum) 通过yum方式安装ansible,先安装epel仓库 yum install epel-release 安装epel仓库 ...

  10. 肖哥HCNP-正式篇笔记

    21.网工学习环境准备. 一. 关掉所有杀毒软件及管家如阿健. 二. 安装环回网卡 (一定要先安装.) 1. 计算机设备管理 2. 在右侧最上端计算机名上方右键,点击过时硬件. 3. 下一步.手动选择 ...