第一章:C++工程结构——认识CMakeLists.txt、头文件与源文件分离、多文件编译入门
各位同学,欢迎来到《Cpp工程编译与GDB调试实战》的第一章。
说实话,我见过太多人写C++,一个main.cpp里塞了几千行代码。编译能过,但维护起来简直噩梦。我自己刚入行那会儿也干过这事,后来被项目毒打了几次,才老老实实把工程结构搞好。
这一章,我们就来聊聊C++工程最基本的骨架:头文件与源文件分离、多文件编译,以及那个绕不开的CMakeLists.txt。
1.1 为什么要分离头文件和源文件?
你想想看,一个项目少则几十个文件,多则上千个。如果所有代码都堆在一起,找函数定义都得翻半天。
分离的核心目的就两个:
- 声明与实现分离:头文件(.h)放声明,源文件(.cpp)放实现。别人看你的代码,只需要看头文件就知道接口长什么样。
- 编译加速:改了一个源文件,只需要重新编译这一个文件,再链接就行。不用全量编译。
我记得有一次接手一个老项目,所有代码都在一个.cpp里,编译一次要5分钟。我花了半天拆成20个文件,编译时间降到30秒。嗯,那种成就感,你懂的。
核心原则:头文件里不要放实现(除了模板和内联函数)。头文件是给别人看的“说明书”,源文件才是“操作手册”。
3.2 一个最简单的多文件工程
我们直接上手。假设我们要写一个计算器,支持加法和乘法。
工程结构长这样:
calculator/
├── include/
│ └── calc.h
├── src/
│ ├── calc.cpp
│ └── main.cpp
└── CMakeLists.txt
calc.h —— 头文件,声明接口:
#ifndef CALC_H
#define CALC_H
int add(int a, int b);
int multiply(int a, int b);
#endif // CALC_H
这里有个小细节:#ifndef 是防止头文件被重复包含。我见过新手忘了写,结果编译报一堆“重定义”错误。嗯,这个习惯要养成。
calc.cpp —— 源文件,实现功能:
#include "calc.h"
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
main.cpp —— 入口文件:
#include <iostream>
#include "calc.h"
int main() {
int x = 10, y = 20;
std::cout << "加法结果: " << add(x, y) << std::endl;
std::cout << "乘法结果: " << multiply(x, y) << std::endl;
return 0;
}
小提示:头文件用双引号 #include "calc.h" 表示先从当前目录找,找不到再去系统路径。用尖括号 #include <iostream> 表示只去系统路径找。这个区别,我曾经在项目里踩过坑——自己写的头文件用了尖括号,结果死活找不到。
3.3 CMakeLists.txt —— 现代C++工程的标配
以前大家用Makefile,手写依赖规则,那叫一个痛苦。现在CMake是主流,跨平台、语法简洁。
我们这个工程的CMakeLists.txt长这样:
cmake_minimum_required(VERSION 3.10)
project(calculator VERSION 1.0)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 收集源文件
file(GLOB_RECURSE SRC_FILES "src/*.cpp")
# 指定头文件路径
include_directories(include)
# 生成可执行文件
add_executable(calculator ${SRC_FILES})
我来解释一下这几行:
cmake_minimum_required:指定CMake最低版本。我一般用3.10以上,太老的版本有些特性不支持。project:项目名称和版本号。set(CMAKE_CXX_STANDARD 17):用C++17标准。我个人习惯用17,既有新特性又足够稳定。file(GLOB_RECURSE ...):自动收集src目录下所有.cpp文件。省得你每加一个文件都要改CMakeLists.txt。include_directories(include):告诉编译器头文件在include目录下。add_executable:生成可执行文件。
避坑指南:我曾经用 GLOB_RECURSE 时,不小心把测试目录的源文件也加进来了。后来我改成手动列出源文件,或者用 GLOB 只匹配特定模式。建议小项目用 GLOB,大项目还是手动列出来更可控。
3.4 编译与运行
在工程根目录下执行:
mkdir build
cd build
cmake ..
make
./calculator
输出:
加法结果: 30
乘法结果: 200
搞定。就这么简单。
3.5 本章知识体系
下面这张图,是我画的一个结构图,帮你理清这一章的核心逻辑:
这张图把本章的核心知识点串起来了。你看,左边是文件组织,中间是编译流程,右边是构建工具。三者缺一不可。
3.6 本章小结
这一章我们干了三件事:
- 搞清楚了头文件和源文件为什么要分离——说白了就是让别人能看懂你的接口,同时编译还能快一点。
- 写了一个多文件工程的例子——从calc.h到calc.cpp再到main.cpp,完整的流程走了一遍。
- 学会了CMakeLists.txt怎么写——从版本声明到源文件收集,再到生成可执行文件。
嗯,这些都是基本功。后面章节我们会在这个基础上,加入调试、优化、单元测试等内容。但今天先把地基打牢。
记住一句话:好的工程结构,是写出来的,不是重构出来的。 一开始就规划好,后面能省很多事。
课后练习:自己动手,把上面的计算器工程完整写一遍。然后试着加一个除法函数,看看需要改哪几个文件。做完这个练习,这一章的内容你就真正掌握了。