C++之多态

多态

概念

多态,简单说就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。比如,在乐队演奏时,钢琴演奏是一种声音,二胡演奏时另一种声音等等,这就是生活中的多态。

#include<iostream>
using namespace std;
class A
{
    int m_a = 10;
public:
    //当不加virtual时,调用的时A
    virtual void show()
    {
        cout << "A::show():" << m_a << endl;
    }
};
class B :public A
{
    int m_b = 20;
public:
    void show()
    {
        cout << "B::show():" << m_b << endl;
    }
};
int main()
{
    B b;
    A* pa = &b;    //基类指针指向派生类
    pa->show();    
    A& ra = b;    //基类引用绑定派生类
    ra.show();
    return 0;
}

运行结果

当不加virtual时,运行结果是A::show():20 \n A::show():10

当家virtual时,运行结果是B::show():20\n,B::show():10

多态的基本概念

1. 基类指针只能调用基类的成员函数,不能调用派生类的成员函数。
2. 如果在基类的成员函数前加virtual 关键字,把它声明为虚函数,基类指针就可以调用派生类中 同名的成员函数通过派生类中同名的成员函数,就可以访问派生对象的成员变量
3. 有了虚函数,基类指针指向基类对象时就使用基类的成员函数和数据,指向派生类对象时就使用派生类的成员函数和数据,基类指针表现出了多种形式,这种现象称为多态
4. 基类引用也可以使用多态。

演示

#include<iostream>
using namespace std;
class A
{
    int m_a = 10;
public:
    //当不加virtual时,调用的时A
    virtual void show() { cout << "A::show():" << m_a << endl; }
};
class B :public A
{
    int m_b = 20;
public:
    void show() { cout << "B::show():" << m_b << endl; }
};
int main()
{
    B b;    
    A* pa = &b;    //基类指针pa指向派生类
    pa->show();    //若加了关键字virtual,则调用派生类
    A& ra = b;    //基类引用ra绑定派生类
    ra.show();
    return 0;
}

我们试想一些,声明的虚函数支持派生类中的重载函数吗?

在派生类中添加一个成员函数

void show(int) { cout << "B::show(int)" << endl; }
void test()
{
    B b;
    A* pa=&b;
    pa->show(10);    //报错,不支持派生类中的重载函数
}

若要使用派生类的重载函数,在基类中也要写出重载的基类函数;

注意

1)只需要在基类的函数声明中加上virtual关键字,函数定义时不能加。
2)在派生类中重定义虚函数时,函数特征要相同。
3)当在基类中定义了虚函数时,如果派生类没有重定义该函数,那么将使用基类的虚函数。
4)在派生类中重定义了虚函数的情况下,如果想使用基类的虚函数,可以加类名和域解析符。
5)如果要在派生类中重新定义基类的函数,则将它设置为虚函数;否则,不要设置为虚函数,有两方面的好处:首先 普通函数效率更高;其次,指出不要重新定义该函数。

多态的应用

#include<iostream>
using namespace std;
class Person
{
public:
    virtual void hello() { cout << "hello" << endl; }
    virtual void wave() { cout << "向你挥手" << endl; }
};
class A:public Person
{
public:
    void hello() { cout << "A:hello" << endl; }
    void wave() { cout << "A:向你挥手" << endl; }
};
class B :public Person
{
public:
    void hello() { cout << "B:hello" << endl; }
    void wave() { cout << "B:向你挥手" << endl; }
};
int main()
{
    //不使用多态 1->A,2->B
    int num;
    cin >> num;
    //if (num == 1)
    //{
    //    A a;
    //    a.hello();
    //    a.wave();
    //}
    //else if (num == 2)
    //{
    //    B b;
    //    b.hello();
    //    b.wave();
    //}
    //使用多态
    Person* ptr = nullptr;
    if (num == 1)
    {
        ptr = new A;
    }
    else if(num==2)
    {
        ptr = new B;
    }
    ptr->hello();
    ptr->wave();
    return 0;
}

多态分类

C++中的多态分为两种:静态多态与动态多态。
静态多态:也成为编译时的多态;在编译时期就已经确定要执行了的函数地址了;主要有函数重载和函数模板。
动态多态:即动态绑定,在运行时才去确定对象类型和正确选择需要调用的函数,一般用于解决基类指针或引用派生类对象调用类中重写的方法(函数)时出现的问题。

对于上述程序我么你打开内存模型(左:虚继承,右:普通)

原来是4字节,在虚继承后,变为8字节,存放了一个虚基类表,存放着基类三个虚函数的地址,如果没有虚函数,编译器直接把成员函数的地址链接到二进制文件中,直接调用基类成员函数,如果右虚函数,编译器不会把成员函数的地址链接到二进制文件中,而是在创建对象时,多创建一个虚函数表;在调用虚函数时,先查虚函数表,得到函数地址,再执行函数,所以调用普通函数比虚函数效率高。

继续查看派生类A的内存模型

如果创建了派生类对象,在虚函数表中,会用派生类成员函数的地址取代基类成员函数的地址。如果用基类指针指向派生类对象,调用虚函数时,由于函数地址已经被替换成派生类的了,则相当于调用派生类的成员函数。

在这里,把派生类A的wave函数删除,则A的内存模型发生改变,如下

可以看到wave函数的地址没有被替换,还是基类的成员函数。

如何析构派生类对象(虚析构函数)

1. 构造函数不能继承,创建派生类对象时,先执行基类构造函数,再执行派生类构造函数。
2. 析构函数不能继承,而销毁派生类对象时,先执行派生类析构函数,再执行基类析构函数。
派生类的析构函数在执行完后,会自动执行基类的析构函数。
3. 如果手工的调用派生类的析构函数,也会自动调用基类的析构函数。

要点

1)析构派生类对象时,会自动调用基类的析构函数。与构造函数不同的是,在派生类的析构函数中不用显式地调用基类的析构函数,因为每个类只有一个析构函数,编译器知道如何选择,无需程序员干涉。
2)析构函数可以手工调用,如果对象中有堆内存,析构函数中以下代码是必要的:
delete ptr;
ptr=nulllptr;
3)用基类指针指向派生类对象时,delete基类指针调用的是基类的析构函数,不是派生类的,如果希望调用派生类的析构函数,就要把基类的析构函数设置为虚函数。
4)C++编译器对虚析构函数做了特别的处理。
5) 对于基类,即使它不需要析构函数,也应该提供一个空虚析构函数。
6)赋值运算符函数不能继承,派生类继承的函数的特征标与基类完全相同,但赋值运算符函数的特征标随类而异,它包含了一个类型为其所属类的形参。
7)友元函数不是类成员,不能继承。

示例如下

#include <iostream>         // 包含头文件。
using namespace std;        // 指定缺省的命名空间。

class AA {                    // 基类
public:
    AA() { cout << "调用了基类的构造函数AA()。" << endl; }
    virtual void func() { cout << "调用了基类的func()。" << endl; }
    virtual ~AA() { cout << "调用了基类的析构函数~AA()。" << endl; }
    //~AA() { cout << "调用了基类的析构函数~AA()" << endl; }
};

class BB :public AA {  // 派生类
public:
    BB() { cout << "调用了派生类的构造函数BB()。" << endl; }
    void func() { cout << "调用了派生类的func()。" << endl; }
    ~BB() { cout << "调用了派生类的析构函数~BB()。" << endl; }
};

int main()
{
    //AA a;
    //BB b;
    AA* a = new BB;
    delete a;
}

我们查看内存模型

基类

派生类

之前我们说过,如果要在派生类中重定义基类的虚函数,函数名和函数列表需要一致才可以,但是连个类的虚析构函数肯定不能一样,这怎么处理??

这里时C++编译器会给其进行特殊处理,我们大可不用深究。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小谢%同学

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

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

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

打赏作者

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

抵扣说明:

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

余额充值