声明与定义
首先来了解一下声明和定义的区别。
声明(declaration)用于向程序表明变量的类型和名字。定义也是声明,当定义变量的时候我们声明了它的类型和名字。可以通过使用extern声明变量名而不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern。
变量的定义(definition)用于为变量分配存储空间,还可以为变量指定初始值。在程序中,变量有且仅有一个定义。
extern声明不是定义,也不分配存储空间。事实上它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。
#include
#include 是一条预处理指令,即在预处理阶段 在该指令处展开被包含的头文件。
有两种使用方式:
-
#include <xxx.h>
第一种方法将待包含的头文件使用尖括号括起来,预处理程序会在系统默认目录或者括号内的路径查找,通常用于包含系统中自带的公共头文件。 -
#include “xxx.h”
第二种方法将待包含的头文件使用双引号引起来,预处理程序会在程序源文件所在目录查找,如果未找到则去系统默认目录查找,通常用于包含程序作者编写的私有头文件。
前向声明
前向声明指在A程序中引入新的类型B,但是此时B并不是一个完整清晰的类;只知道有这么一个类,但是不清楚它的具体函数接口实现以及成员变量;
因此不能定义一个B的对象,只能使用指针和引用的方式;
直到后面找到B的定义我们才真正知道B的具体实现,才可以调用它的构造函数定义对象;
举一个栗子:
c++的文件IO类定义在 fstream 头文件中;
但是 fstream 中定义的是模板类 basic_ifstream 和 basic_ofstream ,我们使用的却是 ifstream 和 ostream。
实际上 ifstream 和 ofstream 是 iosfwd 头文件中实例化模板类后取的别名,iosfwd 包含在 iostream 中。
而当我们单独包含 iostream 时使用 ifstream 会报错,类型未定义;
只包含 fstream 时会报未声明的标识符;
因为iostream并未include fstream,因此找不到类型定义。实例化时使用的正是 前向声明
前向声明的应用
1. 中断循环引用
假如有类A在a.h中,类B在b.h中,而类 A 和 B 都需要调用对方。
而循环包含是禁止的,也编译不过。这时候就需要用到前向声明了。
一个头文件include另一个,另一个则使用前向声明。
例:
// a.h
#pragma once
#include <stdio.h>
#include "b.h"
class A
{
public:
void funcA() {
printf("This is A.\n");
b.funcB();
}
void funcB() {
printf("A is called.\n");
}
B b;
};
// b.h
#pragma once
#include <stdio.h>
class A;
class B
{
public:
void funcA();
void funcB() {
printf("B is called.\n");
}
A *a;
};
然后需要实际用到的时候就在cpp中include;
// b.cpp
#include "b.h"
#include "a.h"
void B::funcA() {
printf("This is B.\n");
a = new A;
a->funcB();
delete a;
}
2. 避免暴露头文件
如果类A用到了某个内部的类,打包时就不得不把内部的头文件也对外开放
库的开发者肯定不希望对外暴露内部的细节 所以需要利用前向声明跳过
接上面的例子,我们编译成静态库。
然后使用这个静态库时只需包含b.h就可以使用了。
而类A中的实现完全被隐藏了起来。
3. 减少构建时间
您可以通过#包含已经包含函数声明的头文件,将函数的声明输入到当前的.cpp或.h文件中。
但是,这会减慢编译的速度,特别是当您将头包含到程序的.h而不是.cpp中时,因为所有包含您正在编写的.h的.h的内容最终都会#包括您编写的所有标头。
突然之间,编译器就有了包含页和页的代码,即使您只想使用一两个函数,也需要编译它们。
为了避免这种情况,您可以使用前向声明,只需自己在文件顶部键入函数的声明即可。
如果您只使用几个函数,这确实可以使您的编译速度比始终包含头的#更快。
对于真正的大型项目,差异可能是一个小时或更多的编译时间,减少到几分钟。