相关文章推荐

必须在使用变量、函数、类等程序元素的名称之前对其进行声明。 例如,不能在没有声明“x”之前编写 x = 42

int x; // declaration
x = 42; // use x

声明告知编译器,元素是 intdouble、函数、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,并为成员声明提供定义。 我们为“my_class.h”文件添加一个 #include 指令,以便立刻将 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 防范

通常,头文件有一个 include 防范#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 */

要放入头文件的内容

由于一个头文件可能会被多个文件执行 include 操作,因此它不能包含可能生成多个同名定义的定义。 不允许以下操作,否则会被视为非常糟糕的做法:

  • 命名空间或全局范围内的内置类型定义
  • 非内联函数定义
  • 非常量变量定义
  • 未命名的命名空间
  • 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;
    
     
    推荐文章