如何正确使用#include指令?

19

有关如何正确使用 #include 的材料吗? 我没有找到任何详细解释此用法的 C/C++ 教材。 在正式项目中,我总是在处理它时感到困惑。

11个回答

26

让我经常被卡住的是这个:

这将在头文件路径中进行搜索:

#include <stdio.h>

这将在您的本地目录中搜索:

#include "myfile.h"

对于每个头文件,你应该做的第二件事情是:

myfilename.h:

#ifndef MYFILENAME_H
#define MYFILENAME_H
//put code here
#endif

这个模式意味着在重新定义编译中的标头时,您不能失误(感谢orsogufo指出这被称为“包含保护”)。请阅读一些关于C编译器如何编译文件(在链接之前)的文章,因为这将使#define和#include的世界对您来说更加清晰。当涉及解析文本时,C编译器本身并不是非常聪明。(然而,当涉及到C编译器本身时,又是另一回事了)


2
这个模式被称为 "包含保护"。 - Paolo Tedesco
5
大多数编译器都支持#pragma once的等效写法。 - xtofl
3
不,它们不支持。这是微软的扩展,很少有其他编译器支持它。 - Martin York
5
@Spence:不,那是不正确的。第二个 include 语句同样会搜索一组目录(只不过是不同的一组)。 - Martin York
3
马丁是正确的。""和<>之间的区别并不确定。这取决于实现,它们之间的差异是什么。然而,大多数编译器在使用""时会首先查找文件相对路径。 - Johannes Schaub - litb
显示剩余3条评论

16
  • 如果你有钱,可以查看John Lakos的《大规模C++软件设计》。
  • Google C++编码指南也有一些不错的东西。
  • 同时也可以在Sutter Herb的博客上查看他的材料。

基本上你需要了解在哪些情况下不需要包含头文件,例如前向声明。另外要确保每个包含文件都可以单独编译,并且只有在必要时(例如模板)才将#includes放在h文件中。


我认为你所说的正是我需要的。上面的东西足够简单,以至于每个人都知道它。 - MainID

14

因此,您的编译器可能支持2个用于包含文件的唯一搜索路径:
非正式地,我们可以称之为“系统包含路径”和“用户包含路径”。
#include <XX> 搜索系统包含路径。
#include "XX" 首先搜索用户包含路径,然后是系统包含路径。

检查草案标准 n2521:
第16.2节:

2 A preprocessing directive of the form 

  # include < h-char-sequence> new-line 

  searches a sequence of implementation-defined places for a header identified
  uniquely by the specified sequence between the < and > delimiters, and
  causes the replacement of that directive by the entire contents of the
  header. How the places are specified or the header identified is
  implementation-defined. 

3 A preprocessing directive of the form 

  # include " q-char-sequence" new-line 

  causes the replacement of that directive by the entire contents of the
  source file identified by the specified sequence between the " " delimiters.
  The named source file is searched for in an implementation-defined manner.
  If this search is not supported, or if the search fails, the directive is
  reprocessed as if it read

  # include < h-char-sequence> new-line 

  with the identical contained sequence (including > characters, if any)
  from the original directive. 

这的一个例子是gcc

  -isystem <dir>              Add <dir> to the start of the system include path
  -idirafter <dir>            Add <dir> to the end of the system include path
  -iwithprefix <dir>          Add <dir> to the end of the system include path
  -iquote <dir>               Add <dir> to the end of the quote include path
  -iwithprefixbefore <dir>    Add <dir> to the end of the main include path
  -I <dir>                    Add <dir> to the end of the main include path

要查看gcc搜索的位置,请执行以下操作:

g++ -v -E -xc++ /dev/null -I LOOK_IN_HERE
#include "..." search starts here:
#include <...> search starts here:
  LOOK_IN_HERE
  /usr/include/c++/4.0.0
  /usr/include/c++/4.0.0/i686-apple-darwin9
  /usr/include/c++/4.0.0/backward
  /usr/local/include
  /usr/lib/gcc/i686-apple-darwin9/4.0.1/include
  /usr/include
  /System/Library/Frameworks (framework directory)
  /Library/Frameworks (framework directory)
End of search list.

那么如何使用这个知识呢?
有几种思路,但我总是将我的库从最具体的到最通用的列出。

示例

文件:plop.cpp

#include "plop.h"
#include "plop-used-class.h"

/// C Header Files
#include <stdio.h>    // I know bad example but I drew a blank

/// C++ Header files
#include <vector>
#include <memory>

这样如果头文件“plop-used-class.h”应该包含<vector>,则编译器将捕获到此错误。如果我将<vector>放在顶部,则这个错误将被隐藏,不会被编译器捕获。


哪些编译器支持“系统路径与用户路径”的区分?看起来你只是在提到GCC ... - Wolf
1
@Wolf 我引用了标准(就像在09年那样)。标准说文件的查找方式是“实现定义的”。然后我展示了gcc实现作为一个“例子”(gcc是当时主流的c++编译器,clang在09年还不太可用,直到2011年才变得相关,MS cl编译器似乎只有一个搜索路径)。 - Martin York
我明白了,谢谢。所以你把“implementation-defined places”翻译成了“系统包含路径”。 - Wolf
@Wolf 除了准确地引用标准回答问题外,我还举了一个实际的例子来说明一个实现如何解释“implementation-defined”以解决一个真实的情况。 - Martin York

3
除了其他评论之外,请记住,如果您只有一个指针或引用,则不需要在另一个头文件中#include一个头文件。例如:
所需的标题:
#include "Y.h"
class X
{
   Y y; // need header for Y
};

无需标题:

class Y; 
class X
{
   Y* y; // don't need header for Y
};
//#include "Y.h" in .cpp file

第二个示例编译速度更快,依赖性更少。这在大型代码库中非常重要。

谢谢,这是所谓的前向声明吗? - MainID
是的,这是一种前向声明。非常有用,可以加快编译速度。仅在编译器不需要细节时才起作用;您不能将其用于基类、实际成员等。 - David Thornley

3

作为对Andy Brice答案的补充,你也可以通过函数返回值的前向声明来解决问题:

class Question;
class Answer;

class UniversityChallenge
{
...
    Answer AskQuestion( Question* );
...
};

这里有一个我之前提出的问题,其中有一些很好的答案,链接如下http://bytes.com/groups/c/606466-forward-declaration-allowed.


2
头文件是C语言将接口和实现分离的方式。它们被分为两种类型:标准头文件和用户定义的头文件。 标准头文件,例如string.h,允许我们访问底层C库的功能。在使用相关功能的每个.c文件中都应该#include它。通常使用括号,如#include 。 用户定义的头文件向其他程序员或C代码的其他部分公开函数的实现。如果您已经实现了一个名为rational.c的用于计算有理数的模块,则应该有一个相应的rational.h文件用于其公共接口。使用该功能的每个文件都应该#include rational.h,并且rational.c也应该#include它。通常使用#include "rational.h"来实现。 进行#includes的编译部分称为C预处理器。它主要执行文本替换和粘贴文本操作。 Spence在防止重复#includes(这会损坏命名空间)的模式方面是正确的。这是包含的基础,GNU Make为您提供了更多的功能,但也带来了更多的麻烦。

1

请查看讨论,了解在C++中使用#include<filename.h>#include<filename>来包含C库的相关内容。


0
有关如何正确使用 #include 的材料吗?
我强烈推荐阅读 C++ Core GuidelinesSF: Source files 章节作为一个很好的起点。
我没有找到任何详细解释这种用法的 C/C++ 教材。
关于 C++ 项目的物理组成方面,许多传统智慧可能可以在 John Lakos 的《"Large-Scale C++ Software Design"》一书中找到。
在正式项目中,我经常对处理它感到困惑。
你身处一个好的环境中。在 C++20 模块 之前,#include 是将多个文件组合成 C++ 翻译单元的唯一实用方式。这是一个简单、有限的工具,通过预处理器将整个文件复制/粘贴到其他文件中。结果编译器的输入通常很大,并且常常从一个翻译单元重复到另一个翻译单元。

0
首先确定你是否需要它。从https://cplusplus.com/articles/Gw6AC542/
  • 如果A根本没有引用B,则不做任何操作
  • 如果对B的唯一引用是在友元声明中,则不做任何操作
  • 如果A包含一个B指针或引用:B* myb;,则前向声明B
  • 如果一个或多个函数将B对象/指针/引用作为参数或返回类型:B MyFunction(B myb);,则前向声明B
  • 如果B是A的父类,则#include "b.h"
  • 如果A包含一个B对象:B myb;,则#include "b.h"

0

编辑:安迪·布赖斯以更简洁的方式提出了同样的观点。

接上null的回答,最重要的是考虑你放置 #include 的位置。

当你写一个 #include 时,预处理器会将你在当前文件中列出的文件内容直接包含进来,包括这些文件中的任何 #include。这显然会导致在编译时文件变得非常大(代码膨胀),所以你需要仔细考虑是否需要 #include。

在标准的代码文件布局中,你为一个类设置一个 .h 文件用于存放类和函数的声明,然后有一个 .cpp 实现文件。在头文件中放置 #include 的数量时要小心。因为每当你对头文件进行更改时,也必须重新编译任何包含它的文件(即使用该类的文件);如果头文件本身有很多 #include,那么使用该类的每个文件在编译时都会显著增大。

最好在可能的情况下使用前向声明,这样你可以编写方法签名等,并在 .cpp 文件中 #include 相关文件,以便你可以实际使用代码所依赖的类和结构。

//In myclass.h
class UtilClass; //Forward declaration of UtilClass - avoids having to #include untilclass.h here

class MyClass
{
    MyClass();
    ~MyClass();

    void DoSomethingWithUtils(UtilClass *util); //This will compile due to forward declaration above
};

//Then in the .cpp
#include utilclass.h

void MyClass::DoSomethingWithUtils(UtilClass *util)
{
    util->DoSomething(); //This will compile, because the class definition is included locally in this .cpp file.
}

这段代码可以编译通过,因为......我认为这不是一个非常有用的注释:解释代码为什么能够编译通过需要很多工作。在你的特定情况下,这个注释还是错误的,因为编译器可能会在#include utilclass.h处停止;所以最好在真正尝试编译之前先检查一下... - Wolf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接