C++基础知识(七:多态)

本文详细解释了多态在面向对象编程中的重要性,包括静态多态(如函数重载)、动态多态(通过虚函数实现)以及虚析构函数的作用。介绍了继承、虚函数表和动态绑定等概念,展示了如何通过基类指针调用子类重写的方法,确保资源的正确释放。
摘要由CSDN通过智能技术生成

目录

多态概念与实践

1. 多态的基础

2. 函数重载(静态多态)

3. 动态多态

4. 虚析构函数

【1】多态的前提

【2】虚函数(virtual)

【4】虚析构函数

示例:


多态概念与实践

多态是面向对象编程的四大基本原则之一,它让程序能够以统一的接口处理不同的对象类型,从而实现了接口与实现分离,提高了代码的灵活性和复用性。多态主要体现在两个层面:静态多态(编译时多态,如函数重载)和动态多态(运行时多态,主要通过虚函数实现)。

1. 多态的基础
  • 继承与多态:继承是实现多态的前提条件。通过继承,子类可以继承父类的属性和方法,并在此基础上进行扩展或重写,从而实现不同类之间相同接口的多种行为。
2. 函数重载(静态多态)
  • 定义:在同一作用域内,函数名相同但参数列表不同(类型、数量或顺序不同),实现不同的功能,这就是静态多态,编译器在编译时期就能确定调用哪个函数。
3. 动态多态
  • 虚函数:通过在基类中声明虚函数(使用virtual关键字),允许子类重写(override)该函数,实现运行时的多态性。虚函数的关键在于,它允许通过基类指针或引用调用子类的实现。
  • 虚函数表(V-Table):每个包含虚函数的类都有一个虚函数表,存储虚函数的地址。对象实例包含一个指向虚函数表的指针,该指针在对象创建时初始化。当子类重写了虚函数,子类的虚函数表会在相应位置存储子类的函数地址。
  • 实现机制:在运行时,通过基类指针调用虚函数时,实际调用的是该指针所指向对象的虚函数表中的函数地址,从而实现动态绑定,达到多态的效果。
4. 虚析构函数
  • 重要性:当使用基类指针删除派生类对象时,如果没有虚析构函数,只会调用基类的析构函数,导致派生类特有的资源无法被正确释放。因此,为保证多态对象能被正确销毁,基类的析构函数应声明为虚函数。
  • 工作原理:虚析构函数确保通过基类指针删除对象时,会首先调用派生类的析构函数,然后再调用基类的析构函数,从而彻底释放派生类的资源。

【1】多态的前提

  1. 继承:多态的实现通常基于类的继承关系。一个类(子类)继承自另一个类(父类),子类可以继承父类的属性和方法,并且可以覆盖或扩展父类的行为。

  2. 虚函数:在基类中定义虚函数(使用virtual关键字),是实现动态多态的关键。虚函数允许子类重写父类中的同名函数,这样在运行时,根据对象的实际类型来决定调用哪个版本的函数。

  3. 父类指针或引用:通过父类的指针或引用来指向子类的对象,这是多态调用的常见方式。这样,即使使用的是父类的接口,也能调用到子类重写后的方法,实现动态行为。

  4. 方法重写:子类在继承父类的过程中,可以重写(override)父类中的虚函数,提供自己的实现。这是多态表现不同行为的基础。

在子类中重写父类的虚函数就是函数重写的过程,可以实现多态

【2】虚函数(virtual)

只要基类中是虚函数,后面的所有子类中该函数都是虚函数

常规来说,在继承时,给父类中的函数加上virtual关键字,定义成一个虚函数,

在子类中,可以对父类中的虚函数进行函数重写(override)

只要有虚函数的类,都会有一个虚函数表和一个虚(函数表)指针

虚指针是指向虚函数表的指针;

虚函数表,存储所有的虚函数的信息

虚函数表:保存所有虚函数的入口地址,每一个包含虚函数的类都会有一张虚函数表

如果发生继承关系,子类先复制父类的虚函数表,如果子类对某个虚函数重写,就去更改虚函数表中,该函数的入口地址

虚函数表指针:指向虚函数表的指针,父类中有一个虚函数表指针,子类中的虚函数表指针是从父类中继承下来的虚函数表指针,指向子类的虚函数表(虚函数表指针存在类中的第一个位置)

【4】虚析构函数

由于实现多态,需要使用父类的指针,指向子类的空间,父类指针可以操作的空间,只有父类自己的部分,所以,在delete父类指针时,并不会释放调子类的空间

解决方法:给基类(父类)的析构函数前面加上virtual关键字,只要基类是虚析构函数,后面继承的所有子类都是虚析构函数,虚析构函数会引导父类的指针,释放掉子类的空间

示例:

#include <iostream>
using namespace std;

class Person {
public:
    virtual ~Person() { cout << "Person的析构" << endl; }
    virtual void play() { cout << "吃饭" << endl; }
    virtual void fun() { cout << "fun" << endl; }
};

class Student : public Person {
public:
    void play() override { cout << "打游戏" << endl; }
    ~Student() { cout << "Student的析构" << endl; }
};

class SubStudent : public Student {
public:
    void play() override { cout << "第二次继承" << endl; }
    void fun() override { cout << "fun的第二次继承" << endl; }
};

int main() {
    Person *p = new Student;
    p->play(); // 通过父类指针调用子类重写的play方法
    delete p;  // 正确释放资源,由于虚析构函数的存在,Student的析构也会被调用

    // 下面是注释掉的代码示例,展示了多态的其他用法
    // Person *p1 = new SubStudent;
    // p1->play();  // 第二次继承
    // p1->fun();   // fun的第二次继承

    return 0;
}

可以看到这里虽然是Person类型的指针但是调用出来的函数确实student的play()。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨笨小乌龟11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值