能否用痰盂盛飯——談談在頭文件中定義外部變量


   “能否用痰盂盛飯”並非是一個技術問題,而是一個哲學問題。
  哲學問題沒有標准答案,只存在不同的選擇。
  有一種觀點認為,痰盂可以盛飯。理由是只要不漏能把飯吃到嘴里就行。我看這個理由任何人都無法反駁。
  另有一種觀點認為,痰盂是用來吐痰的,不可以用來盛飯。他們覺得用痰盂盛飯是一種不可理喻的行為。然而這種看法可能會被“痰盂派”視為一種不必要的潔癖。

  C語言中也有類似的“痰盂”問題:

  頭文件除了可以包含函數原型和宏定義外,也可以包括結構體類型定義和全局變量定義
    ————譚浩強,《C程序設計》(第四版)學習輔導,清華大學出版社,2010年7月,p188

  在頭文件中究竟能否定義外部變量呢?這個問題同樣有兩種選擇。
  “痰盂派”會認為可以。比如
  在 abnormal.h 中寫

int e_v = 1 ;

 

  然后在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中兩次定義這個外部變量的情況,這是絕對不允許的。這就是“非痰盂派”認為在頭文件中不可以寫外部變量定義的理由。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM