能否用痰盂盛饭——谈谈在头文件中定义外部变量
能否用痰盂盛飯——談談在頭文件中定義外部變量 - garbageMan - 博客園
能否用痰盂盛飯——談談在頭文件中定義外部變量
? “能否用痰盂盛飯”并非是一個技術問題,而是一個哲學問題。
哲學問題沒有標準答案,只存在不同的選擇。
有一種觀點認為,痰盂可以盛飯。理由是只要不漏能把飯吃到嘴里就行。我看這個理由任何人都無法反駁。
另有一種觀點認為,痰盂是用來吐痰的,不可以用來盛飯。他們覺得用痰盂盛飯是一種不可理喻的行為。然而這種看法可能會被“痰盂派”視為一種不必要的潔癖。
C語言中也有類似的“痰盂”問題:
頭文件除了可以包含函數原型和宏定義外,也可以包括結構體類型定義和全局變量定義。
————譚浩強,《C程序設計》(第四版)學習輔導,清華大學出版社,2010年7月,p188
在頭文件中究竟能否定義外部變量呢?這個問題同樣有兩種選擇。
“痰盂派”會認為可以。比如
在 abnormal.h 中寫
?
然后在abnormal.c中寫
#include "abnormal.h" #include <stdio.h> int main(void) {printf("%d\n",e_v);return 0; } 沒有人會說這段源程序有什么語法問題,它也沒有違背C語言的任何規定。就像痰盂可以用于盛飯是一個道理。
但是,“非痰盂派”會覺得這段源程序透著一種莫名其妙的詭異:既然定義變量e_v是為了在abnormal.c中使用,那么把這個變量定義在abnormal.c中顯然最直接也最便利,為什么要舍近求遠地把它定義到abnormal.h文件中呢?
在“非痰盂派”的眼中,“*.h”文件不是用來寫變量定義的,就如痰盂不是用來盛飯的一樣。那么“*.h”文件是做什么用的呢?
首先來看最簡單的情況
| #include <stdio.h>#include <math.h>int main(void){??printf("%f\n",sqrt( 9.0 ));??return 0;} |
?
? 在這段代碼中,出現了printf和sqrt這樣兩個標識符,由于編譯器在編譯時不認得這兩個標識符,所以需要告訴編譯器這兩個標識符的含義。這就是代碼前面兩條預處理命令
?| #include <stdio.h>#include <math.h> |
的意義。這兩個頭文件中分別包含printf和sqrt這樣兩個標識符的類型聲明。只有告訴了編譯器這兩個標識符的含義,編譯器才能把它正確地編譯成目標文件(*.obj或*.o)。(如果函數返回值類型為int可不聲明,但這是一種落后的、逐漸被淘汰的風格)
所以“*.h”文件的一個基本功能就是提供函數類型聲明。
另外要注意到的一點是,stdio.h、math.h是由庫函數作者提供的,相當于給其他模塊提供了一個使用“說明書”,使用庫函數的模塊用這個“說明書”向編譯器說明自己所用到的在其他模塊定義的函數名的數據類型。
下面舉例進一步說明。
假設一個源程序由兩個*.c源文件組成,第一個*.c提供求兩個double類型數據和的函數,第二個使用這個函數。那么,第一個*.c文件的作者僅僅寫出
1.c
| double add(double d1,double d2){??return d1 + d2 ;} |
?
是遠遠不夠的,因為這個1.c雖然可以編譯目標文件(*.obj或*.o)以供連接時使用,但是在鏈接之前,2.c文件在編譯時還需要告訴編譯器add這個標識符的含義,否則無法正確地編譯出與之相應的目標文件。為此,第一個*.c文件的作者還應該給出一個“說明書”——*.h文件
1.h
| double add(double,double); |
?
? 2.c文件可以寫為
?| #include "1.h"int main(void){??printf("%f\n",add(3.0,4.0));??return 0;} |
這樣就解決了第二個*.c文件的編譯問題(add得到了說明)。
需要說明的是,在很多情況下1.c自己也往往需要包含這個1.h文件,因為這個1.c中可能有其他函數也需要調用這個add()函數,因而也需要這個函數類型聲明。所以一般1.c寫為
1.c
| #include "1.h"double add(double d1,double d2){??return d1 + d2 ;} |
? 再來看*.h文件中出現數據類型聲明的情況。
假設在1.c中使用了一種新的用戶定義的數據類型(不一定是“結構體類型”),例如
| typedef double DOUBLE; |
這時通常也應該把該數據類型的聲明寫在“*.h"文件中提供給其他模塊,除非這個類型僅僅在1.c中使用。
這時的1.h應該為
1.h
| typedef double DOUBLE;DOUBLE add(DOUBLE,DOUBLE); |
? 這時的1.c為
1.c
| #include "1.h"DOUBLE add(DOUBLE d1,DOUBLE d2){??return d1 + d2 ;} |
? 而2.c則為
?| #include "1.h"int main(void){??DOUBLE d1=3.0,d2=4.0;??printf("%f\n",add(d1,d2));??return 0;} |
? 在這里共有兩處用到了DOUBLE類型,一次是定義d1,d2這兩個變量,另一次是調用add()函數。由于在1.h中這種類型已經得到了聲明,所以這段代碼可以順利通過編譯。
除了函數類型聲明、數據類型聲明出現在*.h文件中,宏定義也可能出現在*.h中。
如果1.c和2.c都需要一個共同的常數,把這常數作為一個符號常量寫在1.h中,顯然可以避免你寫你的、我寫我的,從而造成大家不協調一致的錯誤。因為這時大家都是在參照著同一個常數(宏)在寫代碼。
但是,如果把外部變量的定義寫在*.h中會出現什么情況呢?很顯然,會出現在1.c和2.c中兩次定義這個外部變量的情況,這是絕對不允許的。這就是“非痰盂派”認為在頭文件中不可以寫外部變量定義的理由。
總結
以上是生活随笔為你收集整理的能否用痰盂盛饭——谈谈在头文件中定义外部变量的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: apache启动报错记录
- 下一篇: Shell图形化监控网络流量