變數、函式、類別等程式專案的名稱必須先宣告,才能使用。 例如,您不只需要先宣告 'x' 即可撰寫 x = 42 。
int x; // declaration
x = 42; // use x
宣告會告知編譯程序專案是否為 int、、函double式或class某些其他專案。 此外,每個名稱都必須在所使用的每個.cpp檔案中宣告(直接或間接)。 當您編譯程式時,每個.cpp檔案都會獨立編譯成編譯單位。 編譯程式不知道在其他編譯單位中宣告哪些名稱。 這表示,如果您定義類別或函式或全域變數,則必須在使用該類別的每個額外.cpp檔案中提供該專案的宣告。 所有檔案中該事物的每個宣告都必須完全相同。 當鏈接器嘗試將所有編譯單位合併成單一程式時,稍微不一致會導致錯誤或非預期的行為。
為了將錯誤的可能性降到最低,C++已採用使用 頭檔 來包含宣告的慣例。 您可以在頭檔中宣告,然後在每個.cpp檔案或其他需要該宣告的頭檔中,使用 #include 指示詞。 #include 指示詞會在編譯之前,直接將頭文件複本插入.cpp檔案中。
注意
在 Visual Studio 2019 中,會將 C++20 模組 功能引進為頭檔改善和最終取代。 如需詳細資訊,請參閱 C++ 中的模組概觀。
範例
下列範例示範宣告類別的常見方式,然後在不同的原始程序檔中使用。 我們將從頭檔案開始, my_class.h。 它包含類別定義,但請注意定義不完整;未定義成員函 do_something 式:
// my_class.h
namespace N
{
class my_class
{
public:
void do_something();
};
}
接下來,建立實作檔案(通常具有.cpp或類似的擴展名)。 我們將呼叫檔案my_class.cpp並提供成員宣告的定義。 我們會新增 #include 「my_class.h」 檔案的指示詞,以便將my_class宣告插入.cpp檔案中,而且我們會在 的宣告<iostream>中加入 std::cout 。 請注意,引號用於與原始程式檔相同目錄中的頭檔,而角括弧則用於標準連結庫標頭。 此外,許多標準連結庫標頭沒有 .h 或任何其他擴展名。
在實作檔案中,我們可以選擇性地使用 using 語句,以避免使用 “n::” 或 “std::” 來限定每個提及 “my_class” 或 “cout”。 請勿將 語句放在 using 頭檔中!
// my_class.cpp
#include "my_class.h" // header in local directory
#include <iostream> // header in standard library
using namespace N;
using namespace std;
void my_class::do_something()
{
cout << "Doing something!" << endl;
}
現在,我們可以在另一個.cpp檔案中使用 my_class 。 我們會 #include 頭檔,讓編譯程式提取宣告。 編譯程式必須知道的是,my_class是具有名為 do_something()之公用成員函式的類別。
// my_program.cpp
#include "my_class.h"
using namespace N;
int main()
{
my_class mc;
mc.do_something();
return 0;
}
編譯程式完成將每個.cpp檔案編譯成.obj檔案之後,會將.obj檔案傳遞至連結器。 當連結器合併物件檔案時,它會找到my_class的一個定義;它位於針對my_class.cpp產生的.obj檔案中,建置會成功。
包含防護
頭檔通常會有 include guard 或 #pragma once 指示詞,以確保它們不會多次插入單一.cpp檔案中。
// my_class.h
#ifndef MY_CLASS_H // include guard
#define MY_CLASS_H
namespace N
{
class my_class
{
public:
void do_something();
};
}
#endif /* MY_CLASS_H */
要放入頭文件的內容
因為頭檔可能由多個檔案包含,所以不能包含可能會產生相同名稱多個定義的定義。 不允許下列專案,或被視為非常糟糕的做法:
- 命名空間或全域範圍的內建類型定義
- 非內嵌函式定義
- 非 const 變數定義
- 匯總定義
- 未命名的命名空間
- using 指示詞
using使用指示詞不一定會造成錯誤,但可能會造成問題,因為它會將命名空間帶入直接或間接包含該標頭的每個.cpp檔案範圍內。
範例頭檔
下列範例顯示頭檔中允許的各種宣告和定義:
// sample.h
#pragma once
#include <vector> // #include directive
#include <string>
namespace N // namespace declaration
{
inline namespace P
{
//...
}
enum class colors : short { red, blue, purple, azure };
const double PI = 3.14; // const and constexpr definitions
constexpr int MeaningOfLife{ 42 };
constexpr int get_meaning()
{
static_assert(MeaningOfLife == 42, "unexpected!"); // static_assert
return MeaningOfLife;
}
using vstr = std::vector<int>; // type alias
extern double d; // extern variable
#define LOG // macro definition
#ifdef LOG // conditional compilation directive
void print_to_log();
#endif
class my_class // regular class definition,
{ // but no non-inline function definitions
friend class other_class;
public:
void do_something(); // definition in my_class.cpp
inline void put_value(int i) { vals.push_back(i); } // inline OK
private:
vstr vals;
int i;
};
struct RGB
{
short r{ 0 }; // member initialization
short g{ 0 };
short b{ 0 };
};
template <typename T> // template definition
class value_store
{
public:
value_store<T>() = default;
void write_value(T val)
{
//... function definition OK in template
}
private:
std::vector<T> vals;
};
template <typename T> // template declaration
class value_widget;
}