这是在C++中使用头文件的正确方法

Which is the correct way to use a header file in C++?

本文关键字:文件 方法 C++      更新时间:2023-10-16

我对这个主题有一些疑问,但首先让我给你一些代码:

例子.hpp

#ifndef EXAMPLE_H_
#define EXAMPLE_H_
using namespace std;
void say_something(string something);
#endif

示例.cpp

#include <string>
#include <iostream>
#include "example.hpp"
void say_something(string something)
{
    cout << something << 'n';
}

主.cpp

#include <iostream>
#include <string>
#include "example.hpp"
using namespace std;
int main()
{
    string hello = "Hello world!";
    say_something(hello);
    return 0;
}

现在,我的问题是:

  • 我应该把 example.cpp 需要的所有头文件放在标题 example.hpp 中,还是应该将它们保留在示例中.cpp就像上面的例子一样?
  • 在这种情况下,C++将如何工作:我在main和example.cpp和example中都包含了"string"和"iostream".cpp...C++编译器是否会使用字符串和 iostream 标头将程序链接(或任何您认为更合格的术语)两次?我应该只把字符串和 iostream 标头放在示例中吗.cpp
  • 最后,我应该将要用于示例标头的命名空间放在标头本身(example.hpp)还是实现(example.cpp)中?

我应该把 example.cpp 需要的所有头文件放在标题 example.hpp 中,还是应该将它们保留在示例中.cpp就像上面的例子一样?

保持头文件最小。

头文件提供函数声明。函数声明依赖于<string>中的事物,但不依赖于<iostream>中的事物。因此,头文件应包含不带<iostream> <string>

如果头文件包含不必要的

内容,则头文件的用户也将包含这些不必要的内容。

在这种情况下,C++将如何工作:我在main和example.cpp中都包含了"string"和"iostream".cpp...C++编译器是否会使用字符串和 iostream 标头将程序链接(或任何您认为更合格的术语)两次?我应该只把字符串和 iostream 标头放在示例中吗.cpp

C++源不与标头链接。每当你写#include <string>时,预处理器(概念上)复制名为"string"的整个头文件并粘贴到#include <string>行。预处理发生在编译和链接之前。

最后,我应该将要用于示例标头的命名空间放在标头本身(example.hpp)还是实现(example.cpp)中?

切勿在头文件的全局范围内写入using namespace std;。由于#include的复制和粘贴性质,using-指令会感染其他包含标头的文件。如果有一个文件想要使用say_something但不想using namespace std怎么办?

应该把我的所有头文件放在示例.cpp将需要 标题 example.hpp 还是我应该将它们保留在示例中.cpp例如 上面的例子?

不,你不应该。头文件应仅包含它所需的头文件。

在这种情况下,C++将如何工作:我在main和example.cpp中都包含了"string"和"iostream".cpp...C++编译器是否会使用字符串和 iostream 标头将程序链接(或任何您认为更合格的术语)两次?我应该只把字符串和 iostream 标头放在示例中吗.cpp

由于包含保护,编译器不会链接两次stringiostream。例如,它只会打开string header,并在包含保护告诉编译器它已被包含时立即返回。

最后,我应该将要用于示例标头的命名空间放在标头本身(example.hpp)还是实现(example.cpp)中?

这与包含问题相同。如果你在头文件中放置一个"using namespace std;",那么包含它的所有其他文件都将被强制使用整个命名空间。顺便说一下,这不是一件好事。

因此,在实现中use namespace std在包含所有标头之后)并不邪恶。

在你的头文件中,在函数体内使用"using namespace std"或只是"using std::string"也可以,它们将仅限于函数体的范围。

  void somef(std::string str_arg)
  {
     using std::string;
     string str;
     // This is not evil either.    
     using namespace std;
     string str;
  }
  void somef2() {
     //string str; //error
  }

如果somef是类的方法,常见的方法是使用 typedef,例如:

class MyClass
{
    typedef std::string string_type;
    //using string_type = std::string; //C++11
    string_type data_member;
    void somef(string_type str)
    {
        string_type local_str;
    }
    void somef2() {
        string_type local_str; // works
    }
};
#include <string>
#include <iostream>
#include "example.hpp"

我想说这也是使用头文件的一个不好的顺序。假设example.hpp使用 std::string 。这不会引发编译错误,因为您在 example.hpp 之前已包含<string>

如果您在另一个文件中使用 example.hpp 而没有在它之前包含<string>,会发生什么情况?您将收到编译错误,因为您的标头使用 std::string 并且您没有包含std::string .

  • 我应该把 example.cpp 需要的所有头文件放在标题 example.hpp 中,还是应该将它们保留在示例中.cpp就像上面的例子一样?

将头文件包含在需要它们的每个文件中。不用担心重复。

  • 在这种情况下,C++将如何工作:我在main和example.cpp和example中都包含了"string"和"iostream".cpp...C++编译器是否会使用字符串和 iostream 标头将程序链接(或任何您认为更合格的术语)两次?我应该只把字符串和 iostream 标头放在示例中吗.cpp

头文件本身确保其内容在任何翻译单元中仅包含一次。这是通过#ifdef EXAMPLE_H_测试实现的。

  • 最后,我应该将要用于示例标头的命名空间放在标头本身(example.hpp)还是实现(example.cpp)中?

永远不要using namespace ...放在头文件的全局范围内。

如果必须使用using namespace ...请仅在自己的命名空间或函数中使用它。即便如此,也不建议将其用于像std这样的大型命名空间,其中包含数千个每个人都一直在使用的非常常见的符号。

此外,理想情况下,您应该只在源.cpp文件中使用using namespace ...。但是(如前所述)对于逻辑上应该存在于您自己的命名空间中的小型命名空间,有时将其包含在标头的非全局部分中是合适的。