在这一环节,有人提到下列事项:

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;
}

我们使用了前向声明,以便编译器在编译main.cpp时知道“add”是什么。如前所述,为您想要使用的位于另一个文件中的每个函数编写前向声明很快就会变得乏味。

你能进一步解释一下“前向申报”吗?如果我们在主函数中使用它会有什么问题?


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

int add( int x, int y )

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

所以“远期申报”就是罐头上写的。它在使用之前就声明了一些东西。

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


One problem is, that the compiler does not know, which kind of value is delivered by your function; is assumes, that the function returns an int in this case, but this can be as correct as it can be wrong. Another problem is, that the compiler does not know, which kind of arguments your function expects, and cannot warn you, if you are passing values of the wrong kind. There are special "promotion" rules, which apply when passing, say floating point values to an undeclared function (the compiler has to widen them to type double), which is often not, what the function actually expects, leading to hard to find bugs at run-time.


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

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


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

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

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

我假设add.h有这一行:

int add(int x, int y); 

一个简短的补充:通常你把这些转发引用放在一个头文件中,属于实现函数/变量等的。c(pp)文件。在你的例子中,它看起来像这样: add.h:

extern int add(int a, int b);

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

#include 
#include "add.h"

int main()
{
.
.
.


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

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

This really just allows the compiler to do a better job of validating the code and allows it to tidy up loose ends so it can produce a neat-looking object file. If you didn't have to forward declare things, the compiler would produce an object file that would have to contain information about all the possible guesses as to what the function add might be. And the linker would have to contain very clever logic to try and work out which add you actually intended to call, when the add function may live in a different object file the linker is joining with the one that uses add to produce a dll or exe. It's possible that the linker may get the wrong add. Say you wanted to use int add(int a, float b), but accidentally forgot to write it, but the linker found an already existing int add(int a, int b) and thought that was the right one and used that instead. Your code would compile, but wouldn't be doing what you expected.

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

声明和定义的区别

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

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

You can get the declaration of a function into your current .cpp or .h file by #includ'ing the header that already contains a declaration of the function. However, this can slow down your compile, especially if you #include a header into a .h instead of .cpp of your program, as everything that #includes the .h you're writing would end up #include'ing all the headers you wrote #includes for too. Suddenly, the compiler has #included pages and pages of code that it needs to compile even when you only wanted to use one or two functions. To avoid this, you can use a forward-declaration and just type the declaration of the function yourself at the top of the file. If you're only using a few functions, this can really make your compiles quicker compared to always #including the header. For really large projects, the difference could be an hour or more of compile time bought down to a few minutes.

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

Additionally, forward-declarations can help you break cycles. This is where two functions both try to use each other. When this happens (and it is a perfectly valid thing to do), you may #include one header file, but that header file tries to #include the header file you're currently writing... which then #includes the other header, which #includes the one you're writing. You're stuck in a chicken and egg situation with each header file trying to re #include the other. To solve this, you can forward-declare the parts you need in one of the files and leave the #include out of that file.

Eg:

文件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++中,术语“前向声明”主要用于类声明。请参阅后面的答案,了解为什么类的“前向声明”实际上只是一个简单的类声明,有一个花哨的名字。

换句话说,“forward”只是给这个术语增加了压载物,因为任何声明都可以被视为forward,只要它在使用之前声明了一些标识符。

(至于什么是声明而不是定义,请参见定义和声明之间的区别是什么?)