十二、C++_多态

一、多态的基本概念

基类指针只能调用基类的成员函数,不能调用派生类的成员函数。

如果在基类的成员函数前加virtual 关键字,把它声明为虚函数,基类指针就可以调用派生类中同名的成员函数通过派生类中同名的成员函数,就可以访问派生对象的成员变量

有了虚函数,基类指针指向基类对象时就使用基类的成员函数和数据,指向派生类对象时就使用派生类的成员函数和数据基类指针表现出了多种形式,这种现象称为多态

基类引用也可以使用多态

注意:

1)只需要在基类的函数声明中加上virtual关键字,函数定义时不能加。

2)在派生类中重定义虚函数时,函数特征要相同。

3)当在基类中定义了虚函数时,如果派生类没有重定义该函数,那么将使用基类的虚函数。

4)在派生类中重定义了虚函数的情况下,如果想使用基类的虚函数,可以加类名和域解析符。

5)如果要在派生类中重新定义基类的函数,则将它设置为虚函数;否则,不要设置为虚函数,有两方面的好处:首先效率更高;其次,指出不要重新定义该函数。

示例:1、基类指针指向基类对象

class People
{
public:
    int ID = 0;//ID号
    virtual void show() //声明为虚函数
    {cout<<"People::show():我是"<<ID<<"号。"<<endl;}
    virtual void show(int a)
    {cout<<"People::show(int a):我是"<<ID<<"号。"<<endl;}
};

class Boy:public People
{
public:
    int age = 0;//年龄
    void show()
    {cout<<"boy::show():我是"<<ID<<"号,"<<age<<"岁。"<<endl;}
    void show(int a)
    {cout<<"boy::show(int a):我是"<<ID<<"号,"<<age<<"岁。"<<endl;}
};

int main() {
    People a;a.ID = 3;//创建基类对象并对成员赋值
    Boy b;b.age =18;b.ID = 6;//创建派生类对象并对成员赋值
    People *p;//声明基类指针
    p=&a;p->show();//让基类指针指向基类对象,并调用虚函数。
    //p=&b;p->show();//让基类指针指向派生类对象,并调用虚函数。
    p->show(5);
    p->People::show(5);
    return 0;
}

2、指向派生类对象 

class People
{
public:
    int ID = 0;//ID号
    virtual void show() //声明为虚函数
    {cout<<"People::show():我是"<<ID<<"号。"<<endl;}
    virtual void show(int a)
    {cout<<"People::show(int a):我是"<<ID<<"号。"<<endl;}
};

class Boy:public People
{
public:
    int age = 0;//年龄
    void show()
    {cout<<"boy::show():我是"<<ID<<"号,"<<age<<"岁。"<<endl;}
    void show(int a)
    {cout<<"boy::show(int a):我是"<<ID<<"号,"<<age<<"岁。"<<endl;}
};

int main() {
    People a;a.ID = 3;//创建基类对象并对成员赋值
    Boy b;b.age =18;b.ID = 6;//创建派生类对象并对成员赋值
    People *p;//声明基类指针
    //p=&a;p->show();//让基类指针指向基类对象,并调用虚函数。
    p=&b;p->show();//让基类指针指向派生类对象,并调用虚函数。
    p->show(5);
    p->People::show(5);//使用派生类的成员函数和变量
    return 0;
}

 

二、多态的应用场景 

示例:创建基类指针,让它指向派生类对象,用基类指针调用派生类的成员函数

using namespace std;

class Hero                        // 英雄基类
{
public:
    int viability;      // 生存能力。
    int attack;         // 攻击伤害。
    virtual void skill1() { cout << "英雄释放了一技能。\n"; }
    virtual void skill2() { cout << "英雄释放了二技能。\n"; }
    virtual void uskill() { cout << "英雄释放了大绝招。\n"; }
};

class XS :public Hero       // 西施派生类
{
public:
    void skill1() { cout << "西施释放了一技能。\n"; }
    void skill2() { cout << "西施释放了二技能。\n"; }
    void uskill() { cout << "西施释放了大招。\n"; }
};

class HX :public Hero       // 韩信派生类
{
public:
    void skill1() { cout << "韩信释放了一技能。\n"; }
    void skill2() { cout << "韩信释放了二技能。\n"; }
    void uskill() { cout << "韩信释放了大招。\n"; }
};

class LB :public Hero       // 李白派生类
{
public:
    void skill1() { cout << "李白释放了一技能。\n"; }
    void skill2() { cout << "李白释放了二技能。\n"; }
    void uskill() { cout << "李白释放了大招。\n"; }
};

int main()
{
    // 根据用户选择的英雄,施展一技能、二技能和大招。
    int id = 0;     // 英雄的id。
    cout << "请输入英雄(1-西施;2-韩信;3-李白。):";
    cin >> id;
    // 创建基类指针,让它指向派生类对象,用基类指针调用派生类的成员函数。
    Hero* ptr = nullptr;
    if (id == 1)	{        // 1-西施
        ptr=new XS;
    }
    else if (id == 2) {      // 2-韩信
        ptr = new HX;
    }
    else if (id == 3) {      // 3-李白
        ptr = new LB;
    }
    if (ptr != nullptr) {
        ptr->skill1();
        ptr->skill2();
        ptr->uskill();
        delete ptr;
    }
}

三、多态的对象模型

类的普通成员函数的地址是静态的,在编译阶段已指定

如果基类中有虚函数,对象的内存模型中有一个虚函数表表中存放了基类的函数名和地址

如果派生类中重定义了基类的虚函数创建派生类对象时,将用派生类的函数取代虚函数表中基类的函数

C++中的多态分为两种:静态多态与动态多态。

静态多态:也成为编译时的多态;在编译时期就已经确定要执行了的函数地址了;主要有函数重载和函数模板。

动态多态:即动态绑定,在运行时才去确定对象类型和正确选择需要调用的函数,一般用于解决基类指针或引用派生类对象调用类中重写的方法(函数)时出现的问题。

四、类多态--如何析构派生类

构造函数不能继承创建派生类对象时先执行基类构造函数再执行派生类构造函数

析构函数不能继承而销毁派生类对象时先执行派生类析构函数再执行基类析构函数

派生类的析构函数在执行完后,会自动执行基类的析构函数。

如果手工的调用派生类的析构函数,也会自动调用基类的析构函数。

析构派生类的要点如下: 

1)析构派生类对象时,会自动调用基类的析构函数。与构造函数不同的是,在派生类的析构函数中不用显式地调用基类的析构函数,因为每个类只有一个析构函数,编译器知道如何选择,无需程序员干涉。

2)析构函数可以手工调用,如果对象中有堆内存,析构函数中以下代码是必要的:

delete ptr;

ptr=nulllptr;

3用基类指针指向派生类对象时,delete基类指针调用的是基类的析构函数,不是派生类的,如果希望调用派生类的析构函数,就要把基类的析构函数设置为虚函数。

4)C++编译器对虚析构函数做了特别的处理。

5)对于基类,即使它不需要析构函数,也应该提供一个空虚析构函数

6)赋值运算符函数不能继承,派生类继承的函数的特征标与基类完全相同,但赋值运算符函数的特征标随类而异,它包含了一个类型为其所属类的形参。

示例

class AA{
public:
    AA(){cout<<"class AA()"<<endl;}
    virtual void func(){cout<<"AA func()"<<endl;}
    virtual ~AA(){cout<<"class ~AA()"<<endl;}//设置为虚函数
};
class BB : public AA
{
public:
    BB(){cout<<"class BB()"<<endl;}
    void func(){cout<<"BB func()"<<endl;}
    ~BB(){cout<<"class ~BB()"<<endl;}
};
int main()
{
    //1.手动调用派生类的析构函数,也会自动调用基类的析构函数
//    BB* b=new BB;
//    b->~BB();
//    delete b;
    //2.用基类指针指向派生类对象,需要将基类中的析构函数设置为虚函数,否则将不调用基类的析构函数
    AA* a= new BB;
    delete a;
    return 0;
}

 五、类多态--纯虚函数和抽象类

纯虚函数只有函数名、参数和返回值类型,没有函数体,具体实现留给该派生类去做。

        语法:virtual 返回值类型 函数名 (参数列表)=0;

含有纯虚函数的类被称为抽象类,不能实例化对象,可以创建指针和引用

派生类必须重定义抽象类中的纯虚函数,否则也属于抽象类。

基类中的纯虚析构函数也需要实现。

class AA{
public:
    AA(){cout<<"class AA()"<<endl;}
    virtual void func()=0;//{cout<<"AA func()"<<endl;}
    virtual ~AA(){cout<<"class ~AA()"<<endl;}
};
class BB : public AA
{
public:
    BB(){cout<<"class BB()"<<endl;}
    void func(){cout<<"BB func()"<<endl;}
    ~BB(){cout<<"class ~BB()"<<endl;}
};
int main()
{
    BB b;
    //1.使用基类指针指向对象
    AA *p = &b;
    p->func();
    //2.使用引用
//    AA &p =b;
//    p.func();
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值