1. 程式人生 > >1.7前置宣告與定義

1.7前置宣告與定義

先來看一個表面看起來沒有錯誤的程式add.cpp。

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

預計結果應為:

The sum of 3 and 4 is: 7

但事實上,這個程式在Visual Studio 2005中根本無法編譯通過,錯誤提示為:

add.cpp(6) : error C3861: ‘add’: identifier not foundadd.cpp(10) : error C2365: ‘add’ : redefinition; previous definition was ‘formerly unknown identifier’

此程式無法編譯的原因是編譯器會按順序讀取檔案,當編譯器到達第六行即main()函式中呼叫add()的部分時,並不能明確add()是什麼,我們知道第10行才開始定義add,於是導致了第一個錯誤:“identifier not found”(未找到識別符號)。同時Visual Studio 2005還會鬱悶為何add被定義了兩遍。這可能產生一些誤解,因為前面根本沒有定義add()函式啊,Visual Studio後續的版本修正了這個錯誤,略去了這個多餘的錯誤提示。雖然第二個提示是多餘的,我們還是要注意一個錯誤造成多個錯誤提示的情況是屢見不鮮的。

Note:解決編譯錯誤時,首先解決提示中的第一個錯誤。

要解決這個問題,首先需要明確編譯器並不知道add是什麼,通常有兩種方法來告訴它。

第一種:將add定義在main()之前。

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

這種情況下,在main函式呼叫add函式時,編譯器已經能夠知道add是什麼,因為這個程式比較簡單,所以這種改動相對來說容易做,但是在一個大型的程式中,試圖弄清楚哪個函式呼叫了哪個函式,以便於將各個函式按照相應的順序定義是一件很煩心的事。

另外,這個方法並不總是可行的,比如我們寫了一個有兩個函式A、B的函式,如果A呼叫了B,B又呼叫了A,就無法找到合適的順序定義A、B以使兩者兼顧。如果先定義A,編譯器就會抱怨自己根本不認識B,先定義B的話,同樣也會產生錯誤。

函式原型與前置宣告

第二種:使用前置宣告

前置宣告允許我們在真正定義一個識別符號前告知編譯器這個識別符號的存在。對於函式來說,這允許我們在定義函式體之前告知編譯器函式的存在,這樣編譯器在遇到函式呼叫時就能明白這是一個函式呼叫並且能夠檢查我們是否正確呼叫了這個函式,即使編譯器還不知道這個函式是如何以及在哪裡被定義的。

為了給函式寫前置宣告,我們使用一個稱為函式原型的宣告語句,函式原型由函式返回值型別、函式名、引數列表組成,不包括函式體(花括號之間的部分),函式原型是一個語句,因為它以分號結束。以下就是函式add的函式原型:

int add(int x, int y);

這樣,之前那個無法編譯通過的程式可以使用函式原型修改為:

#include <iostream>
 
int add(int x, int y); // 使用函式原型的前置宣告
 
int main()
{
    using namespace std;
    cout << "The sum of 3 and 4 is: " << add(3, 4) << endl; 
    return 0;
}
 
int add(int x, int y) // add()的函式體在此定義
{
    return x + y;
}

當編譯器遇到main中的add時,就能夠大致瞭解add是什麼樣的了:有兩個整型形參的返回值型別為整型的函式。

值得注意的是,函式原型中並不需要指定引數的名稱,也可以這樣宣告函式:

int add(int, int);

但是我們習慣上都是要命名引數的,這樣便於通過函式原型理解函式,否則就需要定位函式體的位置。

Tip:可以使用複製貼上函式體處的語句來建立函式原型,別忘了在後面新增分號。

忘寫函式體

也許有人會好奇,如果我們前置聲明瞭函式卻沒有定義它會發生什麼?

答案是:視情況而定。如果宣告的函式並沒有被呼叫,程式則可以編譯通過並且能夠正確執行。如果已經宣告卻未定義的函式被呼叫了,那麼程式同樣可以編譯,但聯結器卻無法完成函式呼叫。(參看0.4開發流程簡介

考慮下面的程式:

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

我們前置聲明瞭add()函式卻沒有定義它,當使用VS2005編譯此程式時,產生如下的提示:

Compiling…add.cppLinking…add.obj : error LNK2001: unresolved external symbol “int __cdecl add(int,int)” ([email protected]@[email protected])add.exe : fatal error LNK1120: 1 unresolved externals

可見,程式可以編譯,但卻在連結階段失敗了因為add()並沒有被定義。

其他型別的前置宣告

前置宣告通常伴隨著函式使用,但是前置宣告也可以用於C++中的其他識別符號,例如變數和使用者自定義型別,只是語法不同。後續課程會繼續討論。

宣告與定義

宣告與定義是C++中常會聽到的兩個詞語,他們是什麼意思呢?現在你應當有足夠的認識來理解兩者的區別了。

定義實際上是實現或者說例項化(分配了相應記憶體空間)了識別符號,下面是兩個定義的例子:

int add(int x, int y) // 定義函式add()
{
   return x + y;
}

int x; // 例項化名為x的整型變數(記憶體分配了空間)

每個識別符號只能有一個定義。定義是用來“滿足”連結器要求的。

宣告是定義識別符號(變數/函式名)及其型別的語句,以下是兩個宣告的例子:

int add(int x, int y); // 宣告一個名為add的函式,擁有兩個整型形參,返回值型別為int,不包括函式體

int x; // 宣告整型變數x

宣告是用來“滿足”編譯器要求的,這就是為什麼只使用前置宣告而無定義也能編譯通過。

你可能已經注意到了,int x;兼有兩種型別,事實上,在C++中,所有的定義同時也能起到宣告的作用,因為int x;是一個定義,所以其預設也是宣告,這種情況適用於大多數宣告。

但是有一小部分宣告並非定義,例如函式原型,這些稱為純粹的宣告(還包括變數、類宣告、型別宣告的前置宣告)。一個識別符號可以有任意數量的純粹宣告,但一個以上通常是冗餘的。

小測驗

1、函式原型與前置宣告的區別是什麼?

2、寫出這個函式的函式原型。

int doMath(int first, int second, int third, int fourth)
{
   return first + second * third / fourth;
}

3、說一說下面幾個程式是無法編譯?無法連結?還是既無法編譯也無法連結?如果無法確定,可以嘗試動手編譯一下。

#include <iostream>
int add(int x, int y);
 
int main()
{
    using namespace std;
    cout << "3 + 4 + 5 = " << add(3, 4, 5) << endl;
    return 0;
}
 
int add(int x, int y)
{
    return x + y;
}

4、

#include <iostream>
int add(int x, int y);
 
int main()
{
    using namespace std;
    cout << "3 + 4 + 5 = " << add(3, 4, 5) << endl;
    return 0;
}
 
int add(int x, int y, int z)
{
    return x + y + z;
}

5、

#include <iostream>
int add(int x, int y);
 
int main()
{
    using namespace std;
    cout << "3 + 4 + 5 = " << add(3, 4) << endl;
    return 0;
}
 
int add(int x, int y, int z)
{
    return x + y + z;
}

6、

#include <iostream>
int add(int x, int y, int z);
 
int main()
{
    using namespace std;
    cout << "3 + 4 + 5 = " << add(3, 4, 5) << endl;
    return 0;
}
 
int add(int x, int y, int z)
{
    return x + y + z;
}

答案

點這裡

相關推薦

1.7前置宣告定義

先來看一個表面看起來沒有錯誤的程式add.cpp。 #include <iostream> int main() { using namespace std; cout << "The sum of 3 and 4 is: "

4.1.7 特殊方法運算符重載

clas 這一 1.7 生成 tle tro .py 地址 定義類   Python的類有大量的特殊方法,其中比較常見的是構造函數和析構函數。Python中類的構造函數是__init__(),一般用來為數據成員設置初始值或進行其他必要的初始化工作,在創建對象時被自動調用和執

C語言的宣告定義

keil的專案中,遇到呼叫其他C檔案函式和變數的情況: 對於函式,在a.c下面進行編寫,之後在a.h下面進行宣告,其他檔案包含a.h即可呼叫。 對於變數,在a.c下面進行定義,在a.h下面也要進行一下宣告,其他檔案使用此變數時,包含a.h即可使用。 關於變數的定義與宣告 變數定義即為

Navicat Premium 12.1.7.0安裝啟用

本文介紹Navicat Premium 12.1.7.0的安裝、啟用與基本使用。 博主所提供的啟用檔案理論支援Navicat Premium 12.0.x系列和Navicat Premium 12.1.x系列的註冊機。由於本文一直在更新,Navicat Premium 12.0.x系列

JAVA 全域性變數 宣告定義

JAVA全域性變數(或稱成員變數)可分兩種,一種是靜態變數,另一種是例項變數,即在類體中定義的變數,有三點得注意: 一、成員變數不能在類體中先宣告(定義)後賦值,但靜態變數可以先在類體中宣告,然後在方法中賦值(當然例項變數是不行的); 1)如以下程式會出問題: publi

Java之路:類的宣告定義

一、類的宣告 在使用類之前,必須先宣告它,然後才可以宣告變數,並建立物件。 類宣告的語法如下: [識別符號] class 類名稱 { //類的成員變數 //類的方法 } 在上面的語法格式中,[識別符號] 可以是public、private、p

1.7變數檢測傳遞

均值不等式是高中甚至是大學數學的一個難點,它涉及1)算術平均值,2)幾何平均值,3)調和平均值,4)平方平均值,試編寫一個程式驗證均值不等式,要求程式輸入引數和輸入引數可變。 【分析】假設程式的每一個

【C語言】宣告定義

前言 引用性宣告 不分配儲存空間,如extern int x; 只是告訴編譯器x是整形,已經在其它地方定義了。 定義 是在記憶體中確定變數的位置、大小。 初始化 是定義變數時候賦給變數的值(從無到有)

Navicat10.1.7版本下載破解,永久使用

由於沒有找到Navicat的最新版本的好的破解方法,別的博主也是發過很多最新12版本的破解方法,可是我的windows10就是不能下載註冊機,所以無法進行破解,相信有很多小白也遇到過這個問題,不過現在不用怕了,這裡有詳細的10版本的安裝和破解方法,不用謝,哈哈 我現在使用的是10.1.7

Go入門自學寶典004-變數(宣告定義)、常量(const )、列舉(iota)

004-變數 變數是幾乎所有程式語言中最基本的組成元素,變數是程式執行期間可以改變的量。 從根本上說,變數相當於是對一塊資料儲存空間的命名,程式可以通過定義一個變數來申請一塊資料儲存空間,之後可以通過引用變數名來使用這塊儲存空間。 004.1 變數宣告 Go語言

c++模板類的成員函式的宣告定義應該放在標頭檔案裡

    今天嘗試自己實現vector資料結構底層,在定義vector模板類的時候,還想像往常一樣把類分為.h檔案和.cpp檔案,把成員函式的宣告放在.h檔案中,把具體實現放在.cpp檔案中,結果在測試時發現在編譯過程中報錯。除錯了很久,重視提示“無法解析的外部符號”,如圖所示

C#學習日記13---類(Class)的宣告定義

        類作為面向物件的靈魂,在C#中有著相當廣泛和深入的應用,對類的深度掌握自然是我們學習C#重要的一個環節.有關類的意義上一篇  C#學習日記12---引用型別 中已經給出了,這裡就不再重

C中變數的宣告定義

在C中,變數的定義主要可分為兩種狀況:在函式內和在函式外。 但變數在函式內定義時,其屬性只能分為static和無static,而該變數便無法被外部函式所引用。而定義為static時表示該變數只能用來初始化一次。 而變數在函式外定義時,其屬性也只能分為static和exter

C語言中變數和函式的宣告定義

一、變數在將變數前,先解釋一下宣告和定義這兩個概念。宣告一個變數意味著向編譯器描述變數的型別,但並不為變數分配儲存空間。定義一個變數意味著在宣告變數的同時還要為變數分配儲存空間。在定義一個變數的同時還可以對變數進行初始化。 區域性變數通常只定義不宣告,而全域性變數多在原始檔中定義,在標頭檔案中宣告。 區域性變

關於模板類中友元函式的宣告定義

//Widget.h #ifndef _WIDGET_H_ #define _WIDGET_H_ #include <iostream> using namespace std; template<class T> class Widget {

The New C++ -- 變數(1. 變數的宣告定義

在C++中,不僅僅是變數才有名字,列舉(enumeration),函式(function),類(class),模板(template)等物件都有名字。在使用任何一個名字之前,必須要先對該名字表示的物件進行宣告(declaration)或者定義(definition)。宣告

C\C++中宣告定義的區別

C語言是面向過程的,而C++是面向物件的 C和C++的區別: C是一個結構化語言,它的重點在於演算法和資料結構。C程式的設計首要考慮的是如何通過一個過程,對輸入(或環境條件)進行運算處理得到輸出(或實現過程(事務)控制)。 C++,首要考慮的是如何構造一個物件模型,讓這

C語言:函式宣告定義的引數不一致問題,後果可能很嚴重哦!!!!!

  具體:在檔案main.c中 int func ();     //宣告中沒引數 int main(){         int c = func();            //呼叫時也不傳參,這樣才能編譯通過         printf("%d\n",c);    

【C++】C++中變數的宣告定義的區別

宣告(declaration):意味著告訴編譯器關於變數名稱、變數型別、變數大小、函式名稱、結構名稱、大小等等資訊,並且在宣告階段不會給變數分配任何的記憶體。 定義(definition):定義就是在變數聲明後,給它分配上記憶體。可以看成“定義 = 宣告 + 記憶體分配”。 例如: #includ

c語言物件(函式,類,變數)的宣告定義

最近在看一本《c缺陷與陷阱》,意識到在大型程式時物件的宣告與定義的重要性 普通變數的定義與宣告 每個外部物件都必須在程式的某個地方進行定義。因此如果一個程式中包含了語句 extern int a;//宣告變數a 那麼,這個程式就必須在別處的某個地方包括語句 in