为什么在C++中需要前置声明
编译器希望确保您没有拼写错误或向函数传递了错误数量的参数。因此,它坚持要先看到“add”(或任何其他类型、类或函数)的声明,然后才能使用。
这实际上只是使编译器更好地验证代码并允许其清理松散的端点,以便它可以生成一个整洁的目标文件。如果不必预先声明某些内容,则编译器将生成一个对象文件,该文件必须包含有关可能是函数“add”的所有可能猜测的信息。链接器必须包含非常聪明的逻辑来尝试确定您实际想要调用哪个“add”,当“add”函数可能存在于连接器正在与使用add的对象文件结合以生成“dll”或“exe”的对象文件不同时。链接器可能会选择错误的“add”。假设您想使用“int add(int a, float b)”,但是不小心忘记了写,但链接器找到了已经存在的“int add(int a, int b)”,并认为那是正确的并代替使用了那个。您的代码将编译,但不会做您期望的事情。
因此,为了保持明确并避免猜测等情况,编译器坚持在使用之前声明所有内容。
声明和定义之间的区别
顺便说一下,了解声明和定义之间的区别很重要。声明只提供足够的代码以显示某些内容的外观,因此对于函数而言,这是返回类型、调用约定、方法名称、参数及其类型。但是不需要方法的代码。对于定义,您需要声明,然后还需要函数的代码。
如何使用前置声明可以显著缩短构建时间
您可以通过“#include”包含已经包含函数声明的头文件将函数的声明放入当前的“.cpp”或“.h”文件中。但是,这可能会减慢编译速度,特别是如果您将头文件“#include”到程序的“.h”文件而不是“.cpp”中,因为“#include”了您写的所有头文件的所有内容都会被“#include”。突然之间,编译器需要编译数页的代码,即使您只想使用一两个函数。为避免这种情况,您可以使用前置声明,在文件顶部仅键入该函数的声明。如果您只使用几个函数,则与始终将头文件“#include”相比,这确实可以加快编译时间。对于非常大的项目,差异可能会从一个小时或更长的编译时间缩短到几分钟。
打破两个定义都互相使用的循环引用
此外,前置声明可以帮助您打破循环。这是两个函数都尝试使用彼此的情况。当发生这种情况(这是完全有效的事情)时,您可能会“#include”一个头文件,但该头文件会尝试“#include”您当前正在编写的头文件......然后“#include”另一个头文件,该头文件“#include”您正在编写的另一个头文件。您陷入了一个鸡和蛋的局面,每个头文件都试图重新“#include”另一个头文件。为解决此问题,您可以在其中一个文件中前置声明所需的部分,并留下该文件的“#include”。#include "Wheel.h" // Include Wheel's definition so it can be used in Car.
#include <vector>
class Car
{
std::vector<Wheel> wheels;
};
文件 Wheel.h
嗯...需要在此处声明 Car
,因为 Wheel
有一个指向 Car
的指针,但是不能在这里包含 Car.h
,否则会导致编译器错误。如果包含了 Car.h
,那么就会尝试包含 Wheel.h
,Wheel.h
又会包含 Car.h
,这样就会无限循环,所以编译器会报错。解决方法是改为前置声明 Car
:
class Car; // forward declaration
class Wheel
{
Car* car;
};
如果Wheel
类中有需要调用Car
类方法的方法,那么这些方法可以在Wheel.cpp
文件中定义,这样Wheel.cpp
就可以包含Car.h
而不会导致循环依赖。