C語言中標頭檔案相互包含問題
.h中一般放的是同名.c檔案中定義的變數、陣列、函式的宣告,需要讓.c外部使用的宣告。
1)h檔案作用
1 方便開發:包含一些檔案需要的共同的常量,結構,型別定義,函式,變數申明;
2 提供介面:對一個軟體包來說可以提供一個給外界的介面(例如: stdio.h)。
2)h檔案裡應該有什麼
常量,結構,型別定義,函式,變數申明。
3)h檔案不應該有什麼
變數定義, 函式定義。
4)extern問題
對於變數需要extern;
對於函式不需要因為函式的預設狀態是extern的.如果一個函式要改變為只在檔案內可見,加static
5)include包含問題
雖然申明和型別定義可以重複,不過推薦使用條件編譯。
#ifndef _FILENAME_H, //如果未定義_filename.h,則定義
#define _FILENAME_H
……
#endif
6)應該在那兒包含h檔案
在需要的地方.比如某個提供介面的h檔案僅僅被1.c檔案需要,那麼就在1.c檔案裡包含。
編寫的程式一般會有.H檔案和相對應的.C檔案,.H檔案是宣告所用,.C檔案是其函式實現部分。在呼叫時只要包含.H檔案即可,我們沒有聽說過#include "delay.c"這類的程式,同時也不提倡使用這個形式。
在delay.h檔案中: //對呼叫的函式宣告
#ifndef __DELAY_H__
#define __DELAY_H__
extern void Delayms(unsigned int n); //包含在保護巨集中
#endif
在delay.c檔案中: //函式實現部分
#include <delay.h> //for crystal 11.0592M 延遲函式
void Delayms(unsigned int n)
{
unsigned int i,j;
for(j=n;j>0;j--)
for(i=112;i>0;i--);
}
在主程式main.c中
#include <delay.h> //在主程式包含.h檔案,不能包含.c檔案
通常一個C程式工程按功能可以分成多個模組, 一個模組通常由兩個文件組成一個頭檔案 *.h, 對模組中的資料結構和函式原型進行描述;另一個為C檔案*.C , 對資料例項或物件進行定義,以及函式演算法的具體實現,如I2C.C, SPI.C, DAC.C, DISPLAY.C 等,為了檔案的呼叫,我們要為每個模組定義一個頭檔案,以I2C.C 來說,定義I2C.H。
#ifndef GRAPHICS_H /*防止graphics.h被重複引用*/
#define GRAPHICS_H
#include <math.h > /*引用標準庫的標頭檔案*/
…
#include “myheader.h” /* 引用非標準庫的標頭檔案*/
…
void Function1(…); /*全域性函式宣告*/
…
class Box /*類結構宣告*/
{
…
};
#endif
******************************************************************************************************************************************************************************************************************
模組化的程式是黑盒,只向外提供介面(全域性變數、外部函式),而不需要讓呼叫者瞭解其中過程。儘可能地少定義介面有利於保持模組的獨立性(不需要讓使用者知道的內部函式與靜態全域性變數不需要在H檔案中給出以避免使用者疑惑)在需要呼叫此模組的檔案中寫入include語句。一個好的工程,H檔案的組織是很清晰的,只看H檔案就能夠寫主程式呼叫相應的C模組。
標頭檔案的格式如下(I2C.H為例):
********************************************************************
#ifndef I2C_H /*是否沒有定義過 "I2C_H”, 防止重定義*/
#define I2C_H /*定義"I2C_H" */
..........
bit SetSDA ( bit Up_Down );
bit SetSCL ( bit Up_Down);
#endif
**********************************************************************
I2C.C格式如下:
**********************************************************************
#include < stdio.h >
#include "I2C.h"
void SendByte ( uchar c ); /*內部函式在.H 標頭檔案中不描述*/
bit SetSDA ( bit Up_Down ) { .......... };
bit SetSCL ( bit Up_Down) { .......... };
**********************************************************************
另外一種寫法:
=============================
#ifndef I2C_H
#define I2C_H
..........
exten bit SetSDA ( bit Up_Down );
exten bit SetSCL ( bit Up_Down);
#endif
=================================================
I2C.C格式如下:
=================================================
#include < stdio.h >
void SendByte ( uchar c ); /*內部函式在.H 標頭檔案中不宣告*/ // 在外部是否可一點用????
bit SetSDA ( bit Up_Down ) { .......... }; //同文件宣告函式的實現
bit SetSCL ( bit Up_Down) { .......... };
=================================================
舉個例子,順便分析一下ifndef/define/endif:
假設你的工程裡面有4個檔案,分別是a.cpp, b.h, c.h, d.h。
a.cpp的頭部是:
#include "b.h"
#include "c.h"
b.h和c.h的頭部都是:
#include "d.h"
而d.h裡面有class D的定義。
這樣一來, 編譯器編譯a.cpp的時候,先根據#include "b.h"去編譯b.h這個問題,再根據b.h裡面的#include "d.h",去編譯d.h的這個檔案,這樣就把d.h裡面的class D編譯了;
然後再根據a.cpp的第二句#include "c.h",去編譯c.h,最終還是會找到的d.h裡面的class D,但是class D之前已經編譯過了,所以就會報重定義錯誤。
加上ifndef/define/endif,就可以防止這種重定義錯誤。在預編譯的過程中,執行到include "C.h"時會因為在上一句的時候已經定義了class D這個巨集,所以此時的ifndef條件不滿足,起到了防止重複引用標頭檔案的效果。
#undef只是撤消掉掉原來定義的巨集,但是不會取消掉你已經用這個巨集定義的變數
#define X extern
x int a;
#undef X
你仍然可以使用這個a,但不能用X了,當然你再定義X成什麼就隨便了
你也可以再定義成
#define X extern
x int a;
#undef X
#define X int
X b;
#undef X
******************************************************************************************************************************************************************************************************************
模組劃分的"劃"是規劃的意思,意指怎樣合理的將一個很大的軟體劃分為一系列功能獨立的部分合作完成系統的需求。C語言作為一種結構化的程式設計語言,在模組的劃分上主要依據功能(依功能進行劃分在面向物件設計中成為一個錯誤,牛頓定律遇到了相對論),C語言模組化程式設計需理解如下概念:
(1) 模組即是一個.c檔案和一個.h檔案的結合,標頭檔案(.h)中是對於該模組介面的宣告;
(2) 某模組提供給其它模組呼叫的外部函式及資料需在.h中檔案中冠以extern關鍵字宣告;
(3) 模組內的函式和全域性變數需在.c檔案開頭冠以static關鍵字宣告;
(4) 永遠不要在.h檔案中定義變數!定義變數和宣告變數的區別在於定義會產生記憶體分配的操作,是彙編階段的概念;而宣告則只是告訴包含該宣告的模組在連線階段從其它模組尋找外部函式和變數。如:
/*module1.h*/
int a = 5; /* 在模組1的.h檔案中定義int a */
/*module1 .c*/
#include "module1.h" /* 在模組1中包含模組1的.h檔案 */
/*module2 .c*/
#include "module1.h" /* 在模組2中包含模組1的.h檔案 */
/*module3 .c*/
#include "module1.h" /* 在模組3中包含模組1的.h檔案 */
以上程式的結果是在模組1、2、3中都定義了整型變數a,a在不同的模組中對應不同的地址單元,這個世界上從來不需要這樣的程式。正確的做法是:
/*module1.h*/
extern int a; /* 在模組1的.h檔案中宣告int a */
/*module1 .c*/
#include "module1.h" /* 在模組1中包含模組1的.h檔案 */
int a = 5; /* 在模組1的.c檔案中定義int a */
/*module2 .c*/
#include "module1.h" /* 在模組2中包含模組1的.h檔案 */
/*module3 .c*/
#include "module1.h" /* 在模組3中包含模組1的.h檔案 */
這樣如果模組1、2、3操作a的話,對應的是同一片記憶體單元。
******************************************************************************************************************************************************************************************************************
程式設計也是如此,如果概念很清晰,那基本上沒什麼難題(會難在數學上,比如演算法的選擇、時間空間與效率的取捨、穩定與資源的平衡上)。但是,要掌握清晰的概念也沒那麼容易。比如下面這個例子,看看你有沒有很清晰透徹的認識。
//a.h
void foo();
//a.c
#include "a.h" //我的問題出來了:這句話是要,還是不要?
void foo()
{
return;
}
//main.c
#include "a.h"
int main(int argc, char *argv[])
{
foo();
return 0;
}
針對上面的程式碼,請回答三個問題:
1.a.c 中的 #include "a.h" 這句話是不是多餘的?
2.為什麼經常見 xx.c 裡面 include 對應的 xx.h?
3.如果 a.c 中不寫,那麼編譯器是不是會自動把 .h 檔案裡面的東西跟同名的 .c 檔案繫結在一起?
(請針對上面3道題仔細考慮10分鐘,莫要著急看下面的解釋。:) 考慮的越多,下面理解的就越深。)
好了,時間到!請忘掉上面的3道題,以及對這三道題引發出的你的想法,然後再聽我慢慢道來。正確的概念是:從C編譯器角度看,.h和.c皆是浮雲,就是改名為.txt、.doc也沒有大的分別。換句話說,就是.h和.c沒啥必然聯絡。.h中一般放的是同名.c檔案中定義的變數、陣列、函式的宣告,需要讓.c外部使用的宣告。這個宣告有啥用?只是讓需要用這些宣告的地方方便引用。因為 #include "xx.h" 這個巨集其實際意思就是把當前這一行刪掉,把 xx.h 中的內容原封不動的插入在當前行的位置。由於想寫這些函式宣告的地方非常多(每一個呼叫 xx.c 中函式的地方,都要在使用前宣告一下子),所以用 #include "xx.h" 這個巨集就簡化了許多行程式碼——讓前處理器自己替換好了。也就是說,xx.h 其實只是讓需要寫 xx.c 中函式宣告的地方呼叫(可以少寫幾行字),至於 include 這個 .h 檔案是誰,是 .h 還是 .c,還是與這個 .h 同名的 .c,都沒有任何必然關係。
這樣你可能會說:啊?那我平時只想呼叫 xx.c 中的某個函式,卻 include了 xx.h 檔案,豈不是巨集替換後出現了很多無用的宣告?沒錯,確實引入了很多垃圾,但是它卻省了你不少筆墨,並且整個版面也看起來清爽的多。魚與熊掌不可得兼,就是這個道理。反正多些宣告(.h一般只用來放宣告,而放不定義,參見拙著“過馬路,左右看”)也無害處,又不會影響編譯,何樂而不為呢?
翻回頭再看上面的3個問題,很好解答了吧?
1.答:不一定。這個例子中顯然是多餘的。但是如果.c中的函式也需要呼叫同個.c中的其它函式,那麼這個.c往往會include同名的.h,這樣就不需要為宣告和呼叫順序而發愁了(C語言要求使用之前必須宣告,而include同名.h一般會放在.c的開頭)。有很多工程甚至把這種寫法約定為程式碼規範,以規範出清晰的程式碼來。
2.答:1中已經回答過了。
3.答:不會。問這個問題的人絕對是概念不清,
相關推薦
C語言中標頭檔案相互包含問題
.h中一般放的是同名.c檔案中定義的變數、陣列、函式的宣告,需要讓.c外部使用的宣告。 1)h檔案作用 1 方便開發:包含一些檔案需要的共同的常量,結構,型別定義,函式,變數申明; 2 提供介面:對一個軟體包來說可以提供一個給外界的介面(例如: stdio
C語言中標頭檔案與庫檔案
1、什麼是庫檔案? 庫檔案中對常用的函式、方法進行實現並打包,使用者在使用時只需要呼叫庫檔案中的函式即可,不用再次對相關函式進行實現。 2、為什麼要有標頭檔案? 標頭檔案中是對庫檔案中的函式、方法實現進行宣告,標頭檔案可以避免重定義的風險,且標頭檔案裡包含了
在C語言中標頭檔案的作用
在C語言家族程式中,標頭檔案被大量使用。一般而言,每個C++/C程式通常由標頭檔案(header files)和定義檔案(definition files)組成。標頭檔案作為一種包含功能函式、資料介面宣告的載體檔案,用
解決C/C++ 標頭檔案相互包含 問題的方法
http://blog.sina.com.cn/s/blog_6ef9a3ad0101emuw.html http://blog.csdn.net/hazir/article/details/38600419 所謂超前引用是指一個型別在定義之前就被用來定義變數和宣告函式。
C語言標頭檔案包含和編寫的幾個基本規則
總想著把所有的標頭檔案都塞到一個頭檔案裡邊,然後,所有.c檔案只包含這一個混雜的標頭檔案就行了,也不用注意太多,, 但是這樣好像不行,摘抄一篇文章備份一下,覺得寫的不錯, 尊重原創,原文連結:http://blog.csdn.net/ison81/article/det
C/C++避免標頭檔案重複包含的方法
C/C++避免標頭檔案重複包含的方法 1. #ifndef 2. #pragma once 3. 混合使用 在實際的程式設計過程中,因為會使用多個檔案,所以在檔案中不可避免的要引入一些標頭檔案,這樣就可能會出現一個頭檔案被 include
C語言標頭檔案stdlib.h裡面有什麼函式
主要含有的內容方向: A.字串轉換 B. 隨機數 C.記憶體管理 D.與環境的介面 E.查詢與排序 F.整數運算 G.多位元組字元 … 友情連結: (1)關於 stdio.h 和 stdlib.h 包含的函式 https://blogG…csdn.net/weixin_42513339/
c語言標頭檔案以及make注意事項
c語言標頭檔案以及make注意事項 標頭檔案說明:自己定義的標頭檔案和專案檔案放在一起,注意使用""而不是使用<>,系統的標頭檔案才使用<> 當main函式要呼叫其他函式的時候在編譯的時候需要將其他的c檔案也一起編譯 gcc main.c test.c make
C語言標頭檔案的定義
每個C++/C程式通常由標頭檔案(header files)和定義檔案(definition files)組成。標頭檔案作為一種包含功能函式、資料介面宣告的載體檔案,主要用於儲存程式的宣告(declaration),而定義檔案用於儲存程式的實現 (implementation)。 .c就是你寫的程式
linux中C語言標頭檔案詳解
linux中C程式標頭檔案的種類 一類:#include<stdio.h> stdio.h檔案就在/usr/include目錄下 二類:#include<arpa/inet.h> arpa/是/usr/include目錄下的子目錄,inet.h其實是/usr/include
C語言標頭檔案作用及寫法
標頭檔案幾個好處: 1,標頭檔案可以定義所用的函式列表,方便查閱你可以呼叫的函式; 2,標頭檔案可以定義很多巨集定義,就是一些全域性靜態變數的定義,在這樣的情況下,只要修改標頭檔案的內容,程式 就可以做相應的修改,不用親自跑到繁瑣的程式碼內去搜索。 3,標頭檔案只是宣告,不佔記憶體空間,要知道
C語言標頭檔案正確寫法
目錄 一般寫法 自動生成 輸入 執行結果 參考連結 一般寫法 例如這樣有一根file.h標頭檔案,一般寫法如下 //file.h //條件編譯 #ifndef _FILE_H_ //如果沒有
MAC環境下 VS Code中C語言標頭檔案匯入錯誤
今天重新拾起了C語言,下載了vs code和CLion, 然後發現vs code寫好程式碼之後標頭檔案下方出現波浪線表示錯誤,百思不得其解。 然後看網上解答,找到了答案,好像是因為需要路徑設定 #include errors detected. Please update your
C語言標頭檔案詳解
1. GCC編譯時問題 在由多個C語言檔案組成的程式中,對於標頭檔案的管理非常重要。標頭檔案提供給使用者一個介面,使用者從中可以知道可以呼叫哪些函式,實現哪些功能,而不必去管這些功能的具體實現。因此,寫好標頭檔案是非常關鍵的。在標頭檔案中,最常見的問題是"標頭檔案的重複包含"。其意思是同一標頭
Linux C/C++語言標頭檔案、庫檔案的查詢路徑
在程式設計中,檔案包含是很有用的。一個大的程式可以分為多個模組,由多個程式設計師分別程式設計。有 些公 用的符號常量或巨集定義等可單獨組成一個檔案,在其它檔案的開頭用包含命令包含該檔案即可使 用。這樣,可避免在每個檔案開頭都去書寫那些公
在C語言標頭檔案裡我們如何應用#ifndef
舉個例子 我在led.h 標頭檔案裡這麼寫 #ifndef __LED_H #define __LED_H ....... ...... #endif 1 這樣寫是如何完成防止重複定義功能的 2 __LED_H 這個巨集定義我可以隨便命名嗎,還是這個#define後面的名
解決標頭檔案相互包含問題的方法
一般情況下,C/C++要求所有的型別必須在使用前被定義,但是在一些特殊情況下,這種要求無法滿足,例如,在類CMyView中保留了一個非模式對話方塊物件指標,該物件用於顯示/修改一些資訊。為了實現對話方塊"應用"按鈕,把對話方塊做的修改立刻更新到view介面上,為此,需要在對話方塊類中需要儲存view類的指標,
c語言標頭檔案的作用以及寫法
標頭檔案幾個好處: 1,標頭檔案可以定義所用的函式列表,方便查閱你可以呼叫的函式; 2,標頭檔案可以定義很多巨集定義,就是一些全域性靜態變數的定義,在這樣的情況下,只要修改標頭檔案的內容,程式 就可以做相應的修改,不用親自跑到繁瑣的程式碼內去搜索。 3,標頭檔案只是
C語言標頭檔案定義全域性變數問題
正確的作法是在c原始檔中定義一個全域性變數,在標頭檔案中加入全域性變數的宣告,在外部檔案呼叫的時候,包含其標頭檔案,加入全域性變數的宣告(不加也是可以的,最好加上)。 //1.c int a;
C語言標頭檔案之float.h
float.h中的符號常量 float.h與limits.h一樣是定義邊界值的,float.h定義的是浮點數的邊界值 double DBL_DIG double小數點後面精確的位數 DBL_EPSILON 小的正數,double的0跨