本文系统整理 C++ 面试中的高频问题,覆盖基础语法、面向对象、内存管理、智能指针、STL、C++11/14/17、多线程并发、编译链接、设计模式以及项目实战追问。适合算法工程师、机器人控制工程师、嵌入式 C++、后端 C++ 等方向面试复习使用。


目录


一、C++ 基础语法高频问题

问题面试重点
C 和 C++ 的区别?C++ 支持面向对象、模板、异常、STL、RAII、泛型编程等特性
structclass 的区别?struct 默认访问权限是 publicclass 默认访问权限是 private
const 的作用?可修饰变量、指针、函数参数、返回值、成员函数,用于限制修改
static 的作用?可用于静态局部变量、静态成员变量、静态成员函数、文件作用域限制
inline 的作用?建议编译器将函数内联展开,减少函数调用开销,但不是强制
extern 的作用?声明外部变量或函数,常用于多文件共享全局变量
volatile 的作用?告诉编译器变量可能被外部改变,避免编译器过度优化
sizeofstrlen 的区别?sizeof 求对象所占内存大小,通常编译期确定;strlen 运行期计算字符串长度
new/deletemalloc/free 的区别?new/delete 会调用构造和析构函数,malloc/free 只负责内存分配和释放
引用和指针的区别?引用必须初始化且不能重新绑定;指针可以为空,也可以改变指向

常见追问

1. const 修饰指针有哪些情况?
const int* p1;      // 指向常量的指针,不能通过 p1 修改值,但 p1 可以改变指向
int const* p2;      // 与 const int* 等价
int* const p3;      // 指针常量,p3 不能改变指向,但可以修改指向的值
const int* const p4;// 指向常量的指针常量,指向和值都不能改
2. newmalloc 的核心区别
int* p1 = new int(10);              // 分配内存并初始化
int* p2 = (int*)malloc(sizeof(int)); // 只分配内存,不调用构造函数

 delete p1;
 free(p2);

二、面向对象高频问题

问题面试重点
面向对象三大特性?封装、继承、多态
重载、重写、隐藏的区别?重载是同作用域函数参数不同;重写是子类覆盖父类虚函数;隐藏是子类同名函数遮蔽父类函数
虚函数是什么?使用 virtual 修饰,通过虚函数表实现运行时多态
纯虚函数是什么?virtual void f() = 0;,含纯虚函数的类是抽象类
构造函数可以是虚函数吗?不可以,对象构造过程中虚表尚未完整建立
析构函数为什么常设为虚函数?防止通过父类指针删除子类对象时,只调用父类析构函数导致资源泄漏
虚函数表是什么?存储虚函数地址的表,对象中通常含有虚表指针
多态的实现原理?父类指针或引用 + 虚函数 + 虚函数表动态绑定
菱形继承问题?多继承导致公共基类被重复继承,可通过虚继承解决
拷贝构造和赋值运算符区别?拷贝构造用于初始化新对象,赋值运算符用于已有对象之间赋值

多态示例

#include <iostream>
#include <memory>

class Controller {
public:
    virtual void computeControl() = 0;
    virtual ~Controller() = default;
};

class PIDController : public Controller {
public:
    void computeControl() override {
        std::cout << "PID control" << std::endl;
    }
};

class MPCController : public Controller {
public:
    void computeControl() override {
        std::cout << "MPC control" << std::endl;
    }
};

int main() {
    std::unique_ptr<Controller> controller = std::make_unique<MPCController>();
    controller->computeControl();
    return 0;
}

三、内存管理高频问题

问题面试重点
C++ 内存分区有哪些?栈区、堆区、全局/静态区、常量区、代码区
栈和堆的区别?栈自动管理、速度快、空间较小;堆手动管理、空间较大、容易内存泄漏
什么是内存泄漏?申请的内存没有被释放,导致长期运行时内存占用不断增加
什么是野指针?指向无效内存地址的指针
什么是悬空指针?指针指向已经释放的对象
如何避免内存泄漏?使用 RAII、智能指针、容器管理资源,减少裸 new/delete
深拷贝和浅拷贝区别?浅拷贝只复制指针地址;深拷贝会重新申请内存并复制数据内容
析构函数作用?对象生命周期结束时释放资源
RAII 是什么?Resource Acquisition Is Initialization,用对象生命周期自动管理资源
内存对齐是什么?按特定边界存储数据,提高 CPU 访问效率

RAII 示例

#include <iostream>
#include <fstream>

class FileGuard {
public:
    explicit FileGuard(const std::string& file_name) {
        file_.open(file_name);
    }

    ~FileGuard() {
        if (file_.is_open()) {
            file_.close();
        }
    }

private:
    std::fstream file_;
};

RAII 的核心思想是:资源在对象构造时获取,在对象析构时自动释放。这样可以减少资源泄漏,也能提高异常安全性。


四、智能指针高频问题

问题面试重点
unique_ptr 是什么?独占所有权,不能拷贝,只能移动
shared_ptr 是什么?共享所有权,通过引用计数管理对象生命周期
weak_ptr 是什么?弱引用,不增加引用计数,常用于解决 shared_ptr 循环引用
shared_ptr 循环引用是什么?两个对象互相持有 shared_ptr,导致引用计数无法归零
make_shared 有什么好处?一次内存分配,提高效率,并且异常安全性更好
智能指针能完全替代裸指针吗?不能。非拥有关系、性能敏感场景仍可使用裸指针或引用
shared_ptr 是否线程安全?引用计数操作通常是线程安全的,但被管理对象本身不是线程安全的

unique_ptr 示例

#include <memory>

class Planner {};

int main() {
    std::unique_ptr<Planner> planner = std::make_unique<Planner>();
    // std::unique_ptr<Planner> p2 = planner; // 错误,不能拷贝
    std::unique_ptr<Planner> p3 = std::move(planner); // 可以移动
    return 0;
}

shared_ptrweak_ptr 示例

#include <memory>

class Node {
public:
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // 用 weak_ptr 打破循环引用
};

五、STL 高频问题

问题面试重点
vector 底层原理?连续内存,支持随机访问,容量不足时动态扩容
vector 扩容机制?容量不足时重新申请更大内存,然后拷贝或移动旧元素
vectorlist 区别?vector 随机访问快,插入删除慢;list 插入删除快,随机访问慢
mapunordered_map 区别?map 底层常为红黑树,有序;unordered_map 底层为哈希表,无序
setunordered_set 区别?set 有序去重;unordered_set 哈希去重,无序
dequevector 区别?deque 分段连续,支持头尾高效插入删除
迭代器失效是什么?容器修改后,原迭代器可能指向无效位置
push_backemplace_back 区别?emplace_back 原地构造对象,减少临时对象
STL allocator 是什么?STL 的内存分配器,用于控制容器内存申请和释放
priority_queue 底层是什么?默认使用大根堆

push_backemplace_back 对比

#include <vector>
#include <string>

struct Student {
    std::string name;
    int age;

    Student(const std::string& n, int a) : name(n), age(a) {}
};

int main() {
    std::vector<Student> students;

    students.push_back(Student("Tom", 18)); // 先构造临时对象,再放入 vector
    students.emplace_back("Jack", 20);      // 直接在 vector 内部构造对象

    return 0;
}

六、C++11/14/17 高频问题

问题面试重点
auto 的作用?自动类型推导
decltype 的作用?推导表达式类型
nullptrNULL 区别?nullptr 是空指针类型,更类型安全
右值引用是什么?T&&,用于移动语义和完美转发
移动语义是什么?转移资源所有权,减少深拷贝开销
std::move 做了什么?将对象强制转换为右值引用,本身不移动数据
完美转发是什么?保持参数的左值或右值属性进行转发
Lambda 表达式是什么?匿名函数对象,常用于回调、排序、自定义逻辑
std::functionstd::bind 是什么?通用函数包装器和参数绑定工具
constexpr 是什么?用于编译期常量计算
override 作用?显式声明重写父类虚函数,避免误写
final 作用?禁止类被继承或虚函数继续被重写

移动语义示例

#include <iostream>
#include <vector>

int main() {
    std::vector<int> a = {1, 2, 3, 4};
    std::vector<int> b = std::move(a);

    std::cout << "b size: " << b.size() << std::endl;
    std::cout << "a size: " << a.size() << std::endl;

    return 0;
}

注意:std::move 本身不移动数据,它只是把对象转换成右值引用,真正的资源转移发生在移动构造或移动赋值中。


七、多线程并发高频问题

问题面试重点
进程和线程区别?进程是资源分配单位,线程是调度执行单位
C++ 如何创建线程?使用 std::thread
mutex 是什么?互斥锁,用于保护共享资源
lock_guardunique_lock 区别?lock_guard 简单自动加解锁;unique_lock 更灵活,可延迟加锁、手动解锁
死锁是什么?多个线程互相等待对方释放资源,导致程序无法继续执行
如何避免死锁?固定加锁顺序、减少锁粒度、使用 try_lock、使用 RAII 管理锁
condition_variable 是什么?条件变量,用于线程等待和通知
atomic 是什么?原子变量,支持无锁原子操作
线程安全是什么意思?多线程访问共享资源时,程序行为仍然正确
生产者消费者模型?一个线程生产数据,另一个线程消费数据,常用队列、锁和条件变量实现

生产者消费者简化示例

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

std::queue<int> data_queue;
std::mutex mtx;
std::condition_variable cv;

void producer() {
    for (int i = 0; i < 5; ++i) {
        {
            std::lock_guard<std::mutex> lock(mtx);
            data_queue.push(i);
        }
        cv.notify_one();
    }
}

void consumer() {
    for (int i = 0; i < 5; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return !data_queue.empty(); });
        int value = data_queue.front();
        data_queue.pop();
        std::cout << value << std::endl;
    }
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);

    t1.join();
    t2.join();

    return 0;
}

八、编译、链接、程序运行高频问题

问题面试重点
C++ 程序从源码到可执行文件经历哪些阶段?预处理、编译、汇编、链接
头文件和源文件作用?头文件放声明,源文件放实现
#include ""#include <> 区别?"" 优先搜索当前目录,<> 通常搜索系统路径
静态库和动态库区别?静态库编译进可执行文件;动态库运行时加载
编译期和运行期区别?编译期生成程序,运行期执行程序
链接错误常见原因?函数只有声明没有定义、库未正确链接、符号重复定义
宏和 inline 函数区别?宏是文本替换,没有类型检查;inline 函数有类型检查
模板为什么通常写在头文件?模板实例化需要看到完整定义

编译流程

源代码 .cpp
   ↓ 预处理
预处理文件 .i
   ↓ 编译
汇编文件 .s
   ↓ 汇编
目标文件 .o/.obj
   ↓ 链接
可执行文件

九、设计模式高频问题

设计模式是 C++ 面试中非常常见的内容,尤其是项目经历中涉及控制器、规划器、传感器模块、通信模块时,面试官很容易追问架构设计。


1. 单例模式 Singleton

核心作用

保证一个类在全局只有一个实例,并提供一个全局访问点。

常见场景
场景示例
日志系统Logger
配置管理器ConfigManager
线程池管理器ThreadPool
全局资源管理ResourceManager
C++11 线程安全单例
class Logger {
public:
    static Logger& instance() {
        static Logger logger;
        return logger;
    }

    void log(const std::string& msg) {
        // 输出日志
    }

private:
    Logger() = default;
    ~Logger() = default;

    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
};
面试常问
问题回答重点
单例模式怎么实现?构造函数私有化,提供静态访问接口
懒汉式和饿汉式区别?懒汉式用到时创建,饿汉式程序启动时创建
单例如何保证线程安全?C++11 局部静态变量初始化是线程安全的
单例模式缺点?全局状态较强,测试困难,可能破坏依赖关系

2. 工厂模式 Factory

核心作用

工厂模式用于封装对象创建过程。调用者不直接 new 对象,而是通过工厂根据类型或配置创建具体对象。

适用场景
  • 多种对象具有统一接口;
  • 对象创建逻辑比较复杂;
  • 需要根据配置动态选择具体实现;
  • 希望降低调用者和具体类之间的耦合。
示例:根据配置创建不同规划器
#include <iostream>
#include <memory>
#include <string>

class Planner {
public:
    virtual void plan() = 0;
    virtual ~Planner() = default;
};

class DWAPlanner : public Planner {
public:
    void plan() override {
        std::cout << "DWA Planner" << std::endl;
    }
};

class MPPIPlanner : public Planner {
public:
    void plan() override {
        std::cout << "MPPI Planner" << std::endl;
    }
};

class PlannerFactory {
public:
    static std::unique_ptr<Planner> createPlanner(const std::string& type) {
        if (type == "DWA") {
            return std::make_unique<DWAPlanner>();
        } else if (type == "MPPI") {
            return std::make_unique<MPPIPlanner>();
        }
        return nullptr;
    }
};

int main() {
    auto planner = PlannerFactory::createPlanner("MPPI");
    if (planner) {
        planner->plan();
    }
    return 0;
}
面试常问
问题回答重点
工厂模式解决什么问题?解耦对象创建和对象使用
简单工厂、工厂方法、抽象工厂区别?创建复杂度和扩展方式不同
工厂模式优点?隐藏创建细节,方便扩展,降低耦合
工厂模式缺点?类数量增加,结构更复杂
什么时候适合用工厂模式?有多个同类对象,需要根据配置或输入动态创建
在机器人项目中的典型使用
createController("PID");
createController("MPC");
createController("MPPI");
createLocalPlanner("DWA");
createLocalPlanner("TEB");
createLocalPlanner("RPP");

3. 策略模式 Strategy

核心作用

策略模式将不同算法封装成不同策略类,使算法可以在运行时自由切换。

常见场景
场景示例
路径跟踪控制器切换PID / MPC / MPPI
局部规划器切换DWA / TEB / RPP / MPPI
代价函数切换距离代价 / 障碍物代价 / 平滑代价
排序规则切换升序 / 降序 / 自定义优先级
轨迹评分方式切换安全优先 / 平滑优先 / 速度优先
策略模式示例
#include <iostream>
#include <memory>

class ControlStrategy {
public:
    virtual void compute() = 0;
    virtual ~ControlStrategy() = default;
};

class PIDStrategy : public ControlStrategy {
public:
    void compute() override {
        std::cout << "PID compute" << std::endl;
    }
};

class MPCStrategy : public ControlStrategy {
public:
    void compute() override {
        std::cout << "MPC compute" << std::endl;
    }
};

class RobotController {
public:
    void setStrategy(std::unique_ptr<ControlStrategy> strategy) {
        strategy_ = std::move(strategy);
    }

    void run() {
        if (strategy_) {
            strategy_->compute();
        }
    }

private:
    std::unique_ptr<ControlStrategy> strategy_;
};
面试常问
问题回答重点
策略模式和工厂模式区别?工厂负责创建对象,策略负责算法替换和行为切换
策略模式优点?避免大量 if-else,方便扩展算法
策略模式缺点?策略类数量增加,调用方需要了解不同策略

4. 观察者模式 Observer

核心作用

当一个对象状态发生变化时,自动通知多个依赖对象。

常见场景
场景示例
ROS 订阅发布Publisher / Subscriber
事件回调键盘事件、传感器事件
GUI 系统按钮点击通知
状态监控机器人状态变化后通知多个模块
面试常问
问题回答重点
观察者模式解决什么问题?一对多通知问题
观察者模式和发布订阅模式区别?发布订阅通常有中间消息总线,耦合更低
缺点是什么?通知链复杂,调试困难,可能出现循环通知

5. 适配器模式 Adapter

核心作用

适配器模式用于把一个类的接口转换成客户端期望的接口。

机器人项目中的典型场景
Gazebo 接口      → 统一 RobotInterface
MuJoCo 接口      → 统一 RobotInterface
真实底盘接口     → 统一 RobotInterface
不同雷达驱动接口 → 统一 SensorInterface
面试常问
问题回答重点
适配器模式解决什么问题?接口不兼容问题
类适配器和对象适配器区别?类适配器通过继承实现,对象适配器通过组合实现
适配器和代理模式区别?适配器改变接口,代理控制访问

6. 代理模式 Proxy

核心作用

代理模式通过代理对象控制对真实对象的访问。

常见场景
场景示例
网络代理远程调用
权限控制登录校验
延迟加载大对象按需创建
日志统计调用前后记录日志

7. 装饰器模式 Decorator

核心作用

装饰器模式可以在不修改原始类代码的情况下,动态增强对象功能。

控制器中的理解
BaseController
    ↓
SafetyControllerWrapper
    ↓
FilterControllerWrapper
    ↓
LogControllerWrapper

也就是说,可以给原始控制器逐层增加功能:

  • 输出限幅;
  • 输出滤波;
  • 安全检查;
  • 日志记录;
  • 异常保护。

8. 模板方法模式 Template Method

核心作用

父类定义整体流程骨架,子类实现具体步骤。

局部规划器流程示例
1. 读取地图
2. 读取机器人状态
3. 读取目标点
4. 生成候选轨迹
5. 评价候选轨迹
6. 输出控制量

不同局部规划器可以复用整体流程,只重写核心算法步骤。


十、常见设计模式对比

模式核心作用典型关键词机器人/算法项目示例
单例模式保证全局唯一实例全局配置、日志参数管理器、日志系统
工厂模式封装对象创建create、new 解耦创建 PID/MPC/MPPI 控制器
策略模式封装算法切换多算法、多策略控制算法切换、局部规划器切换
观察者模式状态变化通知回调、订阅发布ROS Topic、传感器事件回调
适配器模式接口转换兼容旧接口Gazebo/MuJoCo/实物接口统一
代理模式控制访问权限、远程、缓存远程控制接口、通信代理
装饰器模式动态增强功能Wrapper、包装器控制器加限幅、滤波、安全检查
模板方法模式固定流程,子类实现细节流程复用规划器统一流程框架

十一、C++ 项目常见追问

问题面试重点
你项目中用到了哪些 C++ 特性?智能指针、STL、多态、模板、多线程、RAII
你怎么管理对象生命周期?RAII、智能指针、避免裸 new/delete
你的模块之间怎么解耦?接口类、多态、工厂模式、策略模式
你的代码怎么避免内存泄漏?智能指针、容器、析构函数释放资源
程序崩溃怎么排查?gdb、core dump、日志、断点、AddressSanitizer
程序如何提高实时性?减少动态分配、预分配内存、降低锁竞争、减少拷贝
你用过多线程吗?传感器读取、控制循环、日志线程、通信线程
控制周期如何保证?定时器、实时线程、减少阻塞、监控循环耗时

十二、机器人/控制算法岗 C++ 追问

问题面试关注点
控制器为什么要用多态?统一接口,方便切换 PID、MPC、MPPI 等控制器
局部规划器如何设计类结构?基类接口 + 子类实现 + 工厂创建
实物和仿真接口如何统一?使用适配器模式,将不同接口封装为统一抽象接口
控制循环怎么保证实时性?固定频率、预分配、避免阻塞、减少动态内存申请
ROS 回调和主控制循环如何通信?线程安全队列、mutex、atomic、双缓冲
怎么避免控制器输出突变?滤波、限幅、平滑约束、控制增量约束
怎么组织参数配置?YAML/JSON + Config 类 + 参数校验
C++ 中如何封装一个控制器?初始化、状态更新、轨迹输入、控制计算、输出控制量、reset

控制器接口设计示例

class ControllerBase {
public:
    virtual bool initialize() = 0;
    virtual void reset() = 0;
    virtual void updateState(const RobotState& state) = 0;
    virtual void setReference(const Trajectory& ref) = 0;
    virtual ControlCommand computeCommand() = 0;
    virtual ~ControllerBase() = default;
};

这种设计的好处是:

  • PID、MPC、MPPI 可以继承同一个接口;
  • 上层模块不用关心具体控制器类型;
  • 后续新增控制算法时,只需要增加子类,不需要大幅修改主流程。

十三、面试常见手写代码题

类型高频内容
手写单例模式C++11 静态局部变量版本
手写智能指针简化版 shared_ptr
手写线程池任务队列 + mutex + condition_variable
手写生产者消费者队列 + 锁 + 条件变量
手写 LRU 缓存list + unordered_map
手写字符串函数strcpystrlenmemcpy
手写排序算法快排、归并、堆排
手写二叉树遍历前序、中序、后序、层序
手写工厂模式基类指针 + 子类对象创建
手写观察者模式注册、注销、通知

十四、推荐复习顺序

建议按照下面顺序准备 C++ 面试:

1. C++ 基础语法
2. 面向对象:继承、多态、虚函数
3. 内存管理:new/delete、RAII、智能指针
4. STL:vector、map、unordered_map、迭代器失效
5. C++11:右值引用、move、lambda、thread
6. 多线程:mutex、condition_variable、atomic
7. 设计模式:单例、工厂、策略、观察者、适配器
8. 项目八股:模块解耦、实时性、内存泄漏、调试方法

如果时间有限,建议优先掌握下面几块:

优先级内容
第一优先级面向对象、虚函数、多态、析构函数、智能指针
第二优先级STL 容器底层、迭代器失效、C++11 特性
第三优先级多线程、锁、条件变量、原子操作
第四优先级设计模式和项目架构设计

十五、设计模式面试总结模板

如果面试官问“你项目中用过哪些设计模式”,可以这样回答:

在项目中,我主要使用了工厂模式、策略模式、适配器模式和观察者模式。工厂模式用于根据配置动态创建不同的控制器或局部规划器,例如 PID、MPC、MPPI、DWA 等;策略模式用于封装不同算法,使多个控制策略可以在统一接口下切换;适配器模式用于统一仿真环境和实物底盘接口,例如将 Gazebo、MuJoCo 和真实机器人底盘封装成统一的 RobotInterface;观察者模式则常见于传感器数据回调、事件通知以及 ROS 发布订阅机制。通过这些设计方式,可以降低模块之间的耦合度,提高代码扩展性、可维护性和复用性。


总结

C++ 面试并不是只考语法细节,更重要的是考察对工程代码的理解能力。对于机器人、自动驾驶、控制算法、嵌入式等方向,除了掌握 C++ 基础,还需要重点理解以下内容:

  • 如何使用多态设计统一接口;
  • 如何用智能指针管理对象生命周期;
  • 如何用 STL 提高开发效率;
  • 如何避免内存泄漏和野指针;
  • 如何设计线程安全的数据传递方式;
  • 如何用工厂模式、策略模式、适配器模式降低模块耦合;
  • 如何结合项目经历讲清楚 C++ 在工程中的实际作用。

如果能够把“语法知识”和“项目架构”结合起来回答,面试效果会更好。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐