1.7 前置声明

from http://www.learncpp.com/cpp-tutorial/17-forward-declarations/

看一下下面这个表面上正确的add.cpp程序

   1: #include <iostream>
   2:  
   3: int main()
   4: {
   5:     using namespace std;
   6:     cout << "The sum of 3 and 4 is: " << add(3, 4) << endl;
   7:     return 0;
   8: }
   9:  
  10: int add(int x, int y)
  11: {
  12:     return x + y;
  13: }

你期望得到3+4=7的结果。

但是它却不能通过编译。在Visual Studio 2005 Express中产生如下的错误:

add.cpp(10) : error C3861: 'add': identifier not found
add.cpp(15) : error C2365: 'add' : redefinition; previous definition was 'formerly unknown identifier'

(译者注:作者编译的程序与黏贴在文章中的程序行数不同,第一个错误应该在第6行,第二个错误应该在第10行,不过在visual studio 2008版本中进行编译,仅仅出现了第一个错误)

该程序没有通过编译的原因是编译器读取是顺序的读取文件的。当它执行到main函数里的add函数的时候,它并不知道add是什么,因为到现在我们没有定义add。于是产生了在第10行(译者注:应该是第6行)的错误。然后当执行到真正的add函数的声明的时候,它抱怨函数被重定义。通常单个的错误会终止程序继续输出错误信息。

规则:当解决你的程序中的编译错误时,总是解决产生的第一个错误(when addressing compile errors in your programs, always resolve the first error produced first)。

在这个例子中,我们需要强调的是编译器并不知道add是什么。这里给出了三个解决方案。

第一个方式将add函数的定义放到main函数的前面:

   1: #include <iostream>
   2:  
   3: int add(int x, int y)
   4: {
   5:     return x + y;
   6: }
   7:  
   8: int main()
   9: {
  10:     using namespace std;
  11:     cout << "The sum of 3 and 4 is: " << add(3, 4) << endl;
  12:     return 0;
  13: }

 

此处,main函数中调用add函数的时候,它已经知道了add是什么。因为这样一个简单的程序,这种改变是相对容易去实现的。但是,在大的项目中,想要按照函数的调用顺序将函数的顺序调整好是一件非常头疼的事情。

此外,这种选择并不总是合理的。当我们写一个程序具有A和B两个函数。如果A函数调用B函数,B函数也调用A函数,那么上述的方式就失去了它的功效。如果你先定义A函数,那么编译器会不知道B函数是什么。如果你先定义B函数,那么编译器就不知道A函数是什么。

因此,一个更好的解决的方式是使用前置声明。在前置声明中,在使用之前,我们声明(并没有定义)我们的函数,一般都在文件的顶部。这种方式,编译器在调用的时候能够理解我们的函数是什么。我们编写一个声明语句被认为是函数的原型。函数的原型是一个包括函数名称,参数,返回类型的声明,但是并没有包含实现部分。

下面是我们最初的函数使用前置声明后的例子:

   1: #include <iostream>
   2:  
   3: int add(int x, int y); // forward declaration of add() using a function prototype
   4:  
   5: int main()
   6: {
   7:     using namespace std;
   8:     cout << "The sum of 3 and 4 is: " << add(3, 4) << endl;
   9:     return 0;
  10: }
  11:  
  12: int add(int x, int y)
  13: {
  14:     return x + y;
  15: }

 

现在当编译器执行到main函数中的add函数时,它将会知道函数add看上去是什么(一个函数接收两个整型参数,返回一个整型参数),它不会产生错误信息。

函数原型中不将参数名具体也是可以的。如:

   1: int add(int, int);

但是,我们更倾向于使用参数名被具体命名的,因为这样能给读者更多的描述。

很多新的程序员都会有这么一个问题:当我们声明了一个前置声明函数,但是在后面没有定义它,那么会发生什么呢?

答案是,不是确定的。如果一个前置声明,但是函数从来没有被调用,程序会正常的编译和运行。但是,如果一个前置声明的函数被调用了,但是没有被定义,程序能够通过编译,但是在链接的时候,会出现错误,因为它不能正常的执行函数的调用。看一下以下的代码:

   1: #include "stdafx.h"
   2: #include <iostream>
   3:  
   4: int add(int x, int y);
   5:  
   6: int main()
   7: {
   8:     using namespace std;
   9:     cout << "The sum of 3 and 4 is: " << add(3, 4) << endl;
  10:     return 0;
  11: }

它在visual studio 2005 express产生了如下的信息:

Compiling...
add.cpp
Linking...
add.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z)
add.exe : fatal error LNK1120: 1 unresolved externals

正如你所看到的,程序编译通过,但是在链接的时候因为没有被定义而失败。

第三个解决方案是使用头文件,我们在随后将会讨论。

posted @ 2012-04-17 14:28  grassofsky  阅读(396)  评论(0编辑  收藏  举报