文件包含意义
C语言文件包含处理在程序开发中会给我们的模块化程序设计带来很大的好处,通过文件包含的方法把程序中的各个功能模块联系起来是模块化程序设计中的一种非常有利的手段。
文件包含处理
是指在一个源文件中,通过文件包含命令将另一个源文件的内容全部包含在此文件中。在源文件编译时,连同被包含进来的文件一同编译,生成目标目标文件。
如何才能避免重定义?
文件包含的两个处理阶段
- 处理时间:文件包含也是以
"#"开头来写的(#include )
, 那么它就是写给预处理器来看了, 也就是说文件包含是会在编译预处理阶段进行处理的; - 处理方法:在预处理阶段,系统自动对
#include
命令进行处理,具体做法是:将包含文件的内容复制到包含语句(#include )
处,得到新的文件,然后再对这个新的文件进行编译。
.c文件和.h文件的意义
.h为头文件
,.c为源文件
,其实两者都是代码。
主要有几点好处:
- .h头文件用于共享,只用一句#include就能包含,当然.c也可以包含;
- 如果你要写库的话,可是你又不想暴露你的源代码,你可以把.c编译成.obj或是.lib发给别人用,然后把.h作为。
所以一般情况下,.h里面全部都是声明,.c里面全部都是实现,有了.h就可以编译,有了.lib或你的.obj就可以连接。
文件包含形式
文件包含分为两种:
- 包含.c文件
包含.c文件
和编译多文件
程序是不同的:
多文件程序
: 是在源文件编译时把多个文件进行编译、连接在一起生成一个可执行文件;
包含.c文件
: 按照我们上边的说法则是把多个文件合并为一个文件进行编译。
a.只包含 .c 文件
main.c 文件中
#include <stdio.h>
#include "LinearListStoragge.h"
#include "TestOne.c" //导入TestOne.c 文件会报错
#pragma mark -- CustomClass
int main(int argc, const char * argv[]) {
orderListMethodCalling();
int a = 5, b = 15;
addMethod(a, b);
return 0;
}
这时Command+B
,就会在Xcode中编译报错,如下图:
当时看别人编写C语言博客时,需要在main.c文件中要导入TestOne.c
文件,需要加入#include "TestOne.c"
。当时我也这么想也这么做的,但是我忽略了Xcode的强大,它把这件事当仁不让的做了,去掉 #include "TestOne.c"
即可。在上面的代码段中,将 .h
文件去除(注释掉:#include "LinearListStoragge.h")
也可以的,在 main 方法中也可以调用其方法。
main.c 文件
#include <stdio.h>
#pragma mark -- CustomClass
int main(int argc, const char * argv[]) {
orderListMethodCalling();
int a = 5, b = 15;
addMethod(a, b);
return 0;
}
TestOne.c 文件
#include <stdio.h>
void addMethod(int a, int b){
int c = a + b;
printf(".c文件中的addMethod: %d\n", c);
}
输出:
.c文件中的addMethod: 20
这个例子是采用包含.c文件 的方法实现的。
在编译时,直接去编译main.c
文件,预处理器会先把TestOne.c
文件中的内容复制到main.c
中来,然后再对新的main.c
进行编译。通过编译命令:
gcc main.c -o main
可以看到,这里并没有对TestOne.c进行编译,但还是生成了最终的main可执行程序。
也可以通过命令来观察一下预处理的结果:
编译命令:
gcc -E main.c -o main.cpp
通过在main.cpp文件末尾可以观察到,其实就是将TestOne.c文件中的内容添加到了main函数之前,然后对新的文件进行编译,生成最终的可执行程序。
编译多文件程序
同样是上边的例子,现在编译器不是Xcode,我们需要在main.c中导入TestOne.c
文件。但是我们把main.c中“#include "TestOne.c"
”注释掉,加上一句:“extern int c;
”因为 c
变量在文件TestOne.c
中定义。
main.c 文件
#include <stdio.h>
//#include "TestOne.c"
//添加上这一句
extern int c;
int main(int argc, const char * argv[]) {
orderListMethodCalling();
int a = 5, b = 15;
addMethod(a, b);
return 0;
}
TestOne.c 文件
#include <stdio.h>
int c = 0;
void addMethod(int a, int b){
c = a + b;
printf(".c文件中的addMethod: %d\n", c);
}
这次如果还是按照上面的方法只编译main.c的话就会出错,因为变量c和函数addMethod(int a, int b)
并没有在main.c中定义,所以编译时需要将TestOne.c
一起编译:
编译命令:
gcc -c main.c -o main.o #编译main.c
gcc -c TestOne.c -o TestOne.o #编译TestOne.c
gcc main.o TestOne.o -o main #用main.o TestOne.o生成main
两者的不足之处是:
包含.c的文件: 容易产生"重定义",大家想想如果一个工程中有多个文件都同时包含了某一个件,那么这个被包含文件的内容就会被复制到多个文件中去,也就相当于每个包含该文件的文件中都定义被包含文件中的变量和函数,这样在链接时就会产生"重定义"错误。
多文件分开编译: 这个比较好,不容易出现"重定义"之类的问题,这也是我们最常用的一种方法,但是并不是像上面这个例子中这样直接去用,而是使用"头文件"将各个.c文件联系起来。
上边这个例子大家会发现,在main.c中需要加上“extern int c;”这样一句声明,如果包含的文件较多?如果全局变量较多?...这个我们可以省掉吗?回答是肯定的!方法就是给它写上一个头文件。
- 包含.h文件
main.c
#include <stdio.h>
#include "LinearListStoragge.h"
int main(int argc, const char * argv[]) {
orderListMethodCalling();
int a = 5, b = 15;
addMethod(a, b);
return 0;
}
LinearListStoragge.h
extern int c; //把c声明为外部可用的
void addMethod(int a, int b); //addMethod函数的声明
LinearListStoragge.c
#include "LinearListStoragge.h"
int c = 0;
void addMethod(int a, int b){
c = a + b;
printf(".c文件中的addMethod: %d\n", c);
}
由上面来看,在要用到fun.c中定义的函数或变量的文件中只要包含.h
文件就可以了,是不是这样???Ha Ha
,当然是了。。。
预处理时会把.h文件
中的内容复制到包含它的文件中去,而复制的这些内容只是声名,不是定义,所以它被复制再多份也不会出现"重定义"的错误。。。
前面说了头文件的方法也是模块化程序设计中的一种非常有利的手段。
把同一类功能写到一个.c
文件中,这样可以把它们划为一个模块,另外再对应的写上一个.h
文件做它的声明。这样以后再使用这个模块时只需要把这两个文件添加进工程,同时在要使用模块内函数或变量的文件中包含.h
文件就可以了。
举个很实际的例子,在单片机、ARM或其他嵌入式开发中,每一个平台可能本身都有多种不同的硬件模块,使用时需要去写相应的驱动程序,这样就可以把各个硬件模块的驱动程序作为一个模块(比如lcd驱动
对对应lcd.c
和lcd.h
,IIC驱动
对应I2C.c
和I2C.h
等),当具体使用到某个模块时,只需要在将对应的.c和.h文件添加进工程,并在文件中包含对就的.h文件即可。
所以关于头文件的写法个人总结以下几点:
- 对应的.c文件中写变量、函数的定义
- 对应的.h文件中写变量、函数的声明
- 如果有数据类型的定义 和 宏定义 ,请写的头文件(.h)中
- 头文件中一定加上#ifndef...#define....#endif之类的防止重包含的语句
- 模块的.c文件中别忘包含自己的.h文件