多态
多态的基本语法
静态多态
- 函数重载、运算符重载
- 表现出来的现象:函数地址早绑定(在编译阶段就确定了地址),如:
#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;
抽象类
如果一个类中有纯虚函数,那么它就是抽象类。
抽象类的特点
- 无法实例化对象,即不能Point p;(堆区、栈区都是不行的)
- 子类必须重写抽象类中的纯虚函数,不然也会是抽象类
抽象类示例
#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;
}