C++ 面向对象 常考面试题总结

1. 访问某些类的私有字段的方法?

  1. 友元机制:在类中声明目标函数/类为friend,友元可直接访问私有字段,是C++语法层面的合法方式;
  2. 类内提供接口:添加public成员函数(如getXXX()),间接返回私有字段值,兼顾封装性与访问需求;
  3. 强制类型转换(不推荐):通过reinterpret_cast将对象指针转为结构体指针,直接访问内存(破坏封装,仅底层调试/应急使用);
  4. 反射/指针操作(高危):通过内存地址偏移计算私有字段位置,或用指针强制解引用访问(未定义行为,严禁生产环境使用)。

2. 一个类可以继承多个类吗?

  1. 可以,C++支持多继承,一个派生类可同时继承多个基类;
  2. 语法规则:继承时按基类声明顺序,依次调用基类构造函数,析构顺序相反;
  3. 核心问题:多继承易引发菱形继承(重复继承同一基类),需通过虚继承解决;
  4. 使用建议:尽量少用多继承,优先用“单继承+组合”替代,降低设计复杂度。

3. 静态字段是否在类构造函数中初始化?

  1. 不能,静态字段的初始化与类对象构造无关;
  2. 初始化时机:编译期/程序启动时(全局静态),类内静态字段需在类外单独初始化(如int A::num = 10;);
  3. 构造函数内的操作:构造函数中可修改静态字段值,但不是初始化,仅为赋值操作。

4. 构造函数/析构函数中会抛出异常吗?如何防止?

构造函数

  1. 可以抛异常,但会导致对象构造失败,已构造的基类/成员对象会自动析构,未完成构造的对象不会调用析构函数;
  2. 风险:构造抛异常,对象处于“半构造状态”,易引发内存泄漏。

析构函数

  1. 禁止抛异常,若析构中抛异常,会导致terminate()调用,程序直接终止;
  2. 防止方案:析构中捕获所有异常,内部处理(如日志记录),不向外抛出。

通用防护

  1. 构造函数尽量避免抛异常,核心资源初始化失败用返回码/错误回调处理;
  2. 析构函数内所有可能抛异常的操作,均包裹在try-catch块中。

5. 什么是虚方法?

  1. 核心定义:用virtual修饰的类成员函数,支持运行时多态;
  2. 实现原理:类对象包含虚表指针(vptr),指向类的虚表(vtable),虚表中存储虚函数地址;
  3. 调用逻辑:通过vptr找到vtable,再根据对象实际类型调用对应虚函数(动态绑定);
  4. 关键特性:基类指针/引用指向子类对象时,调用虚方法会执行子类重写的实现。

6. 为什么需要虚拟析构函数?

  1. 核心原因:基类指针指向子类对象时,若析构函数非虚,仅调用基类析构,子类资源无法释放,引发内存泄漏;
  2. 实现方式:基类析构函数加virtual修饰,子类析构自动继承虚属性;
  3. 效果:通过基类指针delete对象时,先调用子类析构,再调用基类析构,保证资源完整释放。

7. 抽象类和接口的区别?

抽象类(C++)

  1. 含纯虚函数(virtual void f() = 0;)的类,无法实例化,仅作为基类;
  2. 可包含普通成员函数、成员变量,支持继承和多态。

接口(概念层面)

  1. C++无原生接口,通常用纯虚函数类模拟(所有成员函数为纯虚,无成员变量);
  2. 核心特点:仅定义行为规范,无数据封装,实现类需重写所有纯虚函数。

核心区别

8. 构造函数可以是虚拟的吗?

  1. 不可以,构造函数不能用virtual修饰;
  2. 原因:构造函数执行时,虚表指针(vptr)尚未初始化完成,无法实现动态绑定;
  3. 替代方案:用“虚构造”模式(如工厂方法+虚函数),间接实现子类对象的动态创建。

9. 关键字const如何用于类方法?

  1. 修饰位置:放在成员函数参数列表后,如void show() const;;
  2. 核心作用:该成员函数不能修改类的非静态成员变量,也不能调用非const成员函数;
  3. 例外情况:mutable修饰的成员,可在const成员函数中修改。

10. 如何保护对象不被复制?

  1. 禁用拷贝:将拷贝构造函数、赋值运算符重载设为私有或delete(C++11);
  2. 单例实现:拷贝构造/赋值运算符delete,仅提供静态获取实例方法;
  3. 核心场景:资源独占类(如智能指针、文件句柄管理类),避免重复释放资源。

11. 解释C++中的多态性,它是如何实现的?

  1. 多态定义:同一接口,不同类有不同实现,分为静态多态(模板)和动态多态(虚函数);
  2. 动态多态实现:类中声明虚函数,子类重写;类生成虚表(vtable),对象存储虚表指针(vptr);运行时通过vptr找到vtable,根据对象实际类型调用对应虚函数;
  3. 静态多态:通过模板/重载实现,编译期确定调用逻辑,无运行时开销。

12. 什么是虚继承,解决什么问题?

  1. 定义:继承时加virtual修饰(如class D : virtual public B),让多个子类共享同一基类实例;
  2. 解决问题:菱形继承中,避免基类被多次继承,导致的成员重复、二义性问题;
  3. 实现原理:虚继承的类,会存储虚基类指针,指向虚基类的共享实例。

13. 类继承的访问修饰符public、protected、private的作用?

14. 如何实现抽象基类?

  1. 核心方式:类中声明至少一个纯虚函数(virtual void f() = 0;);
  2. 注意事项:纯虚析构函数需提供实现(否则链接报错);
class Shape {
public:
    virtual double area() = 0;
    virtual ~Shape() = default;
};

15. 什么是菱形继承?抽象类?

菱形继承

  1. 定义:派生类D同时继承B1、B2,而B1、B2均继承自基类A,导致D中包含两份A的成员;
  2. 问题:二义性(访问A的成员需指定基类)、内存冗余;
  3. 解决:B1、B2采用虚继承A。

抽象类

  1. 含纯虚函数的类,无法实例化,仅作为基类,用于定义接口规范;
  2. 与普通类的核心区别:是否含纯虚函数。

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;
}

关键注意点

  1. 自赋值判断:必须先判断this != &other,避免释放自身资源;
  2. 异常安全:先分配新资源,再释放旧资源,防止分配失败导致资源丢失;
  3. 返回值:返回*this,支持连续赋值(如a = b = c);
  4. 深拷贝:类含堆内存时,必须深拷贝,避免浅拷贝导致的double free。

17. 简述一下什么是面向对象

面向对象(OOP)是一种编程范式,核心是将数据和操作数据的方法封装为“对象”,通过对象之间的交互来完成程序逻辑。它强调以“事物”为中心,而非以“过程”为中心,更贴近现实世界的建模方式。

18. 简述一下面向对象的三大特征

  1. 封装:将对象的内部状态(数据)和行为(方法)隐藏起来,只对外暴露必要的接口,保证数据的安全性和一致性。
  2. 继承:子类可以继承父类的属性和方法,实现代码复用,并在此基础上扩展新的功能。
  3. 多态:同一接口可以有不同的实现,通过基类指针/引用调用时,会根据对象的实际类型执行对应的方法,提升代码的可扩展性。

19. 简述一下C++的重载和重写,以及它们的区别

  • 重载(Overload):在同一作用域内,函数名相同但参数列表(参数个数、类型、顺序)不同的函数,编译期根据参数列表确定调用哪个函数。
  • 重写(Override):子类重写基类的虚函数,函数名、参数列表、返回值必须完全一致,运行期根据对象实际类型确定调用哪个函数。
  • 核心区别:重载是编译期多态,重写是运行期多态;重载发生在同一类中,重写发生在继承关系中。

20. 说说构造函数有几种,分别什么作用

  1. 默认构造函数:无参构造,用于创建对象时初始化成员为默认值。
  2. 带参构造函数:接收参数,用于自定义初始化对象的成员。
  3. 拷贝构造函数:参数为同类型对象的引用,用于用已有对象创建新对象。
  4. 移动构造函数(C++11):参数为同类型对象的右值引用,用于转移临时对象的资源,避免拷贝开销。
  5. 委托构造函数(C++11):在一个构造函数中调用另一个构造函数,减少代码冗余。

21. 只定义析构函数,会自动生成哪些构造函数

只定义析构函数,编译器仍会自动生成默认构造函数、拷贝构造函数、拷贝赋值运算符,但不会生成移动构造和移动赋值运算符。

22. 说说一个类,默认会生成哪些函数

一个空类,编译器会默认生成6个成员函数:

  1. 默认构造函数
  2. 拷贝构造函数
  3. 拷贝赋值运算符
  4. 析构函数
  5. 移动构造函数(C++11,未显式定义拷贝/赋值/析构时生成)
  6. 移动赋值运算符(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++工程能力。

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务