多态、重载、覆盖、隐藏、虚析构函数

4.1 通⽤接⼝

1. 通⽤接⼝
需求: 在王者荣耀游戏中设计⼀个选择英雄玩游戏的函数接⼝?
void playGameByAngela(Angela &angela);
void playGameByXiangYu(XiangYu &xiangyu);
void playGameByLuban(Luban &lanban);
void playGame(AngelaHero &angela);
void playGame(XiangYu &xiangyu);
void playGame(Luban &lanban);
void playGame(AngelaHero &angela);
void playGame(XiangYu &xiangyu);
void playGame(Luban &lanban);
void playGame(MonkSha &monkSha);

        在 C++ 中可以使⽤⽗类的引⽤来引⽤⼦类的对象,也可以使⽤⽗类的指针来指向⼦类的对象,反过来则不⾏。“只能爹管⼉⼦,不能⼉⼦管爹”
  • void playGame(Hero &hero);
⽗类的函数接⼝是⽤ virtual 关键字修饰变成虚函数
class Hero{
public:
    virtual void skill(void);
};
⼦类继承⽗类,对⽗类的函数接⼝进⾏重写
class Angela:public Hero
{
public:
    virtual void skill(void);
};
class XiangYu:public Hero{
public:
    virtual void skill(void);
};
class Luban:public Hero{
public:
    virtual void skill(void);
};
⽗类的引⽤来引⽤⼦类的对象或⽗类的指针来指向⼦类的对象
void playGame(Hero &hero)
{
    hero.skill();
}

4.2 多态 (polymorphic) 介绍

一、概念
        不同的对象接收到同⼀消息的时候,产⽣不同的效果
二、理解
        在⾯向对象中,多态是 **** 指通过基类 (⽗类) 的指针或者引⽤指向⼦类的对象在运⾏时动态调⽤实际绑定对象函数的⾏为。即 ⼀个接⼝,多种实现
三、分类
  • 静态多态编译器在编译期间完成的,编译器会根据实参类型来选择调⽤合适的函数,如果有合适的函数就调⽤,没有的话就会发出警告或者报错;
  • 动态多态是 ** 在程序运⾏时 **** 根据基类的引⽤(指针)** 指向的对象来确定⾃⼰具体该调⽤哪⼀个类的虚函数。
四、作用
        把不同的⼦类对象都当作⽗类来看,可以屏蔽不同⼦类对象之间的差异,写出通⽤的代码,做出通⽤的编程,以适应需求的不断变化。多态是设计模式的基础,多态是框架的基础。
五、前提条件
  • 要有继承
  • 要有虚函数重写(被 virtual 声明的函数叫虚函数)
  • 要有⽗类指针(⽗类引⽤)指向⼦类对象
六、实现步骤
  • 基类 (⽗类) ⾥⾯的⽅法是⽤ virtual 关键字修饰,变成虚函数
  • ⼦类继承⽗类重写⽗类⾥⾯的⽅法
  • ⽤⽗类的指针或引⽤指向⼦类对象
  • 通过⽗类指针或引⽤调⽤⼦类重写的⽗类⽅法
七、任务
        在⼀个物联⽹设备中,有蓝⽛、4G、Wifi 三种⽆线传输数据形式,使⽤多态的思想实现数据传输的接⼝。

4.3 多态的实现原理

一、虚函数表与vptr指针

class Parent{
public:
    virtual void vir_function1() {
        cout << "vir_function1" << endl;
}

    virtual void vir_function2() {
        cout << "vir_function2" << endl;
}
private:
    int a;
    int b;
};
⽤ 64bit 编译器编译出 32bit 程序:
linux@ubuntu:~/cpp/lesson17$ g++ Test.cpp -g -m32
In file included from Test.cpp:1:0:
/usr/include/c++/7/iostream:38:10: fatal error: bits/c++config.h:
No such file or directory
#include <bits/c++config.h>
^~~~~~~~~~~~~~~~~~

解决⽅案:
sudo apt-get install g++-multilib
gdb a.out
gdb 调试:
  • 增加调试信息
    • -g
  • 设置断点
    • b function/line
  • 按照层次打印
    • set p pretty <on/off>
  • 显示虚函数表
    • set p obj <on/off>
  • 数组内容层次显示
    • set p array on
  • 数据显示格式
    • x 按⼗六进制格式显示变量
    • d 按照⼗进制格式显示变量
    • u 按⼗六进制格式显示⽆符号整型
    • o 按⼋进制格式显示变量
    • t 按⼆进制格式显示变量
    • a 按⼗六进制格式显示变量
    • c 按字符格式显示变量
    • f 按浮点数格式显示变量
  • 查看函数⼊⼝地址
    • info line 10(查看第 10 ⾏函数的⼊⼝地址)
总结:
  • 当类中声明虚函数时,编译器会在类中⽣成⼀个虚函数表
  • 虚函数表存放的是虚函数的⼊⼝地址
  • 虚函数表是由编译器⾃动⽣成与维护的
  • virtual 成员函数会被编译器放⼊虚函数表中
  • 存在虚函数时,每个对象中都有⼀个指向虚函数表的指针 (vptr 指针)
二、子类继承父类的虚函数表
#include <iostream>
using namespace std;
class Parent
{
public:
    virtual void vir_function1()
{
        cout << "virtual_function1" << endl;
}
    virtual void vir_function2()
{
        cout << "vir_function2" << endl;
}
private:
    int a;
    int b;
};
class Child:public Parent
{
public:
    virtual void vir_function1()
{
        cout << "virtual_function1" << endl;
}
    virtual void vir_function2()
{
        cout << "vir_function2" << endl;
}
private:
    int c;
};
int main(void)
{
    Child object;
    return 0;
}

静态绑定 : 程序编译结束之后,就已经确定了需要调⽤的函数 (根据对象类型或指针的类型来调⽤函数)
动态绑定 : 程序编译的时候,没有确定需要调⽤的函数, 程序在运⾏的时候,才确定需要调⽤的函数
三、静态绑定与动态绑定
  • 静态绑定 : 程序编译结束之后,就已经确定了需要调⽤的函数 (根据对象类型或指针的类型来调⽤函数)
  • 动态绑定 : 程序编译的时候,没有确定需要调⽤的函数, 程序在运⾏的时候,才确定需要调⽤的函数
  • 只有在多态的场合才会出现动态绑定否则就是静态绑定
四、思考
问题 : 为什么⽤⽗类指针或引⽤指向⼦类对象的时候,可以根据⼦类对象的不同,调⽤不同⼦类对象的函数?
** 回答:** 因为在多态的场合是动态绑定的,** 程序运⾏的时候,会根据⼦类对象⾥⾯的 vptr 找到虚函数表,然后通过查虚函数表找到要调⽤的函数。** 由于是不同的⼦类对象,每个⼦类对象对应的虚函数表中函数实现不⼀样,最终调⽤的函数也就不⼀样了。
五、任务
请写出如下程序 (32bit 和 64bit) 运⾏的结果
#include <iostream>
using namespace std;
class A{
public:
    virtual void function1(void)
{
        cout << "A::function1" << endl;
}

private:
    int a;
    static int b;
};
    int A::b = 0;
class B{
public:
    void function2(void)
{
        cout << "B::function2" << endl;
}
    virtual void function(void)
{
        cout << "B::function" << endl;
}
private:
    int b;
};
class C:public A,public B
{
public:
    void function1(void)
{
        cout << "C:function1" << endl;
}

    void function2(void)
{
        cout << "C:function2" << endl;
}

private:
    int c;
};
    void function1(A &obj)
{
        obj.function1();
}
    void function2(B &obj)
{
        obj.function2();
}
int main(void)
{
    C obj;
    cout << "sizeof(obj) : " << sizeof(obj) << endl;
    function1(obj);
    function2(obj);
    return 0;
}

4.4 重载、覆盖、隐藏

一、重载
成员函数被重载的特征:
  • 1. 相同的范围(在同⼀个类中)
  • 2. 函数名字相同
  • 3. 参数不同
  • 4. virtual 关键字可有可⽆。
二、覆盖
覆盖是指派⽣类函数覆盖基类函数,特征是:
  • 不同的范围(分别位于派⽣类与基类)
  • 函数名字相同;
  • 参数、返回值相同
  • 基类函数必须有 virtual 关键字
#include <iostream>
using namespace std;
class Parent
{
public:
    virtual void vir_function1()
{
        cout << "virtual_function1" << endl;
}
    virtual void vir_function2()
{
        cout << "vir_function2" << endl;
}
private:
    int a;
    int b;
};
class Child:public Parent
{
public:
    virtual void vir_function1()
{
        cout << "virtual_function1" << endl;
}
    virtual void vir_function2()
{
        cout << "vir_function2" << endl;
}
private:
    int c;
};
int main(void)
{
    Child object;
    return 0;
}

#include <iostream>
using namespace std;
class Parent
{
public:
    virtual void vir_function1()
{
        cout << "Parent virtual_function1" << endl;
}
    virtual void vir_function2()
{
        cout << "Parent vir_function2" << endl;
}
private:
    int a;
    int b;
};
class Child:public Parent
{
public:
    virtual void vir_function1()
{
        cout << "Child virtual_function1" << endl;
}
    virtual void vir_function2()
{
        cout << "Child vir_function2" << endl;
}
private:
    int c;
};
int main(void)
{
    Child object;
    Parent *p = &object;
    p->vir_function1();//Child virtual_function1
    p->vir_function2();//Child virtual_function2
    return 0;
}

三、隐藏

这⾥ “隐藏” 是指派⽣类的函数屏蔽了与其同名的基类函数,规则如下:
  • 1. 如果派⽣类的函数与基类的函数同名,但是参数不同。此时,不论有⽆ virtual 关键字,基类的函数将被隐藏(注意别与重载混淆)。
  • 2. 如果派⽣类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual 关键字。此时基类的函数被隐藏(注意别与覆盖混淆)。
#include <iostream>
using namespace std;
class Parent
{
public:
    void vir_function1()
{
        cout << "Parent virtual_function1" << endl;
}
    virtual void vir_function2()
{
        cout << "Parent virtual_function2" << endl;
}
private:
    int a;
    int b;
};
class Child:public Parent
{
public:
    virtual void vir_function1()
{
        cout << "Child virtual_function1" << endl;
}
    virtual void vir_function2(int a)
{
        cout << "Child virtual_function2" << endl;
}
private:
    int c;
};
int main(void)
{
    Parent object;
    object.vir_function1();//Parent virtual_function1
    object.vir_function2();//Parent virtual_function2
    cout << "-----------------------" << endl;
    Child child;
    child.vir_function1();//Child virtual_function1(隐藏了⽗类函数)
//error:no matching function for call to ‘Child::vir_function2()’
    child.vir_function2();//(隐藏了⽗类函数)
//maybe you must child.Parent::vir_function2();

    cout << "-----------------------" << endl;
    Parent *p = &child; //behavior depends on type of the pointer
    p->vir_function1();//Parent virtual_function1

//
    p->vir_function2();//Parent virtual_function2
    return 0;
}

四、任务

写出可以体现出重载、覆盖、隐藏的代码

4.5 虚析构函数

一、分析代码
#include <iostream>
using namespace std;
class A{
public:
    A(){
        cout << "A()" << endl;
        aptr = new char[100];
}

    ~A(){
        cout << "~A()" << endl;
        delete[] aptr;
}
private:
    char *aptr;
};
class B:public A{
public:
    B(){
        cout << "B()" << endl;
        bptr = new char[100];
}

    ~B(){
        cout << "~B()" << endl;
        delete[] bptr;
}
private:
    char *bptr;
};
void free_function(A *p)
{
    delete p;
}
int main(void)
{
    A *aobject = new A;
    free_function(aobject);
    cout << "--------------------------" << endl;
    B *bobject = new B;
    free_function(bobject);
    return 0;
}
程序运⾏结果如下:
A()
~A()
--------------------------
A()
B()
~A()

二、将基类的析构函数变成虚函数

#include <iostream>
using namespace std;
class A{
public:
    A(){
        cout << "A()" << endl;
        aptr = new char[100];
}
    ~A(){
        cout << "~A()" << endl;
        delete[] aptr;
}
private:
    char *aptr;
};
class B:public A{
public:
    B(){
        cout << "B()" << endl;
        bptr = new char[100];
}
    ~B(){
        cout << "~B()" << endl;
        delete[] bptr;
}
private:
    char *bptr;
};
void free_function(A *p)
{
    delete p;
}
int main(void)
{
    A *aobject = new A;
    free_function(aobject);
    cout << "--------------------------" << endl;
    B *bobject = new B;
    free_function(bobject);
    return 0;
}
程序运⾏结果如下:
A()
~A()
--------------------------
A()
B()
~B()
~A()
三、分析
1. 基类析构函数不是虚函数时 free_function 实现
2. 基类析构函数是虚函数时 free_function 实现
3.gdb 调试打印 B 类对象
四、总结
  • 将基类的析构函数声明为虚函数,则派⽣类的析构函数⾃动为虚函数
  • 基类的指针或引⽤指向派⽣类对象的时候,可以通过删除基类指针调⽤派⽣类析构函数和基类 析构函数,防⽌资源的未释放
  • 建议将基类的析构函数声明为虚函数防⽌在多态场合造成资源未释放问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值