什么是C ++中的前向声明?

时间:2011-01-21 10:13:34

标签: c++ declaration forward-declaration

在:http://www.learncpp.com/cpp-tutorial/19-header-files/

提到以下内容:

add.cpp:

int add(int x, int y)
{
    return x + y;
}

main.cpp中:

#include <iostream>

int add(int x, int y); // forward declaration using function prototype

int main()
{
    using namespace std;
    cout << "The sum of 3 and 4 is " << add(3, 4) << endl;
    return 0;
}
  

我们使用了前向声明,以便编译器在编译add时知道“main.cpp”是什么。如前所述,为要使用的每个函数写入前向声明可能会很快变得乏味。

你能进一步解释“前瞻声明”吗?如果我们在main()函数中使用它会有什么问题?

8 个答案:

答案 0 :(得分:347)

为什么在C ++中需要前向声明

编译器希望确保您没有拼写错误或将错误数量的参数传递给函数。因此,它坚持在使用之前首先看到“添加”(或任何其他类型,类或函数)的声明。

这实际上只是允许编译器更好地验证代码,并允许它整理松散的末端,以便它可以生成整洁的目标文件。如果您不必转发声明内容,编译器将生成一个目标文件,该文件必须包含有关函数“add”可能的所有可能猜测的信息。链接器必须包含非常聪明的逻辑来尝试找出你实际打算调用的'add',当'add'函数可能存在于不同的目标文件中时,链接器与使用add生成的链接器连接一个DLL或exe。链接器可能会得到错误的添加。假设你想使用int add(int a,float b),但是不小心忘了写它,但是链接器发现了一个已经存在的int add(int a,int b)并且认为它是正确的并且使用了它。您的代码会编译,但不会按预期执行。

因此,为了保持事实清楚并避免猜测等,编译器坚持要在使用之前声明所有内容。

声明和定义之间的区别

顺便说一句,了解声明和定义之间的区别非常重要。声明只提供足够的代码来显示某些内容,因此对于函数,这是返回类型,调用约定,方法名称,参数及其类型。但是不需要该方法的代码。对于定义,您还需要声明,然后还需要函数的代码。

前向声明如何显着缩短构建时间

您可以通过#include包含已包含函数声明的标头,将函数声明输入到当前的.cpp或.h文件中。但是,这会降低编译速度,特别是如果你将#include包含在.h而不是程序的.cpp中,因为#includes你正在编写的.h的所有内容都会结束#include所有的标题你也写了#includes。突然,编译器有#included页面和代码页面,即使你只想使用一个或两个函数,它也需要编译。为了避免这种情况,您可以使用前向声明,只需在文件顶部键入函数声明即可。如果你只使用一些函数,那么与总是#include标题相比,这可以真正使你的编译更快。对于非常大的项目,差异可能是一小时或更长的编译时间,只需几分钟。

打破两个定义互为使用的循环引用

此外,前向声明可以帮助您打破周期。这是两个函数都试图互相使用的地方。当发生这种情况时(并且这是完全有效的事情),您可以#include一个头文件,但该头文件尝试#include您当前正在编写的头文件....然后#includes其他头,#包括你正在写的那个。你陷入鸡蛋和鸡蛋的境地,每个头文件试图重新#include另一个。要解决此问题,您可以在其中一个文件中转发所需的部分,并将#include保留在该文件之外。

例如:

文件Car.h

#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,其中包括Car.h,其中包括Wheel.h,这将永远持续下去,因此编译器会引发错误。解决方案是转发声明Car:

class Car;     // forward declaration

class Wheel
{
    Car* car;
};

如果类Wheel有需要调用car方法的方法,那么这些方法可以在Wheel.cpp中定义,而Wheel.cpp现在可以包含Car.h而不会导致循环。

答案 1 :(得分:25)

编译器查找当前转换单元中使用的每个符号先前是否在当前单元中声明。只是在源文件开头提供所有方法签名的样式问题,而后面提供定义。它的重要用途是当你使用指向类的指针作为另一个类的成员变量时。

//foo.h
class bar;    // This is useful
class foo
{
    bar* obj; // Pointer or even a reference.
};

// foo.cpp
#include "bar.h"
#include "foo.h"

因此,尽可能在类中使用forward-declarations。如果你的程序只有函数(带有ho头文件),那么在开头提供原型只是风格问题。无论如何,如果头文件存在于具有仅具有函数的头的普通程序中,则无论如何都是如此。

答案 2 :(得分:11)

因为C ++是从上到下解析的,所以编译器需要在使用之前了解它们。所以,当你参考:

int add( int x, int y )

在主函数中,编译器需要知道它存在。要证明这一点,请尝试将其移至主函数下方,否则会出现编译错误。

所以'前瞻性声明'正如它所说的那样。它在使用之前宣布了一些东西。

通常,您会在头文件中包含前向声明,然后以与包含 iostream 相同的方式包含该头文件。

答案 3 :(得分:10)

C ++中的术语“ 前向声明 ”主要仅用于类声明。请参阅({end})this answer,了解为什么类的“前向声明”实际上只是一个带有花哨名称的简单类声明

换句话说,“转发”只是为术语添加了镇流器,因为任何声明可以看作是前进的,因为它在之前声明了一些标识符它被使用了。

(至于什么是 声明 ,而不是 定义 ,请再次参阅{{3} })

答案 4 :(得分:2)

当编译器看到add(3, 4)时,它需要知道这意味着什么。使用前向声明,您基本上告诉编译器add是一个带有两个整数并返回一个int的函数。这对于编译器来说是重要的信息,因为它需要将4和5放在正确的表示中并且需要知道add返回的是什么类型。

那时,编译器并不担心add实际实现,即它的位置(或者 甚至一个)如果它编译。稍后会看到在调用链接器时编译源文件。

答案 5 :(得分:1)

int add(int x, int y); // forward declaration using function prototype
  

你能解释一下“前言宣言”吗?   更进一步?如果有什么问题   我们在main()函数中使用它吗?

#include"add.h"相同。如果您知道,预处理器会在您编写#include指令的.cpp文件中展开您在#include中提及的文件。这意味着,如果你写#include"add.h",你会得到同样的东西,就像你在做“前向宣言”一样。

我假设add.h有这一行:

int add(int x, int y); 

答案 6 :(得分:1)

一个快速的附录:通常你将这些前向引用放入属于.c(pp)文件的头文件中,其中实现了函数/变量等。在您的示例中,它看起来像这样: add.h:

extern int add(int a, int b);

关键字extern声明该函数实际上是在外部文件中声明的(也可以是库等)。 你的main.c看起来像这样:

#include 
#include "add.h"

int main()
{
.
.
.

答案 7 :(得分:0)

一个问题是编译器不知道你的函数传递了哪种值;假设,在这种情况下函数返回int,但这可能是正确的,因为它可能是错误的。另一个问题是,如果您传递的是错误类型的值,编译器不知道您的函数需要哪种参数,并且无法发出警告。有一些特殊的“促销”规则,在传递时适用,将浮点值表示为未声明的函数(编译器必须将它们扩展为double类型),这通常不是,函数实际期望的,导致很难找到错误在运行时。