C++ 面向对象 常考面试题总结
1. 访问某些类的私有字段的方法?
- 友元机制:在类中声明目标函数/类为friend,友元可直接访问私有字段,是C++语法层面的合法方式;
- 类内提供接口:添加public成员函数(如getXXX()),间接返回私有字段值,兼顾封装性与访问需求;
- 强制类型转换(不推荐):通过reinterpret_cast将对象指针转为结构体指针,直接访问内存(破坏封装,仅底层调试/应急使用);
- 反射/指针操作(高危):通过内存地址偏移计算私有字段位置,或用指针强制解引用访问(未定义行为,严禁生产环境使用)。
2. 一个类可以继承多个类吗?
- 可以,C++支持多继承,一个派生类可同时继承多个基类;
- 语法规则:继承时按基类声明顺序,依次调用基类构造函数,析构顺序相反;
- 核心问题:多继承易引发菱形继承(重复继承同一基类),需通过虚继承解决;
- 使用建议:尽量少用多继承,优先用“单继承+组合”替代,降低设计复杂度。
3. 静态字段是否在类构造函数中初始化?
- 不能,静态字段的初始化与类对象构造无关;
- 初始化时机:编译期/程序启动时(全局静态),类内静态字段需在类外单独初始化(如int A::num = 10;);
- 构造函数内的操作:构造函数中可修改静态字段值,但不是初始化,仅为赋值操作。
4. 构造函数/析构函数中会抛出异常吗?如何防止?
构造函数
- 可以抛异常,但会导致对象构造失败,已构造的基类/成员对象会自动析构,未完成构造的对象不会调用析构函数;
- 风险:构造抛异常,对象处于“半构造状态”,易引发内存泄漏。
析构函数
- 禁止抛异常,若析构中抛异常,会导致terminate()调用,程序直接终止;
- 防止方案:析构中捕获所有异常,内部处理(如日志记录),不向外抛出。
通用防护
- 构造函数尽量避免抛异常,核心资源初始化失败用返回码/错误回调处理;
- 析构函数内所有可能抛异常的操作,均包裹在try-catch块中。
5. 什么是虚方法?
- 核心定义:用virtual修饰的类成员函数,支持运行时多态;
- 实现原理:类对象包含虚表指针(vptr),指向类的虚表(vtable),虚表中存储虚函数地址;
- 调用逻辑:通过vptr找到vtable,再根据对象实际类型调用对应虚函数(动态绑定);
- 关键特性:基类指针/引用指向子类对象时,调用虚方法会执行子类重写的实现。
6. 为什么需要虚拟析构函数?
- 核心原因:基类指针指向子类对象时,若析构函数非虚,仅调用基类析构,子类资源无法释放,引发内存泄漏;
- 实现方式:基类析构函数加virtual修饰,子类析构自动继承虚属性;
- 效果:通过基类指针delete对象时,先调用子类析构,再调用基类析构,保证资源完整释放。
7. 抽象类和接口的区别?
抽象类(C++)
- 含纯虚函数(virtual void f() = 0;)的类,无法实例化,仅作为基类;
- 可包含普通成员函数、成员变量,支持继承和多态。
接口(概念层面)
- C++无原生接口,通常用纯虚函数类模拟(所有成员函数为纯虚,无成员变量);
- 核心特点:仅定义行为规范,无数据封装,实现类需重写所有纯虚函数。
核心区别
8. 构造函数可以是虚拟的吗?
- 不可以,构造函数不能用virtual修饰;
- 原因:构造函数执行时,虚表指针(vptr)尚未初始化完成,无法实现动态绑定;
- 替代方案:用“虚构造”模式(如工厂方法+虚函数),间接实现子类对象的动态创建。
9. 关键字const如何用于类方法?
- 修饰位置:放在成员函数参数列表后,如void show() const;;
- 核心作用:该成员函数不能修改类的非静态成员变量,也不能调用非const成员函数;
- 例外情况:mutable修饰的成员,可在const成员函数中修改。
10. 如何保护对象不被复制?
- 禁用拷贝:将拷贝构造函数、赋值运算符重载设为私有或delete(C++11);
- 单例实现:拷贝构造/赋值运算符delete,仅提供静态获取实例方法;
- 核心场景:资源独占类(如智能指针、文件句柄管理类),避免重复释放资源。
11. 解释C++中的多态性,它是如何实现的?
- 多态定义:同一接口,不同类有不同实现,分为静态多态(模板)和动态多态(虚函数);
- 动态多态实现:类中声明虚函数,子类重写;类生成虚表(vtable),对象存储虚表指针(vptr);运行时通过vptr找到vtable,根据对象实际类型调用对应虚函数;
- 静态多态:通过模板/重载实现,编译期确定调用逻辑,无运行时开销。
12. 什么是虚继承,解决什么问题?
- 定义:继承时加virtual修饰(如class D : virtual public B),让多个子类共享同一基类实例;
- 解决问题:菱形继承中,避免基类被多次继承,导致的成员重复、二义性问题;
- 实现原理:虚继承的类,会存储虚基类指针,指向虚基类的共享实例。
13. 类继承的访问修饰符public、protected、private的作用?
14. 如何实现抽象基类?
- 核心方式:类中声明至少一个纯虚函数(virtual void f() = 0;);
- 注意事项:纯虚析构函数需提供实现(否则链接报错);
class Shape {
public:
virtual double area() = 0;
virtual ~Shape() = default;
};
15. 什么是菱形继承?抽象类?
菱形继承
- 定义:派生类D同时继承B1、B2,而B1、B2均继承自基类A,导致D中包含两份A的成员;
- 问题:二义性(访问A的成员需指定基类)、内存冗余;
- 解决:B1、B2采用虚继承A。
抽象类
- 含纯虚函数的类,无法实例化,仅作为基类,用于定义接口规范;
- 与普通类的核心区别:是否含纯虚函数。
16. 拷贝赋值运算符如何实现?有什么注意点?
核心实现
ClassName& operator=(const ClassName& other) {
if (this == &other) return *this; // 防止自赋值
// 释放当前资源
delete[] m_data;
// 深拷贝新资源
m_size = other.m_size;
m_data = new int[m_size];
for (int i = 0; i < m_size; i++) {
m_data[i] = other.m_data[i];
}
return *this;
}
关键注意点
- 自赋值判断:必须先判断this != &other,避免释放自身资源;
- 异常安全:先分配新资源,再释放旧资源,防止分配失败导致资源丢失;
- 返回值:返回*this,支持连续赋值(如a = b = c);
- 深拷贝:类含堆内存时,必须深拷贝,避免浅拷贝导致的double free。
17. 简述一下什么是面向对象
面向对象(OOP)是一种编程范式,核心是将数据和操作数据的方法封装为“对象”,通过对象之间的交互来完成程序逻辑。它强调以“事物”为中心,而非以“过程”为中心,更贴近现实世界的建模方式。
18. 简述一下面向对象的三大特征
- 封装:将对象的内部状态(数据)和行为(方法)隐藏起来,只对外暴露必要的接口,保证数据的安全性和一致性。
- 继承:子类可以继承父类的属性和方法,实现代码复用,并在此基础上扩展新的功能。
- 多态:同一接口可以有不同的实现,通过基类指针/引用调用时,会根据对象的实际类型执行对应的方法,提升代码的可扩展性。
19. 简述一下C++的重载和重写,以及它们的区别
- 重载(Overload):在同一作用域内,函数名相同但参数列表(参数个数、类型、顺序)不同的函数,编译期根据参数列表确定调用哪个函数。
- 重写(Override):子类重写基类的虚函数,函数名、参数列表、返回值必须完全一致,运行期根据对象实际类型确定调用哪个函数。
- 核心区别:重载是编译期多态,重写是运行期多态;重载发生在同一类中,重写发生在继承关系中。
20. 说说构造函数有几种,分别什么作用
- 默认构造函数:无参构造,用于创建对象时初始化成员为默认值。
- 带参构造函数:接收参数,用于自定义初始化对象的成员。
- 拷贝构造函数:参数为同类型对象的引用,用于用已有对象创建新对象。
- 移动构造函数(C++11):参数为同类型对象的右值引用,用于转移临时对象的资源,避免拷贝开销。
- 委托构造函数(C++11):在一个构造函数中调用另一个构造函数,减少代码冗余。
21. 只定义析构函数,会自动生成哪些构造函数
只定义析构函数,编译器仍会自动生成默认构造函数、拷贝构造函数、拷贝赋值运算符,但不会生成移动构造和移动赋值运算符。
22. 说说一个类,默认会生成哪些函数
一个空类,编译器会默认生成6个成员函数:
- 默认构造函数
- 拷贝构造函数
- 拷贝赋值运算符
- 析构函数
- 移动构造函数(C++11,未显式定义拷贝/赋值/析构时生成)
- 移动赋值运算符(C++11,未显式定义拷贝/赋值/析构时生成)
23. 说说什么是虚继承,解决什么问题,如何实现?
- 虚继承:在继承时使用virtual关键字,让派生类共享同一个基类实例。
- 解决问题:解决菱形继承中基类被多次继承导致的成员重复、二义性问题。
- 实现方式:class Derived : virtual public Base {};,派生类会存储虚基类指针,指向共享的基类实例。
24. 说说什么是虚基类,可否被实例化?
- 虚基类:被虚继承的基类,在派生类中只存在一份实例。
- 可否实例化:虚基类本身可以被实例化,它只是在被虚继承时表现出特殊行为。
25. 说说纯虚函数能实例化吗,为什么?派生类要实现吗,为什么?
- 纯虚函数所在类不能实例化:纯虚函数没有实现,类是抽象的,无法创建对象。
- 派生类必须实现:若派生类未实现基类的纯虚函数,派生类也会成为抽象类,同样无法实例化;只有实现了所有纯虚函数,派生类才能实例化。
26. 解释C++中的多态性,它是如何实现的?
- 多态性:同一接口,不同实现,分为静态多态(重载、模板)和动态多态(虚函数)。
- 动态多态实现:基类声明虚函数,子类重写;类生成虚表(vtable),存储虚函数地址;对象包含虚表指针(vptr),指向所属类的虚表;运行时通过vptr找到虚表,根据对象实际类型调用对应函数。
27. 说说C++中虚函数与纯虚函数的区别
28. 说说C++中什么是菱形继承问题,如何解决?
- 菱形继承问题:派生类D同时继承B1、B2,而B1、B2均继承自基类A,导致D中包含两份A的成员,访问A的成员时产生二义性。
- 解决方法:B1、B2采用虚继承A,让D中只保留一份A的实例,消除二义性和内存冗余。
29. 简述下深拷贝和浅拷贝,如何实现深拷贝
- 浅拷贝:仅拷贝对象的表层数据(如指针),多个对象共享同一块内存,易导致double free。
- 深拷贝:拷贝指针指向的内存内容,每个对象拥有独立的内存,互不影响。
- 实现方式:手动实现拷贝构造函数和拷贝赋值运算符,为指针重新分配内存并拷贝内容。
30. 虚函数表里存放的内容是什么时候写进去的?
虚函数表在编译期由编译器生成,表中存放类的虚函数地址;虚表指针在对象构造时由构造函数初始化,指向所属类的虚表。
31. 解释下C++中类模板和模板类的区别
- 类模板:是模板的定义,如template <typename T> class A {};,是生成类的“蓝图”,不是具体的类。
- 模板类:是类模板实例化后的具体类,如A<int>,是可用于创建对象的实际类型。
32. 请问构造函数中的能不能调用虚方法
可以调用,但不会触发多态。因为构造函数执行时,虚表指针(vptr)已初始化为当前类的虚表,而非子类的虚表,调用的是当前类的虚函数实现,而非子类的重写实现。
C++面试总结 文章被收录于专栏
本专栏系统梳理C++面试高频考点,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力。