稍微大一点的项目都不会是单个文件的,多文件的好处实在太多,模块开发、分工协作、代码复用、结构清晰、模块更新…不细扯,直接梳理一下C++源代码的文件类型、多文件编译。
1、文件类型
C++程序一般分三类文件:
- 头文件( .h )。放置各种声明,用于被cpp文件包含。
- 模块文件(.cpp)。放置一些函数定义,也称为功能模块。
- 主程序文件(.cpp)。包含main()的文件,程序入口,调用模块文件实现的方法。
1.1、头文件
头文件的存在是为了联系多个源文件,是源文件之间的接口。C++与C一样,要求先声明后使用,可是编译的时候是单文件编译的。看一个没有头文件的例子。
//main.cpp
void fun(); //必须先声明,不然调用出错
int main(){
fun(); // 调用fun
}
//fun.cpp
#include <iostream>
void fun(){
std::cout<<"This is fun."<<std::endl;
}
使用命令g++ main.cpp fun.cpp
编译两个文件之后是可以正常运行的。这里你需要知道一点C++编译过程原理:C++把每个文件单独编译出来,再通过链接把编译出来的多个文件组成一个可执行程序。编译的时候只检查函数声明,只要该文件能在之前声明过函数就能编译成功。函数定义是在链接阶段检查的,而链接是多文件共同参与的。上例中,main.cpp必须先声明函数fun()才能通过编译, 尽管没有在main.cpp中定义,但在链接的时候发现fun.cpp定义了该函数,程序正确。
但是,每次使用其它文件的函数前都要自己先声明显然很繁琐也容易出错。所以,我们把声明类语句放到一类文件里,称之“头文件”,如果你需要使用到某个函数,就把它所在的头文件包含进来,头文件的内容会在编译前被粘贴到源文件中,这样在编译的时候就能正常通过了。看使用了头文件之后的例子。
//main.cpp
#include "header.h"
int main(){
fun();
}
//header.h
#ifndef HEADER_H
#define HEADER_H
void fun();
#endif
//fun.cpp
#include <iostream>
void fun(){
std::cout<<"This is fun."<<std::endl;
}
头文件的内容一般都会使用条件编译预处理语句(如上)包住,防止因为依赖关系多次被包含。
既然知道头文件的作用,那哪些东西应该放在头文件?哪些不能放在头文件?很好理解,如果这部分需要复制给每个相关的cpp,就把它放在头文件,如果被多个cpp复制之后,可能导致它们在链接过程出错,就不要放在头文件。 一 一来看:
- 函数声明:显然应该放在头文件中,前面很清楚。
- 类定义、结构定义:用函数定义的逻辑想,似乎不能放在头文件中。但它应该放在头文件。第一,每个cpp文件应该有一个定义,在编译的时候编译器才知道怎么为对象分配空间。其次,类型定义不会在内存上分配空间。
- 模板函数:编译器必须在编译的时候根据函数模板实例化对应的函数,所以应该放在头文件。
- 内联函数:编译期间被插到调用位置,所以也要放在头文件。
- 函数定义:不要!C++规定一个程序同签名的函数只能有一个定义。如果你把函数定义放在头文件件,并且同一个程序的多个cpp文件包含了该头文件,这样,在链接的时候会发现多个定义版本,链接报错。
- 变量定义:不要!与上面类似,被多个文件包含的时候会出现多次定义同一个变量,链接错误。但是,static变量和extern变量可以,以及宏定义的常量,因为这些在多个文件出现并不会出错。
1.2、cpp源文件
把头文件的东西抽离出去之后,剩下的就可以放在cpp文件了。在cpp文件上,除了需要include对应的头文件之外,没有什么变化。
尽管都是cpp文件,还是有必要理解成模块文件和主程序文件,二者类似功能的提供者和使用者,在实际中,也常常由不同的人开发。
2、多文件的编译
如果是使用Windows下的集成开发环境的,大可直接Build,一键搞定,但你对这个过程的理解就可能差点。所以我以linux上为例,最简单的多文件编译命令:
g++ main.cpp fun.cpp -o out #列举所有要编译的文件
也可以单独编译成 .o文件之后再链接:
g++ -c main.cpp -o main.o
g++ -c fun.cpp -o fun.o
g++ main.o fun.o -o out
第二种是不是引起了你的疑惑——这么麻烦?可是,这种方式提供了一个优点——模块编译更新。你只需要编译你修改过的文件,再跟其他的链接,无需再次对整个项目编译。
在实际的项目中,项目文件可不是小数目,无论上面的那种方式,你都需要一一列出文件,不实际。IDE工具自然没有这种烦恼。不过Linux也会使用Makefile进行项目管理,实现自动编译。行了,又多了一样要学。