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
时知道“add
”是什么。 如前所述,为每个要使用的函数编写前向声明,这些声明可以很快变得乏味。
你能进一步解释“ 前置声明 ”吗? 如果我们在main()
函数中使用它,有什么问题?
为什么前向声明在C ++中是必要的
编译器希望确保您没有犯过拼写错误或将错误的参数数量传递给函数。 因此,它坚持认为它在使用之前首先会看到“添加”(或任何其他types,类或函数)的声明。
这实际上只是允许编译器更好地validation代码,并允许它整理松散的结束,因此它可以产生一个整洁的目标文件。 如果你不需要转发声明,那么编译器会产生一个对象文件,这个文件必须包含关于函数“add”可能的所有可能的猜测信息。 而链接器将不得不包含非常聪明的逻辑来试图找出你实际打算调用哪个“add”,当“add”函数可能存在于链接器join的另一个对象文件中,一个DLL或EXE。 链接器可能会得到错误的添加。 假设你想使用int add(int a,float b),但是不小心忘了写它,但是链接器find了一个已经存在的int add(int a,int b),并认为这是正确的,并用它来代替。 你的代码可以编译,但不会做你期望的。
所以,为了保持事物的明确性和避免猜测等,编译器坚持要在使用之前声明所有东西。
声明与定义的区别
顺便说一下,了解声明和定义之间的区别很重要。 一个声明只是给出足够的代码来显示什么东西看起来像一个函数,这是返回types,调用约定,方法名称,参数和它们的types。 但是该方法的代码不是必需的。 对于一个定义,你需要声明,然后也是函数的代码。
如何前瞻性声明可以显着减less构build时间
您可以通过#include包含已经包含函数声明的头文件来将函数声明放入当前的.cpp或.h文件中。 然而,这可能会减慢你的编译速度,尤其是如果你将#头文件包含到.h文件中,而不是程序的.cpp文件中,因为所有包含你正在编写的.h文件都将包含所有头文件你也写了#includes。 突然,编译器有#included需要编译的页面和代码页,即使你只想使用一个或两个函数。 为了避免这种情况,您可以使用前向声明,只需在文件顶部键入函数的声明即可。 如果你只使用了一些函数,那么与包含头文件相比,这可以让你的编译更加快速。 对于非常大的项目,差异可能是一个小时或更长时间的编译时间购买到几分钟。
打破两个定义都相互使用的循环引用
此外,前向声明可以帮助您打破周期。 这是两个function都试图互相使用的地方。 当发生这种情况时(这是一个完全有效的做法),你可以#include一个头文件,但该头文件试图#include你正在写的头文件….然后#includes其他头,其中#包括你正在写的那个。 你被困在鸡和鸡蛋的情况下,每个头文件试图重新包括另一个。 为了解决这个问题,你可以在一个文件中转发你需要的部分,并把#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,这将永远持续下去,所以编译器会产生一个错误。 解决的办法是转发申报车,而不是:
class Car; // forward declaration class Wheel { Car* car; };
如果Wheel类具有需要调用汽车方法的方法,那么可以在Wheel.cpp中定义这些方法,并且Wheel.cpp现在可以包含Car.h而不引起循环。
编译器查找当前翻译单元中正在使用的每个符号是否在当前单元中。 只是在源文件的开始处提供所有方法签名的样式,稍后提供定义。 它的重要用途是当你使用一个类的指针作为另一个类的成员variables时。
//foo.h class bar; // This is useful class foo { bar* obj; // Pointer or even a reference. }; // foo.cpp #include "bar.h" #include "foo.h"
所以,尽可能在类中使用前向声明。 如果你的程序只有函数(带有头文件),那么在开始时提供原型只是一个风格问题。 如果头文件存在于只有函数头的普通程序中,则无论如何都是这种情况。
由于C ++是自上而下parsing的,因此编译器在使用之前需要了解一些事情。 所以,当你引用:
int add( int x, int y )
在编译器需要知道它存在的主函数中。 为了certificate这个尝试把它移到主函数的下面,你会得到一个编译器错误。
所以,“ 前进宣言 ”正是它所说的。 它在使用之前就宣布了一些东西。
一般来说,你可以在头文件中包含前向声明,然后以包含iostream的相同方式包含该头文件。
C ++中的“ 前向声明 ”一词大多只用于类声明 。 关于为什么一个类的“前向声明”实际上只是一个简单的类名声明而已( 这个答案的末尾)。
换句话说,“前进”只是给术语增加了压力,因为任何声明在被使用之前都可以被看作是前向的。
(关于什么是声明而不是定义 ,再看看定义和声明有什么区别? )
当编译器看到add(3, 4)
它需要知道这意味着什么。 使用forward声明,基本上告诉编译器add
是一个函数,它接受两个int并返回一个int。 这是编译器的重要信息,因为它需要将4和5以正确的表示方式放入堆栈,并且需要知道add返回的是什么types。
那时候,编译器并不担心add
的实际实现,也就是说在哪里(或者甚至是哪一个)编译。 稍后, 在链接器被调用时编译源文件之后,就可以看到这一点。
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);
一个问题是,编译器不知道你的函数传递了哪种types的值; 是假设,在这种情况下函数返回一个int
,但是这可能是正确的,因为它可能是错误的。 另一个问题是,编译器不知道函数期望的参数types,如果传递的是错误types的值,则不能提醒您。 有一些特殊的“升级”规则,这些规则适用于传递浮点值的时候,向一个未声明的函数(编译器必须把它们扩大到doubletypes),这通常不是,函数实际期望的是什么,导致很难find错误在运行时。
一个简短的附录关于:通常你把这些前向引用放到属于.c(pp)文件的函数/variables等被实现的头文件中。 在你的例子中,它看起来像这样:add.h:
extern int add(int a,int b);
关键字extern声明该函数实际上是在外部文件中声明的(也可以是一个库等)。 你的main.c看起来像这样:
#包括 #include“add.h” int main() { 。 。 。