#include预处理命令几乎使我们在第一次接触C的时候就会碰到的预处理命令,可我现在还不怎么清楚,这次争取一次搞懂。
一、#include预处理指令的基本使用
预处理指令可以将别处的源代码内容插入到现在的位置,可以标识出某一段程序代码只有在特定条件下才会被编译。
#include预处理指令告诉预处理器,将指定头文件的内容插入到预处理器指令的相应位置。
指定插入头文件有两种方式:
1 #include <filename> 2 #include "filename"
当要包含标准链接头文件,或者实现版本所提供的头文件时,应该使用第一种格式(尖括号的格式)。范例:
1 #include <math.h> // 数学函数的原型
如果想包含程序所开发出的文件,则使用第二种格式(双引号的格式)。
#include预处理指令所插入的文件,通常文件扩展名是.h,并且文件内部不外乎是函数原型、宏定义、类型定义。
头文件中最常用的形式如下:
字面常量——例如,stdio.h中的EOF、NULL和BUFFSIZE(标准I/O缓冲区大小)。
宏函数——例如,getc(stdin)通常用getchar()定义,而getc()经常用于定义较复杂的宏,
头文件ctype.h通常包含ctype系列函数的宏定义。
函数声明——例如,string.h头文件(一些旧的系统中是strings.h)包含字符串函数系列的函数声明。在ANSI C和后面的标准中,
函数声明都是函数原型形式。
结构模板定义——标准I/O函数中使用FILE结构,该结构包含了文件和文件缓冲区的信息,FILE结构在头文件stdio.h中。
类型定义——标准I/O函数使用指向FILE的指针作为参数。通常stdio.h用#define或typedef把FILE定义为指向结构体的指针。
类似的,size_t和time_t类型也定义在头文件中。
只要使用#include预处理指令,这些定义就可以被任何源代码文件所使用。
可以在#include预处理指令中使用宏。如果使用宏,此宏被取代之后,必须生成正确的#include预处理指令。
#ifdef _DEBUG_
#define MY_HEADER "myProject_dbg.h"
#else
#define MY_HEADER "myProject.h"
#endif
#include MY_HEADER
上述代码被处理时,_DEBUG_宏时有定义的,那么预处理器会插入myProject_dbg.h的内容,否则会插入myProject.h的内容。
二、预处理如何找到头文件
不同的C语言实现版本有自己的搜索路径,想办法找出#include预处理指令所要求包含的文件。"文件名是否区分大小写"由实现版本自行决定。
对使用尖括号的方式包含的文件,预处理器通常会在特定的系统路径下搜索,例如Unix系统的/usr/local/include和/usr/include。
对于使用双引号指定的文件("filename")来说,预处理器通常会在当前目录下寻找,通常也就是包含此程序其他原始文件的目录。
如果在当前目录下没有找到,那么预处理也会搜索系统的include路径。filename可以包含路径。如果真的包含路径,则预处理只会到此目录中寻找。
也可以为#include预处理器指定自己的是搜索路径,做法可以使用编译器命令行选项,或在环境变量中加入搜索路径,这样的环境变量常常被命名为INCLUDE。
具体做法参见所使用编译器的文件说明。
三、嵌套的#include预处理指令
#include预处理器可以被嵌套,也就是说,通过#include预处理器而插入的源代码文件本身也可以有#include预处理指令。
预处理器允许至多15层的嵌套包含(nested include)。因为头文件有时候会被彼此互相包含,很容易发生相同的一个文件被多次包含的情况。
例如,假设myProject.h包含如下代码:
1 #include<stdio.h>
如果源代码文件包含下面的#include预处理指令,就会包含两次stdio.h,一次直接包含,一次间接包含:
1 #include <stdio.h> 2 #include "myProject.h"
然而,可以轻易的利用条件式编译的预处理指令避免多次包含相同的文件。
四、避免多次包含
1 #ifndef INCFILE_H_ 2 #define INCFILE_H_ 3 /* incfile.h实际的内容写在这里 */ 4 #endif /* INCFILE_H_ */
第一出现包含incfile.h的预处理指令时,INCFILE_H_宏是没有被定义的。
预处理因此插入#ifndef和#endif之间的内容,包含了“定义INCFILE_H_宏”的预处理指令。
一旦后续包含incfile文件,#ifndef的定义就不会成真,预处理会忽略#ifndef和#endif之间的内容。
五、定义自己的头文件
可以定义自己的头文件,通常其扩展名是.h,可以使用操作系统允许的任何文件名。
理论上不一定对头文件使用扩展名.h,但是它是大多数C程序员惯用的扩展名,所以最好使用它。
头文件不能包含实现代码,即可执行代码。头文件可以包含声明,但不能包含函数定义或初始化的全局数据。
函数定义和初始化的全局数据应该放在扩展名为.c的源文件中。
可以在头文件中放置函数原型、struct类型定义、符号定义、extern语句和typedef。
一个常用的技巧是创建一个头文件,它含有程序中所有函数的原型以及类型声明。
然后讲这些作为一个独立的单元来管理,并放在程序源文件的开头。
如果源文件包含多个头文件,必须避免信息的重复,这个可以利用条件编译避免。
任何文件的内容都可以通过#include这种方法包含到程序中,只要在引号中指定文件名即可。
六、管理多个源文件
复杂程序总是包含多个源文件和头文件。
理论上,可以使用一个#include指令把另一.c源文件的内容包含在当前的.c文件中,但是通常这是不必要的,甚至不合理。
在.c文件中应该只使用#include指令包含头文件。当然,头文件常常包含#include指令,以包含其他的头文件。
复杂程序中的.c文件一般包含一组相关的函数。在编译开始前,预处理器会插入#include指令指定的每个头文件的内容。
编译器从每个.c源文件中创建有个对象文件。所以的.c文件都编译好之后,链接器就会把对象文件合并到有个可执行的模块中。
如果C编译器带有交互式的开发环境,则通常提供项目功能,即一个项目包含并管理组成程序的所有源文件和头文件。
这通常意味着,不必担心在创建可执行文件的过程中把文件存储在什么地方,开发环境会处理。
但对于大型应用程序,最好自己创建一个适当的文件夹结构,而不是让IDE把所有的文件都放在同一个文件夹中。
七、外部变量
一个由几个源文件组成的程序通常需要使用在其他文件内定义的全局变量。
为此,可以使用关键字extern将它们声明为外部变量。
例如,使用如下语句在其他文件内定义了一个全局变量(是在任何函数之外):
1 int number = 0; 2 double in_to_mm = 2.54;
然后,要在一个函数中访问访问,可以使用一下语句指定这些变量是外部的:
1 extern int number; 2 extern double in_to_mm;
这些语句不会创建这些变量,只会告诉编译器,这些名称在文件外定义,但可以应用于源文件的其他地方。
指定为extern的变量在程序的外部声明和定义,通常实在另一个源文件中。
如果要让当前文件中的所有函数都可以访问这些外部变量,必须在文件的开头,在任何函数的定义之前将它们声明为外部变量。
程序是由几个文件组成的,可以把所有已初始化的全局变量放在一个文件的开头,将所有的extern语句放在另一个头文件中。
使用include语句包含该头文件,所有的extern语句就合并到需要访问这些变量的程序文件中。
每个全局变量在文件中只能定义一次,但是,全局变量可以根据需要在许多文件中声明为外部变量。
八、多文件编译
一般都不会用#include直接包含C文件,而是用头文件把需要的东西(其他源文件中的函数、变量、类型)放在一起,引用头文件。
编译的时候,命令要链接多个源文件。