--C語言的要求:函數先聲明後定義
--example 1: test1.c中
#include<stdio.h>
int main(void)
{
printf("%d\n",fun(5));
}
int fun()
{
printf("Hello World\n");
}
//代碼編譯,連結都能夠通過。但是會有警告:
warning C4013: 'fun' undefined; assuming extern returning int
warning C4716: 'fun' : must return a value
通過可以看出雖然fun(5)在使用前沒有聲明,但是編譯器僅僅是給出警告,並提供預設的函式宣告類型:int name(void); (參考C語言創始人的書),編譯器在遇到 fun(5) 的“fun(”的時候就假設fun是一個函數,如果沒有找到fun函數,就提供預設的函式宣告,且忽略fun中的函數參數。並在連結的過程中找到了fun函數的定義,則通過連結,產生可執行代碼。如下代碼,能夠通過編譯,卻不能通過連結,因為連結的時候找不到fun函數的定義。
#include<stdio.h>
int main(void)
{
printf("%d\n",fun(5));
}
--註:第一個程式在C++編譯器裡不能通過編譯,因為C++編譯器嚴格要求函數先聲明後使用,如果找不到聲明,不能通過編譯,編譯器並不會提供預設的函式宣告。
--多檔案組織中函數的先聲明後定義
--test1.c
#include<stdio.h>
int main(void)
{
printf("%d\n",fun(5));
}
--test2.c
int fun()
{
printf("Hello World\n");
}
--上述檔案在編譯時間能夠通過編譯,並且能偶通過連結,但是會產生如下警告:
Test1.c : warning C4013: 'fun' undefined; assuming extern returning int
Test2.c:
warning C4013: 'printf' undefined; assuming extern returning int
warning C4716: 'fun' : must return a value
test1.c中產生警告的原因是上面已經解釋過了,連結能夠通過是連結器能夠自動的把一起編譯的test1.c和test2.c中可執行代碼連結起來,這樣test1.c中的fun函數就找到了定義。
Test2.c中第一個警告是由於上述同樣的原因,能夠通過連結是由於test1.c中已經包含了標頭檔<stdio.h>,把printf的可執行代碼給連結過來了,所以test2.c中的printf也就能夠找到定義。
--上述例子說明多個源檔案檔案在編譯環境下可以通過連結器自動連接到一起並不需要標頭檔的參與,但是為了程式的可移植性和正確性,一定要遵循先聲明後使用的原則:這個原則不但適用於函數,也使用全域變數。
--標頭檔中應該包含什麼
--test.h
int a;
void show(void)
{
printf("Hello world\n");
}
--test1.c
int fun()
{
printf("Hello World\n");
}
--test2.c
#include<stdio.h>
#include "test.h"
int main(void)
{
printf("%d\n",fun(5));
}
上述編譯和連結同樣能夠通過,通過上面的例子不難理解:因為就是在標頭檔中定義了全域變數,並把它包含在test2.c中;
--test.h
int a;
void show(void)
{
printf("Hello world\n");
}
--test1.c
#include “test.h ”
int fun()
{
printf("Hello World\n");
}
--test2.c
#include<stdio.h>
#include "test.h"
int main(void)
{
printf("%d\n",fun(5));
}
--上述多檔案程式能夠通過編譯,卻不能通過連結:
error LNK2005: _show already defined in test1.obj
fatal error LNK1169: one or more multiply defined symbols found
具體原因有以下兩個:
1:test1.c包含了test.h的標頭檔,定義了全域變數 a,同理test.2也通過相同的手段定義了全域變數a;
2: test1.c和test2.c通過上述手段分別提供了show的定義
--上述分析指出在連結的過程中由於出現了同一個函數和變數的多個定義,所以連結出錯。
所以在標頭檔不要包含函數的定義和全域變數的定義,這樣做雖然有時候連結和編譯都能通過,但是還是有一定的隱患。
總結,在標頭檔中一般只包含函數的聲明和全域變數的聲明。函數的聲明大家都知道,全域變數的聲明大家不一定清楚:必須顯式的提供extern
如:extern int a; 注意在標頭檔中這樣只是聲明,並不提供實際的定義,在使用該變數的源檔案中還有重新以 int a ; 的形式重新定義,而其他使用該全域變數的源檔案只需包含該標頭檔即可。