当前位置: 首页 > ops >正文

C++封装、多态、继承

C++封装、多态、继承

C++封装

封装的定义与目的:把数据和方法统一组织在类中,对外隐藏内部实现,隐藏实现细节- 提供稳定接口- 保证数据有效性- 降低耦合性,提高维护性。

封装的实现机制:类作用域、访问控制符、this指针

类作用域

作用域:代码中名字(变量、函数等)可被识别和使用的有效范围。

在 C++ 中,常见作用域有:{全局作用域、命名空间作用域、函数作用域、块作用域、类作用域}

类的作用域控制了:类内部成员的名字有效范围、成员访问权限上下文

补充:

如果将类指针强转,则也可以访问到public数据。

访问控制符

虽然类作用域统一包含了所有成员,但访问控制符将它分成了不同“访问级别的区域”,

常见的访问控制符如下:

访问控制符

类内访问

子类访问

类外访问

private

允许

禁止

禁止

protected

允许

允许

禁止

public

允许

允许

允许

访问控制符(private / protected / public)的底层逻辑:

访问控制符是 “编译器层面的语义标签”,它不影响内存,不存在运行时信息,也不会生成额外的机器指令

编译器经历如下处理流程:源代码 -> 词法分析 -> 语法分析 -> 抽象语法树(AST) -> 语义分析 -> 生成中间表示(IR)

访问控制实现阶段:语法分析 + 语义分析阶段

编译器构建 AST(抽象语法树)时,会为每个类成员生成节点,并在节点上打上访问标签。

示例:

CXXRecordDecl MyClass

  |- FieldDecl x (access: private)

  |- FieldDecl y (access: protected)

  |- FieldDecl z (access: public)

  |- CXXMethod setX (access: public)

当编译器在解析类体的时候,它会维护一个 currentAccessSpecifier,一开始是 private(因为 class 默认是 private)。

遇到:private,它会更新 currentAccessSpecifier = AS_private

遇到:public,会更新 currentAccessSpecifier = AS_public

遇到:protected,会更新 currentAccessSpecifier = AS_ protected

然后,编译器接下来遇到的所有成员声明,就会统一打上当前 access 标记

访问控制符不会影响内存结构,无论成员是 public / protected / private,其在类对象中所占用的内存空间是完全一样的。

this指针

this 指针是 C++ 类的成员函数 内部隐式存在的一个指针,指向当前对象自身的地址。

  1. this 是一个 指针,类型为 ClassName* const(常量指针,指向不可变,但指向的对象可变)。
  2. 用于在成员函数内部明确表示 “当前对象”。
  3. 隐藏传入,每个成员函数的调用背后,都隐含传入了 this 指针。

示例:

class Person {

private:

    int age;

public:

    void setAge(int a) { this->age = a; }

};

编译器底层会转成类似这样:

void setAge(Person* this, int a) {

    this->age = a;

}

C++继承

继承:子类(派生类)复用父类(基类)的成员,形成 “is-a” 的关系。

{

注解:

is-a 表示“X 是一种 Y”。

is-a 的本质:子类“属于”父类这个大分类、子类对象可以隐式转换为父类对象指针 / 引用、      子类对象前半部分布局 == 父类布局(子类对象开头就是父类对象,这就是指针兼容性的硬逻辑依据)

}

如果想要运行时多态,继承是必要前提。

继承的三种访问控制

继承方式

父类 public 成员

父类 protected 成员

父类 private 成员

public

保持 public

变成 protected

访问不到

protected

变成 protected

变成 protected

访问不到

private

变成 private

变成 private

访问不到

子类对象的内存模型

class Base {

    int a;

};

class Derived : public Base {

    int b;

};

对象内存布局:

Derived 对象:

[ Base::a ][ Derived::b ],父类成员在子类对象中有“物理存在”!这就是为什么子类对象可以当父类对象用(对象模型兼容性)。

this 指针与继承

Derived d;

d.breathe();

内部等价于:

Animal::breathe(&d);  // this 指向 Derived 对象,但用父类部分去访问

this 指针可以自动向上转为父类类型。这也是为什么多态里 virtual 必须基于指针或引用。

底层实现:对象布局与虚表

普通继承:没有虚表,仅通过偏移访问成员。

Derived d;

Base* p = &d;

p->breathe();  // 编译期已确定,直接偏移访问成员

多态(virtual 函数):

编译器在对象头部插入 vptr(虚表指针):

Derived 对象:

[ vptr ][ Base::a ][ Derived::b ]

vptr 指向 vtable(虚表),vtable 记录所有虚函数地址

C++多态

概念

多态(Polymorphism):同一个接口,不同对象有不同表现。

用统一的父类指针 / 引用,调用不同子类的实现。

动态绑定:运行时决定调用哪一个函数(虚函数机制)。

多态 = vptr 指向 vtable,运行时根据对象真实类型查表找函数

class Animal {

public:

    virtual void speak() { std::cout << "Animal speaks" << std::endl; }

};

class Dog : public Animal {

public:

    void speak() override { std::cout << "Dog barks" << std::endl; }

};

Animal* p = new Dog();

p->speak();   // 输出:Dog barks

如果没有 virtual,输出就永远是 Animal speaks。

静态绑定vs动态绑定

类型

绑定时间

依赖条件

效果

静态绑定

编译期

成员函数(非虚函数)

快,无灵活性

动态绑定

运行期

虚函数 + 指针 / 引用

灵活,代价更高

为什么用指针 / 引用?

只有指针 / 引用才能脱离对象实际类型,让编译器 defer 到运行时决定实际类型。

底层实现原理

本质:虚表(vtable+ 虚表指针(vptr

步骤:

1.编译器生成 vtable

类名

vtable 内容

Animal

&Animal::speak

Dog

&Dog::speak

2. 对象布局插入 vptr

Dog d;

[ vptr -> Dog::vtable ]

[ Animal::成员 ]

[ Dog::成员 ]

3. 调用过程拆解

p->speak();

调用过程等价于

(*(p->vptr)[0])(p);

图解结构:

Dog 对象:

┌──────────┐

vptr ─────────┐

└──────────┘   

               

          ┌────────────┐

          vtable    

          ───────   

          speak ──┐ 

          └────────┬┘ 

                     

                     

               ┌───┴───┴────────────┐

               void Dog::speak()  

               └────────────────────┘

this 指针与多态的结合

void Dog::speak() {

    std::cout << "Dog says, this->type = " << this->type << std::endl;

}

本质:

void speak(Dog* this) {

    ...

}

虚表继承规则

行为

结果

子类未重写虚函数

继承父类 vtable 项

子类重写虚函数

重写覆盖 vtable 对应项

子类新增虚函数

vtable 追加新项

虚表多态 vs 非虚函数

场景

调用行为

非虚函数(静态绑定)

直接根据类型偏移确定地址

虚函数(动态绑定)

运行时查 vtable 决定地址

多态的条件总结

必须条件

理由

虚函数

没有虚表就没有动态绑定

指针 / 引用

否则编译期已确定类型

派生类重写

否则子类不会改变行为

额外知识:vptr 位置

编译器

vptr 插入位置

MSVC

对象头部第一个字节

GCC

对象头部第一个成员位置(隐式)

多态的核心底层模型总结

多态成立条件

底层逻辑

有虚函数

产生虚表,vptr 指向

指针 / 引用

通过 vptr 查虚表调用

重写函数

更新虚表中的函数地址

易错与误解总结

错误理解

正确理解

虚表存在于类

vtable 存在于类,vptr 存在对象

子类一定有新表

没有重写就共享父类表

无指针也有多态

多态只有指针 / 引用才有效

示例:

#include <iostream>

using namespace std;

// -----------  父类 -----------

class Animal {

public:

    string name = "Animal";

    virtual void speak() {  // 虚函数,多态基础

        cout << "Animal speaks!" << endl;

    }

    void breathe() {        // 非虚函数

        cout << "Animal breathes." << endl;

    }

};

// -----------  子类 -----------

class Dog : public Animal {

public:

    string breed = "Bulldog";

    void speak() override {   // 重写虚函数

        cout << "Dog barks!" << endl;

    }

    void fetch() {

        cout << "Dog fetches!" << endl;

    }

};

// -----------  使用父类指针演示多态 -----------

void animalSpeak(Animal* a) {

    a->speak();    // 多态,运行时动态绑定

    a->breathe();  // 非虚函数,静态绑定

}

int main() {

    Dog d;

    cout << "直接调用子类对象:" << endl;

    d.speak();    // Dog::speak,静态绑定

    d.breathe();  // Animal::breathe,静态绑定

    d.fetch();    // Dog::fetch,静态绑定

    cout << "----------" << endl;

    cout << "父类指针指向子类对象,演示多态:" << endl;

    Animal* p = &d;

    p->speak();    // 动态绑定 -> Dog::speak

    p->breathe();  // 静态绑定 -> Animal::breathe

    cout << "----------" << endl;

    cout << "通过统一接口调用:" << endl;

    animalSpeak(&d);

}

输出:

直接调用子类对象:

Dog barks!

Animal breathes.

Dog fetches!

----------

父类指针指向子类对象,演示多态:

Dog barks!

Animal breathes.

----------

通过统一接口调用:

Dog barks!

Animal breathes.

http://www.xdnf.cn/news/15294.html

相关文章:

  • RFCOMM协议详解:串口仿真与TCP/IP协议栈移植技术——面试高频考点与真题解析
  • 在Intel Mac的PyCharm中设置‘add bin folder to the path‘的解决方案
  • 【Scratch】从入门到放弃(六):指令大全-扩展类
  • iOS高级开发工程师面试——关于优化
  • 在AI应用中Prompt撰写重要却难掌握,‘理解模型与行业知识是关键’:提升迫在眉睫
  • 关于数据库的慢查询
  • C/C++数据结构之多维数组
  • MyBatis04-MyBatis小技巧
  • QT 多线程 管理串口
  • Node.js特训专栏-实战进阶:16. RBAC权限模型设计
  • 沃尔玛 卡号查询 滑块 分析
  • 深度学习图像分类数据集—角膜溃疡识别分类
  • TensorFlow深度学习实战(24)——变分自编码器详解与实现
  • spring-ai-alibaba 1.0.0.2 学习(十六)——多模态
  • IP 地址与网络基础全面解析
  • ARC 02 runner scale set chart:对接集群与 Github Action 服务器
  • 在 OCI 生成式 AI 上搭一个「指定地区拉面店 MCP Server」——从 0 到 1 实战记录
  • 基于SpringBoot3集成Kafka集群
  • CSS个人笔记分享【仅供学习交流】
  • Utils系列之内存池(MultiSizePool)
  • 电商系统未来三年趋势:体验升级、技术赋能与模式重构
  • 关于ISO 26262的Single-Point Fault/Residual Fault/Latent Fault/Dual-Point Fault的整理
  • Android 响应式编程完整指南:StateFlow、SharedFlow、LiveData 详解
  • Docker 基于 Cgroups 实现资源限制详解【实战+源码】
  • CAU数据挖掘第四章 分类问题
  • Linux修炼:开发工具
  • 软件开发中的瀑布式开发与敏捷开发
  • 2025湖北省信息安全管理与评估赛项一阶段技能书
  • 在 JetBrains 系列 IDE(如 IntelliJ IDEA、PyCharm 等)中如何新建一个 PlantUML 文件
  • 新手向:使用Python构建高效的日志处理系统