程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 關於函數聲明、函數原型與函數定義

關於函數聲明、函數原型與函數定義

編輯:關於C語言

對函數的“定義”和“聲明”不是一回事。函數的定義是指對函數功能的確立,包括指定函數名,函數值類型、形參及其類型以及函數體等,它是一個完整的、獨立的函數單位。而函數的聲明的作用則是把函數的名字,函數類型以及形參的類型、個數和順序通知編譯系統,以便在調用該函數時進行對照檢查(例如,函數名是否正確,實參與形參的類型和個數是否一致),它不包括函數體。——譚浩強 ,《C程序設計》(第四版),清華大學出版社,2010年6月,p182

這段論述包含了許多概念性錯誤,這些概念錯誤在許多C語言書中都同樣普遍存在。為了說明這些錯誤,首先來回顧一下C語言演變和發展的一些情況。

最早,C語言的代碼可以這樣寫:

main()
{
	printf("hello,world!\n");
}

注意,這段代碼對標識符printf沒有進行任何說明。這是因為printf()函數的返回值為int類型。當時的C語言規定,對於沒有任何說明的函數名,編譯器會默認為返回值為int類型,因此對這樣的函數名可以不做任何說明。那個時期的C語言,很多情況下int可以不寫。例如main()函數返回值的類型為int就可以不寫。

但是需要特別說明的是,這種“省勁”的寫法已經過時,從C90標准起,這種寫法就步入了被逐步拋棄的過程(盡管當時還沒有完全立即廢止)。C99廢除了隱式函數聲明法則(remove implicit function declaration),另外,省略main()前面的int也已經不再容許了。

在C語言早期,盡管有時不需要對函數名進行說明,但有些情況下對函數名進行說明還是必須的,比如:

double sqrt();
int main()
{
	printf("%f\n" , sqrt(9.) );
}

這是因為函數sqrt()返回值的類型不是int類型而是double類型,編譯器編譯時需要知道sqrt(9.)這個表達式的類型。

不難注意到這種對函數名的說明非常簡單,這是最早期的一種函數類型說明的形式。這種說明只著重說明函數名是一個函數及其返回值類型,如果程序員在調用函數時存在參數類型或個數方面的錯誤編譯器是無法察覺的,因為函數類型說明中“()”內沒有任何信息。

這種辦法只說明了函數名與()進行運算的結果也就是函數返回值的數據類型,無法進一步檢查參數方面的錯誤是這種寫法的不足之處。

如果不寫函數類型說明,也可以把函數定義寫在函數調用之前:

double square ( double x) 
{
	return x * x ;
}
int main(void)
{
	printf("%f\n" , square(3.) );
	return 0;
}

這表明函數定義也具有對函數名的類型加以說明的效果,因此從這個意義上來說,函數定義也是一種對函數類型的說明。這種辦法可以檢查出函數調用時在參數個數和類型方面的錯誤。

但是,用這種辦法說明函數名並不好,因為這樣做在編程時還需要考慮應該把哪個函數定義寫在前面,哪個寫在後面的問題。假如函數A調用函數B,函數B調用函數C,函數C又調用函數A,究竟如何安排函數定義的順序就會讓人感到無所適從。此外這種辦法也不利於代碼的組織,在由多個源文件組成的源程序時,這種寫法就更會捉襟見肘、漏洞百出。因此,在1990年,C標准借鑒C++語言規定了一種新的說明函數名的方法,這就是函數原型(Function Propotype)式說明函數類型的方法:

double square ( double );  //或 double square ( double x)
int main(void)
{
  	printf("%f\n" , square(3.) );
    return 0;
}
double square ( double x) 
{
   	return x * x ;
}

使用這種辦法,不但可以檢查函數調用時參數類型和個數方面的錯誤,同時解決了源代碼的組織問題,因為程序員不必再考慮該把哪個函數寫在前面、哪個寫在後面這種無聊的問題了。這種辦法全面地說明了函數名的數據類型。此外要說明的是,把形參及其數據類型寫在“()”內形式的函數定義也屬於函數原型(Function Propotype)的范疇。

由此可見,古老的、不對參數進行任何說明的函數類型說明方式、函數定義以及函數原型式的函數類型說明方式都具有說明函數名意義的效用。從這個意義上講它們都是函數聲明。在C語言中,聲明(Declaration)這個詞的本義就是指定標識符的意義和性質(A declaration specifies the interpretation and attributes of a set of identifiers.),某個標識符的定義(Definition)同時也是這個標志符的“聲明”(Declaration)。函數定義(Function definition)則意指包括函數體。(A definition of an identifier is a declaration for that identifier that: ……for a function, includes the function body;)。函數原型則特指包括說明參數類型的函數聲明,它同樣包含用這種方式寫出的函數定義。

現在回過頭來看樣本中的第一句話:“對函數的“定義”和“聲明”不是一回事”。由於函數定義本身就是一種函數聲明,怎麼可以說它們不是一回事呢?這句話的邏輯就如同說“男人”和“人”不是一回事。你可以說男人和女人不是一回事,因為他們沒有交集。但沒法說男人和人不是一回事,因為男人是人的子集,男人就是人的一種,怎麼可以說男人和人不是一回事呢?

那麼,不帶函數體的函數聲明應該如何稱呼呢?在C語言中,它們叫被做“函數類型聲明”(Function type declaration)。函數類型聲明最主要的特點是聲明了函數名是一個函數及其返回值的類型,如果也聲明了參數的類型,則是函數原型式的函數類型聲明。

樣本中的“而函數的聲明的作用則是把函數的名字,函數類型以及形參的類型、個數和順序通知編譯系統,以便在調用該函數時進行對照檢查(例如,函數名是否正確,實參與形參的類型和個數是否一致),它不包括函數體”這句話同樣不通。其主要錯誤是它混淆了“函數原型式類型聲明”與“函數聲明”這兩個概念,前一個概念只是後一個概念的子集。函數聲明中不但包含“函數類型聲明”,也包含“函數定義”和老式的“函數類型聲明”。由於函數定義本身就是一種函數聲明,所以無法斷定函數的聲明是否包括函數體;而且老式的函數類型聲明(例如double sqrt();)也屬於函數聲明,這種函數聲明並不檢查參數類型及個數方面的錯誤。此外函數聲明也並沒有檢查“函數名”正確與否的功能。

這段文字中的“函數類型”這個概念也有錯誤,函數類型所描述的不但包括函數返回值類型,也可能一並描述參數的個數和類型(如果是函數原型),因此不能與“形參的類型、個數”相提並論。

現代的C語言的函數定義和函數類型聲明都采用函數原型式的風格,C99把舊的非原型形式視為過時,這意味著非原型形式以後可能被禁止。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved