(类与对象的最后一篇了)
目录
三、多态
多态:面向对象的三大特征之一。多态,顾名思义就是多种形态。下面是我自己的理解,
对于一个类,可能有很多个子类,子类会继承父类的成员,而当通过父类指针创造子类对象时,会导致无法调用子类成员函数,这时就需要用到多态。
1.多态分为两类
1.静态多态:函数重载和运算符重载都属于静态多态,复用函数名
2.动态多态:派生类和虚函数实现运行时多态
特征:
静态多态地址早绑定,即在编译阶段已绑定;动态多态地址晚绑定,即在运行阶段绑定,而后者就是我们接下来要讲的,通过动态多态实现地址晚绑定,在执行时才确定调用的是哪个对象的成员函数。
我们会使用虚函数,将父类的成员函数设置为虚函数,让函数地址不在编译阶段绑定,而在运行阶段绑定具体子类对象。语法:在父类成员函数前+virtual, 下面示例代码
#include<iostream>
using namespace std;
class Animal{
public:
//虚函数 想让猫说话,就得让函数地址晚绑定,在运行阶段绑定 ,称为动态联编
virtual void speak(){
cout<<"动物在说话"<<endl;
}
};
class Cat:public Animal{
public:
void speak(){
cout<<"小猫在说话"<<endl;
}
};
class Dog:public Animal{
public:
void speak(){
cout<<"小狗在说话"<<endl;
}
};
void doSpeak(Animal &animal){
animal.speak();
}
int main(){
Cat cat;
doSpeak(cat);//Animal &animal=cat
Dog dog;
doSpeak(dog);
return 0;
}
从这个案例我们可以总结一下,多态的使用场景,多态一般满足以下条件:
1.有继承关系
2.子类重写了父类的虚函数
多态使用
·用父类的指针或引用指向子类对象
·重写函数的返回值,函数名,参数列表完全一样(区别于函数重载)
2.多态的优点
没接触面向对象时,我们写的程序是没有经过封装的,对于临时的使用确实不成问题,但是如果是一个长期使用的项目,你可能需要对原先的代码进行修改、拓展,又或需要与别人合作,这时需要与别人共享代码。这时多态的优势会体现出来,多态的使用会使代码
1.代码组织结构清晰
2.可读性强
3.利于前期和后期的扩展和维护
下面通过一个计算器的实现作为例子
#include<iostream>
using namespace std;
//下面利用多态写一个计算器
//计算器抽象类(虚基类)
class counter{
public:
int a;
int b;
virtual int getResult(){
return 0;
}
virtual ~counter(){
}
};
//加法计算器
class Addcounter:public counter{
public:
int getResult(){
return a+b;
}
};
//减法计算器
class Subcounter:public counter{
public:
int getResult(){
return a-b;
}
};
//乘法计算器
class mulcounter:public counter{
public:
int getResult(){
return a*b;
}
};
//不同计算写成不同的类,结构清晰,可读性强
void func(){
/*多态使用的条件
有继承关系;
父类指针指向子类对象*/
counter *c=new Addcounter;//堆区开辟
c->a=10;
c->b=20;
cout<<c->a<<"+"<<c->b<<"="<<c->getResult()<<endl;
delete c;//用完记得手动销毁
c=new Subcounter;
c->a=10;
c->b=20;
cout<<c->a<<"-"<<c->b<<"="<<c->getResult()<<endl;
delete c;
c=new mulcounter;
c->a=10;
c->b=20;
cout<<c->a<<"*"<<c->b<<"="<<c->getResult()<<endl;
delete c;
}
int main(){
func();
return 0;
}
如果未使用多态,我们通常会使用条件语句来做计算器,但是当你需要修改计算器,或者添加功能时你就需要去修改源代码。而使用多态,若需要修改只需修改有错误的类,其他的不用动,如果需要扩展功能也不需要修改源码,只需添加子类即可。
3.纯虚函数和抽象类
上文我们讲到了在父类中写虚函数来实现多态,这时我们会发现,父类的虚函数内容失去了实际意义,这时候可以把它写成纯虚函数。
语法:virtual 返回值类型 函数名(参数列表)=0;
一个类只要写了一个纯虚函数,就成为抽象类,有以下两个特点:
1.抽象类无法实例化对象
2.抽象类的子类如果不重写纯虚函数,则也为抽象类
#include<iostream>
using namespace std;
class father{
public:
virtual void func()=0;//纯虚函数
virtual ~father(){};//为了防止释放堆区数据时的警告
};
class son1:public father{
public:
void func(){
cout<<"son1-func()调用"<<endl;
}
};
class son2:public father{
public:
void func(){
cout<<"son2-func()调用"<<endl;
}
};
int main(){
//father f;不可实例化对象
father *f=new son1;
f->func();
delete f;
f=new son2;
f->func();
delete f;
return 0;
}
注意:子类一定要重写父类纯虚函数,否则无法实例化
下面通过一个实例进行演示,制作饮品,大致流程:煮水->冲泡->倒入杯中->加入佐料,实现不同饮品的制作
#include<iostream>
using namespace std;
class AbstractDrinking{
public:
virtual void boil()=0;
virtual void brew()=0;
virtual void Pour()=0;
virtual void addsomething()=0;
virtual ~AbstractDrinking(){}
void makeDrinking(){
boil();
brew();
Pour();
addsomething();
}
};
class coffee:public AbstractDrinking{
public:
void boil(){
cout<<"煮个水先"<<endl;
}
void brew(){
cout<<"冲泡咖啡"<<endl;
}
void Pour(){
cout<<"倒入咖啡杯"<<endl;
}
void addsomething(){
cout<<"加入糖和牛奶"<<endl;
}
};
class tea:public AbstractDrinking{
public:
void boil(){
cout<<"先煮个水"<<endl;
}
void brew(){
cout<<"冲泡茶叶"<<endl;
}
void Pour(){
cout<<"倒入茶杯"<<endl;
}
void addsomething(){
cout<<"加入枸杞"<<endl;
}
};
void doDrinking(AbstractDrinking *abs){
abs->makeDrinking();
delete abs;
}
int main(){
doDrinking(new coffee);
cout<<"******************************"<<endl;
doDrinking(new tea);
return 0;
}
4.多态原理剖析
父类中的函数成为虚函数时,其内部结构是一个虚函数指针指向一个虚函数表,虚函数表内部写记录了父类定义域下的函数地址;而子类继承父类后,其内部结构同父类
图片来自B站黑马程序员《黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难》
而当子类重写父类虚函数时,其虚函数表内部会被替换成子类虚函数地址
而当我们通过父类指针创建子类对象时,即发生多态,调用函数,就会指向对应子类的虚函数表中的地址,这个过程实在运行阶段实现的,所以这时函数实现了晚绑定
图片来自B站黑马程序员《黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难》
5.最后通过一个案例
实现多台电脑组装,具体实现就是结合上述知识,示例代码
#include<iostream>
using namespace std;
class Cpu{
public:
//抽象计算函数
virtual void calculate()=0;
virtual ~Cpu(){
// cout<<"Cpu的析构函数调用"<<endl;
}
};
class Gpu{
public:
//抽象显示函数
virtual void display()=0;
virtual ~Gpu(){
// cout<<"Gpu的析构函数调用"<<endl;
}
};
class Memory{
public:
//抽象存储函数
virtual void storge()=0;
virtual ~Memory(){
// cout<<"Memory的析构函数调用"<<endl;
}
};
class Computer{
public:
Computer(Cpu *cpu,Gpu *gpu,Memory *memory){
this->cpu=cpu;
this->gpu=gpu;
this->memory=memory;
}
void dowork(){
cpu->calculate();
gpu->display();
memory->storge();
}
~Computer(){
if(cpu!=NULL){
delete cpu;
cpu=NULL;
}
if(gpu!=NULL){
delete gpu;
gpu=NULL;
}
if(memory!=NULL){
delete memory;
memory=NULL;
}
}
private:
Cpu *cpu;
Gpu *gpu;
Memory *memory;
};
class intelCpu:public Cpu{
void calculate(){
cout<<"intel的cpu正在计算"<<endl;
}
~intelCpu(){
// cout<<"intelCpu的析构函数调用"<<endl;
}
};
class intelGpu:public Gpu{
void display(){
cout<<"intel的Gpu正在显示"<<endl;
}
~intelGpu(){
// cout<<"intelGpu的析构函数调用"<<endl;
}
};
class intelMemory:public Memory{
void storge(){
cout<<"intel的内存条正在储存"<<endl;
}
~intelMemory(){
// cout<<"intelMemory的析构函数调用"<<endl;
}
};
class lenovoCpu:public Cpu{
void calculate(){
cout<<"lenovo的cpu正在计算"<<endl;
}
~lenovoCpu(){
// cout<<"lenovoCpu的析构函数调用"<<endl;
}
};
class lenovoGpu:public Gpu{
void display(){
cout<<"lenovo的Gpu正在显示"<<endl;
}
~lenovoGpu(){
// cout<<"lenovoGpu的析构函数调用"<<endl;
}
};
class lenovoMemory:public Memory{
void storge(){
cout<<"lenovo的内存条正在储存"<<endl;
}
~lenovoMemory(){
// cout<<"lenovoMemory的析构函数调用"<<endl;
}
};
int main(){
Computer *c1=new Computer(new intelCpu,new intelGpu,new intelMemory);
c1->dowork();
delete c1;
cout<<"*******************************************************"<<endl;
c1=new Computer(new lenovoCpu,new lenovoGpu,new lenovoMemory);
c1->dowork();
delete c1;
cout<<"*******************************************************"<<endl;
c1=new Computer(new intelCpu,new lenovoGpu,new intelMemory);
c1->dowork();
delete c1;
return 0;
}