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

链接中,提到了以下内容:

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函数中使用它会有什么问题?

332438 次浏览

因为c++是由上而下解析的,所以编译器需要在它们被使用之前知道它们。所以,当你引用:

int add( int x, int y )

在main函数中,编译器需要知道它的存在。为了证明这一点,尝试将它移动到主函数的下面,你会得到一个编译器错误。

所以'前置声明'就是罐头上写的。它在使用之前就声明了一些东西。

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

一个问题是,编译器不知道你的函数传递的是哪种值;在这种情况下,函数返回int,但这可能是正确的,也可能是错误的。另一个问题是,编译器不知道你的函数需要哪种类型的参数,如果你传递了错误类型的值,它也不能警告你。有一些特殊的“提升”规则,当将浮点值传递给一个未声明的函数时应用(编译器必须将它们扩大为double类型),这通常不是函数实际期望的,导致在运行时很难发现错误。

当编译器看到add(3, 4)时,它需要知道这是什么意思。通过前向声明,你基本上告诉编译器add是一个接受两个int型并返回一个int型的函数。这对编译器来说是很重要的信息,因为它需要将正确表示形式的4和5放到堆栈中,并且需要知道add返回的东西是什么类型。

那时,编译器并不担心add实际实现,即它在哪里(或者是否有)以及它是否编译。后面会看到,在调用链接器时编译源文件。

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

你能解释一下“向前声明”吗? 更多的进一步的吗?如果有什么问题 我们在main()函数中使用它?< / p >

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

我假设add.h有这样一行:

int add(int x, int y);
一个快速的补充:通常你把这些转发引用放在一个头文件中,属于实现函数/变量等的。c(pp)文件。在你的例子中,它看起来像这样: add.h: < / p >
extern int add(int a, int b);
关键字extern表示函数实际上是在外部文件中声明的(也可以是库等)。 你的main.c看起来像这样:

#include
#include "add.h"


int main()
{
.
.
.


为什么前向声明在c++中是必要的

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

这实际上只是允许编译器更好地验证代码,并允许它整理松散的部分,从而生成一个整洁的目标文件。如果你不需要转发声明,编译器将生成一个对象文件,该文件必须包含关于add函数可能是什么的所有可能猜测的信息。当add函数可能位于不同的目标文件中时,连接器将与使用add生成dllexe的对象文件连接,连接器将必须包含非常聪明的逻辑来尝试并找出你实际想要调用的add。链接器可能会得到错误的add。假设你想使用int add(int a, float b),但不小心忘记写它,但链接器发现一个已经存在的int add(int a, int b),并认为这是正确的,并使用它代替。您的代码将被编译,但不会执行您所期望的操作。

因此,为了保持显式并避免猜测,编译器坚持在使用之前声明所有内容。

声明和定义的区别

顺便说一句,了解声明和定义之间的区别很重要。声明只是给出了足够的代码来显示一些东西的样子,所以对于一个函数,这是返回类型、调用约定、方法名、参数及其类型。但是,该方法的代码不是必需的。对于定义,您需要声明,然后还需要函数的代码。

前向声明如何能够显著减少构建时间

你可以通过包含已经包含函数声明的头文件,将函数声明放入当前的.cpp.h文件中。然而,这可能会降低你的编译速度,特别是当你将一个头文件#include到程序的.h而不是.cpp时,因为所有#包含你正在编写的.h的内容最终都会变成#include'ing所有你写的#也包含for的头文件。突然之间,即使你只想使用一两个函数,编译器也需要编译大量的代码。为了避免这种情况,您可以使用前向声明,并在文件的顶部键入函数的声明。如果你只使用几个函数,与总是#包含头文件相比,这确实可以让你的编译更快。对于真正的大型项目,差异可能是一个小时或更长时间的编译时间缩短到几分钟。

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

此外,前向声明可以帮助您打破循环。这是两个函数试图相互使用的地方。当这种情况发生时(这是一件完全有效的事情),你可以#include一个头文件,但该头文件试图#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,而Wheel.h将包含Car.h,而Car.h将包含Wheel.h,这将永远持续下去,因此编译器会引发一个错误。解决方案是转发声明Car:

class Car;     // forward declaration


class Wheel
{
Car* car;
};

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

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

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


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

因此,尽可能在类中使用前向声明。如果您的程序只有函数(带有ho头文件),那么在开始时提供原型只是一个风格问题。如果头文件出现在一个只有函数头的普通程序中,那么无论如何都会出现这种情况。

在c++中,术语“< em >前置声明< / em >”主要只用于类声明。请参阅这个答案的结尾,了解为什么类的“前向声明”实际上只是一个简单的类声明和一个花哨的名字。

换句话说,“forward”只是给这个词增加了镇流器,因为任何声明可以被视为forward,因为它声明了一些它所使用的标识符之前

(至于什么是< em > < / em >声明而不是< em > < / em >定义,再次参见定义和声明之间的区别是什么?)