C语言规定:源程序中可以加入一些预处理指令。但预处理指令并不是C语言本身的组成部分,编译器不能识别它们,不能直接对这些指令进行编译。在使用时需要以“#”开头,用以与C语言区分。
所谓预处理,就是指源程序在进行编译的第一遍扫描(词法分析和语法分析)之前所做的工作由预处理程序完成。当对一个源程序进行编译时,系统将自动引用预处理程序对源程序中的预处理部分进行处理(代替),处理完毕自动进入对源程序的编译。
在C语言源程序中允许用一个标识符来表示字符串,称为宏。宏定义是以“#”开头的,均为预处理命令,同时使用“define”作为宏定义命令。宏定义,根据标识符的形式,大体可以分成两种,分别是变量式宏定义和函数式宏定义。变量式宏定义的书写类似于变量的声明,用来定义常量;函数式宏定义的书写类似于函数,用来定义稍复杂些的带参数的表达式。
变量式宏定义也就是不带参数的宏定义,其定义的一般形式为:
#define 标识符 字符串
其中:字符串部分可以为浮点数、运算符、字符串、表达式等。比如:
#define PI 3.1415926
#define LARGE >
#define NEWLINE "\n"
#define M (x*x+2*x)
在使用标识符代替表达式的时候,需要注意:
文件中的宏定义默认是从定义就有效到文件结束,但也可以对其作用范围进行圈定,使用的方法为:
#define /* 宏定义开始 */
... /* 宏定义内容 */
#undef /* 宏定义结束 */
在宏定义的过程中,宏也是可以嵌套定义的。比如:
#define A (1+2)
#define B A*A
#define C B+B
那么,宏展开对应为:
#define A (1+2)
#define B (1+2)*(1+2)
#define C (1+2)*(1+2)+(1+2)*(1+2)
函数式宏定义也就是带参数的宏定义,其定义的一般形式为:
#define 标识符(参数列表) 字符串
其中:字符串中应包含标识符中的参数。比如:
#define MAX(a,b) a>b?a:b
#define MUL(a,b) (a*b)
对于这两个函数我们乍一看没有什么问题,我们看一下下面的例子:
#include
#define MAX(a,b) a>b?a:b
#define MUL(a,b) a*b
int main()
{
int a, b, c, d;
scanf_s("%d %d %d %d", &a, &b, &c, &d);
printf("MAX(%d,%d)=%d\n", a + b, c + d, MAX(a + b, c + d));
printf("MUL(%d,%d)=%d\n", a + b, c + d, MUL(a + b, c + d));
return 0;
}
这段程序的运行结果为:
3 4 5 6
MAX(7,11)=11
MUL(7,11)=29
请按任意键继续. . .
我们向函数式宏定义的参数传递的是表达式a+b,c+d,而不是单纯的一个变量,这个时候我们进行代替看一下:
MAX(a + b, c + d)=a + b > c + d ? a + b : c + d
MUL(a + b, c + d)=a + b * c + d
也就是说,在函数式宏定义的时候,我们必须将每个参数用小括号括起来:
#define MAX(a,b) ((a)>(b)?(a):(b))
#define MUL(a,b) ((a)*(b))
宏函数和函数之间的主要区别:
通常宏定义必须是单行的,但是也可以使用多行来定义一个宏,定义的方法就是使用反斜杠“\”。比如:
#define MAX(a,b) \
((a)>(b)?(a):(b))
这里第一行的末尾必须是反斜杠,第二行的开头可以为了美观增添一些空格。也就是说,反斜杠后面必须紧跟回车符。
宏定义与操作符的区别:
宏定义是C语言的预处理命令之一,它是一个替代操作,不进行计算和表达式求解,不占用内存和编译时间。
要终止宏名的作用域,可以使用命令:
#undef 标识符
预定义宏是gcc自带的宏定义,用户不需要定义就可以直接用来使用的。分别有__FUNCTION__、__FILE__、__LINE__,它们表示的是函数名、文件名、行号。例如:在文件hello.c中:
#include
int main()
{
printf("%s,%s,%d",__FUNCTION__,__FILE__,__LINE__);
return 0;
}
那么输出结果为:main,hello.c,5
一般在某些C语言代码中,会发现这样的内容,它们的含义见后面:
#define ABC(x) #x //字符串化
#define DAY(x) day##x //连接符号
例如:
#include
#define ABC(x) #x
#define DAY(x) day##x
int main()
{
int day1=10;
printf(ABC(ab\n));
printf("%d\n",DAY(1));
return 0;
}
最终的输出为ab 10。
文件包含指令的功能是把指定的文件插入该指令行位置取代指令行,从而把指定的文件和当前的源文件连成一个源文件。我们通常以头文件来编写这些被包含的文件,也就是以.h作为这些文件的扩展名。当然不是非要这样命名才可以。也可以使用.c为扩展名,或者干脆不写扩展名,这样都是允许的。只是使用.h更为普遍而已。
文件包含通常有以下两种形式:
#include "文件名"
#include <文件名>
两种文件包含方式的区别:
所谓“条件编译”,就是程序的内容指定编译的条件。一般情况下,源程序的所有行都参与编译,但是我们希望部分行在满足一定条件的情况下再进行编译,这就要用到条件编译。
一般条件编译的形式有#ifdef、#ifndef、#if三种形式,下面依次介绍:
#ifdef 标识符
程序段1
#else
程序段2
#endif
作用是:如果标识符已经被定义了,则对程序段1进行编译;否则编译程序段2。但也可以不写#else分句,也是没有问题的。
其中:标识符是用#define指令进行定义的,程序段可以是语句,可以是命令行。
#ifndef 标识符
程序段1
#else
程序段2
#endif
作用是:如果标识符没有被定义了,则对程序段1进行编译;否则编译程序段2。但也可以不写#else分句,也是没有问题的。
#if 表达式
程序段1
#else
程序段2
#endif
作用是:如果if表达式的值非0,则对程序段1进行编译;否则编译程序段2。但也可以不写#else分句,也是没有问题的。
由于嵌套包含文件的原因,一个头文件可能会被多次包含在一个源文件中,而使用条件指示符就可以防止这种头文件的重复处理。例如:
#ifndef COMMONEILE_H
#define COMMONFILE_H
...(.h文件内容)
#endif
条件指示符检测COMMONFILE_H标识符是否已经被定义了,如果没有定义,就定义并包含头文件到源文件;如果定义了,说明该头文件已经被包含进去了,就不执行接下来的内容,从而达到防止文件的嵌套包含。
使用条件编译还是需要判断某个标识符是否被定义,这还是需要对源代码进行添加和删除操作的,gcc提供了一种方式可以避免。即 gcc -D,使用方法为:
gcc hello -D标识符名 -o hello.c
这就相当于在hello.c文件中define了一个标识符,注意-D后面不需要加空格。
assert()宏是在标准库
assert(a == b);
当a和b相等时,表达式的结果为true,也就是1;否则为false,也就是0。
断言机制可以和符号常量NDEBUG配合使用,由NDEBUG的符号决定是否启用断言机制。
如果在
#define NDEBUG
#include
这样,源文件中使用的assert()就会被忽略掉。在某些系统里,断言机制默认是不可使用的,在这种情况下,需要开启断言机制:
#undef NDEBUG
#include