【C++】面试:面向对象与多态

C++ 面向对象核心重难点,面试重中之重,笔试、代码题、底层原理追问高频出现,答题话术标准化,直接背诵即可满分作答。


1. 类与对象基础、成员分类

1.1 类与对象核心概念

  • :抽象模板,描述事物的属性与行为,是一种自定义数据类型。
  • 对象:类的实例,占用独立内存空间,拥有类定义的成员。
  • C++ 中structclass仅默认访问权限不同:struct默认publicclass默认private,其余特性完全一致。

1.2 类成员分类

  1. 按访问权限划分
    • private:私有成员,仅本类内部可访问,外部、派生类均无法访问。
    • protected:保护成员,本类、派生类可访问,外部无法访问。
    • public:公有成员,任意位置均可访问,对外暴露接口。
  2. 按存储属性划分
    • 普通成员:属于对象,每个对象独有一份,随对象创建/销毁。包含成员变量、普通成员函数。
    • 静态成员:属于类,所有对象共享同一份,程序运行期间一直存在。包含静态成员变量、静态成员函数。
  3. 特殊成员:构造函数、析构函数、拷贝构造、赋值运算符重载等编译器默认生成的成员函数。

高频追问

:静态成员函数能访问普通成员变量吗?

:不能。静态成员函数无this指针,无法指向具体对象,因此不能访问非静态成员。


2. 构造函数、拷贝构造、析构函数

2.1 构造函数

  • 特性:函数名与类名相同,无返回值,对象创建时自动调用,用于初始化成员。
  • 规则:可重载;若类未手动定义,编译器会生成默认无参构造
  • 分类:无参构造、有参构造、拷贝构造。

2.2 拷贝构造函数

  • 格式:类名(const 类名& 源对象)
  • 触发场景(3 种必考)
    1. 使用已有对象初始化新对象
    2. 对象以值传递方式作为函数形参
    3. 函数以值方式返回对象
  • 规则:若未手动实现,编译器生成默认拷贝构造,执行逐字节拷贝。

2.3 析构函数

  • 特性:函数名~类名,无返回值、无参数,不能重载
  • 执行时机:对象生命周期结束时自动调用,主要用于释放堆资源、关闭句柄等收尾工作。
  • 规则:编译器默认生成空析构函数;有动态内存申请时,必须手动自定义析构。

面试结论

构造负责初始化,拷贝构造负责对象复制,析构负责资源释放,三者是类的三大基础特殊函数。


3. 深拷贝 vs 浅拷贝

3.1 浅拷贝(默认拷贝)

  • 原理:逐字节赋值,仅拷贝变量本身的值。若成员包含指针,只会拷贝指针地址,不会拷贝指针指向的堆内存。
  • 问题:多个对象的指针指向同一块堆内存;对象析构时会重复释放内存,触发程序崩溃、内存错误。
  • 适用场景:类中无堆内存、无指针成员。

3.2 深拷贝(自定义拷贝)

  • 原理:除拷贝指针变量本身,额外重新开辟一块堆内存,拷贝原指针指向的数据,两个对象指针指向不同内存。
  • 特点:资源相互独立,析构不会冲突,彻底解决浅拷贝隐患。
  • 实现方式:手动重写拷贝构造函数 + 赋值运算符重载

速记口诀

浅拷贝:只抄地址,共享内存;深拷贝:另开空间,数据独立。


4. 赋值运算符重载

4.1 基础说明

  • 运算符:=,默认赋值同样执行浅拷贝,存在默认拷贝构造一致的内存风险。
  • 必须作为类的成员函数重载,不能全局重载。
  • 标准函数原型:类名& operator=(const 类名& 源对象)

4.2 四大必要步骤(答题踩分点)

  1. 判断自赋值if (this == &src) return *this;,防止自己给自己赋值引发内存异常。
  2. 释放自身原有堆内存:避免内存泄漏。
  3. 重新分配内存:根据源对象大小开辟新空间。
  4. 拷贝数据 ,最后return *this支持连续赋值。

4.3 补充区分

  • 拷贝构造:创建新对象时调用。
  • 赋值重载:已有对象之间赋值时调用。

5. 三种继承方式(public/protected/private)

核心规则:继承方式决定基类成员在派生类中的访问权限

  1. public 公有继承(工程最常用)
    • 基类public → 派生类public
    • 基类protected → 派生类protected
    • 基类private → 派生类不可见
  2. protected 保护继承
    • 基类public → 派生类protected
    • 基类protected → 派生类protected
    • 基类private → 派生类不可见
  3. private 私有继承
    • 基类public/protected → 派生类private
    • 基类private → 派生类不可见

总结

  • 基类private成员,无论哪种继承,派生类都无法直接访问。
  • 公有继承保持原有权限;保护/私有继承会收紧访问权限。

6. 菱形继承问题与虚继承

6.1 菱形继承(钻石继承)

  • 结构:一个基类被两个派生类继承,这两个派生类又共同派生出一个新类。
  • 两大问题
    1. 数据冗余 :最终派生类会保留多份基类成员,占用多余内存。
    2. 二义性:访问基类成员时,编译器无法判断调用哪一份父类数据,编译报错。

6.2 虚继承解决方案

  • 语法:在继承前加关键字 virtualclass 派生类 : virtual 继承方式 基类
  • 原理:通过虚基类表 ,让所有派生类共享同一份基类数据,只保留一份基类成员。
  • 作用:彻底解决菱形继承的数据冗余与访问二义性。
  • 代价:引入虚基类指针,轻微增加内存开销。

7. 虚函数与多态实现原理

7.1 多态概念

  • 分类:静态多态 (编译期)、动态多态(运行期)。
  • 动态多态三要素(必考):公有继承 + 虚函数重写 + 父类指针/引用指向子类对象

7.2 虚函数底层原理

  1. 包含虚函数的类,会生成一张虚函数表 (vtable),存储虚函数入口地址。
  2. 类对象内部会新增一个虚表指针 (vptr),指向所属类的虚函数表。
  3. 继承关系中,子类会继承父类虚表;重写虚函数后,子类虚表对应位置会替换为自身函数地址。

7.3 执行流程

程序运行时,通过对象的虚表指针找到虚函数表,查表调用对应函数,实现运行时动态绑定

补充

  • 虚函数表属于 ,全局唯一;虚表指针属于对象,每个对象独有。

8. 虚析构函数作用与必要性

8.1 问题场景

父类指针指向子类对象 ,使用父类指针delete释放对象时:

  • 若析构非虚函数:只调用父类析构,子类析构不执行,子类堆资源无法释放,造成内存泄漏。

8.2 虚析构函数

  • 语法:virtual ~类名()
  • 作用:实现析构函数的多态。通过父类指针释放子类对象时,先调用子类析构,再调用父类析构,完整释放所有资源。

八股结论

只要类有可能被继承,并且会用父类指针/引用管理子类对象,析构函数必须声明为虚析构。

拓展

构造函数不能声明为虚函数。对象创建时虚表指针还未初始化,无法实现动态绑定。


9. 纯虚函数 & 抽象类

9.1 纯虚函数

  • 语法:virtual 返回值 函数名(参数) = 0;
  • 特点:只有声明,无具体实现。

9.2 抽象类

  • 定义:包含至少一个纯虚函数的类,称为抽象类。
  • 核心规则
    1. 无法实例化对象,只能作为基类被继承。
    2. 派生类必须重写所有纯虚函数,否则派生类也会变成抽象类。

9.3 应用场景

用于定义接口规范,统一派生类的行为标准,常见于框架、接口设计。


10. 重载、重写 (覆盖)、隐藏三者区别

10.1 函数重载(静态多态 / 同作用域)

  • 范围:同一作用域
  • 规则:函数名相同,参数列表不同(个数、类型、顺序);返回值不同不构成重载。
  • 时机:编译期绑定。

10.2 重写/覆盖(动态多态 / 继承关系)

  • 范围:父子类继承关系
  • 规则:父类为virtual虚函数,子类函数名、参数列表、返回值完全一致
  • 时机:运行期绑定,实现多态。

10.3 隐藏(重定义)

  • 范围:父子类继承关系
  • 规则:子类函数与父类函数名相同,不满足重写条件 ,直接隐藏父类同名函数。
    1. 父类非虚函数,子类同名函数 → 隐藏
    2. 父子类参数列表不一致 → 隐藏
  • 特点:无多态,父类函数被屏蔽,通过作用域分辨符::才可访问父类函数。

极简区分口诀

  • 同域不同参 = 重载
  • 跨域虚函数、三要素一致 = 重写
  • 跨域同名、不满足重写 = 隐藏

🔥 本章综合高频追问

  1. :虚函数、虚基类、虚析构分别解决什么问题?

    虚函数 实现动态多态;虚基类 解决菱形继承数据冗余与二义性;虚析构保证父类指针释放子类对象时完整析构。

  2. :抽象类可以有构造函数吗?

    :可以。抽象类虽不能实例化,但派生类创建对象时,会调用父类构造初始化成员。

  3. :为什么构造函数不能是虚函数?

    :虚函数依赖虚表指针,而虚表指针在构造函数执行阶段才初始化,顺序矛盾,因此构造不能虚。

  4. :默认的拷贝构造和赋值重载一定安全吗?

    :不安全。类含有指针/堆成员时,默认函数执行浅拷贝,会造成内存重复释放、泄漏。


📝 模块总结

本模块围绕封装、继承、多态三大面向对象核心特性展开,重点考察特殊成员函数、拷贝机制、继承规则、虚函数底层、接口设计。是 C++ 面试核心拉分模块,全部掌握可应对绝大多数 OOP 与多态相关笔试、面试考题。

相关推荐
Mortalbreeze1 小时前
C++11类的新特性:移动语义、default、delete、override详解
开发语言·c++
星辰_mya2 小时前
限流、漏斗桶和令牌桶的区别
java·开发语言·面试·架构·高并发
Shadow(⊙o⊙)2 小时前
信号1.0,信号概念、signal()处理、前后台进程、闹钟设置、初识信号三张表。
linux·运维·服务器·开发语言·c++
nazisami2 小时前
深入学习C++11
c++·c++11
(Charon)2 小时前
【C++ 面试高频:STL 容器 vector、map、unordered_map 总结】
开发语言·c++·面试
Irissgwe2 小时前
二叉树进阶
数据结构·c++·算法·c·二叉搜索树
hairenwangmiao2 小时前
c++排序(第一章----桶排序与sort排序)
数据结构·c++·排序
郝学胜-神的一滴2 小时前
[简化版 GAMES 101] 计算机图形学 13:从光栅化到着色——赋予三维像素光影灵魂
c++·计算机视觉·unity·godot·图形渲染·opengl·unreal
Shadow(⊙o⊙)2 小时前
信号2.0,深入信号三张表block pending handlers,core文件的使用,信号执行逻辑:CPU虚拟内存物理内存,时钟源,软中断。
linux·运维·服务器·开发语言·c++