C++ include头文件的顺序以及双引号““和尖括号<>的区别

news/2024/10/16 22:20:38 标签: c++

本文章进一步详细解释 #include 的头文件包含机制,包括搜索路径的处理、双引号 "" 和尖括号 <> 在不同环境中的使用差异,以及它们的底层机制。

1. 头文件包含机制和搜索路径详解

#include 是一个预处理指令,用于在编译前将指定的头文件的内容插入到当前源文件中。在 C++ 编译过程中,头文件的查找顺序和搜索路径的选择会影响项目的编译成功与否。

双引号 "" 的搜索路径机制

当使用双引号 #include "filename" 包含头文件时,编译器通常按照以下顺序查找头文件:

  1. 当前文件所在的目录
    • 编译器首先查找与当前源文件同目录的头文件。这是因为我们通常使用双引号来包含本地定义的头文件(如类定义的 .h 文件)。
  2. 包含该文件的文件夹
    • 如果该源文件本身是通过 #include 被其他文件引用的,编译器会在最先引用该文件的文件夹内查找。
  3. 指定的项目包含路径(通过编译器参数指定的路径,例如 -I 选项):
    • 编译器会查找通过编译器命令行参数指定的头文件路径(-I/path/to/include)。这个路径可以是多个目录,依赖于构建系统的设置。
  4. 全局系统路径
    • 如果前面的路径中没有找到文件,编译器会在全局头文件目录中查找。通常这些路径是操作系统或者 IDE 预先配置好的系统路径,比如 /usr/include(Linux 系统)或者 C:\Program Files\(Windows 系统)。
尖括号 <> 的搜索路径机制

当使用尖括号 #include <filename> 包含头文件时,编译器仅在全局或系统路径中查找头文件,忽略当前目录。这是为了更高效地查找标准库和第三方库中的头文件。查找路径如下:

  1. 标准库路径

    • 编译器首先在 C++ 标准库的路径中查找头文件。这些路径可能包含 <iostream>, <vector>, <string> 等标准库文件。
  2. 系统库路径

    • 然后,编译器会查找系统头文件路径。比如,在 Linux 系统中,常见路径为 /usr/include/usr/local/include,而在 Windows 系统中,可能是 C:\Program Files (x86)\Microsoft Visual Studio\
  3. 指定的头文件路径

    • 依赖于编译器设置,可能还会根据用户指定的第三方库路径(如通过 -I 指定的路径)来查找第三方库的头文件,例如 Boost、Qt 等。
双引号和尖括号的选择与差异
  • 使用双引号 "" 更适合引用 项目内部 或者 本地定义的头文件,因为它会首先查找当前文件目录,确保本地头文件的优先级较高。这种方式适用于项目间的模块化开发。
  • 使用尖括号 <> 通常用于 标准库外部库 的头文件,避免在当前目录下无意中引用到同名的头文件。
更详细的搜索过程比较
使用符号查找顺序适用场景
双引号 ""1. 当前目录
2. 引用文件所在目录
3. 编译器指定的项目路径(如 -I
4. 系统路径
用于引用本地的头文件
尖括号 <>1. 标准库路径
2. 系统路径
3. 第三方路径
用于标准库和外部库的头文件
编译器选项与头文件路径

在使用编译器时,可以通过选项配置头文件的查找路径。例如:

  • GCC/Clang

    • -I:指定额外的头文件路径。例如,g++ -I/path/to/headers main.cpp 会告诉编译器在 /path/to/headers 中查找头文件。
  • Visual Studio

    • 在项目属性的 "C/C++" > "附加包含目录" 中,可以指定额外的包含目录。编译器会在这些目录中查找双引号 "" 和尖括号 <> 所包含的头文件。
  • CMake

    • 使用 include_directories()target_include_directories() 指定头文件的搜索路径。

2. 双引号和尖括号的深层机制

双引号和尖括号的主要区别在于它们告诉编译器如何查找文件的路径。尖括号中的头文件往往是一些不常修改的外部依赖,比如标准库文件、系统级文件或者第三方库,而双引号中的头文件是用户自定义的文件,更容易发生变化。因此,双引号和尖括号的查找路径和优先级设计不同。

双引号 vs 尖括号查找行为

双引号 "filename":
1. 查找当前目录/源文件目录
2. 查找编译器路径(通过 -I 指定)
3. 查找标准系统路径

尖括号 <filename>:
1. 查找标准系统路径
2. 查找第三方路径

3. #include 的例子与实践

包含自定义头文件的实例

#include "MyClass.h" // 当前目录查找
#include "subfolder/Helper.h" // 指定子文件夹查找

#include <iostream> // 系统库查找,标准C++库
#include <cmath>    // 系统路径下的数学库

在这个例子中,"MyClass.h""subfolder/Helper.h" 都是项目内的文件,编译器会从源文件所在的目录或项目结构中的路径查找。<iostream><cmath> 是标准库的头文件,因此编译器直接在标准路径中查找。

4. 如何正确组织头文件

为了避免潜在的查找冲突和提高编译速度,建议遵循以下头文件组织原则:

  1. 使用前向声明

    • 尽量在头文件中使用前向声明来减少包含不必要的头文件。例如,在类成员中引用其他类的指针时,可以通过前向声明避免引入整个类的定义。

                class MyClass; // 前向声明
                class AnotherClass {
                    MyClass* ptr; // 使用指针或引用即可避免包含整个 MyClass 头文件
                };

        

      2.防止多次包含(Include Guard)

  • 为了避免头文件的多次包含,常用的做法是使用包含防护,即 #ifndef#define#endif 组合,或者在现代 C++ 中使用 #pragma once
  • // MyClass.h
    #ifndef MYCLASS_H
    #define MYCLASS_H

    class MyClass {
        // Class Definition
    };

    #endif // MYCLASS_H

  1. 确保头文件独立

    • 每个头文件应该可以独立包含,不依赖其他头文件的顺序。通过包含防护和适当的前向声明,避免头文件之间的复杂依赖关系。

5. 总结

  • 双引号 "":优先用于包含项目中的本地文件,编译器会先从当前目录查找,再到系统路径查找。
  • 尖括号 <>:用于包含标准库或外部库文件,编译器直接从系统路径查找。
  • 头文件包含顺序:优先包含与当前实现文件相关的头文件,然后是标准库头文件,接着是第三方库,最后是项目内部其他模块的头文件。


http://www.niftyadmin.cn/n/5708624.html

相关文章

gitlab配置ssh密钥

1.配置用户信息 git config --global user.name "你的名字" git config --global user.email "你的邮箱" 查看配置是否成功 git config --global --list 2.生成密钥 终端 或 右键文件夹open git bash here 输入命令 ssh-keygen -t rsa -C 随意(生…

查询使用方法:

查询&#xff1a; 什么是查询&#xff1a; 查询是产生一个虚拟表 看到的是表形式显示的结果&#xff0c;但结果并不真正存储 每次执行查询只是从数据表中提取数据&#xff0c;并按照表的形式显示出来。 查询机制简介&#xff1a; 查询语法&#xff1a; SELECT(列名)&#x…

浙大数据结构:11-散列4 Hashing - Hard Version

这道题主要在于思路&#xff0c;感觉像个模拟题&#xff0c;用到了线性探测的算法 机翻 1、条件准备 visit数组看这个位置有没有放进来数&#xff0c;num存非负整数&#xff0c;s存未到放入时机的数。 answer存答案。n为总共数量。 #include <iostream> #include<…

zookeeper kafka集群配置

一.下载安装包 地址&#xff1a;https://download.csdn.net/download/cyw8998/16579797 二.配置文件 zookeeper.properties dataDir/data/kafka/zookeeper_data/zookeeper # the port at which the clients will connect clientPort2181 # disable the per-ip limit on the…

Vant 日期时间组件拓展

基于 "vant": "^4.8.3", 效果图 <template><!-- 弹出层 --><van-popupv-model:show"isPicker"position"bottom"><van-pickerref"picker":title"title"v-model"selectedValues"…

关于移动通信网络中各个组成部分的基础入门

移动通信网络的详细组成 移动通信网络是一个复杂的系统&#xff0c;由多个层次和组件构成&#xff0c;每个组件都有其特定的功能和作用。以下是对移动通信网络各个组成部分的详细阐述&#xff1a; 1. 终端设备&#xff08;End User Devices&#xff09; 定义&#xff1a; 终…

【返璞归真】-泰勒展开式

泰勒展开式是将一个函数在某点附近展开为一个无穷级数的方式&#xff0c;其原理是通过函数在该点的导数来近似函数值。公式为&#xff1a; f ( x ) f ( a ) f ′ ( a ) ( x − a ) f ′ ′ ( a ) 2 ! ( x − a ) 2 f ′ ′ ′ ( a ) 3 ! ( x − a ) 3 ⋯ f(x) f(a) f(a)…

架构设计笔记-11-未来信息综合技术

知识要点 云原生架构原则包括&#xff1a;服务化原则、弹性原则、可观测原则、韧性原则、所有过程自动化原则、零信任原则和架构持续演进原则。 区块链是一种按照时间顺序将数据区块以顺序相连的方式组合成的一种链式数据结构&#xff0c;并以密码学方式保证的不可篡改和不可…