【C++】多文件程序结构

来源:互联网 发布:ios 跳转淘宝客户端 编辑:程序博客网 时间:2024/05/11 19:34

以前写一个C++多文件程序的时候经常为哪些东西应该放在.h文件里,哪些东西应该放在.cpp文件里而疑惑。稍有不慎就搞出一个“error:LNK2005 已经在*.obj中定义”的重复定义错误,就算解决了这个问题自己实际上也还是一知半解。最近去了解了C++多文件程序结构的知识,才搞清楚了这些问题的本质。在此总结一下,如有错误,欢迎指出。

声明与定义

首先从声明和定义说起。
声明是数据对象的和函数的描述。声明的作用就是让编译器知道实体的名字,以及其数据类型或函数签名。如:
external int x; //变量声明
void fun(); //函数声明
class A; //类声明

定义则是实体本身,代表着实体在一个作用域的唯一描述。
如:
int x; //变量定义
void fun() {…} //函数定义
class A {…}; //类定义

因此,可以理解为,声明是定义的引用,而定义是实体本身。

外部链接性与内部链接性

定义具有链接性。链接性分为内部链接和外部链接。

外部链接:外部链接的定义可被定义所处的翻译单元(.cpp)内看见,也可以被其他翻译单元引用。
具有外部链接性的:
• 非inline函数。包括命名空间中非静态函数、类成员函数和类静态成员函数
• 类静态成员变量总有外部链接。
• 命名空间(不包括无名命名空间)中非静态变量

内部链接:内部链接的定义只能在该定义所处的翻译单元内看见。
具有内部链接性的:
• 所有的声明
• 命名空间(包括全局命名空间)中的静态自由函数、静态友元函数、静态变量的定义、const常量定义
• enum定义
• inline函数定义(包括自由函数和非自由函数)
• 类(class、struct、union)的定义
注:在类体中定义的成员函数为内联(inline)函数,属于内部链接。

实质上声明没有链接性的概念,但可以理解成声明总是内部链接的,因为它只对它所在的翻译单元有效。如果我们把声明置于头文件,则由于包含该头文件的每个翻译单元都独立复制了该声明(见下文预处理部分的说明),因此每个翻译单元都能“看到”这个声明。

预处理、编译和链接

C++中,源程序要被翻译成可执行文件,都要经过三个步骤:预处理、编译和链接。

预处理:阅读源程序,执行预处理指令,嵌入指定源文件。预处理指令以“#”号开始。如#include指令实现文件包含。当一个.cpp文件编译前,它首先递归地包含头文件,形成一个含有所有必要信息的单个源文件,也就是一个翻译单元。

编译: 编译器每次翻译一个.cpp文件(翻译单元),并输出对象文件(.o或.obj)。对象文件含有.cpp文件内定义的所有函数编译后的机器码,也包含.cpp文件内定义的全局变量和静态变量。此外,对象文件也可能含有未定义引用,这些未定义的引用就是该翻译单元内有声明,但是在这个.cpp文件中没有定义的函数和全局变量。
那么,这些没有定义的东西在哪?答案是这些东西定义在其他.cpp文件中。
要怎么找到呢?这就是链接器的任务了。

链接:有外部链接的定义可以在对象文件中产生外部符号,这些外部符号可以被所有其他的翻译单元访问,用来解析他们未定义的引用。链接器的工作就是读取所有对象文件,并尝试解决对象文件之间的交差引用。如果成功,则产生可执行程序。当无法解决外部引用的时候,根据情况链接器有两种报错:
1、当找不到引用的目标时,就会产生“无法解决的外部符号”错误。
2、当找到两个或以上相同名字的实体(函数或变量时),就会产生“符号被多重定义”错误。

因此,要让程序正确地链接,首先不能声明一个实体,却没有相应的定义。比如在A.cpp里面声明一个void fun();但在这个文件和其他文件中都没有这个函数的定义(也就是函数的实现),这就会产生“无法解决的外部符号”错误。同时,也不能重复地定义具有外部链接性的实体。比如,在A.cpp文件里面定义int x,同时在B.cpp里面又定义一个int x。这样就会出现“符号被多重定义”。

头文件

了解了上面几点知识,我们就可以理解和回答一些问题了。

(1)为什么不要把外部链接的定义放在头文件里面?
因为我们知道cpp在预编译的时候会递归包含头文件,因此,如果一个头文件包含了一个外部链接的定义,其他包含它的.cpp文件都会有一个相同的外部链接的定义。出现“符号被多重定义“也就不难理解了。要特别注意的是,类的静态成员变量和一般的静态变量不一样,它具有外部链接性,因此假设你在头文件中定义一个:
class A
{
static int member;
}
那么该静态成员变量的定义不能放在这个头文件里面。而是应该在某个.cpp文件里面写定义: int A::member;

(2)有内部链接性的定义可以放到头文件中去吗?
要让内部链接的定义影响程序的其他部分,可以把它放到头文件中,这样包含这个头文件的其他文件都知道了这个定义。但不推荐在头文件中定义const或者static常量,因为会污染全局命名空间,同时在每个包含头文件的翻译单元中浪费了数据空间。由于声明可以看做是内部链接,因此我们可以把声明放在头文件中的时候,例如在头文件中放 void fun(); 所有包含这个头文件的其他文件都可以使用fun()函数了。

(3)为什么我们经常在头文件中定义类,却在cpp文件中定义类成员函数?
因为类定义是内部链接性,而类的成员函数的定义是外部链接性的。

(4)可不可以在一个头文件中包含其他所有的头文件,然后其他cpp文件都include这个头文件?
不要这样,因为这样的话,每此更改这个头文件,都会引起其他的所有cpp文件的重新编译,严重影响编译速度,同时也浪费了数据空间。

参考:
《大规模C++程序设计》
维基百科:https://zh.wikipedia.org/wiki/%E9%93%BE%E6%8E%A5%E6%80%A7

0 0
原创粉丝点击