c++语言支持“分别编译”(separatecompilation)。对于一个程序,可以将实现它的源码放在多个cpp文件中独立进行编译,之后只需要将编译生成的各个目标文件链接到一起就可以了
对于在一个文件中定义的函数
a.cpp
若要在另外的文件中使用,只需要使用之前,对此函数进行声明,就可以使用了
b.cpp
g++ a.cpp b.cpp -o a.exe
./a.exe
其中的原因,在编译时对于没有定义的函数或变量,如果有对应的声明,就会等到链接阶段再去寻找他的定义,如果能够从别的源文件生成的目标文件中找到,就可以成功生成程序,如果别的目标文件中还是没有找到对其进行的定义,就会出错。
对于定义与声明,"定义"就是把一个符号完完整整地描述出来:它是变量还是函数,返回什么类型,需要什么参数等等。而"声明"则只是声明这个符号的存在。对于一个符号,声明可以有多次,而定义只能有一次
如果在源文件中有成百上千的函数,我们是无法记住每一个声明的。这时就可以使用一个头文件来包含它们全部的声明,之后在使用这些函数的源文件中包含此头文件,这样就相当于在源文件中声明了这些函数,之后再与实现了这些函数的源文件或者库文件一起编译链接就可以了
a.cpp
b.cpp
c.h
d.cpp
g++ a.cpp b.cpp d.cpp -o a.exe
./a.exe
头文件中,只能存在变量或者函数的声明,而不要放定义,如下面是正确的
test.h
如果是这种
test.h
而被多个源文件包含,就会导致重复定义而在链接阶段出错
但是以下情况在头文件内可以存放定义
const和static对象,在多个文件中出现相当于独立的定义,对外不可见
内联函数(inline)的定义
类定义和结构体定义,类定义默认为static,只在本文件内使用,对外不可见
在头文件中定义类和结构体不会有重复定义的问题,但是使用结构体、类在头文件中去定义一个变量,并且被多个文件包含才会导致重复定义,如
test.h
不要将头文件加入VS等IDE的项目列表,因为#include指令会管理头文件。IDE的项目列表实际上也是编译列表,项目里面的源文件会进行编译,而不属于项目里面的源文件,不会进行编译。
程序生成的阶段:
预处理(-E):
头文件参与预编译,主要进行文件包含、条件编译、宏替换等工作
编译(-S):
源文件参与编译,进行语法语义分析,生成汇编代码(.s文件)
汇编(-c):
将汇编代码转换成为机器可执行的二进制目标文件
链接:
链接阶段主要解决多个文件之间符号引用的问题,链接器使用目标文件和库文件,去掉无用符号表(大概就是头文件中声明而从未使用的各种函数和变量),将所有目标文件链接到一起,最终生成可执行文件
避免重复定义符号的办法?
头文件只应该包含符号的声明,而在源文件中进行定义和实现。这样在多个模块包含头文件时,只是对该符号进行了多次声明,并且只在它的源文件中定义了一次。
避免重复包含头文件的方法?
对于MSVC编译器,在头文件中使用
可以让头文件只被包含一次
对于gcc编译器,可以使用
的写法避免头文件被重复包含
在源文件中包含所需的其他功能模块的头文件,而在头文件只应该对模块中的函数和类进行声明
源文件在编译时生成目标文件,在链接阶段使用目标文件生成程序,而库文件是将源文件编译后进行打包的文件,在链接阶段使用生成程序。
声明只是一个符号,编译阶段告诉编译器有这个变量或函数存在,等到链接时再到各种目标文件和库文件里面找相应的符号
在同一个文件内重复定义符号,在编译环节就会出错,而不同文件重复定义,由于单独编译,彼此不可见,而会在链接时出错。C++ 链接时会在每个 .o 或 .obj 文件中都去找一下所需要的符号,而不是只在某个文件中找或者说找到一个就不找了,因此在不同的文件内定义相同符号就会出现链接错误