C++难点十一:多态

多态的基本语法

静态多态

  • 函数重载、运算符重载
  • 表现出来的现象:函数地址早绑定(在编译阶段就确定了地址),如:
#include <iostream>
using namespace std;
class Animal
{
public:
   void Speak()
   {
       cout<<"动物在说话"<<endl;
   }
};

class Cat:public Animal
{
public:
   void Speak()
   {
       cout<<"小猫在说话"<<endl;
   }
};

void doSpeak(Animal &animal)
{
   animal.Speak();
}

int main()
{
   Cat cat;
   doSpeak(cat);
}

运行结果是“动物在说话”。

动态多态

  • 条件:
    -1.父类和子类中有同名函数(且是“重写”,而不是“重载”,也即参数列表都一样,只是父类中的同名函数加了个virtual);
    -2.调用的时候是用父类的指针或者引用来接收子类对象。
  • 表现出来的现象:函数地址晚绑定,如:
#include <iostream>
using namespace std;
class Animal
{
public:
    virtual void Speak()
    {
        cout<<"动物在说话"<<endl;
    }
};

class Cat:public Animal
{
public:
    void Speak()
    {
        cout<<"小猫在说话"<<endl;
    }
};

void doSpeak(Animal &animal)
{
    animal.Speak();
}

int main()
{
    Cat cat;
    doSpeak(cat);
}

仅仅在父类函数中加了一个virtual,但这就实现了地址晚绑定。

多态的原理剖析

只对Animal类的函数先做一个分析

class Animal
{
public:
    void Speak()
    {
        cout<<"动物在说话"<<endl;
    }
};


int main()
{
    cout<<sizeof(Animal)<<endl;
}
  • 结果是1,因为Speak()函数不是静态函数,所以不属于Animal类,因此Animal类只占一个字节(只要用一个字节,知道这存了一个类就行。)
  • 但是,如果Speak()函数前加一个virtual,结果变为4(32位平台下)或8(64位平台下)。因为,此时class中存的实际上是一个指针vfptr,这个vfptr指向一个vftable,vftable里装的是虚函数的地址,如上面这个例子,记录的就是&Animal::Speak()。
  • 更清晰的图示说明如下:
    在这里插入图片描述

加上一个继承的子类之后

如果这个子类里暂时还没有重写父类的函数

子类中的虚函数表就是父类中的虚函数表。
在这里插入图片描述

如果这个子类里重写了父类的函数

子类中的虚函数表内部会替换成子类的虚函数地址。
在这里插入图片描述
当用父类指针或引用调用子类对象时,发生多态,也即

Animal &animal = cat;
animal.speak;

此时cat会找到&Cat::Speak()。

多态示例

#include <iostream>
using namespace std;

class VirtualCalculator
{
public:
    virtual int calculate()
    {
        return 0;
    }
    int a;
    int b;
};

class SubCalculator:public VirtualCalculator
{
public:
    virtual int calculate()
    {
        return a-b;
    }
};

class MultiplyCalculator:public VirtualCalculator
{
public:
    virtual int calculate()
    {
        return a*b;
    }
};

class DivideCalculator:public VirtualCalculator
{
public:
    virtual int calculate()
    {
        return a/b;
    }
};

class AddCalculator:public VirtualCalculator
{
public:
    virtual int calculate()
    {
        return a+b;
    }
};

int main()
{
    VirtualCalculator* ptr = new AddCalculator;
    ptr->a=1;
    ptr->b=2;
    cout<<ptr->calculate();
    
    delete ptr;
    return 0;
}

虽然代码量加大了,但是组织结构清晰,易于定位维护,可读性强。

纯虚函数和抽象类

纯虚函数

在多态中,通常父类中虚函数的实现是无意义的,主要都是调用子类重写的内容。
因此,可以将虚函数改成纯虚函数,即:
virtual 返回值类型函数名(参数列表) = 0;

抽象类

如果一个类中有纯虚函数,那么它就是抽象类。

抽象类的特点

  1. 无法实例化对象,即不能Point p;(堆区、栈区都是不行的)
  2. 子类必须重写抽象类中的纯虚函数,不然也会是抽象类

抽象类示例

#include <iostream>
using namespace std;

class VirtualCalculator
{
public:
    virtual int calculate() = 0;
};

class Calculator:public VirtualCalculator
{
public:
    virtual int calculate()
    {
        cout<<"子类函数"<<endl;
        return 0;
    }
    
};

int main()
{
    VirtualCalculator* ptr= new Calculator;
    ptr->calculate();
    return 0;
}

虚析构与纯虚析构

多态中,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

问题的根源剖析

#include <iostream>
#include <string.h>
using namespace std;

class Animal
{
public:
    Animal(){cout<<"Animal的构造函数"<<endl;}
    ~Animal(){cout<<"Animal的析构函数"<<endl;}
    virtual void Speak()=0;
};

class Cat:public Animal
{
public:
    Cat(string name)
    {
        cout<<"Cat的构造函数"<<endl;
        M_name = new string(name);
    }
    void Speak()
    {
        cout<<*M_name<<"小猫在说话"<<endl;
    }
    string* M_name;
    ~Cat()
    {
        if(M_name != NULL)
        {
            cout<<"Cat析构函数调用"<<endl;
            delete M_name;
            M_name = NULL;
        }
    }
};

void test01()
{
    Animal* ptr = new Cat("Tom");//实现“多态”:父类指针或引用来创建子类对象
    ptr->Speak();
    delete ptr;
}

int main()
{
    test01();
    return 0;
}

在这里插入图片描述
可以发现,在Animal的析构函数前缺失了Cat的析构函数。
这是因为父类指针在析构的时候,不会调用子类中的析构函数,导致子类如果有堆区属性,会造成内存泄漏。
为了解决这一问题,需要将父类中的析构函数也改成虚析构或纯虚析构

虚析构语法

在父类中将析构函数前加一个virtual即可。
如:

virtual ~Animal()
{
cout<<"Animal析构函数调用"<<endl;
}

此时输出结果反映,目前的析构是符合我们的释放内存的期望的。
在这里插入图片描述

纯虚析构的语法

父类内声明,父类外具体实现:

class Animal
{
public:
    Animal(){cout<<"Animal的构造函数"<<endl;}
    virtual ~Animal()=0;//这一句√
    virtual void Speak()=0;
};
Animal::~Animal(){cout<<"Animal的析构函数"<<endl;}//别忘了这一句√

注:这里就体现出纯虚函数与纯虚析构函数的不同了,只有纯虚析构一定要有具体实现。

虚析构与纯虚析构的共性

  • 都能解决父类指针释放子类对象的问题
  • 都需要有具体的函数实现

虚析构与纯虚析构的不同点

纯虚析构所在的类为抽象类,无法实例化对象

一个例子

#include <iostream>
#include <string.h>
using namespace std;

class CPU
{
public:
    virtual void calculate()=0;
};

class GPU
{
public:   
    virtual void display()=0;
};

class Memory
{
public:    
    virtual void storage()=0;
};

class Computer
{
public:
    Computer(CPU* cpu,GPU* gpu,Memory* memory)
    {
        m_cpu=cpu;
        m_gpu=gpu;
        m_memory=memory;
    }
    
    void work()
    {
        m_cpu->calculate();
        m_gpu->display();
        m_memory->storage();
    }
    
    ~Computer()
    {
        if(m_cpu!=NULL)
        {
            delete m_cpu;
            m_cpu=NULL;
        }
        if(m_gpu!=NULL)
        {
            delete m_gpu;
            m_gpu=NULL;
        }
        if(m_memory!=NULL)
        {
            delete m_memory;
            m_memory=NULL;
        }
    }
private:
    CPU* m_cpu;
    GPU* m_gpu;
    Memory* m_memory;
};

class IntelCPU:public CPU
{
    virtual void calculate()
    {
        cout<<"Intel的cpu开始计算了~"<<endl;
    }
};

class IntelGPU:public GPU
{
    virtual void display()
    {
        cout<<"Intel的gpu开始显示了~"<<endl;
    }
};

class IntelMemory:public Memory
{
    virtual void storage()
    {
        cout<<"Intel的memory开始存储了~"<<endl;
    }
};

class LenovoCPU:public CPU
{
    virtual void calculate()
    {
        cout<<"Lenovo的cpu开始计算了~"<<endl;
    }
};

class LenovoGPU:public GPU
{
    virtual void display()
    {
        cout<<"Lenovo的gpu开始显示了~"<<endl;
    }
};

class LenovoMemory:public Memory
{
    virtual void storage()
    {
        cout<<"Lenovo的memory开始存储了~"<<endl;
    }
};

void test01()
{
  //配三台电脑 
  cout<<"第一台电脑:"<<endl;
  //第一台电脑的零件
  CPU* intelCPU = new IntelCPU;
  GPU* intelGPU = new IntelGPU;
  Memory* intelMemory = new IntelMemory;
  //创建第一台电脑
  Computer* computer1 = new Computer(intelCPU,intelGPU,intelMemory);
  computer1->work();
  delete computer1;
  
   cout<<"第二台电脑:"<<endl;
  //创建第二台电脑,可以不用创建再赋值,直接就在参数列表new
  Computer* computer2 = new Computer(new IntelCPU,new LenovoGPU,new LenovoMemory);
  computer2->work();
  delete computer2;
  
  cout<<"第三台电脑:"<<endl;
  //创建第三台电脑
  Computer* computer3 = new Computer(new LenovoCPU,new IntelGPU,new IntelMemory);
  computer3->work();
  delete computer3;
  
}

int main()
{
    test01();
    return 0;
}

在这里插入图片描述
参考:
bilibili 黑马程序员C++课程 P142

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值