三七互娱 游戏开发-C++ 一面
1. 自我介绍
2. 这两个项目是你自己写的吗?
3. 指针和引用的区别是啥?
核心区别
(1)引用底层实现
语法层面引用是“变量别名”,底层编译器通过指针实现:
- 编译时会为引用分配与指针相同的内存(如64位系统8字节),存储绑定变量的地址;
- 运行时访问引用等同于通过指针解引用访问原变量(如int &a = b; a = 10;等价于int *p = &b; *p = 10;);
- 区别仅在于编译器做了语法约束(如强制初始化、禁止修改指向),无额外性能开销。
(2)引用作为函数返回值的风险
风险点:
- 返回局部变量的引用:局部变量存在栈上,函数结束后栈帧销毁,引用指向非法内存(野引用);示例:int& func() { int a = 10; return a; } → 调用func()后引用指向已释放的栈空间;
- 返回临时对象的非const引用:临时对象生命周期仅在表达式内,引用悬空。
正确场景:
- 返回类成员变量的引用(如std::string& operator[](int idx));
- 返回全局/静态变量的引用。
4. 栈内存和堆内存的分配和释放
核心区别
(1)堆申请空间是真实内存还是虚拟内存
堆申请的是虚拟内存:
- 操作系统为进程分配虚拟地址空间(如64位系统48位虚拟地址),new/malloc仅在虚拟地址空间中标记一段地址为“已分配”;
- 只有当程序首次访问该虚拟地址时,操作系统才会通过MMU(内存管理单元)将虚拟地址映射到物理内存(真实内存),触发“缺页中断”;
- 虚拟内存的优势:进程间地址隔离、突破物理内存大小限制。
(2)new和malloc的区别
(3)堆内存碎片化的原因及解决方案
碎片化原因:
- 频繁申请/释放不同大小的堆内存块,导致内存空间被分割为大量不连续的小空闲块(外部碎片);
- 内部碎片:分配的内存块大于实际需求(如malloc按8字节对齐,申请1字节会分配8字节)。
解决方案:
- 内存池:预分配大块连续堆内存,划分为固定大小的内存块,统一管理申请/释放,减少系统调用和碎片;
- 内存对齐:按固定大小(如16字节)分配,减少内部碎片;
- 碎片整理:空闲时合并相邻空闲块(内存池的“内存合并”逻辑);
- 使用智能指针:减少手动释放导致的内存块零散问题。
5. 怎么避免内存泄漏
核心方法
- 使用智能指针:替代裸指针管理堆内存,自动释放;
- 遵循RAII原则:将资源(内存/文件句柄/锁)封装到类中,构造时申请,析构时释放;
- 工具检测:用Valgrind、AddressSanitizer、Visual Leak Detector排查泄漏;
- 规范编码:配对使用new/delete、new[]/delete[],避免重复释放/悬空指针;
- 内存池:统一管理堆内存申请/释放,减少零散内存块。
(1)RAII思想 -> 智能指针
- RAII(资源获取即初始化)核心:将资源的生命周期与对象绑定,对象创建时获取资源,对象销毁时自动释放资源;
- 智能指针是RAII的典型实现:封装裸指针,析构函数中调用delete释放内存,无需手动管理。
① 智能指针有几种,有什么特点
② shared_ptr的循环引用是怎么回事
- 循环引用场景:A的shared_ptr指向B,B的shared_ptr指向A → 双方引用计数互锁(均为1),析构时计数无法归0,对象无法释放,导致内存泄漏;
- 解决方法:将其中一方的shared_ptr改为weak_ptr(如B中用weak_ptr<A>指向A),weak_ptr不增加引用计数,外部释放后计数可归0。
③ 智能指针和裸指针有什么优缺点
④weak_ptr的lock()方法返回空的场景及处理
- lock()返回空的场景:weak_ptr观察的对象已被销毁(shared_ptr引用计数为0);
- 处理方式:调用lock()前先通过expired()判断对象是否存活;lock()返回shared_ptr后判空,再访问对象,避免空指针访问:
std::weak_ptr<Obj> wp = ...;
if (auto sp = wp.lock()) { // lock()返回shared_ptr,非空则对象存活
sp->do_something();
} else {
// 处理对象已销毁的逻辑
}
6. C++的多态是怎么实现的
C++多态分为静态多态(编译期) 和动态多态(运行期):
(1)模板+函数重载(静态多态)
- 函数重载:同一作用域内函数名相同、参数列表不同,编译器根据实参类型在编译期绑定函数;
- 模板:template <typename T> T add(T a, T b),编译器根据传入类型实例化不同版本,编译期确定调用;
- 核心:编译期确定调用的函数,无运行时开销。
(2)继承+虚函数
- 实现步骤:父类声明虚函数(virtual void func());子类重写虚函数(void func() override);父类指针/引用指向子类对象,运行期调用子类重写的函数;
- 底层原理:编译器为含虚函数的类生成虚表(vtable)(存储所有虚函数地址);类对象包含虚表指针(vptr),指向所属类的虚表;运行期通过vptr找到对应类的vtable,调用重写后的虚函数。
(3)虚函数存放在哪里
- 虚函数的代码存放在代码区(text段)(与普通函数一致);
- 虚函数的地址存放在虚表(vtable) 中,虚表属于类级别的数据,存放在全局/静态区(data段);
- 虚表指针(vptr)存放在对象的内存布局中(通常是对象的第一个成员)。
(4)虚析构函数的底层原理及不写虚析构的坑
- 底层原理:析构函数声明为virtual后,会被加入虚表;子类重写析构函数(编译器自动处理),运行期通过虚表调用子类析构函数,保证“先析构子类、再析构父类”。
- 不写虚析构的坑:父类指针指向子类对象时,析构仅调用父类析构函数,子类析构函数不执行 → 子类资源泄漏(如子类堆内存未释放)
class Base { ~Base() {} }; // 非虚析构
class Derived : public Base { int *p = new int; ~Derived() { delete p; } };
Base *ptr = new Derived();
delete ptr; // 仅调用Base::~Base(),Derived的p未释放,内存泄漏
7. 用过什么排序或者你最常用的排序是什么
(推荐回答:快速排序,兼顾性能和实用性)我最常用的是快速排序,其次是归并排序和插入排序(小规模数据)。
(1)快速排序的原理是什么,简要说明一下
核心是“分治思想”,步骤:
- 选基准:从数组中选一个元素作为基准(pivot,通常选首/尾/中间元素);
- 分区:遍历数组,将小于基准的元素放到左区间,大于基准的放到右区间,基准归位;
- 递归:对左右区间重复上述步骤,直到区间长度为1(有序)。
(2)快速排序的最坏时间复杂度及优化方案
- 最坏时间复杂度:(如数组已排序,选首元素为基准,分区后左区间为空,右区间n-1个元素);
- 优化方案:基准优化:选“首+中+尾”三个元素的中位数作为基准;小规模数据优化:区间长度小于10时,改用插入排序(避免递归开销);三路快排:将数组分为“小于基准、等于基准、大于基准”三部分,避免重复元素导致的性能退化;随机选基准:随机选取基准元素,降低最坏情况概率。
8. 设计模式用过哪些?
(推荐回答:创建型+结构型各1-2个,突出落地场景)我用过创建型的单例模式、简单工厂模式,结构型的适配器模式:
- 单例模式:用于内存池、日志器(全局唯一实例);
- 简单工厂模式:用于TCP服务器的客户端连接处理(根据协议类型创建不同的处理类);
- 适配器模式:适配不同版本的第三方库接口。
(1)单例模式是怎么实现的
单例模式核心是“保证类仅有一个实例,提供全局访问点”,主要有两种实现:
- 饿汉模式:
class Singleton {
private:
static Singleton instance; // 程序启动时初始化(全局区)
Singleton() = default; // 私有化构造
Singleton(const Singleton&) = delete; // 禁用拷贝
Singleton& operator=(const Singleton&) = delete; // 禁用赋值
public:
static Singleton& getInstance() { return instance; }
};
Singleton Singleton::instance; // 类外初始化
2. 懒汉模式:
class Singleton {
private:
static std::unique_ptr<Singleton> instance;
static std::mutex mtx;
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance() {
if (!instance) { // 第一次检查(减少锁
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
C++ 常考面试题总结 文章被收录于专栏
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.