程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C語言頭文件的使用,c語言頭

C語言頭文件的使用,c語言頭

編輯:關於C語言

C語言頭文件的使用,c語言頭


 讓我們的思緒乘著時間機器回到C語言老師正在講台上講著我們的第一個C語言程序: Hello world!

 

 文件名 First.c

main()

{

     printf(“Hello world!”);

}

     例程-1

看看上面的程序,沒有.h文件。是的,就是沒有,世界上的萬物都是經歷從沒有到有的過程的,我們對.h的認識,我想也需要從這個步驟開始。這時確實不需要.h文件,因為這個程序太簡單了,根本就不需要。那麼如何才能需要呢?讓我們把這個程序變得稍微復雜些,請看下面這個,

文件名 First.c

 

 printStr()

{

     printf(“Hello world!”);

}

main()

{

printStr()

}

     例程-2

 

還是沒有, 那就讓我們把這個程序再稍微改動一下.

 

文件名 First.c

main()

{

printStr()

}

 

 

 printStr()

{

     printf(“Hello world!”);

}

     例程-3

 

等等,不就是改變了個順序嘛, 但結果確是十分不同的. 讓我們編譯一下例程-2

和例程-3,你會發現例程-3是編譯不過的.這時需要我們來認識一下另一個C語言中的概念:作用域.

我們在這裡只講述與.h文件相關的頂層作用域, 頂層作用域就是從聲明點延伸到源程序文本結束,就printStr()這個函數來說,他沒有單獨的聲明,只有定義,那麼就從他定義的行開始,到first.c文件結束, 也就是說,在在例程-2的main()函數的引用點上,已經是他的作用域. 例程-3的main()函數的引用點上,還不是他的作用域,所以會編譯出錯. 這種情況怎麼辦呢? 有兩種方法 ,一個就是讓我們回到例程-2, 順序對我們來說沒什麼, 誰先誰後不一樣呢,只要能編譯通過,程序能運行, 就讓main()文件總是放到最後吧. 那就讓我們來看另一個例程,讓我們看看這個方法是不是在任何時候都會起作用.

文件名 First.c

play2()

{

  ……………….

  play1()

  ………………..

}

play1()

{

……………………..

     play2()

  ……………………

);

}

main()

{

play1()

}

例程-4

 

也許大部分都會看出來了,這就是經常用到的一種算法, 函數嵌套, 那麼讓我們看看, play1和play2這兩個函數哪個放到前面呢?

 

這時就需要我們來使用第二種方法,使用聲明.

文件名 First.c

play1();

play2();

play2()

{

  ……………….

  play1()

  ………………..

}

play1()

{

……………………..

     play2()

  ……………………

);

}

main()

{

play1()

}

例程-4

 

經歷了我的半天的唠叨, 加上四個例程的說明,我們終於開始了用量變引起的質變, 這篇文章的主題.h文件快要出現了。

一個大型的軟件項目,可能有幾千個,上萬個play, 而不只是play1,play2這麼簡單, 這樣就可能有N個類似 play1(); play2(); 這樣的聲明, 這個時候就需要我們想辦法把這樣的play1(); play2(); 也另行管理, 而不是把他放在.c文件中, 於是.h文件出現了.

 

文件名 First.h

play1();

play2();

文件名 First.C

#include “first.h”

play2()

{

  ……………….

  play1()

  ………………..

}

play1()

{

……………………..

     play2()

  ……………………

);

}

main()

{

play1()

}

例程-4

 

各位有可能會說,這位janders大蝦也太羅嗦了,上面這些我也知道, 你還講了這麼半天, 請原諒, 如果說上面的內容80%的人都知道的話,那麼我保證,下面的內容,80%的人都不完全知道. 而且這也是我講述一件事的一貫作風,我總是想把一個東西說明白,讓那些剛剛接觸C的人也一樣明白.

上面是.h文件的最基本的功能,  那麼.h文件還有什麼別的功能呢? 讓我來描述一下我手頭的一個項目吧.

 

這個項目已經做了有10年以上了,具體多少年我們部門的人誰都說不太准確,況且時間並不是最主要的,不再詳查了。 是一個通訊設備的前台軟件, 源文件大小共 51.6M, 大小共1601個文件, 編譯後大約10M, 其龐大可想而知,  在這裡充斥著錯綜復雜的調用關系,如在second.c中還有一個函數需要調用first.c文件中的play1函數, 如何實現呢?

 

Sencond.h 文件

 

play1();

 

sencond.c文件

 

***()

{

…………….

Play();

……………….

}

例程-5

 

在sencond.h文件內聲明play1函數,怎麼能調用到first.c文件中的哪個play1函數中呢? 是不是搞錯了,沒有搞錯, 這裡涉及到c語言的另一個特性:存儲類說明符.

C語言的存儲類說明符有以下幾個, 我來列表說明一下

 

說明符                      用法

Auto               只在塊內變量聲明中被允許, 表示變量具有本地生存期.

Extern                          出現在頂層或塊的外部變量函數與變量聲明中,表示聲明的對象

具有靜態生存期, 連接程序知道其名字.

Static              可以放在函數與變量聲明中. 在函數定義時, 其只用於指定函數

                                   名,而不將函數導出到連接程序. 在函數聲明中,表示其後面會有

定義聲明的函數, 存儲類為static. 在數據聲明中, 總是表示定義

的聲明不導出到連接程序.

無疑, 在例程-5中的second.h和first.h中,需要我們用extern標志符來修飾play1函數的聲明,這樣,play1()函數就可以被導出到連接程序, 也就是實現了無論在first.c文件中調用,還是在second.c文件中調用,連接程序都會很聰明的按照我們的意願,把他連接到first.c文件中的play1函數的定義上去, 而不必我們在second.c文件中也要再寫一個一樣的play1函數.

但隨之有一個小問題, 在例程-5中,我們並沒有用extern標志符來修飾play1啊, 這裡涉及到另一個問題, C語言中有默認的存儲類標志符. C99中規定, 所有頂層的默認存儲類標志符都是extern . 原來如此啊,  哈哈.  回想一下例程-4, 也是好險, 我們在無知的情況下, 竟然也誤打誤撞,用到了extern修飾符, 否則在first.h中聲明的play1函數如果不被連接程序導出,那麼我們在在play2()中調用他時, 是找不到其實際定義位置的 .

 

那麼我們如何來區分哪個頭文件中的聲明在其對應的.c文件中有定義,而哪個又沒有呢? 這也許不是必須的,因為無論在哪個文件中定義,聰明的連接程序都會義無返顧的幫我們找到,並導出到連接程序, 但我覺得他確實必要的. 因為我們需要知道這個函數的具體內容是什麼,有什麼功能, 有了新需求後我也許要修改他, 我需要在短時間內能找到這個函數的定義, 那麼我來介紹一下在C語言中一個人為的規范:

 

在.h文件中聲明的函數,如果在其對應的.c文件中有定義,那麼我們在聲明這個函數時,不使用extern修飾符, 如果反之,則必須顯示使用extern修飾符.

 

這樣,在C語言的.h文件中,我們會看到兩種類型的函數聲明. 帶extern的,還不帶extern的, 簡單明了,一個是引用外部函數,一個是自己生命並定義的函數.

最終如下:

Sencond.h 文件

 

Extern play1();

 

 

上面洋洋灑灑寫了那麼多都是針對函數的,而實際上.h文件卻不是為函數所御用的. 打開我們項目的一個.h文件我們發現除了函數外,還有其他的東西, 那就是全局變量. 

 

在大型項目中,對全局變量的使用不可避免, 比如,在first.c中需要使用一個全局變量G_test, 那麼我們可以在first.h中,定義 TPYE G_test. 與對函數的使用類似, 在second.c中我們的開發人員發現他也需要使用這個全局變量, 而且要與first.c中一樣的那個, 如何處理? 對,我們可以仿照函數中的處理方法,在second.h中再次聲明TPYE G_test, 根據extern的用法,以及c語言中默認的存儲類型, 在兩個頭文件中聲明的TPYE G_test,其實其存儲類型都是extern, 也就是說不必我們操心, 連接程序會幫助我們處理一切. 但我們又如何區分全局變量哪個是定義聲明,哪個是引用聲明呢?這個比函數要復雜一些, 一般在C語言中有如下幾種模型來區分:

 

1、 初始化語句模型

頂層聲明中,存在初始化語句是,表示這個聲明是定義聲明,其他聲明是引用聲明。C語言的所有文件之中,只能有一個定義聲明。

按照這個模型,我們可以在first.h中定義如下TPYE G_test=1;那麼就確定在first中的是定義聲明,在其他的所有聲明都是引用聲明。

2、 省略存儲類型說明

在這個模型中,所有引用聲明要顯示的包括存儲類extern, 而每個外部變量的唯一定義聲明中省略存儲類說明符。

這個與我們對函數的處理方法類似,不再舉例說明。

 

       這裡還有一個需要說明,本來與本文並不十分相關,但前一段有個朋友遇到此問題,相信很多人都會遇到, 那就是數組全局變量。

 

他遇到的問題如下:

在聲明定義時,定義數組如下:

int G_glob[100];

 

在另一個文件中引用聲明如下:

int * G_glob;

 

在vc中,是可以編譯通過的, 這種情況大家都比較模糊並且需要注意,數組與指針類似,但並不等於說對數組的聲明起變量就是指針。 上面所說的的程序在運行時發現了問題,在引用聲明的那個文件中,使用這個指針時總是提示內存訪問錯誤,原來我們的連接程序並不把指針與數組等同,連接時,也不把他們當做同一個定義,而是認為是不相關的兩個定義,當然會出現錯誤。正確的使用方法是在引用聲明中聲明如下:

 

int G_glob[10];

 

並且最好再加上一個extern,更加明了。

 

extern int G_glob[10];

 

       另外需要說明的是,在引用聲明中由於不需要涉及到內存分配,可以簡化如下,這樣在需要對全局變量的長度進行修改時,不用把所有的引用聲明也全部修改了。

 

extern int G_glob[];

 

       C語言是現今為止在底層核心編程中,使用最廣泛的語言,以前是,以後也不會有太大改變,雖然現在java,.net等語言和工具對c有了一定沖擊,但我們看到在計算機最為核心的地方,其他語言是無論如何也代替不了的,而這個領域也正是我們對計算機癡迷的程序員所向往的。

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