目录
- [一、C++ 基础 & 面向对象](#一、C++ 基础 & 面向对象)
-
- [1. C 和 C++ 的核心区别是什么?](#1. C 和 C++ 的核心区别是什么?)
- [2. 面向对象三大特性是什么,分别解释?](#2. 面向对象三大特性是什么,分别解释?)
- [3. 重载、重写、隐藏分别是什么,有什么区别?](#3. 重载、重写、隐藏分别是什么,有什么区别?)
- [4. struct 和 class 的区别是什么?](#4. struct 和 class 的区别是什么?)
- [5. const 有哪些用法和作用?](#5. const 有哪些用法和作用?)
- [6. static 有哪些用法和作用?](#6. static 有哪些用法和作用?)
- [7. inline 内联函数是什么,有什么特点?](#7. inline 内联函数是什么,有什么特点?)
- [8. explicit 关键字的作用是什么?](#8. explicit 关键字的作用是什么?)
- [9. friend 友元函数/友元类的作用是什么?](#9. friend 友元函数/友元类的作用是什么?)
- 二、构造、析构、拷贝、RAII
-
- [1. 构造函数的作用和特点?](#1. 构造函数的作用和特点?)
- [2. 析构函数的作用和特点?](#2. 析构函数的作用和特点?)
- [3. 拷贝构造函数什么时候会被调用?](#3. 拷贝构造函数什么时候会被调用?)
- [4. 浅拷贝与深拷贝是什么,区别是什么?](#4. 浅拷贝与深拷贝是什么,区别是什么?)
- [5. 拷贝赋值与移动赋值的区别?](#5. 拷贝赋值与移动赋值的区别?)
- [6. RAII 是什么,核心思想是什么?](#6. RAII 是什么,核心思想是什么?)
- [7. 右值引用和移动语义的作用?](#7. 右值引用和移动语义的作用?)
- 三、指针、引用、智能指针
-
- [1. 指针和引用的区别是什么?](#1. 指针和引用的区别是什么?)
- [2. 空指针、野指针、悬空指针分别是什么?](#2. 空指针、野指针、悬空指针分别是什么?)
- [3. 指针常量和常量指针的区别?](#3. 指针常量和常量指针的区别?)
- [4. C++ 有哪些智能指针?](#4. C++ 有哪些智能指针?)
- [5. unique_ptr 的特点和使用场景?](#5. unique_ptr 的特点和使用场景?)
- [6. shared_ptr 原理是什么,引用计数作用?](#6. shared_ptr 原理是什么,引用计数作用?)
- [7. weak_ptr 作用是什么,解决什么问题?](#7. weak_ptr 作用是什么,解决什么问题?)
- [8. 智能指针循环引用问题是什么,如何解决?](#8. 智能指针循环引用问题是什么,如何解决?)
- 四、多态、虚函数、继承(超级高频)
-
- [1. 多态是什么,分为哪两种,实现原理?](#1. 多态是什么,分为哪两种,实现原理?)
- [2. 虚函数、虚表、虚指针分别是什么?](#2. 虚函数、虚表、虚指针分别是什么?)
- [3. 纯虚函数和抽象类是什么?](#3. 纯虚函数和抽象类是什么?)
- [4. 为什么析构函数需要定义为 virtual?](#4. 为什么析构函数需要定义为 virtual?)
- [5. 构造函数可以是虚函数吗,为什么?](#5. 构造函数可以是虚函数吗,为什么?)
- [6. public/protected/private 三种继承权限区别?](#6. public/protected/private 三种继承权限区别?)
- [7. 菱形继承问题是什么,虚继承如何解决?](#7. 菱形继承问题是什么,虚继承如何解决?)
- 五、内存管理(嵌入式/机器人必考)
-
- [1. C++ 内存分区有哪几个?](#1. C++ 内存分区有哪几个?)
- [2. 栈和堆的区别是什么?](#2. 栈和堆的区别是什么?)
- [3. malloc/free 和 new/delete 区别?](#3. malloc/free 和 new/delete 区别?)
- [4. new/delete 与 new[]/delete[] 区别?](#4. new/delete 与 new[]/delete[] 区别?)
- [5. 内存泄漏是什么,如何避免?](#5. 内存泄漏是什么,如何避免?)
- [6. 内存对齐是什么,为什么需要?](#6. 内存对齐是什么,为什么需要?)
- [7. 野指针、悬空指针如何避免?](#7. 野指针、悬空指针如何避免?)
- 六、C++11/14/17(必问现代C++)
-
- [1. 右值引用、移动语义是什么?](#1. 右值引用、移动语义是什么?)
- [2. 完美转发 std::forward 作用?](#2. 完美转发 std::forward 作用?)
- [3. lambda 表达式是什么,有哪些捕获方式?](#3. lambda 表达式是什么,有哪些捕获方式?)
- [4. constexpr 和 const 区别?](#4. constexpr 和 const 区别?)
- [5. nullptr 和 NULL 区别?](#5. nullptr 和 NULL 区别?)
- [6. auto 关键字作用与注意事项?](#6. auto 关键字作用与注意事项?)
- [7. 范围 for 循环的原理是什么?](#7. 范围 for 循环的原理是什么?)
- 七、多线程与并发(必问)
-
- [1. 进程与线程的区别?](#1. 进程与线程的区别?)
- [2. 互斥锁 mutex 作用是什么?](#2. 互斥锁 mutex 作用是什么?)
- [3. 条件变量作用与使用场景?](#3. 条件变量作用与使用场景?)
- [4. 死锁四个必要条件,如何避免?](#4. 死锁四个必要条件,如何避免?)
- [5. 原子操作 atomic 作用?](#5. 原子操作 atomic 作用?)
- [6. 线程安全问题是什么?](#6. 线程安全问题是什么?)
- [7. 生产者消费者模型实现思路?](#7. 生产者消费者模型实现思路?)
- 八、底层/嵌入式/机器人特化(必问)
-
- [1. 大小端字节序是什么,如何判断?](#1. 大小端字节序是什么,如何判断?)
- [2. volatile 关键字作用是什么?](#2. volatile 关键字作用是什么?)
- [3. 函数调用栈帧大致过程?](#3. 函数调用栈帧大致过程?)
- [4. 中断上下文为什么不能 sleep 和 mutex?](#4. 中断上下文为什么不能 sleep 和 mutex?)
- [5. 如何写出低延迟代码?](#5. 如何写出低延迟代码?)
- [6. 如何减少内存拷贝?](#6. 如何减少内存拷贝?)
一、C++ 基础 & 面向对象
1. C 和 C++ 的核心区别是什么?
C 是面向过程,关注步骤和流程;C++ 是面向对象 + 泛型编程,支持类、继承、多态、STL、智能指针等。
C 适合底层驱动、嵌入式;C++ 适合机器人、自动驾驶、后台、大型项目。
2. 面向对象三大特性是什么,分别解释?
- 封装:把数据和方法打包,隐藏内部细节,只暴露接口,安全又好维护。
- 继承:复用已有类的代码,扩展新功能,减少重复。
- 多态:同一个接口,不同对象表现不同行为,灵活可扩展。
3. 重载、重写、隐藏分别是什么,有什么区别?
- 重载:同一个类里,函数名相同,参数不同(个数/类型/顺序),和返回值无关。
- 重写:子类重写父类的虚函数,函数名、参数完全一样,实现多态。
- 隐藏:子类和父类函数同名,父类函数被隐藏,和是否虚函数无关。
4. struct 和 class 的区别是什么?
唯一区别:
- struct 默认权限是 public
- class 默认权限是 private
其他功能几乎一样,struct 多用于简单数据结构,class 用于对象。
5. const 有哪些用法和作用?
- 修饰变量:变量只读,不能修改。
- 修饰指针:可以限定指针本身不能改,或指向内容不能改。
- 修饰成员函数:函数内不能修改成员变量。
作用:防止误改,增加安全性,编译器可优化。
6. static 有哪些用法和作用?
- 全局 static:作用域仅限当前文件,外部不可见。
- 局部 static:函数内变量,函数退出不销毁,值保留。
- 类 static 成员:所有对象共享一份,不属于某个对象。
- 类 static 函数:只能访问静态成员,没有 this 指针。
7. inline 内联函数是什么,有什么特点?
编译时把函数体直接展开到调用处,省去函数调用开销。
适合短小、高频函数。
特点:快,但代码会膨胀,不能有循环、递归、虚函数。
8. explicit 关键字的作用是什么?
禁止构造函数隐式类型转换 。
不加 explicit,单个参数的构造会被自动隐式转换,容易出bug;
加了只能显式调用,更安全。
9. friend 友元函数/友元类的作用是什么?
友元可以访问类的 private/protected 成员。
友元不是成员,是外部函数/类被授权访问。
优点:灵活;缺点:破坏封装,少用。
二、构造、析构、拷贝、RAII
1. 构造函数的作用和特点?
创建对象时自动调用,初始化成员变量。
- 函数名和类名相同
- 无返回值
- 可以重载
- 没写会自动生成默认构造
2. 析构函数的作用和特点?
对象销毁时自动调用,释放资源(内存、文件、锁等)。
- 函数名是 ~类名
- 无参无返回值
- 不能重载
- 有动态资源必须自己写
3. 拷贝构造函数什么时候会被调用?
用一个已存在的对象初始化同类型新对象时调用:
- A a2 = a1;
- A a2(a1);
- 函数传值(对象作为参数)
- 函数返回对象
4. 浅拷贝与深拷贝是什么,区别是什么?
- 浅拷贝:只拷贝指针地址,两个对象共用同一块内存,会重复释放崩溃。
- 深拷贝:重新开辟内存,拷贝数据,各自独立,安全。
只要类有指针/动态资源,必须深拷贝。
5. 拷贝赋值与移动赋值的区别?
- 拷贝赋值:赋值给已存在对象,拷贝数据,深拷贝。
- 移动赋值:把临时对象(右值)的资源"偷过来",不拷贝,直接接管,更快。
6. RAII 是什么,核心思想是什么?
Resource Acquisition Is Initialization,资源获取即初始化。
利用对象生命周期管理资源:
- 构造申请资源
- 析构自动释放
不用手动管理,避免泄漏。智能指针、锁都是 RAII。
7. 右值引用和移动语义的作用?
右值引用(&&)引用临时对象。
移动语义:把临时对象的资源直接"转移",不拷贝,大幅提升性能。
避免无谓拷贝,尤其大对象、容器。
三、指针、引用、智能指针
1. 指针和引用的区别是什么?
- 指针可空、可改指向、多级指针;
- 引用必须初始化、不能为空、绑定后不能改。
引用更安全,指针更灵活。
2. 空指针、野指针、悬空指针分别是什么?
- 空指针:nullptr,指向空,安全。
- 野指针:未初始化,指向随机地址,极危险。
- 悬空指针:指向的内存已释放,指针没置空。
3. 指针常量和常量指针的区别?
- 常量指针(const int* p):指向的内容不能改,指针能改。
- 指针常量(int* const p):指针不能改,内容能改。
从右往左读:const 右边的不能改。
4. C++ 有哪些智能指针?
- unique_ptr:独占,不能拷贝,性能最好。
- shared_ptr:共享,引用计数。
- weak_ptr:解决 shared_ptr 循环引用。
5. unique_ptr 的特点和使用场景?
独占对象,同一时间只有一个 owner。
不能拷贝,只能移动。
性能接近裸指针,优先用它。
6. shared_ptr 原理是什么,引用计数作用?
内部有引用计数 ,拷贝时+1,析构/赋值时-1。
计数为0才释放内存。
多个指针共享同一个对象。
7. weak_ptr 作用是什么,解决什么问题?
弱引用,不增加引用计数,只观察对象。
解决 shared_ptr 循环引用导致内存无法释放的问题。
8. 智能指针循环引用问题是什么,如何解决?
两个对象互相用 shared_ptr 持有对方,计数永远不为0,内存泄漏。
解决:把其中一个改成 weak_ptr。
四、多态、虚函数、继承(超级高频)
1. 多态是什么,分为哪两种,实现原理?
多态就是**"一个接口,多种实现"**,同样的调用,不同对象做不同事。
- 分两种:
- 编译期多态(静态):重载、模板
- 运行期多态(动态):虚函数 + 继承 + 重写(面试重点)
- 实现原理:
基类指针/引用指向派生类对象,运行时通过虚表找到真正要调用的函数。
2. 虚函数、虚表、虚指针分别是什么?
- 虚函数:前面带
virtual的成员函数,用来实现多态。 - 虚表(vtable):有虚函数的类,编译器会生成一张函数地址表,存所有虚函数地址。
- 虚指针(vptr):对象里藏着一个指针,指向自己类的虚表。
一句话:调用虚函数时,通过对象里的虚指针 -> 找到虚表 -> 找到正确函数。
3. 纯虚函数和抽象类是什么?
- 纯虚函数:没有函数体,格式
virtual void func() = 0; - 抽象类:包含至少一个纯虚函数 的类。
特点:不能创建对象,只能当接口用,强制子类必须实现它。
4. 为什么析构函数需要定义为 virtual?
如果基类指针指向派生类对象,析构函数不是虚函数,
销毁时只会调用基类析构,不会调用派生类析构 ,造成资源泄漏。
所以:基类析构一般都写成 virtual。
5. 构造函数可以是虚函数吗,为什么?
不能!
- 构造函数是用来创建对象的
- 虚函数依赖虚指针,虚指针要等对象构造完才存在
- 鸡和蛋的问题:还没构造好,就没有虚指针,不可能用虚函数
所以构造函数不能是虚函数。
6. public/protected/private 三种继承权限区别?
记一句话:继承方式决定父类成员在子类里变成什么权限
- public 继承:父类权限不变
- protected 继承:父类 public → protected,其他不变
- private 继承:父类所有成员 → private
实际开发99%用 public 继承。
7. 菱形继承问题是什么,虚继承如何解决?
菱形继承:B、C 继承 A,D 继承 B+C → D 里有两份 A,冲突、冗余。
虚继承:让 B、C 虚继承 A,D 里只保留一份 A,解决数据冗余和二义性。
五、内存管理(嵌入式/机器人必考)
1. C++ 内存分区有哪几个?
一共 5 个,非常好记:
- 栈:局部变量,自动释放
- 堆:new/malloc,手动释放
- 全局/静态区:全局变量、static
- 常量区:字符串、const 常量
- 代码区:程序代码
2. 栈和堆的区别是什么?
- 栈:自动分配释放,速度快,空间小,连续内存
- 堆:手动分配释放,速度慢,空间大,不连续
栈像"储物柜",系统管;堆像"大仓库",自己管。
3. malloc/free 和 new/delete 区别?
- malloc/free:C 语言函数,只分配/释放内存,不调用构造、析构
- new/delete:C++ 运算符,分配内存 + 调用构造 ,释放 + 析构
必须配对使用,不能混用。
4. new/delete 与 new[]/delete[] 区别?
- new/delete:用于单个对象
- new[]/delete[]:用于数组对象
用 new[] 必须用 delete[],否则只析构一个对象,内存泄漏。
5. 内存泄漏是什么,如何避免?
内存泄漏:new 出来的内存,忘记 delete ,一直占着不释放。
后果:程序越跑越慢,最后崩。
避免方法:
- 用智能指针(unique_ptr/shared_ptr)
- 用 RAII
- 不裸指针乱飞
6. 内存对齐是什么,为什么需要?
内存对齐:数据在内存里放的地址,必须是固定大小的倍数(如4、8字节)。
为什么?
- CPU 读内存是按块读的
- 对齐后一次就能读完,不用拼数据,速度更快
- 嵌入式/硬件必须对齐,否则直接报错
7. 野指针、悬空指针如何避免?
- 野指针:指针一定要初始化,没指向就赋值 nullptr
- 悬空指针:内存释放后,立刻把指针置空 nullptr
口诀:初始化、释放置空、不用裸指针。
六、C++11/14/17(必问现代C++)
1. 右值引用、移动语义是什么?
- 右值引用
&&:专门引用临时对象(用完就丢的那种) - 移动语义:不拷贝数据,直接把临时对象的资源"抢过来"
好处:快!不浪费内存拷贝。
2. 完美转发 std::forward 作用?
在函数模板里,把参数原封不动、保持属性 转发给下一个函数。
左值还是左值,右值还是右值,不丢失属性,叫完美转发。
3. lambda 表达式是什么,有哪些捕获方式?
lambda 就是就地写的匿名函数 。
捕获方式:
-
\] 不捕获
-
\&\] 引用捕获
简单、方便,写回调、线程、算法时超级常用。
4. constexpr 和 const 区别?
- const:运行时常量,只读
- constexpr:编译期就能算出值 ,性能更好
能用 constexpr 就不用 const。
5. nullptr 和 NULL 区别?
- NULL 本质是 0,容易被当成整数
- nullptr 是真正的空指针,类型安全
C++11 以后一律用 nullptr。
6. auto 关键字作用与注意事项?
auto:让编译器自动推导类型 ,代码简洁。
注意:
- 不能用于函数参数
- 不能用于数组
- 不要滥用,否则可读性差
7. 范围 for 循环的原理是什么?
底层就是迭代器:
自动调用 begin()、end(),遍历每个元素。
写法简单:for(auto x : 容器)
注意:想修改元素用 for(auto& x : ...)
七、多线程与并发(必问)
1. 进程与线程的区别?
- 进程:资源分配的基本单位,有独立内存空间,进程间不共享,切换开销大。
- 线程:调度执行的基本单位,共享进程的内存(堆、全局变量),只独有栈,切换快。
一句话:进程是房子,线程是房子里的人;人共享房子,但各自有小房间。
2. 互斥锁 mutex 作用是什么?
多个线程同时修改共享数据时,会出现混乱。
mutex 保证同一时间只有一个线程进入临界区 ,避免竞争。
作用:保护共享资源,保证原子性。
3. 条件变量作用与使用场景?
条件变量用来让线程等待某个条件成立 ,不忙轮询。
场景:生产者消费者------队列空时消费者等,队列满时生产者等。
必须配合 mutex 使用:先加锁,再 wait,wait 会自动释放锁。
4. 死锁四个必要条件,如何避免?
四个条件缺一不可:
- 互斥(资源独占)
- 持有并等待(拿着锁等别的锁)
- 不可抢占(锁不能被抢走)
- 循环等待(A等B,B等A)
避免:
- 按固定顺序加锁
- 避免持有锁时等待其他锁
- 加锁超时
- 破坏循环等待
5. 原子操作 atomic 作用?
简单操作(+1、赋值)本来看似一步,底层可能分多步,多线程会错乱。
atomic 让这些操作CPU 指令级原子性 ,不可分割,不用锁也线程安全。
优点:无锁、快、轻量。
6. 线程安全问题是什么?
多个线程同时访问共享数据,读写交错,导致结果错误、不可预测。
原因:竞争条件 + 非原子操作 。
解决:锁、原子变量、避免共享。
7. 生产者消费者模型实现思路?
- 共享队列(缓冲区)
- 互斥锁:保护队列
- 两个条件变量:队列不为空、队列不满
流程:
- 生产者:加锁 → 满则等 → 放数据 → 通知消费者 → 解锁
- 消费者:加锁 → 空则等 → 取数据 → 通知生产者 → 解锁
八、底层/嵌入式/机器人特化(必问)
1. 大小端字节序是什么,如何判断?
- 大端:高字节存低地址(人类书写顺序)
- 小端:低字节存低地址(x86/ARM 默认)
判断方法:
把 int 数 0x11223344 转成 char*,看第一个字节是 0x11 还是 0x44。
2. volatile 关键字作用是什么?
告诉编译器:这个变量可能随时被外部改变(硬件、中断、其他线程),不要优化它,每次都从内存读,别用寄存器缓存。
场景:硬件寄存器、中断变量、多线程共享。
3. 函数调用栈帧大致过程?
- 调用方把参数压栈
- 压入返回地址
- 进入被调用函数,保存旧栈帧ebp
- 分配局部变量空间
- 执行函数
- 恢复栈帧,返回
形象理解:栈帧就是函数的"临时小本子",调用时翻开,结束撕掉。
4. 中断上下文为什么不能 sleep 和 mutex?
- 中断不属于任何进程/线程,没有调度信息,一睡就醒不来,系统崩溃。
- mutex 可能阻塞等待,中断里不能阻塞、不能调度 。
中断只能用:自旋锁、关中断、信号量(不阻塞版)。
5. 如何写出低延迟代码?
- 少用锁,用无锁、原子
- 避免内存拷贝
- 避免动态分配(malloc/new)
- 线程绑核,避免切换
- 减少中断、软中断
- 临界区极短
- 用实时调度策略
6. 如何减少内存拷贝?
- 用引用 &,不用值传递
- 用移动语义 move
- 用指针/索引代替拷贝
- mmap、零拷贝
- 大容器直接复用,不创建新的
- 用 shared_ptr 代替深拷贝