头文件应使用#include "xxx.h"引入自定义头文件并配合-I指定路径,避免用<>;必须加include guard或#pragma once;声明与定义分离,仅inline/constexpr/template可放头文件;路径由构建系统管理,禁用相对路径。

头文件用 #include 引入,但路径写法决定搜索行为
编译器对 #include "xxx.h" 和 #include <xxx.h> 的查找逻辑完全不同。双引号从当前源文件所在目录开始找,再查 -I 指定的路径;尖括号直接跳过当前目录,只查系统路径和 -I 路径(且顺序受编译器实现影响)。
常见错误现象:#include "mylib/utils.h" 在 IDE 里能点开,但命令行编译报 fatal error: mylib/utils.h: No such file or directory——大概率是没加 -I./include 或路径层级不对。
- 项目根目录下有
include/mylib/utils.h,源文件在src/main.cpp,应写#include "mylib/utils.h"并配g++ -Iinclude src/main.cpp - 不要把自定义头文件扔进
/usr/include或用<>包裹——这会污染系统路径、破坏可移植性,CI 构建时必然失败 - 头文件名避免用下划线开头(如
_utils.h),C++ 标准保留给实现用,可能触发警告或未定义行为
自定义头文件必须加 include guard 或 #pragma once
不加防护的头文件被多次 #include 会导致重复定义错误,比如 error: redefinition of 'struct Config' 或 multiple definition of 'inline void log()'。
两种写法都有效,但行为略有差异:
立即学习“C++免费学习笔记(深入)”;
-
#pragma once简洁,主流编译器(GCC/Clang/MSVC)都支持,但属于非标准扩展;它依赖文件路径唯一性,硬链接或符号链接可能导致失效 -
#ifndef MYLIB_UTILS_H+#define MYLIB_UTILS_H+#endif是标准写法,宏名必须全局唯一,推荐用项目前缀 + 路径大写 +_H后缀(如MYLIB_UTILS_H) - 二者不要混用——有些旧版 Clang 对
#pragma once+ 宏 guard 共存处理异常,徒增维护负担
头文件里只放声明,别放定义(除非 inline / constexpr / template)
在头文件中写 int global_counter = 0; 或 void helper() { ... },会导致每个包含它的 .cpp 文件都生成一份副本,链接时报 multiple definition。
正确做法分场景:
- 变量:用
extern int global_counter;声明,只在某个 .cpp 里定义(int global_counter = 0;) - 函数:声明放头文件,定义放 .cpp;若要内联,必须加
inline关键字(C++17 起inline变量也合法) - 模板和
constexpr函数天然适合放头文件——它们不产生独立符号,实例化由编译器按需生成 - 静态局部变量(
static int cache;)可以,但注意每个翻译单元一份副本,不是全局共享
头文件路径别写死,用构建系统管理依赖
手写 #include "../../src/core/data.h" 看似能跑通,但重构目录结构时所有相关 #include 都得改,CI 失败率飙升。
现代 C++ 项目该让构建系统解决路径问题:
- CMake 中用
target_include_directories(myapp PRIVATE include),之后所有源文件统一用#include "mylib/config.h" - Bazel 用
includes = ["include"];Meson 用include_directories('include') - 绝对不要在代码里出现
../或../../——这是构建逻辑泄漏到源码,说明项目结构或构建配置没理清
跨平台时尤其要注意:Windows 路径分隔符反斜杠在字符串里要转义,但头文件路径一律用正斜杠,编译器自动适配。










