您現在的位置是:首頁 > 手機遊戲首頁手機遊戲
CC++標頭檔案以及避免標頭檔案包含造成的重定義方法
- 2022-09-23
如何進行分解模組的設計
C語言標頭檔案
標頭檔案是副檔名為
.h
的檔案,包含了 C 函式宣告和宏定義,被多個原始檔中引用共享。有兩種型別的標頭檔案:程式設計師編寫的標頭檔案和編譯器自帶的標頭檔案。
在程式中要使用標頭檔案,需要使用 C 預處理指令
#include
來引用它。前面我們已經看過
stdio.h
標頭檔案,它是編譯器自帶的標頭檔案。
引用標頭檔案相當於複製標頭檔案的內容,但是我們不會直接在原始檔中複製標頭檔案的內容,因為這麼做很容易出錯,特別在程式是由多個原始檔組成的時候。
A simple practice in C 或 C++ 程式中,建議把所有的常量、宏、系統全域性變數和函式原型寫在標頭檔案中,在需要的時候隨時引用這些標頭檔案。
引用標頭檔案的語法
使用預處理指令
#include
可以引用使用者和系統標頭檔案。它的形式有以下兩種:
#include
這種形式用於引用系統標頭檔案。它在系統目錄的標準列表中搜索名為 file 的檔案。在編譯原始碼時,您可以透過 -I 選項把目錄前置在該列表前。
#include “file”
這種形式用於引用使用者標頭檔案。它在包含當前檔案的目錄中搜索名為 file 的檔案。在編譯原始碼時,您可以透過 -I 選項把目錄前置在該列表前。
引用標頭檔案的操作
#include
指令會指示 C 預處理器瀏覽指定的檔案作為輸入。預處理器的輸出包含了已經生成的輸出,被引用檔案生成的輸出以及
#include
指令之後的文字輸出。例如,如果您有一個頭檔案 header。h,如下:
char *test (void);
和一個使用了標頭檔案的主程式
program。c
,如下:
int x;#include “header。h”int main (void){ puts (test ());}
編譯器會看到如下的程式碼資訊:
int x;char *test (void);int main (void){ puts (test ());}
只引用一次標頭檔案
如果一個頭檔案被引用兩次,編譯器會處理兩次標頭檔案的內容,這將產生錯誤。為了防止這種情況,標準的做法是把檔案的整個內容放在條件編譯語句中,如下:
#ifndef HEADER_FILE#define HEADER_FILEthe entire header file file#endif
這種結構就是通常所說的包裝器
#ifndef
。當再次引用標頭檔案時,條件為假,因為 HEADER_FILE 已定義。此時,預處理器會跳過檔案的整個內容,編譯器會忽略它。
有條件引用
有時需要從多個不同的標頭檔案中選擇一個引用到程式中。例如,需要指定在不同的作業系統上使用的配置引數。您可以透過一系列條件來實現這點,如下:
#if SYSTEM_1 # include “system_1。h”#elif SYSTEM_2 # include “system_2。h”#elif SYSTEM_3 。。。#endif
但是如果標頭檔案比較多的時候,這麼做是很不妥當的,預處理器使用宏來定義標頭檔案的名稱。這就是所謂的
有條件引用
。它不是用標頭檔案的名稱作為
#include
的直接引數,您只需要使用宏名稱代替即可:
1 #define SYSTEM_H “system_1。h”2 。。。3 #include SYSTEM_H
SYSTEM_H 會擴充套件,預處理器會查詢 system_1。h,就像
#include
最初編寫的那樣。SYSTEM_H 可透過 -D 選項被您的 Makefile 定義。
在一段時間的程式設計中,時常會遇到重定義(redefinition)問題。一般都是#include在包含頭。h檔案時出現了重複包含的關係。運氣好的話可以比較容易的發現問題,運氣不好的話只好列出所有的標頭檔案。h中的包含關係,挨個檢查是哪裡出了問題。最近發現如果遵循“
在標頭檔案.h中不再包含標頭檔案.h
”的原則,可以從根本上避免這個問題。雖然這樣做會使得在程式碼檔案。c或。cpp中必須各自包含進來所需的標頭檔案。h,還要注意在包含時可能會存在順序的問題,但這比起查詢何處進行了重定義來說簡單了許多,也使包含關係更加清晰。
對原來的專案中的所有檔案按上述原則進行了修改,暫未發現不良影響,感覺還不錯。
加上一句,也可以在預編譯區加上 #pragma once來防止重定義,
注意#ifndef。。。 #define。。。 #end if 以及 extern 的用法
#ifndef和#pragma once兩者的使用方式有何區別?
示例程式碼如下:
方式一:
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
。。。 。。。 // 宣告、定義語句
#endif
方式二:
#pragma once
。。。 。。。 // 宣告、定義語句
兩者各有何特點?
(1)#ifndef
#ifndef的方式受C/C++語言標準支援。它不僅可以保證同一個檔案不會被包含多次,也能保證內容完全相同的兩個檔案(或者程式碼片段)不會被不小心同時包含。
當然,
缺點就是如果不同標頭檔案中的宏名不小心“撞車”,可能就會導致你看到標頭檔案明明存在,但編譯器卻硬說找不到宣告的狀況——這種情況有時非常讓人鬱悶。
由於編譯器每次都需要開啟標頭檔案才能判定是否有重複定義,因此在編譯大型專案時,ifndef會使得編譯時間相對較長,因此一些編譯器逐漸開始支援#pragma once的方式。
(2)#pragma once
#pragma once 一般由編譯器提供保證:同一個檔案不會被包含多次。注意這裡所說的“同一個檔案”是指物理上的一個檔案,而不是指內容相同的兩個檔案。
你無法對一個頭檔案中的一段程式碼作pragma once宣告,而只能針對檔案。
其好處是,你不必再擔心宏名衝突了,當然也就不會出現宏名衝突引發的奇怪問題。大型專案的編譯速度也因此提高了一些。
對應的缺點就是如果某個標頭檔案有多份複製,本方法不能保證他們不被重複包含。當然,相比宏名衝突引發的“找不到宣告”的問題,這種重複包含很容易被發現並修正。
另外,這種方式不支援跨平臺!
兩者之間有什麼聯絡?
#pragma once 方式產生於#ifndef之後,因此很多人可能甚至沒有聽說過。目前看來#ifndef更受到推崇。因為#ifndef受C/C++語言標準的支援,不受編譯器的任何限制;
而#pragma once方式卻不受一些較老版本的編譯器支援,一些支援了的編譯器又打算去掉它,所以它的相容性可能不夠好。
一般而言,當程式設計師聽到這樣的話,都會選擇#ifndef方式,為了努力使得自己的程式碼“存活”時間更久,通常寧願降低一些編譯效能,這是程式設計師的個性,當然這是題外話啦。
還看到一種用法是把兩者放在一起的:
#pragma once
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
。。。 。。。 // 宣告、定義語句
#endif
總結:
看起來似乎是想兼有兩者的優點。不過只要使用了#ifndef就會有宏名衝突的危險,也無法避免不支援#pragma once的編譯器報錯,所以混用兩種方法似乎不能帶來更多的好處,倒是會讓一些不熟悉的人感到困惑。
選擇哪種方式,應該在瞭解兩種方式的情況下,視具體情況而定。只要有一個合理的約定來避開缺點,我認為哪種方式都是可以接受的。而這個已經不是標準或者編譯器的責任了,應當由程式設計師自己或者小範圍內的開發規範來搞定。
為了避免同一個檔案被include多次
1 #ifndef方式
2 #pragma once方式
在能夠支援這兩種方式的編譯器上,二者並沒有太大的區別,但是兩者仍然還是有一些細微的區別。
方式一:
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
。。。 。。。 // 一些宣告語句
#endif
方式二:
#pragma once
。。。 。。。 // 一些宣告語句
#ifndef的方式依賴於宏名字不能衝突,這不光可以保證同一個檔案不會被包含多次,也能保證內容完全相同的兩個檔案不會被不小心同時包含。當然,缺點就是如果不同標頭檔案的宏名不小心“撞車”,可能就會導致標頭檔案明明存在,編譯器卻硬說找不到宣告的狀況
#pragma once則由編譯器提供保證:同一個檔案不會被包含多次。注意這裡所說的“同一個檔案”是指物理上的一個檔案,而不是指內容相同的兩個檔案。帶來的好處是,你不必再費勁想個宏名了,當然也就不會出現宏名碰撞引發的奇怪問題。對應的缺點就是如果某個標頭檔案有多份複製,本方法不能保證他們不被重複包含。當然,相比宏名碰撞引發的“找不到宣告”的問題,重複包含更容易被發現並修正。
方式一由語言支援所以移植性好,方式二 可以避免名字衝突