4. C++基础(上):编译流程、基本语法、指针与引用、内存管理、STL容器

各位同学,欢迎来到C++基础部分。说实话,在自动驾驶这个行当里,C++是绕不开的坎。你想想看,从传感器数据采集到路径规划,从控制指令下发到底层驱动,几乎全是C++的天下。我见过不少转行做自动驾驶的朋友,Python写得飞起,一到C++就卡壳。嗯,今天我们就来把这层窗户纸捅破。

4.1 C++编译流程:从源码到可执行文件

先聊聊编译流程。很多新手上来就写代码,编译报错就懵了。其实搞懂编译流程,很多问题就迎刃而解。

C++的编译分四步走:预处理、编译、汇编、链接。我习惯把这四步比作「做菜」:预处理是洗菜切菜(处理宏、头文件),编译是炒菜(翻译成汇编),汇编是装盘(生成机器码),链接是上桌(把各个菜拼成一桌)。

核心流程:

  • 预处理:处理 #include、#define、#ifdef 等指令。生成 .i 文件。
  • 编译:将预处理后的文件翻译成汇编代码。生成 .s 文件。
  • 汇编:将汇编代码转换成机器码。生成 .o 或 .obj 文件。
  • 链接:将多个目标文件和库文件合并成可执行文件。

我曾经在项目里遇到过一个诡异的问题:明明改了代码,编译出来的程序还是老样子。排查了半天,发现是同事只改了头文件,没重新编译依赖这个头文件的源文件。说白了,就是没搞懂编译单元的概念。每个 .cpp 文件是一个独立的编译单元,头文件改了,所有包含它的编译单元都得重新编译。

我的习惯:写 Makefile 或 CMakeLists.txt 时,一定要把依赖关系写清楚。别偷懒用「make clean && make」,项目大了这招能让你等到怀疑人生。

4.2 基本语法:那些你容易忽略的细节

基本语法我就不从头讲了,大家多少都有基础。我挑几个自动驾驶开发中容易踩坑的点说说。

命名空间: 项目中一定要用命名空间。我见过一个项目,全局变量、函数名满天飞,链接的时候各种重定义。后来重构时加了命名空间,世界清净了。

// 推荐写法
namespace perception {
    class LidarProcessor {
        // ...
    };
}

// 使用时
perception::LidarProcessor processor;

const 关键字: 能加 const 的地方一定要加。这不仅是给编译器看的,更是给人看的。我在代码 review 时,看到函数参数没加 const,第一反应就是「这哥们是不是要改我数据?」

// 好的习惯
void ProcessPointCloud(const std::vector<Point>& points) {
    // 这里只能读,不能改
}

引用 vs 值传递: 自动驾驶里经常要处理大量点云、图像数据。值传递会触发拷贝,性能直接崩掉。记住一条铁律:自定义类型尽量用 const 引用传递。

注意: 内置类型(int、float、double 等)用值传递反而更快,因为引用本身也有开销。别盲目用引用。

4.3 指针与引用:自动驾驶开发中的双刃剑

指针和引用,是C++里最容易出问题的地方,也是自动驾驶开发中绕不开的工具。

指针: 说白了就是存地址的变量。我刚开始做激光雷达驱动时,经常遇到空指针崩溃。后来养成了一个习惯:每次用指针前,先判空。

void ProcessLidarData(LidarFrame* frame) {
    if (frame == nullptr) {
        // 记录日志,返回或抛出异常
        return;
    }
    // 安全地处理数据
}

引用: 引用是指针的语法糖,但更安全。引用一旦初始化,就不能再指向其他对象。这特性在函数参数传递时特别好用。

智能指针: 现代C++开发,我建议尽量用智能指针。std::shared_ptr 和 std::unique_ptr 能帮你自动管理内存。我在项目中遇到过内存泄漏,排查了三天,最后发现是 new 了没 delete。从那以后,我基本不用裸指针了。

// 推荐使用智能指针
std::shared_ptr<LidarDriver> driver = std::make_shared<LidarDriver>();
// 不用手动 delete,离开作用域自动释放

避坑指南: 我曾经在回调函数里用了裸指针,结果对象被销毁了,回调还在执行,直接段错误。后来改用 std::weak_ptr 配合 std::shared_ptr,完美解决。

4.4 内存管理:new/delete 的正确姿势

内存管理是C++的硬骨头。自动驾驶系统对实时性要求极高,内存分配和释放不能有半点马虎。

new 和 delete: 这是最基础的内存管理方式。new 分配内存并调用构造函数,delete 调用析构函数并释放内存。记住:new 和 delete 必须成对出现。

// 单个对象
PointCloud* cloud = new PointCloud(1000);
// 使用 cloud
delete cloud;

// 数组
Point* points = new Point[1000];
// 使用 points
delete[] points;  // 注意是 delete[]

内存泄漏: 这是C++开发者的噩梦。我见过一个项目,每帧都 new 一堆对象,从来不 delete。跑了半小时,内存占用飙到几个G,系统直接卡死。

我的建议: 能用栈分配就别用堆分配。栈分配速度快,自动释放。实在要用堆,优先考虑智能指针或 RAII(资源获取即初始化)模式。

内存池: 在自动驾驶的实时模块中,频繁的 new/delete 会导致内存碎片和性能抖动。我习惯用内存池来预分配一大块内存,然后自己管理分配和释放。这样能保证实时性。

4.5 STL容器:vector 和 map 的实战技巧

STL容器是C++的宝藏。用好它们,能省下大量开发时间。我重点讲两个最常用的:vector 和 map。

vector: 动态数组,连续内存存储。随机访问 O(1),尾部插入 O(1),中间插入 O(n)。

// 基本用法
std::vector<Point> points;
points.reserve(10000);  // 预分配内存,避免多次扩容
points.push_back(Point(1.0, 2.0, 3.0));

// 遍历
for (const auto& p : points) {
    // 只读遍历
}

// 删除
points.erase(points.begin() + 5);  // 删除第6个元素

map: 红黑树实现,有序键值对。查找、插入、删除都是 O(log n)。在自动驾驶中,我常用 map 来存储配置信息、传感器ID到数据的映射。

// 基本用法
std::map<int, std::string> sensor_map;
sensor_map[1] = "Lidar";
sensor_map[2] = "Camera";

// 查找
auto it = sensor_map.find(1);
if (it != sensor_map.end()) {
    // 找到了
}

// 遍历
for (const auto& [id, name] : sensor_map) {
    // C++17 结构化绑定
}

性能优化: 如果你不需要有序性,用 std::unordered_map。哈希表实现,查找 O(1)。我在做高精地图匹配时,把 map 换成 unordered_map,性能提升了30%。

容器选择指南:

场景 推荐容器 原因
需要随机访问 vector O(1) 访问,内存连续
频繁头部/中间插入 list 或 deque vector 中间插入太慢
键值对查找 unordered_map O(1) 平均查找时间
需要有序键值对 map 红黑树保证有序
先进先出队列 queue 封装好的 FIFO

嗯,C++基础(上)就讲到这里。这些内容看着简单,但都是我在实际项目中踩过坑、流过泪总结出来的。你想想看,自动驾驶系统动辄几十万行代码,一个内存泄漏、一个空指针,可能就让车在路上出问题。所以,基础一定要打牢。

C++基础(上)知识体系 C++基础(上) 编译流程 预处理 → 编译 → 汇编 → 链接 基本语法 命名空间 / const / 引用传递 指针与引用 裸指针 / 引用 / 智能指针 内存管理 new/delete / 内存池 / RAII STL容器 vector / map / unordered_map 打好基础,才能写出健壮的自动驾驶软件

专注资料整理