本文描述了对C++前向声明的理解,和c++的头文件以及实现文件的重新理解,更重要的一点是对c++中两个类相互包含引用问题的总结。文章引用了部分前人经验并加上一点自己的理解,因此写下来以加深自己的理解。
1、c++的#include的预编译
#后是指令关键字,在关键字和#号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。#include预处理指令的作用是在指令处展开被包含的文件。展开被包含的文件之后,在代码就可以正常地调用该文件中所声明的变量和函数。#include指令有两种使用方法:第一种:#include <xxx.h> 第二种:#include "xxx.h"。
第一种方法将待包含的头文件使用尖括号括起来,预处理程序会在系统默认目录或者括号内的路径查找,通常用于包含系统中自带的公共头文件。
第二种方法将待包含的头文件使用双引号引起来,预处理程序会在程序源文件所在目录查找,如果未找到则去系统默认目录查找,通常用于包含程序作者编写的私有头文件。
2、c++中的单独编译
-
首先,我们可以将所有东西都放在一个.cpp文件内,然后编译器就将这个.cpp编译成.obj。就是编译单元了,一个程序,可以由一个编译单元组成,也可以有多个编译单元组成.。如果你不想让你的源代码变得很难阅读的话,就请使用多个编译单元吧(一个函数不能放到两个编译单元里面,但两个以上就可以分别放在一个单元,也就是cpp里面)。那么就是一个.cpp对应一个.obj,然后将所有的obj链接起来(通过一个叫链接器的程序),
组成一个.exe,也就是程序了。如果一个.cpp要用到另一个.cpp定义的函数怎么办? 只需在这个.cpp种写上他的函数声明
就可以了.其余工作由链接器帮你完成,你可以随便调用该函数。
-
c++程序预编译和链接阶段是分开的,更加详细需要了解编译原理,声明仅仅是将一个符号引入到一个作用域。而定义提供了一个实体在程序中的唯一描述。在一个给定的定义域中重复声明一个符号是可以的 , 但是却不能重复定义 , 否则将会引起编译错误。在编译时,编译器只检测程序语法和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成目标文件。而在链接程序时,链接器会在所有的目标文件中找寻函数的实现。如果找不到,那到就会报链接错误码( Linker Error)。在 VC 下,这种错误一般是: Link 2001 错误,意思说是说,链接器未能找到函数的实现。链接把不同编译单元产生的符号联系起来。有两种链接方式:内部链接和外部链接。
3、c++中的前向声明以及两个类相互包含引用问题
- 前向声明:可以声明一个类而不定义它。这个声明被称为前向声明(forward declaration)。例如:class name。在声明之后,定义之前,类name是一个不完全类型(incompete type),即已知name是一个类型,但不知道包含哪些成员。不完全类型只能以有限方式使用,不能定义该类型的对象,不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。
类的前向声明之适用于指针和引用的定义,如果是普通类类型就得使用include了。 - 两个类之前的相互引用包含问题。
class A
{
int i;
B b;
}
{
int i;
A* a;
}
请注意上面的定义内容,一般情况下是不能出现类A,类B相互引用都定义对象,即如下的样子:
class A
{
int i;
B b;
}
class B
{
int i;
A a;
}
在这种情况下,想想能够有a.b.a.b.a.b.a.b.a.b…………,很有点子子孙孙无穷尽之状,那么我的机器也无法承受。最主要的还是这种关系很难存在,也很难管理。这种定义方式类同程式中的死循环。所以,一般来说,两者的定义,至少有一方是使用指针,或两者都使用指针,但是决不能两者都定义实体对象。
言归正传,那么,在定义时因为相互引用肯定会需要相互包含头文档,假如仅仅只是在各自的头文档中包含对方的头文档,是不能通过编译的,如下:
//class A.h
#include "B.h"
class A
{
int i;
B b;
}
//class B.h
#include "A.h"
class B
{
int i;
A *a;
}
如上的包含方式可能会造成编译器有错误提示:A.h文档中使用了未知类型B。怎么办?
一般的做法是:两个类的头文档之中,选一个包含另一个类的头文档,但另一个头文档中只能采用class *的申明形式,而在实现文档中(*.cpp)中包含头文档,如下:
//class A.h
#include "B.h"
class A
{
int i;
B b;
}
//class B.h
class A;
class B
{
int i;
A *a;
}
//B.cpp
//在B.cpp中的文档包含处要有下面语句,否则不能调用成员a的任何内容
#include "A.h"
B::B()
{
……
}
当两个类互相包含和互相在本类中定义另外一个类的对象指针引用时候,必须保证当一个类先声明或者定义时候才可以在另外一个类中使用
如在类A的前面声明Class B 在B类前声明Class A,这种方法是只适用于用来定义类指针或者类应用 而不能来定义类对象变量或者函数的变量名 这是因为上面仅仅声明了类 说明这个类是一个标识 还没有实现所以不能定义对象变量。
但是对于如果想要在一个类中定义对象来说比如在A.h文件中定义B类对象这样的话,就需要在A.h中包含B类头文件 #include “B.H” 。
一般所见的内容就是定义了B类对象变量后 则只能在B.H中定义A类指针或者引用了,所以只需在B.H里面声明 CLASS A就可以了。
确实一般都是如此:两个类一个包含头文件,另外一个声明类并且在其实现文件中当然还得包含这个头文件了。所以一旦是个类名字声明 则只能是定义对象指针或引用(无论是单个定义还是对于函数参数 定义都如此)。