天天看點

Learning C++ 之1.7 提前定義和聲明

看看下面這個看似好用的示例程式:

#include <iostream>
 
int main()
{
    std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl;
    return 0;
}
 
int add(int x, int y)
{
    return x + y;
}
           

你可能期望這個程式輸出:

The sum of 3 and 4 is:7

事實上這個是不對的,在編譯的時候就上報了錯誤。

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

這個程式之是以編譯失敗就是因為程式是從上到下依次執行的,main函數中第一次使用add函數的時候根本不知道add是什麼意思。因為add的第一次定義在第九行。這就導緻了編譯錯誤。

到了第九行碰到第一次定義的時候,編譯器還會報錯,這次上報了重複定義的錯誤。這個有點誤導性,糾錯的時候,我們需要從上到下一條一條來進行。

為了解決這個問題,我們會修改一下讓add函數在被調用的時候知道他的定義。有兩種方法來改正這個錯誤:

1.把add函數添加到main函數的前面,這樣main函數在調用的時候就知道add的定義了。

#include <iostream>
 
int add(int x, int y)
{
    return x + y;
}
 
int main()
{
    std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl;
    return 0;
}
           

當然這種方法并不總是可行的,比如有兩個函數A和B,A調用了B,同時B也調用了A,這種方法就無法解決這個問題了。是以需要下面的方法。

函數原型和提前聲明

2.用一個提前的聲明量

提前的聲明能夠在我們定義辨別符之前讓我們告訴編譯器辨別符的存在。

在函數中的具體展現就是,當我們還沒有真正定義函數的時候,先告訴編譯器這個函數的存在。當編譯器碰到這個函數的聲明的時候,他會知道我們已經定義了這個函數,然後再檢查我們是否可以正确地調用這個函數,即使他并不知道這個函數在哪裡定義的。

為了定義一個函數的聲明,我們用到了函數原型的概念。函數原型包含了函數的名字,傳回值,參數,但是不包括函數的實體。因為函數的聲明是一個語句,是以必須以;結尾。

下面是函數的聲明:

int add(int x, int y); // function prototype includes return type, name, parameters, and semicolon.  No function body!
           

下面是我們之前沒有編譯通過的程式,增加了一條函數聲明:

#include <iostream>
 
           
int add(int , int);
           

// forward declaration of add() (using a function prototype) int main(){ std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl; // this works because we forward declared add() above return 0;} int add(int x, int y) // even though the body of add() isn't defined until here{ return x + y;}

現在當main函數第一次調用到add的時候,他明白了add是一個怎樣的函數(傳回值是int,兩個int參數,函數名字是add)。現在就不會報錯了。

當然函數的聲明沒有必要聲明函數參數的名字,下面的定義也是可以的:

int add(int , int );
           

當然還是建議大家寫上參數的名字,因為這樣可以明确的知道函數的參數名稱以及幹什麼用的。要不然的話,你得去查找真正的函數定義的地方。

忘掉函數體:

有一個問題是假如我們把函數體的定義給忘掉了,會發生什麼事情呢。

這個要分情況而定。假如這個聲明的函數在具體的函數中沒有用到,那麼就沒有問題。但是假如在函數中調用了這個函數,編譯的時候是沒有問題的,但是連結的時候,由于找不到該函數的函數體,就會報錯。

#include <iostream>
 
int add(int x, int y); // forward declaration of add() using function prototype
 
int main()
{
    std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl;
    return 0;
}
           

上面的函數就會報下面的錯誤:

Compiling...
add.cpp
Linking...
add.obj : error LNK2001: unresolved external symbol "int __cdecl add(int,int)" ([email protected]@[email protected])
add.exe : fatal error LNK1120: 1 unresolved externals
           

其他類型的提前聲明:

提前聲明通常用在函數裡,但是在C++中的其它聲明符也可以使用提前聲明的方法,向變量和使用者自己定義的類型。其他類型的提前聲明有不同的文法。

這些我們會在後續的課程中深入學習。

聲明和定義:

在C++裡你常常聽到如下的“定義”和“聲明”。這個具體的差別是什麼?你現在已經有了足夠的架構來了解這兩個詞語了。

定義實際上就是實作和執行個體化具體的辨別符(配置設定記憶體)。下面是一些定義的例子:

int add(int x, int y) // implements function add()
{
    return x + y;
}
 
int x; // instantiates (causes memory to be allocated for) an integer variable named x
           

定義需要滿足連接配接器,如果用一個辨別符但是沒有定義,那麼連結器就會報錯。

對于一個辨別符來說,你隻可以定義一次。這就稱做一次性定義原則,也叫ODR。在大多數情況下多次定義同一個辨別符是錯誤的,即使他們可能是相同的。

一個聲明隻是告訴編譯器存在這個辨別符和它的類型。下面是一些聲明的例子:

int add(int x, int y); // tells the compiler about a function named "add" that takes two int parameters and returns an int.  No body!
int x; // tells the compiler about an integer variable named x
           

聲明隻需要滿足編譯器就可以了,如果你使用一個辨別符直接定義,沒有聲明,那麼就會上報編譯的錯誤。

你可以看到int x;來說都是滿足定義和聲明的,這是因為在C++中,所有的定義也可以當作聲明。int x;是一個定義的話,他當然也算是一個聲明。是以在大多數情況下我們隻需要一個定義就可以了,但是一定要在定義之前,提供一個辨別符一個詳細的聲明。

當然有一些聲明并不是定義,比如函數原型。這種被稱作純聲明。其他類型的純聲明,如變量聲明,類聲明,類型聲明。你可以想你期望的那樣,對辨別符有很多純聲明,當然除了一個意外,其他的純聲明都是無效的。