C++动态多态

本文详细介绍了C++中的多态性,包括静态多态(函数重载)和动态多态(虚函数)。动态多态主要通过虚函数实现,要求基类包含虚函数并在派生类中重写。同时,抽象类通过纯虚函数定义接口,不能实例化。文章还讨论了虚函数表、多态调用的原理及其实现,并给出了代码示例。
摘要由CSDN通过智能技术生成

要学习C++多态,那么要先了解多态这个词在C++里面是什么意思:简单来说就是:试图用不变的代码或者函数来实现不同的算法,比如重载、模板、虚函数;

多态又分为静态多态和动态多态,静态多态指的是在编译阶段就确定下来的内容,比如函数的重载 , 而动态多态指的是在运行时才能决定的事情,比如虚函数;

静态多态

静态多态指的是在编译阶段就确定下来的内容,比如函数的重载

先说重载,

1、首先两个函数的作用域必须相同,(这点很重要,基类和派生类不在一个作用域,所以基类和派生类的函数不可能重载);

2、函数名相同;

3、参数列表(参数类型,参数的个数,参数了顺序)不相同;

4、与返回值无关,叫重载;

动态多态

动态多态指的是在运行时才能决定的事情,主要通过虚函数实现。

动态多态实现条件

(1) 基类中必须包含虚函数,在派生类中必须对基类的虚函数进行重写

(2) 必须通过基类指针或引用调用虚函数

被调用的函数必须是虚函数,也就是说必须要在两个产生多态的函数前面加virtual关键字。调用函数的形参对象必须是基类对象,这里是因为派生类只能给基类赋值,会发生切片操作。基类不能给派生类赋值。

调用函数的参数必须是指针或引用,因为派生类改变了虚表,那么这个虚表就属于派生类对象,赋值的时候只会把基类的成员给过去,虚表指针不会给。所以在调用函数的时候会发生语法检查,如果满足多态的条件,就会触发寻找虚表中虚函数地址。如果不满足条件,则会直接用基类对象调用基类函数。

重写

(1)基类中的函数必须为虚函数

(2)派生类中重写的虚函数必须与基类的虚函数类型保持一致(返回值、函数名字(参数列表))

​ 例外:

​ 协变:基类中虚函数返回基类对象的指针或引用,派生类中虚函数返回派生类对象的指针或引用——返回值类型不同

​ 析构函数——基类和派生类中函数的名字不同

(3)基类中虚函数和派生类虚函数的访问限定符可以不同

​ 重载、重写、重定义

​ 重载:在同一作用域下,函数名相同、参数不同,返回值可以不同

​ 重写(覆盖):不在同一作用域(分别在基类和派生类),函数名相同、参数相同、返回值相同(协变例外),基类函数必须有virtual关键字,访问修饰符可以不同

​ 重定义(隐藏):在不同作用域中(分别在基类和派生类),函数名相同,在基类和派生类中只要不构成重写就是重定义

动态多态调用原理

​ 基类:

​ 将基类中虚函数按照其在基类声明中的次序添加到虚表(虚函数表)中。

​ 派生类:

​ 1 将基类的虚表拷贝一份

​ 2 如果派生类重写了基类中的某个虚函数,用派生类中的虚函数替换相同偏移量位置的基类虚函数。

​ 3 将派生类自己新增加的虚函数添加到虚表的最后

多态的条件已经完全满足

​ 1)从对象的前4个字节中取虚表地址

​ 2) 传参(虚函数形参+this)

​ 3) 从虚表中取虚函数地址

​ 4) 调用该虚函数

具体实现:

​ 每个含有虚函数的类都有一个虚函数表(Virtual Table)来实现的。简称为V-Table。 C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

​ 1、 每一个类都有虚函数列表。

​ 2、 虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。如果基类3个虚函数,那么基类的虚表中就有三项(虚函数地址),派生类也会有虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现。如果派生类有自己的虚函数,那么虚表中就会添加该项。

​ 3、 派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同,子类独有的虚函数放在后面。

​ 当定义一个有虚函数类的对象时,对象的第一块的内存空间就是一个指向虚函数列表的指针。

抽象类
概念

​ 在成员函数(必须为虚函数)的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。

总结

​ (1)只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数

​ (2)如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加

​ (3)构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆

​ (4)不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为

​ (5)最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)

​ (6)虚表是所有类对象实例共用的

代码示例:

#include <iostream>

using namespace std;

class Person          //抽象类
{
public:
​    virtual void money() = 0;   //纯虚函数
​    friend void TestVirtualFuc(Person *p)
​    {
​       p->money();
​    }   
};

class Man : public Person
{
public:
​    void money()
​    {
​       cout << "自己挣钱" << endl;
​    }
};

class Child : public Person
{
public:
​    void money()
​    {
​       cout << "父母给钱" << endl;
​    }
}; 

class Woman : public Person
{
public:
​    Woman(Person *b)
​    {
​       _b = b;
​    } 
​    void money()
​    {
​       cout << "老公给钱" << endl; 
​    }

​    Person *_b;
};

int main()
{
​    Person *p1 = new Man;  //父类指针指向子类对象
​    p1->money();        //用该指针,调用子类的虚函数

​    Person *p2 = new Child;
​    TestVirtualFuc(p2);    //用该指针,调用子类的虚函数

​    Woman p5 = Woman(p1);   //父类指针指向子类对象(传参)
​    p5.money();

​    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值