1. 为什么选择多文件编程
C/C++通常不需要将程序分成多个文件,但是在项目代码量大时,将源码拆成多可文件,可以使代码管理方便很多,且减少编译大型程序的编译时间,每次改代码,只需要重新编译更改的文件(类似于java中class作单独一个文件的思想)。
或者将需要与其他程序共享的代码抽出来放到单独的文件中。
2. 组成结构
-
zzz.h
头文件,里面含:-
全局变量和全局常数的定义
-
类,结构,联合,枚举类型的定义
-
用于创建typedef类型的语句
// 示例 typedef struct { char id[8]; int grade; }
-
函数的声明,称为“原型”(重点!!!)
// zzz.h头文件中,示例 int add(int a, int b);
-
include其他文件的语句,包括C/C ++库文件
// 示例,例如iostream.h,math.h等 #include <string>
-
-
zzz.c
与.h
同名源文件(注意是同名的!!!)这里写同名
.h
文件中,声明的函数的函数体(函数的定义)一定要包含同名头文件
// 示例 #include "zzz.h" // 一定要包含同名头文件 int add(int a, int b) { return a + b; }
-
a.c
:欲使用zzz.h
的源文件这里include头文件后,能直接调用该头文件中声明的函数。如:包含
stdio.h
后,能直接使用printf()
格式化输出函数// 示例,没有显式定义add()函数,但它由头文件包含 #include <stdio.h> #include "zzz.h" int main() { printf("%d\n", add(2, 3)); return 0; } // 输出结果:5
3. #include中""与<>
<>
:在编译器预设目录中寻找头文件,如果找不到则报错
""
:在工程所在路径下寻找头文件,如果找不到,则去编译器预设目录中寻找,再找不到,则报错
可见,""
寻找范围大于<>
,但""
效率低于<>
;
注意用户指定自定义的头文件只能采用""
方式。
4. 如何编译
- 一遍编译:每个源文件都要编译,
.h
可不写在编译语句中
# 示例,头文件可以不写在编译语句中
gcc a.c zzz.c zzz.h -o a
- 分步编译:利用gcc的-c选项(compile只编译不连接),先将源码编译成
.o
目标代码,再一起连接
gcc -c frac.c # 编译frac.c为目标代码,frac.o
gcc -c main.c # 编译main.c为目标代码,main.o
gcc frac.o main.o -o main.out # 连接目标代码为可执行文件"main.out"
这种方法可以实现单文件编译,最后link在一起,可减少大项目的编译时间
5. 改进(本文重点)
5.1 可能导致的错误
文件a.h
// 示例,文件a.h中包含b.h
#include <b.h>
文件b.h
// 示例,文件b.h中包含a.h
#include <a.h>
导致错误:头文件中的函数等内容被重复定义
5.2 如何改进
-
#ifndef
:条件编译,在编译前由编译前处理如果未定义
#ifndef
后面的宏替换名,则对语句段进行编译;如果定义#ifndef
后面的宏替换名,则不执行语句段。与
#endif
(结束符)配合用。 -
#define
:预处理命令
// _HEADERNAME_H为任意名称,仅做标志用
#ifndef _HEADERNAME_H
#define _HEADERNAME_H
// 头文件内容
#endif // 不要忘记结束
过程描述:当头文件第一次被包含时,是正常处理的,然后_HEADERNAME_H
(可起任意名字)被定义为1。当头文件被再次包含时,通过条件编译#ifndef
,它的内容会被忽略,由此避免发生多重包含
注意:第二次读入文件时虽然啥都忽略了,但是也要消耗资源,拖慢编译速度。所以一定尽量避免多重包含的情况
Windows特有的方法
Windows支持这两种方法
// 示例
#pragma once
// 头文件内容
注意这一种方法只能在Windows系统中使用