C++八股文面经

目录

说明:重点在于收集问题,答案内容较简洁,此答案仅供参考。

1、c和c++最大的区别

C:纯过程式编程语言,以函数和数据结构为核心,通过函数调用组织代码,强调 “怎么做”(流程)。 C++:多范式语言,核心是面向对象编程(OOP),支持封装、继承、多态,将数据和操作绑定为 “类”,强调 “做什么”(对象交互)。

C:适合底层开发,追求极致性能和硬件控制: 操作系统内核、嵌入式系统、驱动程序、高性能服务器等。 C++:适合复杂应用开发,兼顾性能与抽象能力: 大型应用程序(如浏览器、游戏引擎)、高性能计算、图形界面开发等。 总结

2、c可以实现c++中的对象吗

虽然 C 语言没有原生支持面向对象编程(OOP)的语法(如 class、public/private、virtual 等),但可以通过结构体、函数指针和一些编程技巧模拟 C++ 中的 “对象” 特性,包括封装、继承和多态。

3、如何理解this指针

本质是 C++ 编译器为成员函数隐式添加的一个参数,用于标识当前调用函数的对象实例,使得成员函数能准确访问该对象的数据。

底层原理:this 指针并非对象本身的一部分(不占用对象的内存空间),而是编译器在调用成员函数时,通过参数传递给函数的。其传递方式依赖于编译器和调用约定(如 __thiscall)。

C++ 编译器会将成员函数编译为类似 “带额外参数的全局函数”,这个额外参数就是 this 指针。

4、你觉得你c++能力100分 能评到多少分

核心是既要展现自信,又要体现对自身能力的清醒认知,避免过度自负或过度谦虚。回答的关键在于:给出具体分数(通常 70-80 分较合适)+ 说明评分依据(结合自身优势和待提升点)+ 体现持续学习的态度。

推荐回答框架: “如果满分是 100 分,我会给自己打 75 分左右。 理由是: 优势方面:我对 C++ 的核心语法(如面向对象特性、模板、STL)和底层原理(如虚函数表、智能指针实现、内存管理)有系统理解,能独立完成中等复杂度的项目开发(比如曾用 C++ 实现过 XX 功能 / 项目,涉及多线程同步、STL 容器优化等),也能解决开发中遇到的常见问题(如内存泄漏排查、模板编译报错分析)。

不足方面:在某些深入场景(如极端性能优化、C++20 + 新特性的工程化落地、大型项目的架构设计经验)上,我的实践还不够充分,相比资深工程师还有提升空间。 这个分数对我来说,既是对现有能力的肯定,也提醒自己还有 25 分的进步空间 —— 我最近在深入学习 XX(如协程、内存池设计),希望能不断填补这些短板。”

5、写一个不会内存泄漏的程序,你的设计思想是什么

核心思想是通过系统化的机制消除 “手动管理资源” 的风险,让资源的生命周期与对象 / 作用域自动绑定,从根源上避免 “忘记释放”“重复释放”“释放后使用” 等问题。

RAII 思想通过将资源绑定到对象的生命周期,让资源在对象创建时获取、在对象销毁时(离开作用域或被析构)自动释放,彻底避免手动释放的疏漏。

动态内存泄漏(new 后未 delete)是最常见的内存泄漏类型。解决方案是用智能指针完全替代裸指针,让指针的生命周期与内存绑定。

内存泄漏常发生在 “资源分配与释放逻辑分散在代码各处” 的场景(例如在函数 A 中 new,却在函数 B 中 delete,中间流程复杂时易遗漏)。设计时应做到:

  • “谁分配,谁释放”:资源的分配和释放逻辑尽量在同一作用域或同一类中,避免跨函数 / 跨模块传递裸指针。
  • 用容器代替零散内存:优先使用 std::vector/std::list 等 STL 容器存储批量数据,容器会统一管理内部元素的内存,避免手动 new[]/delete[]。

一些危险的编码习惯会间接导致内存泄漏,需通过规范杜绝:

  • 禁用 void* 与强制类型转换:void* 会丢失类型信息,增加指针误用风险(如释放时类型不匹配)。
  • 避免 “野指针”:指针赋值后及时初始化(用 nullptr),不访问已释放的内存。
  • 禁止 “内存泄漏温床” 的语法:如 goto 跳过多重释放逻辑、delete 后未置空指针等。

6、如果项目中遇到内存泄漏,你是如何进行排查的

一、第一步:快速 “冻结” 现场,保存关键信息

  • Linux 环境:用 gcore 生成 core dump(进程内存全量快照)
  • 进程资源使用:top -p 1234(Linux)或任务管理器(Windows),记录内存占用(RES/VSS)、线程数、CPU 使用率。
  • 系统状态:free -m(内存总量 / 剩余)、vmstat(换页情况)、netstat(网络连接数)—— 排除系统资源不足导致的间接泄漏(如内存碎片化)。
  • 业务场景:记录当前程序正在处理的任务(如用户输入、请求 ID、数据量),偶发泄漏常与特定输入 / 并发量相关。

二、第二步:实时分析当前内存分布,锁定异常对象 通过工具解析内存快照,统计 “数量异常多” 或 “总大小异常大” 的对象类型,这些往往是泄漏的源头。

在 gdb 中执行以下操作:

  • 查看堆内存使用概况:info proc mem(确认堆区是否异常增长)。
  • 检查全局 / 静态容器:若程序中有全局缓存(如 std::map<Key, Value>),打印其大小(p global_map.size()),判断是否未清理。
  • 查找自定义类的实例:若有 MyClass 类,用 info variables 找到其构造函数地址,结合内存搜索(find 命令)统计实例数量(正常逻辑下数量应稳定,泄漏时会持续增长)。

Linux:用 lsof -p 1234 列出进程打开的文件 / 网络句柄,若发现大量 REG(文件)或 IPv4(网络连接)句柄且状态为 CLOSE_WAIT(未正常关闭),可能是资源泄漏导致的内存占用。

三、第三步:结合代码逻辑,分析泄漏根源

7、说说你对c++中STL理解

STL 的核心思想是将数据的存储(容器) 和数据的操作(算法) 解耦,通过迭代器(Iterator) 作为两者的 “桥梁”,使得同一算法可以作用于不同容器,同一容器也可以适配不同算法。这种设计带来了极强的灵活性:

  • 例如,std::sort 算法可以对 std::vector、std::array、std::deque 等多种容器进行排序,只要它们提供符合要求的迭代器;
  • 反之,std::vector 可以被 std::find、std::for_each、std::count 等数十种算法操作。

STL 由五大核心组件构成,它们相互配合,形成完整的泛型编程体系:

  1. 容器(Containers):管理数据存储的 “数据结构”
  2. 迭代器(Iterators):容器与算法的 “接口”
  3. 算法(Algorithms):操作数据的 “通用函数”
  4. 适配器(Adapters):“包装” 现有组件,改变接口
  5. 适配器(Adapters):“包装” 现有组件,改变接口

8、lambda表达式使用需要注意什么

值捕获([x])会在 lambda 创建时复制变量的当前值(快照),后续外部变量的修改不会影响 lambda 内部的副本。 引用捕获([&x])不会复制变量,而是直接引用外部变量。若 lambda 的生命周期超过被引用变量的生命周期,会导致悬垂引用(访问已销毁的内存)。

9、C++类的大小由什么决定

遵循 “只计算实际存储的数据,忽略成员函数” 的基本原则,同时特殊情况(如虚函数、继承)会影响最终大小。

  • 类的大小首先由所有非静态成员变量的大小决定,静态成员变量不计算在内(静态成员属于类本身,存储在全局区,而非对象中)。
  • 为了提高 CPU 访问效率,编译器会对成员变量进行内存对齐(按一定规则在成员间插入空白字节),这会导致类的实际大小可能大于成员变量的总和。
  • 如果类中包含虚函数(或继承了包含虚函数的类),编译器会为类添加一个虚函数表指针(vptr),用于实现多态。
    • 虚函数表指针的大小为 4 字节(32 位环境)或 8 字节(64 位环境);
    • 无论类中有多少个虚函数,都只需要一个虚函数表指针。

10、设计模式的原则

设计模式的核心原则(通常称为 “面向对象设计原则”)是一套指导软件设计的思想准则,目的是提高代码的可复用性、可维护性和灵活性。这些原则是设计模式的基础,多数模式都遵循或组合了这些原则。最经典的是SOLID 五大原则,此外还有一些常用补充原则。

一、SOLID 五大核心原则

  1. 单一职责原则(Single Responsibility Principle - SRP) 定义:一个类 / 模块应该只有一个引起它变化的原因(即只负责一项职责)。 意义:减少类的复杂度,降低修改风险(修改一个职责时不会影响其他职责)。 反例:一个 User 类同时负责 “用户信息管理” 和 “用户登录验证”,当登录逻辑变化时,可能影响信息管理功能。 正例:拆分为 UserInfo(管理信息)和 UserAuthenticator(处理登录)两个类。
  2. 开放 - 封闭原则(Open-Closed Principle - OCP) 定义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。 意义:通过扩展已有代码来适应新需求,而非修改原有代码,避免引入新 bug。 实现方式:依赖抽象(如接口或抽象类),通过子类继承或实现接口来扩展功能。
  3. 里氏替换原则(Liskov Substitution Principle - LSP) 定义:子类对象必须能够替换掉所有父类对象,且不改变程序的正确性。 意义:保证继承关系的合理性,确保多态逻辑正确(父类引用指向子类对象时行为一致)。 反例:正方形(Square)继承自长方形(Rectangle),但修改正方形的 “宽” 会同时改变 “长”,违反长方形的行为契约,导致替换后逻辑错误。 要求:子类不得违反父类的前置条件(如输入参数范围)和后置条件(如返回值约束)。
  4. 接口隔离原则(Interface Segregation Principle - ISP) 定义:客户端不应该被迫依赖它不需要的接口(即接口应最小化,避免臃肿)。 意义:防止 “胖接口” 导致的依赖冗余,减少客户端与无用接口的耦合。 反例:一个 Worker 接口包含 work()、eat()、sleep() 方法,让 “机器人” 类实现时,被迫实现它不需要的 eat() 和 sleep()。 正例:拆分为 Workable(work())、Eatable(eat())、Sleepable(sleep())三个小接口,客户端按需实现。
  5. 依赖倒置原则(Dependency Inversion Principle - DIP) 定义:高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。 意义:降低模块间的耦合,使得低层模块变化时,高层模块无需修改。 反例:高层的 PaymentService 直接依赖低层的 WeChatPay 类,当需要支持 Alipay 时,必须修改 PaymentService。 正例:定义 Payment 抽象接口,PaymentService 依赖 Payment,WeChatPay 和 Alipay 实现该接口,新增支付方式时无需修改高层。

二、其他重要原则

  1. 迪米特法则(Law of Demeter - LoD,最少知识原则) 定义:一个对象应该对其他对象保持最少的了解(只与直接朋友通信,不访问朋友的朋友)。 意义:减少对象间的耦合,降低系统复杂度。 示例:A 依赖 B,B 依赖 C,则 A 不应直接调用 C 的方法(需通过 B 间接访问)。
  2. 合成复用原则(Composite Reuse Principle - CRP) 定义:优先使用对象组合(has-a),而非继承(is-a)来实现代码复用。 意义:避免继承带来的强耦合(子类依赖父类实现),组合更灵活(可动态替换组件)。 示例:Car 类通过组合 Engine、Wheel 等对象实现功能,而非继承 EngineCar 等子类。
  3. 开闭原则的补充:优先使用抽象类和接口 抽象是实现 “开放 - 封闭” 的基础,通过抽象定义稳定的接口,具体实现留给子类或实现类,从而隔离变化。

总结:原则的核心目标 所有设计原则的最终目的都是 “降低耦合,提高内聚”

  • 内聚:一个模块 / 类内部的功能应高度相关(如单一职责)。
  • 耦合:模块 / 类之间的依赖应尽可能弱(如依赖倒置、接口隔离)。

这些原则不是 “必须严格遵守的规则”,而是 “权衡利弊的指导”。实际开发中需根据场景灵活应用,避免过度设计(如为了遵循原则引入不必要的复杂度)。设计模式则是这些原则的具体体现 —— 例如,工厂模式体现了依赖倒置,装饰器模式体现了开放 - 封闭。

11、c++中如何选择容器使用

选择容器前,先回答以下问题,缩小范围:

  • 数据访问方式:需要随机访问([]或at())还是只能顺序访问?
  • 插入 / 删除位置:主要在尾部、头部还是中间位置操作?
  • 是否需要排序:数据是否需要保持有序,或需要范围查询(如 “找大于 x 的元素”)?
  • 是否需要键值对:是存储单一元素(如int),还是键值映射(如id→name)?
  • 性能优先级:优先考虑时间效率(访问 / 插入速度)还是空间效率(内存占用)?

12、单继承下虚函数表数量?多继承为什么会有多个虚函数表及对应表头指针?

详情见:D:\interview\Storage\c++\class\类的大小计算 如果父类有虚函数,则有多少虚函数父类就有多少个虚函数表指针。 另外自身的虚函数表指针跟最后继承的类指针会合并,即单继承中,父类有虚函数,子类也有虚函数,则虚函数表指针为同一个,然后做相应的位移。

13、虚函数相比普通函数的性能开销?

4.虚函数重写的时机? 5.什么是右值引用? 6.move 的操作过程? 7.string 类型的移动构造做了哪些事情? 8.forward 函数?为什么不用forward会变成左值? C++ 的 RAII 机制核心是什么? 10.RAII 如何配合异常处理的流程? 11.dynamic_cast、static_cast 的区别? 12.设计模式的原则? 13.单例模式怎么实现? 14.观察者模式的应用场景? 15.工厂模式的分类及作用? 16.STL 的空间分配器是怎么设计的? 17.STL 是怎么调用 allocator 的?(如 vector 的构造过程) 18.vector 扩容过程? 19.vector 扩容时如何判断哪些元素需要移动哪些需要拷贝? 20.push_back 和 emplace_back 区别? 21.shared_ptr 的控制块设计? 22.shared_ptr 的引用计数存储在哪里? 23.程序编译过程?(源码到二进制) 24.动态链接为什么要加上 -fPIC 标记? 25.进程初始化时操作系统做了什么? 26.操作系统怎么分配进程的虚拟地址? 27.操作系统怎么实现从虚拟地址到物理地址的映射? 28.页表初始化时会不会把所有虚拟内存都映射到物理内存? 29.C++ 常见的锁的类型? 30.互斥锁怎么实现? 31.死锁的四个必要条件? 32.死锁怎么调试? 33.计算机网络协议分层? 34.ping 命令工作在哪一层? 35.IP 头字段有哪些? 36.TCP 三次握手流程? 37.内存泄漏怎么定位? 38.内存泄漏的影响?