您現在的位置是:首頁 > 手機遊戲首頁手機遊戲

CC++標頭檔案以及避免標頭檔案包含造成的重定義方法

  • 由 C語言程式媛馮麗 發表于 手機遊戲
  • 2022-09-23
簡介當然,缺點就是如果不同標頭檔案的宏名不小心“撞車”,可能就會導致標頭檔案明明存在,編譯器卻硬說找不到宣告的狀況#pragma once則由編譯器提供保證:同一個檔案不會被包含多次

如何進行分解模組的設計

C語言標頭檔案

CC++標頭檔案以及避免標頭檔案包含造成的重定義方法

標頭檔案是副檔名為

.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則由編譯器提供保證:同一個檔案不會被包含多次。注意這裡所說的“同一個檔案”是指物理上的一個檔案,而不是指內容相同的兩個檔案。帶來的好處是,你不必再費勁想個宏名了,當然也就不會出現宏名碰撞引發的奇怪問題。對應的缺點就是如果某個標頭檔案有多份複製,本方法不能保證他們不被重複包含。當然,相比宏名碰撞引發的“找不到宣告”的問題,重複包含更容易被發現並修正。

方式一由語言支援所以移植性好,方式二 可以避免名字衝突

Top