在C程序中,以 # 开头的命令就是预处理命令,这些命令都是放在函数之外,而且一般都放在源文件的前面,如下面的两条命令:
#include <stdio.h>
#define PI 3.1415926
宏可以看做是一些命令的集合,在预编译阶段将 宏名 展开替换为后面的替换体. 在 c 语言中用 #define 来定义宏,格式如下:
#define macro_name(var_list) stuff
// macro_name 为宏名,va_list 为宏的参数,参数可选
// stuff 为宏的替换体,是一个字符串,也是可选的
// var_list 与 macro_name 之间不能有空格
// stuff 与 macro_name 之间用空格隔开
看下面例子:
对于宏定义,以下几点需要注意:
1)宏定义只是简单用宏名来表示一个字符串,字符串中可以含任何字符,可以是常数、也可以是表达式,预处理程序不对它进行任何检查
2)宏定义不是说明或语句,在行末尾不必加上分号,如果加上分号会连分号一起置换
3)宏定义必须写在函数之外,其作用域为从宏定义命令到源程序结束,如果要提前终止其作用可以使用 #undef 命令,也就是说宏定义命令的作用域为:
#define 宏
....作用域
#undef 宏
4)宏名如果在源程序中使用引号括起来,则预处理程序不会对宏进行替换,如下:
5)宏定义允许嵌套
6)习惯上宏名用大写字母表示,如上面的PI
7)可以用宏定义表示数据类型,使程序书写方便,如下:
#define INTEGER int
注意区别宏定义和 typedef 说明符:
宏定义只是简单的字符串替换,是在预处理阶段完成的,而 typedef 是在编译时处理的,它不是简单的替换而是对类型说明符进行重命名,被命名的标识符具有类型定义说明的功能;
来看下面的例子:
由于 define 仅仅是做字符串的替换,所以23行相当于:
char* x, y;
x为指针类型,而 y 为char 类型,所以打印的结果如上;而 typedef 是对类型说明符进行重命名,所以24行相当于:
char *a, *b;
所以a和b都是指针类型;注意,对比18和19行可以看到,宏定义语句后不带分号,而 typedef 语句后带分号,这也是二者重要的区别;
8)宏的字符串可以使用 来续行,目的是增加代码的可读性,如下:
#define PFINT printf("hello world!n");
printf("goodbye world!n");
C语言中允许宏带有参数,在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数;
在调用带参数的宏是,不仅宏要展开,而且要用实参去代换形参,带参宏的定义的一般形式为:
#define 宏名(形参表) 字符串
调用带参宏的形式:
宏名(实参列表);
上面的第8行调用了带参宏,相当于:max = (x>y)?x:y;打印结果如下:
对于带参宏,以下几点需要特别注意:
1)在定义带参宏时,宏名和形参列表之间不能存在空格
例如把上面的第2行写成下面的形式是错误的:
#define MAX (a, b) (a>b)?a:b
// 此时如果引用宏 MAX,会将 (a, b) (a>b)?a:b 一起引用
2)在带参宏的定义时,形参不分配内存单元,因此不必作类型定义;而宏调用时,实参要用具体的值去代换形参,因此必须作类型说明;
这一点与函数不同,函数的形参和实参是两个不同的量,有各自的作用域,调用时需要把实参通过“值传递”的方式传递给形参,而在带参宏中,仅仅是符号代换,不存在值传递;
3)在宏定义中,形参是标识符,而宏调用过程中的实参可以是表达式;如下:
4)在宏定义中,字符串内的形参通常需要用括号括起来以避免出错,来看下面的例子:
如果将括号去掉,改为如下:
此时第22行的宏调用时,替换后的结果如上面的注释所示,所以打印结果如下:
所以,不建议用宏来替换表达式,这样的结果很可能会出错!!!
c语言中不支持嵌套注释,比如下面的例子:
/*
这是注释
/*
这也是注释
*/
*/
这个时候就可以使用条件编译,它包括以下几种方式:
第一种形式:
#ifdef 标识符
程序段1
#else
程序段2
#endif
如果标识符已经被 #define 命令定义过则执行程序段1,否则执行程序段2;也可以是下面的形式:
#ifdef 标识符
程序段1
#endif
第二种形式:
#ifndef 标识符
程序段1
#else
程序段2
#endif
第三种形式:
#ifdef 常量表达式
程序段1
#else
程序段2
#endif
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值
预定义符号
预定义符号都是语言内置的,可以认为是系统预定义的宏,主要包括:
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义