头文件只放声明不放定义,避免重复定义错误;函数实现、全局变量定义须移至.cpp文件;类成员函数在类内定义默认inline,否则需在.cpp中实现;用#pragma once或#ifndef防止重复包含;所有.cpp必须参与编译链接。

头文件里只放声明,不放定义
重复定义错误(multiple definition of 'xxx')几乎都源于头文件中写了函数实现或全局变量定义。比如在 utils.h 里直接写 int add(int a, int b) { return a + b; },一旦被两个 .cpp 文件包含,链接时就会报错。
正确做法是:头文件只写函数声明、类声明、extern 变量声明、内联函数(inline)或模板定义;具体实现一律挪到对应的 .cpp 文件中。
- 类成员函数若在类体内定义,默认是
inline,可保留在头文件;但逻辑稍长就该拆到源文件 - 全局变量要用
extern int g_count;声明在头文件,再在某个.cpp中写int g_count = 0;定义一次 - 用
#pragma once或传统#ifndef XXX_H_ #define XXX_H_ ... #endif防止头文件被重复包含
每个 .cpp 文件独立编译,最后链接成一个可执行文件
编译器不会自动知道你的 main.cpp 依赖 utils.cpp——你得明确告诉它:“把这两个目标文件一起链接”。常见错误是只编译了 main.cpp,忘了编译 utils.cpp,结果链接时报 undefined reference to 'add(int, int)'。
典型命令流程:
立即学习“C++免费学习笔记(深入)”;
g++ -c main.cpp -o main.o
g++ -c utils.cpp -o utils.o
g++ main.o utils.o -o program
也可以一步到位(但不利于增量编译):g++ main.cpp utils.cpp -o program
-
-c表示只编译不链接,生成目标文件(.o) - 所有用到的
.cpp都必须参与编译,哪怕它没main函数 - 顺序无关紧要,但链接阶段必须包含所有定义了被调用符号的
.o文件
include 路径与相对路径要写对
写 #include "utils.h" 是相对当前 .cpp 文件路径查找;而 #include 是查系统/标准库路径。工程变大后,头文件常放在 include/ 子目录,这时光靠 "utils.h" 找不到。
解决方案是用 -I 指定头文件搜索路径:
g++ -I./include main.cpp utils.cpp -o program
这样 #include "utils.h" 就会去 ./include/utils.h 找。
- 避免在
#include中写绝对路径或过深的相对路径(如"../src/utils.h"),易出错且难迁移 - 如果头文件和源文件同级,
"utils.h"就够用;如果分层了,统一用-I管理比硬编码路径更可靠 - CMake 或 Makefile 中对应的是
include_directories()或INCLUDES = -I./include
类的定义与实现分离时,构造函数/析构函数别漏写
类声明在 Person.h 中写了 Person(); 和 ~Person();,但忘了在 Person.cpp 中实现,链接时就会报 undefined reference to 'Person::Person()' —— 即使函数体为空也得写出来。
例如:
// Person.h
class Person {
public:
Person();
~Person();
void say();
};
// Person.cpp
#include "Person.h"
Person::Person() {} // 不能省
Person::~Person() {} // 不能省
void Person::say() { /*...*/ }
- 编译器不会为你自动生成非内联的构造/析构函数定义;声明了就必须实现(哪怕空)
- 如果用了默认行为(如无参构造、析构),又不想写空实现,可显式写
Person() = default;在头文件中(C++11起) - 成员函数在类外定义时,作用域解析符
::和类名不能拼错,否则变成新函数而非重载
undefined reference 错误,90% 是因为某个 .cpp 根本没参与编译,不是代码写错了。**










