什么是C中的前向声明?

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

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

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

回答(8)

2 years ago

Why forward-declare is necessary in C++

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

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

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

Difference between declaration and definition

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

How forward-declarations can significantly reduce build times

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

Break cyclic references where two definitions both use each other

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

例如:

File Car.h

#include "Wheel.h"  // Include Wheel's definition so it can be used in Car.
#include <vector>

class Car
{
    std::vector<Wheel> wheels;
};

File 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而不会引起循环 .

2 years ago

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

//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头文件),那么在开头提供原型只是风格问题 . 无论如何,如果头文件存在于正常情况下,情况就会如此带有只有函数的 Headers 的程序 .

2 years ago

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

int add( int x, int y )

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

所以' Forward Declaration ' is just what it says on the tin. It'在使用之前宣布了一些事情 .

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

2 years ago

C中的术语“ forward declaration ”主要仅用于类声明 . 有关为什么类的"forward declaration"实际上只是一个带有花哨名称的简单类声明,请参见(结尾)this answer .

换句话说,"forward"只是为该术语添加了镇流器,因为任何声明都可以看作是前向的,只要它在使用之前声明了一些标识符 .

(至于什么是 declaration 而不是 definition ,再次见What is the difference between a definition and a declaration?

2 years ago

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

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

2 years ago

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

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

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

我假设 add.h 有这条线:

int add(int x, int y);

2 years ago

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

2 years ago

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

extern int add(int a, int b);

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

#include 
#include "add.h"

int main()
{
.
.
.