前言
C++采用了多文件编译,在逻辑上,整个程序包含所有文件,但编译是针对单个文件生成目标文件,多文件是在链接时才进行整合,所以在编译过程中,我们需要指明当前文件所需要的其他文件的信息。C++编译是区分.h
和.cpp
文件的,cpp
会生成目标文件,而.h
不会生成目标文件。
一般而言,我们利用 .h
作为头文件来传递文件间的信息, .cpp
文件用于具体程序实现,在此总结下各个关系。
函数
函数默认是全局的,也就是说,在一个文件内定义了一个函数,另一个文件是可以引用的,但这个文件如何知道怎么调用这个函数,是通过声明来进行传递。头文件内应包含函数的声明,而不应该有定义,因为,include
的本质是直接替换,当头文件被include
到多个cpp
中时,工程中将出现多个同名函数定义,连接时,自然就会因重复定义而失败。
如果函数定义为static
,则即便在另一文件中声明,也无法调用,因为static
限制了使用范围为当前文件。
变量
全局变量默认也是整个工程的,即全局的,但变量的声明和定义在格式上有些冲突,故若只是声明,应为
extern T a;
定义则可省去extern
当然若有static
,则该变量只在单文件中可见。
类
类与函数和变量,就有点不一样了,因为类的定义和声明,有点模棱两可,类的声明其实就包含其数据成员和函数成员,函数成员的实现可以在声明中实现,也是可以分离出来在cpp中实现,但数据成员的初始化,尽量分离出来,编译器版本不一样,效果有偏差。
再看一下类的作用范围,应该还是全局的,这里存在一些问题:
如果把成员方法实现写在类的声明里面,当不同文件的多次定义同名类,且存在不同的函数实现方式时,编译器会选择编译时,排在前面的文件的实现方式,也就是说,不同的文件,能够定义同名类,同名方法,但在编译后程序其实只存在一个该类,所以这种方式算是一个标准未定义的选项,慎用,最好还是方法实现分离。
上面的实验:
a.cpp
定义类A
,方法b()
b.cpp
同样定义类 A
,方法b()
二者,有不同的b()
的实现,结果有一样的成员函数地址,均为a.cpp
的b()
,交换g++
的编译顺序,返现可变为b.cpp
中的b()
总结一下,若在类的声明文件中实现了函数体,程序只会只使用编译时遇到的第一个文件的该类方法的实现,所以,定义类时若采用在声明中实现方法,则方法的实现应该只存在于一个文件中。当然若方法体分离为两个文件,此时,编译器会检测重复定义,此时必然会保持一致。
宏
关于宏,记住宏的作用域除了某些自身为块作用域,其余作用域均为文件内,所以宏的最广作用域是文件内。
总结
C++采用了头文件和实现文件的方式来进行文件间的引用,但在编译阶段时,采用的是单文件编译,所以当前文件需要引用其余文件中的信息,站在编译器的角度,当然需要预先声明要用的信息,所以按照这个思路,可以很自然的明白各种声明,定义的原理。