標題包括警衛

幾乎每個標頭檔案都應該遵循 include guard 慣用語:

我的頭,file.h

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// Code body for header file

#endif

這可以確保當你在多個地方使用時,你不會獲得函式,變數等的重複宣告。想象一下以下檔案層次結構:

首標升·小時

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

報頭 2.H

#include "header-1.h"

int myFunction2(MyStruct *value);

main.c 中

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

這段程式碼有一個嚴重的問題:MyStruct 的詳細內容定義了兩次,這是不允許的。這將導致難以跟蹤的編譯錯誤,因為一個標頭檔案包括另一個標頭檔案。如果你改為使用標題保護:

首標升·小時

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

報頭 2.H

#ifndef HEADER_2_H
#define HEADER_2_H

#include "header-1.h"

int myFunction2(MyStruct *value);

#endif

main.c 中

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

然後這會擴充套件到:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

#ifndef HEADER_2_H
#define HEADER_2_H

#ifndef HEADER_1_H // Safe, since HEADER_1_H was #define'd before.
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

int myFunction2(MyStruct *value);

#endif

int main() {
    // do something
}

當編譯器達到 header- 1.h 的第二個包含時,HEADER_1_H 已經被前一個包含定義。因此,它歸結為以下幾點:

#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#define HEADER_2_H

int myFunction2(MyStruct *value);

int main() {
    // do something
}

因此沒有編譯錯誤。

注意:命名標題保護有多種不同的約定。有些人喜歡把它命名為 HEADER_2_H_,有些人喜歡像 MY_PROJECT_HEADER_2_H 這樣的專案名稱。重要的是確保你遵循的約定使你的專案中的每個檔案都具有唯一的標題保護。

如果結構詳細資訊未包含在標頭中,則宣告的型別將是不完整的或不透明的型別 。這些型別可能很有用,隱藏了函式使用者的實現細節。出於許多目的,標準 C 庫中的 FILE 型別可以被視為不透明型別(儘管它通常不是不透明的,因此標準 I / O 函式的巨集實現可以利用結構的內部)。在這種情況下,header-1.h 可能包含:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct MyStruct MyStruct;

int myFunction(MyStruct *value);

#endif

請注意,結構必須具有標記名稱(此處為 MyStruct - 位於 tags 名稱空間中,與 typedef 名稱 MyStruct 的普通標識名稱空間分開),並且省略了 { … }。這說“有一種結構型別 struct MyStruct,並且有一個別名為 MyStruct”。

在實現檔案中,可以定義結構的詳細資訊以使型別完整:

struct MyStruct {
    …
};

如果你使用的是 C11,則可以重複 typedef struct MyStruct MyStruct; 宣告而不會導致編譯錯誤,但早期版本的 C 會抱怨。因此,最好使用 include guard 慣用法,即使在這個例子中,如果程式碼只使用支援 C11 的編譯器進行編譯,它也是可選的。

許多編譯器都支援 #pragma once 指令,它具有相同的結果:

我的頭,file.h

#pragma once

// Code for header file

但是,#pragma once 不是 C 標準的一部分,因此如果你使用它,程式碼就不那麼便攜了。

一些標題不使用包含保護語成語。一個具體的例子是標準的 <assert.h> 標題。它可以在單個翻譯單元中被多次包括,並且這樣做的效果取決於每次包括頭部時是否定義巨集 NDEBUG。你可能偶爾會有類似的要求; 這種情況很少見。通常,你的標題應該受到包含保護慣用語的保護。