头文件 (C++)
本文内容
必须在使用变量、函数、类等程序元素的名称之前对其进行声明。 例如,不能在没有声明“x”之前编写 x = 42
。
C++复制
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
:
C++复制
// my_class.h
namespace N
{
class my_class
{
public:
void do_something();
};
}
接下来,创建一个实现文件(通常使用 .cpp 或类似的扩展名)。 我们将调用文件 my_class.cpp,并为成员声明提供定义。 我们为“my_class.h”文件添加一个 #include
指令,以便立刻将 my_class 声明插入到 .cpp 文件中。我们包括 <iostream>
,用于拉入 std::cout
的声明。 请注意,引号用于源文件所在目录中的头文件,尖括号用于标准库标头。 此外,许多标准库标头没有 .h 或任何其他文件扩展名。
在实现文件中,可以选择使用 using
语句来避免使用“N::”或“std::”限定每个提及的“my_class”或“cout”。 不要在头文件中放置 using
语句!
C++复制
// 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()
的公共成员函数。
C++复制
// 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 防范
通常,头文件有一个 include 防范或 #pragma once
指令,用于确保它们不会多次插入到单个 .cpp 文件中。
C++复制
// 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 */
要放入头文件的内容
由于一个头文件可能会被多个文件执行 include 操作,因此它不能包含可能生成多个同名定义的定义。 不允许以下操作,否则会被视为非常糟糕的做法:
- 命名空间或全局范围内的内置类型定义
- 非内联函数定义
- 非常量变量定义
- 聚合定义
- 未命名的命名空间
- using 指令
使用 using
指令不一定会导致错误,但可能会导致问题,因为它将命名空间引入每个直接或间接包含该标头的 .cpp 文件中的范围。
示例头文件
以下示例显示了头文件中允许的各种声明和定义:
C++复制
// 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;
}