【C++基础】多态的使用和内部机制

目录

一、多态的类型

编译时多态(静态多态)

运行时多态(动态多态)

二、编译时多态的实现(静态多态)

函数重载

运算符重载

编译时多态的意义

三、运行时多态的实现(动态多态)

代码示例

输出

解析

四、多态的意义

灵活性

代码重用

可扩展性

五、虚函数表(多态内部机制)

工作原理

虚函数指针(虚函数表指针)

虚函数表

动态绑定

​编辑

例子


在C++中,多态(Polymorphism)是面向对象编程的重要特性之一。

多态允许使用基类的指针或引用来调用派生类的函数,具体调用哪个函数由运行时对象的类型决定。

这使得同一个函数名在不同的上下文中表现出不同的行为,从而实现代码的灵活性和可扩展性。

一、多态的类型

C++ 中的多态主要有两种类型:

  1. 编译时多态(静态多态)

    通过 函数重载 和 运算符重载 实现。

    在编译时决定调用哪一个重载函数。

  2. 运行时多态(动态多态)

    通过 继承 和 虚函数 实现。

    在运行时决定调用哪一个派生类的函数。这种多态是通过指针或引用来实现的。

二、编译时多态的实现(静态多态)

之前文章详细讲解过重载,所以在这里不过多讲述。

  1. 函数重载

    • 函数重载是指在同一个作用域中定义多个同名函数,但它们的参数列表不同(参数的类型、数量或顺序不同)。

    • 编译器根据函数调用时传递的参数类型和数量来选择调用哪个函数。

  2. 运算符重载

    • 运算符重载允许为用户定义的类型(如类)重载C++内置的运算符,使其能够用于类的对象。

    • 运算符重载可以视为一种特殊的函数重载。

编译时多态的意义

  • 效率:编译时多态在编译时就确定了具体的函数调用,避免了运行时的动态绑定,执行效率高。

  • 代码可读性:通过函数重载和运算符重载,可以让代码看起来更直观、易读。

    例如,自定义类的对象可以像内置类型一样使用运算符进行操作。

  • 灵活性:允许函数和运算符根据上下文自动选择适当的实现,提供了极大的灵活性。

三、运行时多态的实现(动态多态)

运行时多态是通过虚函数实现的,具体步骤如下:

  1. 基类定义虚函数

    在基类中定义一个虚函数(使用 virtual 关键字)。

  2. 派生类重写虚函数

    在派生类中重写该虚函数。

  3. 通过基类指针或引用调用虚函数

    在使用基类指针或引用指向派生类对象时,调用的虚函数会根据对象的实际类型来决定。

代码示例

#include <iostream>
using namespace std;
​
// 基类:动物
class Animal {
public:
    // 虚函数:发出声音
    virtual void sound() const {
        cout << "Animal makes a sound" << endl;
    }
​
    // 虚析构函数
    virtual ~Animal() {
        cout << "Animal destroyed" << endl;
    }
};
​
// 派生类:狗
class Dog : public Animal {
public:
    // 重写基类的虚函数
    void sound() const override {
        cout << "Dog says: Woof!" << endl;
    }
​
    ~Dog() {
        cout << "Dog destroyed" << endl;
    }
};
​
// 派生类:猫
class Cat : public Animal {
public:
    // 重写基类的虚函数
    void sound() const override {
        cout << "Cat says: Meow!" << endl;
    }
​
    ~Cat() {
        cout << "Cat destroyed" << endl;
    }
};
​
int main() {
    Animal* myDog = new Dog();  // 基类指针指向派生类对象
    Animal* myCat = new Cat();  // 基类指针指向派生类对象
​
    myDog->sound();  // 调用 Dog 的 sound 函数
    myCat->sound();  // 调用 Cat 的 sound 函数
​
    delete myDog;  // 销毁 Dog 对象
    delete myCat;  // 销毁 Cat 对象
​
    return 0;
}

输出

Dog says: Woof!
Cat says: Meow!
Dog destroyed
Cat destroyed
Animal destroyed
Animal destroyed

解析

  • 在上述代码中,Animal 类有一个虚函数 sound(),并且 DogCat 类重写了该函数。

  • main 函数中,Animal 类型的指针 myDogmyCat 分别指向 DogCat 对象。

  • 虚函数的调用是在运行时决定的,因此当调用 myDog->sound()myCat->sound() 时,分别调用了 DogCat 类中的 sound 函数。

  • 最后销毁对象时,基类的虚析构函数确保派生类的析构函数被正确调用,避免内存泄漏。

四、多态的意义

  1. 灵活性

    通过使用多态,可以在不修改代码的情况下增加新功能。

    例如,添加一个新的 Animal 派生类时,现有代码无需更改即可使用新类型。

  2. 代码重用

    基类提供通用接口,派生类实现具体行为,减少代码重复。

  3. 可扩展性

    通过多态机制,可以轻松扩展程序的功能,同时保持代码的简洁和可读性。

五、虚函数表(多态内部机制)

当一个类声明了虚函数时,编译器会为这个类创建一个虚函数表。这个表中存储了该类所有虚函数的地址。

(每个包含虚函数的类都有一个虚函数表)

虚函数表是用于支持动态多态性的一种内部机制。

工作原理

  1. 虚函数指针(虚函数表指针)

    当一个类包含虚函数时,编译器会在该类的对象中添加一个指向虚函数表的指针,通常称为虚函数指针(vptr)。

  2. 虚函数表

    对于每个有虚函数的类,编译器都会生成一个虚函数表。

    虚函数表是一个指针数组,其中每个元素都是类中一个虚函数的地址。

    当一个派生类继承了包含虚函数的基类时,那么这个派生类也会拥有自己的虚函数表。

    当子类继承了基类并重写了虚函数时,子类的虚函数表中对应的虚函数指针会指向子类实现的函数。

  3. 动态绑定

    当通过基类指针或引用调用虚函数时,程序会通过虚函数指针(vptr)找到虚函数表(vtable),然后根据虚函数表中存储的地址调用实际的函数实现。这样就实现了动态多态。

例子

#include <iostream>
using namespace std;
​
class Base {
public:
    virtual void show() {
        cout << "Base class show function" << endl;
    }
    virtual ~Base() = default;
};
​
class Derived : public Base {
public:
    void show() override {
        cout << "Derived class show function" << endl;
    }
};
​
int main() {
    Base* b = new Derived();
    b->show();  // 通过基类指针调用虚函数
    delete b;
    return 0;
}

解释:

  • 当基类 Base 定义了虚函数 show() 时,编译器为 Base 类生成了一个虚函数表。

  • Derived 类继承了 Base 并重写了 show() 函数,编译器也为 Derived 类生成了一个虚函数表。

    此表中的 show() 函数指针指向 Derived 类中的 show() 函数。

  • main 函数中,使用 Base 类的指针指向 Derived 类的对象。

    当调用 b->show() 时,程序会通过 vptr 找到 Derived 类的虚函数表,从而调用 Derived 类的 show() 函数。

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值