一、文件包含指令
当预处理器发现#include 指令时,会查看后面的文件名并把文件的内容包含到当前文件中,即替换源文件中的#include指令,这相当于把被包含文件的全部内容输入到源文件#include指令所在的位置。
#include 指令有两种形式:
- #include <stdio.h> 文件名在尖括号中 查找系统目录
- #include “mystuff” 文件名在双引号中 查找当前目录
尖括号告诉预处理器在标准系统目录中查找该文件。双引号告诉预处理器首先在当前目录查找该文件,如果未找到再查找标准系统目录。
二、define的用法
1.定义标识符
语法:
#define name stuff
#define MAX 100
#define reg register //为register 这个关键字,创建一个简短的名字
每行#define (逻辑行)都由3部分组成:
第一部分:#define 指令本身;
第二部分:是选定的缩写,也称为宏(宏的名称中不允许有空格);
第三部分:指令行的其余部分,称为替换列表或替换体
一旦预处理指器在程序中找到宏的示例后,就会用替换体代替该宏(有例外).从宏变成最终文本的过程称为宏展开.
有下面两种情况:在define 定义标识符的时候,要不要在后面加上 ; 呢?最好不要!
#define MAX 100;
#define MAX 100
if(condition)
{
max =MAX; //如果在定义标识符的时候,后面加了; 在后面使用时有可能会发生语法错误
printf("%d\n",MAX);
}
2.在#define中使用参数
声明方式:看上去很像函数调用,但是他的执行过程和函数调用完全不同。
#define name(paramentlist) stuff //类函数宏定义的圆括号中可以有一个或多个参数,随后这些参数出现在替换体中
其中paramentlist是一个由逗号隔开的符号表,他们可以出现在stuff中
注意:参数列表的左括号必须与name紧邻,如果两者之间有空格,其中的空格也会被解释为stuff的一部分
在上面的宏定义过程中可以到每个参数都加了括号,这样可以尽可能避免出现因宏的特性而出现的问题,但不能100%避免。
这是因为预处理器不做计算,不对表达式求值,只进行替换。
如:定义下面一个宏
#define SQUARE(x) x*x
printf("%d\n",SQUARE(2)); //结果为 4
printf("%d\n",SQUARE(2+3)); //结果为 11
/*
这是因为预处理器不做计算,不对表达式求值,只进行替换,
printf("%d\n",SQUARE(2+3))替换之后的结构为printf("%d\n",2+3*2+3);
所以输出结果为11
*/
要想解决这个问题就需要在宏定义时就要使用足够多的圆括号来确保运算和结合的正确顺序;
#define SQUARE(x) (x)*(x)
但这样并未解决所有的问题:
#define SQUARE(x) (x)*(x)
100/SQUARE(2) //我们预想的结果是 100/2*2)==25,
//但实际的结果是 (100/2)*2 == 100;
所以我们应该这样定义:使用足够多的圆括号来确保运算和结合的正确顺序;
#define SQUARE(x) ((x)*(x))
即使这样,也无法保证结果100%的正确:当参数为 x++,++x时i,其结果是未知的,如:
#define SQUARE(x) ((x)*(x))
printf("%d",SQUARE(++3) ); //替换后的结果为:((++3)*(++3))其结果根据编译器的不同,结果并不唯一
解决这个问题的最简单的方法是:避免使用++x 作为宏参数。(但是++x可以作为函数的参数,因为编译器会对++x求值得4后,将4传递给函数)
1、在宏参数和#define定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
2、当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索
3.用宏参数创建字符串:#运算符
在类函数宏的替换中,#作为一个预处理运算符,可以将宏参转换成字符串。例如,x是一个宏形参,那么#x就是转换为字符串“#x”的形参名。这个过程称为字符串化。
#define PRINT(X) printf("the square of" #X "is %d\n",((x)*(x)))
PRINT(2+3); //输出结果为:the square of 2+3 is 25
//当宏调用时:2+3 替换 #x ,由字符串的串联特性将这些字符串与其他字符串组合,形成最终的字符串
4.预处理粘合剂:##运算符
与#运算符类似,##运算符可以用于类函数宏的替换部分。而且,##还可以用于对象宏的替换部分。##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符;
例如:
#define ADD_TO_NUM(NUM,VALUE) sum##NUM + = VALUE
. . .
ADD_TO_NUM(5,10); //作用是:给sum5增加10
注:这样连接必须产生一个合法的标识符。否则其结果是未定义的。
##经典应用案例:
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n "= %d\n",x ## n)
int main()
{
int XNAME(1) = 14; //结果变成:int x1 = 14
int XNAME(2) = 20; //结果变成:int x2 = 20
int x3 = 30;
PRINT_XN(1); //结果变成:printf("x1 = %d\n",x1);
PRINT_XN(2); //结果变成:printf("x2 = %d\n",x2);
PRINT_XN(3); //结果变成:printf("x3 = %d\n",x3);
return 0;
}
//输出结果
//x1 = 14
//x2 = 20
//x3 = 30
总结
- 宏名中不能有空格,但是在替换字符串中可以有空格.
- 用圆括号把宏的参数和整个替换体扩起来
- 用大写字母表示宏函数名称
- 如果打算使用宏来加快程序的运行速度,那么首先要确定使用宏和使用函数是否会导致较大的差异。在程序中只使用一次的宏无法明显减少程序的运行时间。在嵌套循环中使用宏更有助于提高效率。