C++之多态

学习c++的多态,首先要认识清楚什么是c++的继承,可以先看这篇博客:C++之继承_昵称就是昵称吧的博客-CSDN博客


目录

一、多态的概念

二、多态的定义及实现

2、虚函数及其重写

2.1虚函数

2.2虚函数重写

3、C++11 override 和 final

4、重载、覆盖(重写)、隐藏(重定义)的对比

三、多态的原理

四、抽象类


一、多态的概念

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。下面举两个例子说明:

1、买票这个行为,不同的对象会产生不同的状态,比如学生买票有优惠,一般人买票是全价;

2、扫码领红包这个行为,不同的对象会产生不同的状态,比如新用户领取的红包会更多更大,普通用户的红包就较少较小。

二、多态的定义及实现

在继承中构成多态必须满足下面的两个条件,缺一不可

1、必须通过基类的指针或者引用调用虚函数
2、被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

2、虚函数及其重写

2.1虚函数

虚函数:即被关键字virtual修饰的类成员函数称为虚函数。

注意:这里的关键字virtual和继承中虚拟继承用的关键字虽然一样,但是它们两个表示的功能不一样,不要混为一谈

class Person 
{
public:
    //virtual修饰的类成员函数就是虚函数
    virtual void BuyTicket() 
    { 
        cout << "买票-全价" << endl;
    }
};

2.2虚函数重写

虚函数的重写:派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称派生类的虚函数重写了基类的虚函数。

注意:虚函数重写也叫虚函数覆盖

如下代码解释:

 虚函数重写的两个例外:

1. 协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。但是必须满足基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

//父类
class A{};
//子类
class B : public A {};

//父类
class Person 
{
public:
    //父类的虚函数的返回值类型用的父类的指针(这个父类的指针可以是任意的父类)
    virtual A* f() {return new A;}
};
//子类
class Student : public Person 
{
public:
    //构成子类的虚函数重写:子类虚函数的返回值类型用的子类的指针(这个子类的指针可以是任意的子类)
    virtual B* f() {return new B;}
};
//父类
class Person 
{
public:
    //父类的虚函数的返回值类型用的父类的指针(这个父类的指针可以是任意的父类)
    virtual Person* f() {return 0;}
};
//子类
class Student : public Person 
{
public:
    //构成子类的虚函数重写:子类虚函数的返回值类型用的子类的指针(这个子类的指针可以是任意的子类)
    virtual Student* f() {return 0;}
};
上面的两种写法后构成虚函数重写的协变!

2. 析构函数的重写(基类与派生类析构函数的名字不同)——这个我们在继承也说过。
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,
都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,
看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处
,编译后析构函数的名称统一处理成destructor。 

//父类
class Person 
{
public:
    virtual ~Person() {cout << "~Person()" << endl;}
};

//子类
class Student : public Person 
{
public:
    virtual ~Student() { cout << "~Student()" << endl; }
};


// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函
数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
    Person* p1 = new Person;
    Person* p2 = new Student;

    //这里会构成多态
    delete p1;
    delete p2;

    return 0;
}

3、C++11 override 和 final

C++11提供了override和final两个关键字,可以帮助用户检测是否重写

1. final:修饰虚函数,表示该虚函数不能再被重写

//父类
class A
{
public:
    //父类的该虚函数被final修饰
    virtual void Drive() final {}
};
//子类
class B :public A
{
public:
    //子类的虚函数,即使满足构成虚函数重写的条件,但是父类的虚函数被final修饰,
    //所以这里子类的虚函数,也不能构成重写。
    virtual void Drive() {cout << "B" << endl;}
};

2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
 

//父类
class A
{
public:
    virtual void Drive(){}
};
//子类
class B :public A
{
public:
    //子类虚函数被override修饰,可以检查是否构成重写,若没有则编译报错
    virtual void Drive() override 
    {
        cout << "B" << endl;
    }
};

4、重载、覆盖(重写)、隐藏(重定义)的对比

上面的三个概念是特别容易混淆的,一定要牢记这三个概念的区别! 


三、多态的原理

我们知道多态实现的条件,那么为什么满足这形成的条件就可以实现多态了?这就要从底层探究下多态形成的原理了! 

  

这里我们发现了类Base的对象b在监视窗口看见的成员变量,发现除了自己的成员变量外,还有一个指针_vfptr,该指针就叫做虚函数表指针,它指向了一块空间,这块空间叫做虚函数表,该空间存放的是虚函数的地址 。


上面是一个单独类里的,下面我们看看在继承后,构成多态的原理: 

//父类
class Base
{
public:
    virtual void Func1()
    {
        cout << "Base::Func1()" << endl;
    }
    virtual void Func2()
    {
        cout << "Base::Func2()" << endl;
    }
    void Func3()
    {
        cout << "Base::Func3()" << endl;
    }
private:
    int _b = 1;
};

//子类
class Derive : public Base
{
public:
    //构成了虚函数重写
    virtual void Func1()
    {
        cout << "Derive::Func1()" << endl;
    }
private:
    int _d = 2;
};

int main()
{
    //父类对象
     Base b;

    //子类对象
     Derive d;
     return 0;
}

 从上面代码可知,父类的虚函数Func1()、虚函数Func2()、还有不是虚函数的Func3(),所以Func3()的函数地址不会进入父类的虚函数表,因为它不是虚函数

而在子类中,虚函数Func1()满足条件,构成了虚函数重写,那么子类的虚函数表,存放的虚函数地址有哪些了??我们通过监视窗口看看父类对象b 以及 子类对象d的成员变量模型

从上面的对象模型可以看出:

父类对象b的虚函数表指针_vfptr指向的虚函数表里,存放的是父类的虚函数Func1()和Func2()的函数地址;

子类 对象d的虚函数表指针_vfptr指向的虚函数表里,存放的是从父类继承的虚函数Func2()的函数地址,所以该函数地址与父类虚函数表的函数地址相同,因为是继承来的,但是因为子类的Func1()构成了虚函数重写,所以虚函数Func1()的函数地址 与 父类虚函数表里Func1()的函数地址不同,这样就体现出了虚函数重写的意义,函数地址都不同,说明函数地址对应的函数也不同。


四、抽象类

概念:在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象派生类继承后也不能实例化出对象只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

 在子类中,将虚函数Drive()重写后,子类就可以实例化出对象。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值