文件包含指令,即 #include 指令,使我们比较容易处理一组 #define 指令以及说明等。在源程序文件中,任何形如:
1 |
或
1 |
的行都被替换成由文件名所指定的文件的内容。如果文件名用引号括起来,那么就在源程序所在位置查找该文件;如果在这个位置没有找到该文件,或者如果文件名用尖括号<
与>
括起来,那么就按实现定义的规则来查找该文件。被包含的文件本身也可包含 #include 指令。
在源文件的开始处一般都要有一些 #include 指令,或包含 #define 语句与 extern 说明,或访问诸如 <stdio.h>
等头文件中库函数的函数原型说明。(严格地说,这些没有必要做成文件。访问头文件的细节依赖于实现。)
对于比较大的程序, #include 指令是把各个说明捆在一起的优选方法。它使所有源文件都被提供以相同的定义与变量说明,从而可避免发生一些特别讨厌的错误。自然地,如果一个被包含的文件的内容做了修改,那么所有依赖于这个被包含文件的源文件都必须重新编译。
1 |
它是一种最简单的宏替换——出现各个的名字都将被替换文本替换。 #define 指令中的名字与变量名具有相同的形式,替换文本可以是任意字符串。正常情况下,替换文本是 #define 指令所在行的剩余部分,但也可以把一个比较长的宏定义分成若干行,这时只需在尚待延续的行后加上一个反斜杠 \
即可。 #define 指令所定义的名字的作用域从其定义点开始到被编译的源文件的结束。在宏定义中也可以使用前面的宏定义。替换只对单词进行,对括在引号中的字符串不起作用。例如,如果 YES 是一个被定义的名字,那么在 printf ( “YES” ) 或 YESMAN 中不能进行替换。
例如:
1 |
|
但这种做法往往会有潜在的问题,通常不推荐这么做。
我们想看第二行的表达式展开成什么样,可以用gcc的-E
选项或cpp
命令:
1 | $ cpp main.c |
可以用 #undef 指令取消对宏名字的定义,这样做通常是为了保证一个调用所调用的是一个实际函数而不是宏:
1 |
|
下面给出的用法模式可以避免使用宏带来的问题; 如果你要使用宏, 尽可能遵守:
在函数式宏定义中,#运算符用于创建字符串,#运算符后面应该跟一个形参(中间可以有空格或Tab),例如:
1 |
|
用cpp命令预处理之后是"hello␣world",自动用"号把实参括起来成为一个字符串,并且实参中的连续多个空白字符被替换成一个空格。
预处理运算符 ##为宏扩展提供了一种连接实际参数的手段。如果替换文本中的参数用 ## 相连,那么参数就被实际参数替换, ## 与前后的空白符被删除,并对替换后的结果重新扫描。例如,下面定义的宏 paste 用于连接两个参数:
1 |
从而宏调用 paste(name, 1)
的结果是建立单词 name1
。
在预处理语句中还有一种条件语句,用于在预处理中进行条件控制。这提供了一种在编译过程中可以根据所求条件的值有选择地包含不同代码的手段。
#if
语句中包含一个常量整数表达式(其中不得包含 sizeof、类型强制转换运算符或枚举常量),若该表达式的求值结果不等于 0时,则执行其后的各行,直到遇到 #endif
、#elif
或 #else
语句为止(预处理语句 #elif
类似于 if
语句的 else if
结构)。在 #if
语句中可以使用一个特殊的表达式 defined (名字)
:当名字已经定义时,其值为 1;否则,其值为 0。
例如,为了保证 hdr.h 文件的内容只被包含一次,可以像下面这样用条件语句把该文件的内容包围起来:
1 | #if !defined(HDR) #define HDR /* hdr.h文件的内容*/ |
被 #if
与 #endif
包含的第一行定义了名字 HDR,其后的各行将会发现该名字已有定义并跳到 #endif
。还可以用类似的样式来避免多次重复包含同一文件。如果连续使用这种,那么每一个头文件中都可以包含它所依赖的其他头文件,而不需要它的用户去处理这种依赖关系。
下面的预处理语句序列用于测试名字 SYSTEM 以确定要包含进哪一个版本的头文件:
1 |
当需要测试一个名字是否已经定义时,可以使用两个特殊的预处理语句: #ifdef
与 #ifndef
。可以使用 #ifdef
将上面第一个关于 #if
的例子改写如下:
1 |
|
最后通过下面的例子说一下#if后面的表达式:
1 |
#if defined x
相当于#ifdef x
,而#if !defined x
相当于#ifndef x
。在这个例子中,如果x这个宏有定义,则把defined x替换为1,否则替换为0,因此变成#if 0 || y || VERSION < 3
。#if 0 || y || 2 < 3
。#if 0 || 0 || 2 < 3
,注意,即使前面定义了一个变量名是y,在这一步也还是替换成0,因为#if的表达式必须在编译时求值,其中包含的名字只能是宏定义。0 || 0 || 2 < 3
像C表达式一样求值,求值的结果是#if 1,因此条件成立。#include <filename.h>
格式来引用标准库的头文件(编译器将从标准库目录开始搜索)。#include "filename.h"
格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。extern int value
这类声明。1 | /* |
1 | /* |