C++ 面试通关:语法基础、内存管理与类设计
这几年带新人面试,发现 C++ 的高频考点很集中——语法细节、内存管理、类机制,再附带一些周边工具。下面把这些点整理出来,方便快速回顾。
语法基础
变量生命周期与存储位置
变量根据作用域和存储期,一般分成全局、局部、静态几类。它们的声明方式、初始化时机和内存位置都不一样,面试时常会问到。
| 变量类型 | 声明方式 | 内存位置 | 作用域 | 生命周期 |
|---|---|---|---|---|
| 全局变量 | 函数外部声明 | 静态存储区 | 整个文件 | 程序运行期间 |
| 局部变量 | 函数或代码块内 | 栈区 | 函数内部 | 函数调用期间 |
| 静态全局 | 加 static 关键字 | 静态存储区 | 整个文件 | 程序运行期间 |
| 静态局部 | 加 static 关键字 | 静态存储区 | 函数内部 | 程序运行期间(值持久化) |
// 全局变量
int globalVar = 10;
void func() {
// 局部变量
int localVar = 20;
// 静态局部变量
static int staticLocal = 30;
}
命名空间
命名空间主要就是为了避免符号冲突。比如标准库的东西都在 std 下面,咱们平时用 std::cout、std::string 都是加前缀。using namespace std; 写起来省事,但会把整个 std 暴露出来,小 demo 无所谓,正经项目还是规规矩矩加前缀好。
include "" 和 <> 的区别
#include <文件名>:用来包含标准库头文件,编译器按标准路径去找。#include "文件名":用来包含自己写的或者项目内部头文件,优先在当前目录查找,找不到再去标准路径。
指针与引用
指针就是存变量地址的变量,用 * 解引用拿到数据,& 取地址。
指针数组与数组指针 这两个概念容易混:
- 指针数组:元素是指针的数组,
int *ptrArray[5];就是 5 个 int 指针的数组。 - 数组指针:指向数组的指针,
int (*arrPtr)[5];表示指向一个 5 个 int 的数组的指针,传二维数组时常用。
引用是给变量起的别名,定义时必须绑定,中途不能换对象。跟指针比,引用不用解引用,语法上更安全舒服。函数参数里,如果想修改实参,传引用比指针更常见。
指针和引用的区别:引用不能空,指针可以;引用不用 *,指针得用;引用必须初始化,指针可以后面再赋值。实际开发里,能用引用就尽量用引用,空指针检查的烦恼少很多。
函数指针与指针函数
- 函数指针:指向函数的指针,可以通过它间接调用函数。
typedef void (*FuncPtr)(int);
void myFunction(int x) {}
FuncPtr ptr = &myFunction;
ptr(10); // 调用
- 指针函数:返回值为函数指针的函数,不常用,知道就行。
常量指针与指针常量
读法上有个小技巧:const 修饰右边最近的东西,被修饰的就是常量。
int* const:指针自身是常量,指向的地址不能变,但地址上的值可以改。const int*:指向常量的指针,指向的值不能通过这个指针修改,但指针可以指向别的地址。 这个经常在笔试题里出现,弄反了就会出错。
智能指针的本意
智能指针其实就是 RAII 在动态内存上的应用——构造时拿资源,析构时自动释放,杜绝忘 delete 的问题。
unique_ptr:独占所有权,不能拷贝,只能移动。shared_ptr:共享所有权,内部维护引用计数,最后一个shared_ptr销毁时释放内存。weak_ptr:配合shared_ptr使用,不增加引用计数,用来观察资源或打破循环引用(比如两个类互相持有对方的shared_ptr会导致内存泄漏)。
weak_ptr 本身不负责内存分配和引用计数,它只是观察。想访问资源时调用 lock(),如果资源还在就返回一个有效的 shared_ptr,否则返回空的智能指针。
类型强制转换
C++ 的四种显式转换,源码里看到能认识就行:
static_cast:基本类型转换、向上转型等。const_cast:去掉或加上const属性。dynamic_cast:用于类层次间的向下转型,依赖 RTTI 进行运行时检查,失败返回nullptr(指针)或抛出bad_cast(引用)。reinterpret_cast:底层重新解释二进制,慎用。
函数参数传递
- 值传递:拷贝一份实参,性能开销大,但不会影响原值。
- 引用传递:别名,直接操作原值,性能好,但要注意不想修改就加
const。 - 指针传递:传地址,也能修改原值,但需要检查空指针。
一般做法:只读不用改的用
const T&;要改的用T&;可选参数可以用T*,并默认nullptr。
进程与线程间通信
- 进程间通信(IPC):管道、消息队列、共享内存、信号量、套接字等,面试官可能会问'说一下你知道的 IPC 方式'。
- 线程间通信:直接共享进程内存,但得用互斥锁(
std::mutex)、条件变量等保护起来,不然数据竞争。
常用关键字
static:修饰变量可改变存储期,修饰成员表示类级别。const:修饰变量为只读,修饰成员函数表示不修改成员变量。sizeof:编译期运算,不是函数。virtual:声明虚函数,运行时多态的基础。final/override:C++11 引入,final禁止进一步重写,override显式标明重写,防止拼写错误。explicit:禁止隐式类型转换,比如单参构造函数。inline:建议编译器内联,但现代编译器自己会判断。volatile:告诉编译器不要优化该变量,每次从内存读,多用于硬件寄存器或信号处理。extern "C":声明按 C 语言方式链接,避免 C++ 名字修饰,用于混合编程。definevstypedef:#define是预处理宏替换,typedef是真正的类型别名,后者有作用域且更安全。
class 与 struct 区别
几乎一样,唯一区别是默认访问权限和默认继承方式:class 默认 private,struct 默认 public。保留 struct 主要是兼容 C,以及用来定义纯数据聚合(POD)。
标准库
STL 包含容器、算法、迭代器等,面试中最常问容器对比。
- 数组和
vector:数组长度固定,vector可以动态扩容。vector用起来方便,但频繁扩容会复制元素,可以提前reserve减少开销。 list和vector:list是双向链表,在中间插入删除是 O(1),但不支持随机访问;vector是连续内存,随机访问快,末端操作快,中间插入删除要移动元素。平时用vector的场景远多于list。set和multiset:set 不允许重复元素,multiset 允许重复。map和unordered_map:map基于红黑树,键值有序,查找 O(log n);unordered_map基于哈希表,无序,平均查找 O(1)。如果不需要排序,用unordered_map一般更快。
内存管理
RAII
资源获取即初始化,是 C++ 管理资源的核心手法。在构造函数里获取资源(如 new、打开文件),析构函数里释放,就算异常发生,栈展开时也会调用析构函数,保证资源不泄漏。
new/malloc 与 delete/free
new/delete是 C++ 操作符,除了内存分配,还会调用构造、析构函数。malloc/free是 C 的库函数,只负责内存,不管对象。 一定要配对使用:new了就用delete,malloc了就用free,否则行为未定义。
内存分布
- 栈:局部变量、函数调用信息,由编译器自动管理。
- 堆:
new/malloc出来的东西,得要自己释放。 - 静态存储区:全局变量、静态变量。
- 常量区:字符串常量等。
堆与栈的区别
栈内存分配快,空间有限(几 MB),函数结束时自动回收;堆内存分配慢一些,空间大,需要手动释放。
内存对齐
按照特定边界排列数据地址,可以提高 CPU 访问效率,有时候是硬件要求。大多数编译器会帮我们处理,但写跨平台代码或网络协议时要注意。
内存泄漏与深浅拷贝
内存泄漏就是分配了没释放。预防手段:优先用智能指针,严格配对 new/delete,用到 Valgrind 或 AddressSanitizer 检查。
- 浅拷贝:只复制指针值,导致多个对象指向同一资源,析构时可能重复释放。
- 深拷贝:复制完整资源,各自独立,避免上述问题。类中有指针成员时,自己写拷贝构造和赋值运算符通常得实现深拷贝。
类相关
封装、继承、多态
老生常谈的三板斧。封装通过 private/protected 隐藏细节;继承复用代码,注意 public 继承体现 is-a 关系;多态用虚函数实现,同一接口不同行为。
多态怎么工作
虚函数通过虚函数表(vtable)和虚指针(vptr)实现。每个有虚函数的类都有个 vtable,存着虚函数地址;对象里藏一个 vptr 指向这个表。调用虚函数时,通过对象 vptr 找到 vtable,再调用对应函数,这就是运行时绑定。
虚函数与纯虚函数
- 虚函数:基类用
virtual声明,派生类可重写。 - 纯虚函数:后面加
= 0,声明为纯虚函数,使类成为抽象类,不能实例化。派生类必须实现它才能成为具体类。
构造与析构
- 构造函数初始化对象,无返回值,可以重载。
- 析构函数清理资源,无返回值,不能重载。
- 基类析构函数应该声明为虚函数,否则
delete基类指针指向派生类对象时,只调基类析构,资源可能泄漏。 - 构造函数不能是虚函数,因为对象还没构造完,vptr 还没设好。
this 指针
成员函数里那个指向当前对象的指针,用来区分成员变量和形参,或者实现链式调用返回 *this。
其他必备技能
Git 与 SVN
- Git:分布式版本控制,离线提交、分支灵活,现在主流。
- SVN:集中式,简单直接,一些老项目或文档管理还在用。
Linux 常用命令
面试时会问几个基础命令,看看你有没有用过。文件操作:ls, cd, cp, mv, rm, cat, vim;权限:chmod, chown;进程:ps, top, kill;网络:ping, netstat, ifconfig 或 ip a;磁盘:df -h, du -sh。这些多敲几次就熟了,没必要死记。


