头文件只能放声明不能放定义,否则会导致多重定义错误;应使用#pragma once或卫哨宏防重复包含;头文件只包含直接必需的头,优先前向声明;.cpp首行应包含对应头文件以验证自完备性。

头文件里只能放声明,不能放定义
很多人一写头文件就直接在里头写 int x = 42; 或者 void func() { ... },结果一被多个 .cpp 文件包含,链接时立刻报 multiple definition 错误。这是因为每个编译单元都会生成一份该定义,链接器发现重复就罢工。
正确做法是:头文件只放类声明、函数声明、extern 变量声明、模板定义(必须)、内联函数(inline 或定义在类体内)。
- 全局变量要写成
extern int g_value;,定义放到某个.cpp里(比如g_value = 0;) - 函数体必须挪到
.cpp中,头文件里只留void do_something(int x); - 类成员函数如果写在类定义内部,默认是
inline,可以留在头文件;但别为了“省事”把几十行逻辑全塞进去
#include 防止重复包含用 #pragma once 还是 #ifndef?
#pragma once 写起来快,也基本没兼容性问题——但它是非标准扩展,极少数老旧编译器(比如某些嵌入式工具链)不支持。而 #ifndef MY_HEADER_H 是标准 C++ 做法,100% 兼容,只是容易手误写错宏名或漏 #endif。
推荐折中方案:优先用 #pragma once,但团队若需支持极端老环境,就切回传统卫哨宏。别混用,也别在同一个项目里两种风格来回切换。
立即学习“C++免费学习笔记(深入)”;
- 卫哨宏命名建议用
PROJECT_MODULE_FILENAME_H格式,比如UTILS_STRINGHELPER_H,避免简单用STRING_H(容易和系统头冲突) -
#pragma once对符号链接(symlink)路径敏感,同一文件被不同路径包含时,部分旧版 GCC 可能判为不同文件——不过现代 clang/gcc 都已修复
头文件里 include 什么、不 include 什么
头文件的 #include 不是“我需要啥就加啥”,而是“我声明依赖啥就暴露啥”。过度包含会导致编译变慢、隐式耦合、循环依赖难解。
原则:头文件只 #include 它的声明所**直接必需**的类型定义。比如函数参数是 std::string,就得 #include <string></string>;但如果参数是 const std::string&,且头文件里只声明不使用其成员,可以用前向声明 class string;(不行——std::string 是模板,不能前向声明),所以仍得包含。
- 能用前向声明(
class Foo;)解决的,就别#include "foo.h",尤其对自定义类 -
std::vector<t></t>、std::shared_ptr<t></t>这类模板,只要不是取 sizeof 或调用成员,前向声明往往不够,得包含对应头 - 绝对不要在头文件里
#include "xxx.cpp"或把实现文件拖进来——这是新手最常犯的“速成陷阱”
源文件怎么组织才不会漏掉头文件或重复定义
一个 .cpp 文件的第一行,应该是它对应头文件的 #include,比如 widget.cpp 开头就是 #include "widget.h"。这能第一时间验证头文件是否自完备(即不依赖其他文件先被包含)。
如果 widget.cpp 编译失败,且错误指向 widget.h 里的某个类型未定义,说明头文件缺 #include 或前向声明;如果错误在 widget.cpp 里,但头文件没问题,那大概率是定义写错位置或者忘了加 extern。
- 所有非 inline / 非模板的函数定义、全局变量定义、静态成员定义,必须且只能出现在一个
.cpp文件里 - 类的静态成员变量(非 const 整型)必须在
.cpp中定义,哪怕头文件里写了static int count; - 不要在头文件里写
using namespace std;——它会污染所有包含该头的编译单元,引发不可预测的名称冲突










