多态顾名思义就是有多种状态,我们之前学的函数重载以及运算符重载就是多态的一种表现形式。
多态是C++面向对象三大特性之一
多态分为两类:
1、静态多态:函数重载和运算符重载属于静态多态,复用函数名
//一个动物的基类
class animal
{
public:
void speak()
{
cout << "动物在叫" << endl;
}
};
//狗的派生类
class dog :public animal
{
public:
void speak()
{
cout << "小狗在叫" << endl;
}
};
//执行叫的一个函数
//地址早绑定,在编译阶段就确定了函数的地址
void dospeak(animal &animal)
{
animal.speak();
}
//测试函数
void test05()
{
dog d1;
dospeak(d1);
}
2、动态多态:派生类和虚函数实现运行时多态:
下列的样例就是通过virtual方法来实现地址晚绑定。
//一个动物的基类
class animal
{
public:
//虚函数
virtual void speak()
{
cout << "动物在叫" << endl;
}
};
动态多态需要满足什么条件呢?
1、需要有继承关系
2、子类要重写父类的虚函数
3、函数返回值的类型、函数的名称、参数列表要完全相同(这种方法也称为重写)。
动态多态的使用方法:
父类的指针或者引用指向子类的对象
样例如下:
void dospeak(animal &animal) //animal &animal = dog
那么静态多态和动态多态又有什么区别呢?
1、静态多态的函数地址早绑定,编译阶段就确定了函数地址
2、动态多态的函数地址晚绑定,在运行阶段才确定函数地址
还是上一节的代码为例:
class animal
{
public:
virtual void speak()
{
cout << "动物在叫" << endl;
}
};
当创建上一个类的时候,会有一个四个字节大小的指针,这个指针被叫做“vfptr”,这个vfptr是virtual function pointer的缩写,也成为虚函数(表)指针。这个指针指向的是一个虚函数表。这个虚函数表称为“vftable”。该表的内部记录的是一个虚函数地址,其具体结构如下:
animal::speak \\记录该函数的入口地址
当创建一个dog这样的子类时(没有发生重写的情况下),会将父类中所有的内容全部都复制过来一份,同时也复制了父类中的虚函数指针。并且指向了子类中的一个虚函数表,该表中记录的内容与父类中的虚函数表的内容一致。
class dog :public animal
{
public:
};
当dog这样的子类发生重写的情况下,子类中继承父类的虚函数表会替换成子类中的虚函数地址:&cat::speak
,同时,父类中的虚函数地址并没有发生重写。当父类指针或者引用指向子类对象时,则发生多态:animal &animal = cat;
当调用aninal,speak()
时,由于其指向的是一个cat对象,会在cat的虚函数表中寻找这一虚函数,以上的操作是在运行阶段发生的多态。
class dog :public animal
{
public:
virtual void speak()
{
cout << "小狗在叫" << endl;
}
};
在c++编程时,提倡的是一个开闭的原则:对扩展进行开放,对修改进行闭合。具体的样例如下:
//计算器的基类
class BaseCalculator
{
public:
//虚函数抽象出计算函数
virtual int GetReult()
{
return 0;
}
public:
int m_num1;
int m_num2;
};
//加法
class add:public BaseCalculator
{
public:
int GetReult()
{
return m_num1 + m_num2;
}
};
//减法
class sub :public BaseCalculator
{
public:
int GetReult()
{
return m_num1 - m_num2;
}
};
//乘法
class mul :public BaseCalculator
{
public:
int GetReult()
{
return m_num1 * m_num2;
}
};
void test06()
{
//实现加法运算
BaseCalculator * bc = new add;
bc->m_num1 = 10;
bc->m_num2 = 10;
cout << bc->m_num1 << "+" << bc->m_num2 << "=" << bc->GetReult() << endl;
delete bc;
//实现减法运算
bc = new sub;
bc->m_num1 = 10;
bc->m_num2 = 10;
cout << bc->m_num1 << "-" << bc->m_num2 << "=" << bc->GetReult() << endl;
delete bc;
//实现乘法运算
bc = new mul;
bc->m_num1 = 10;
bc->m_num2 = 10;
cout << bc->m_num1 << "*" << bc->m_num2 << "=" << bc->GetReult() << endl;
delete bc;
}
以上为通过多态来实现计算器的样例,从上述样例中我们可以看出,多态有如下好处:
1、组织结构清晰;
2、可读性很强;
3、对于前期和后期扩展以及维护性高
从上述案例我们可以看出,在多态中,通常基类中虚函数的实现是毫无意义的,主要都是调用子类冲写的内容,因此可以将虚函教改为纯虚函数,纯虚函数语法:virtual 返回值类型 函数名〔参数列表) = 0
。当类中有了纯虚函数,这个类也称为抽象类,抽象类有如下特点:
1、无法实例化对象
具体样例如下:
class Base
{
public:
virtual void func() = 0;
};
void test07()
{
Base b; //抽象类无法实例化对象
new Base; // 抽象类无法实例化对象
}
2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类
样例如下:
class Base
{
public:
virtual void func() = 0;
};
class Son:public Base
{
public:
};
void test07()
{
Son s; //抽象类无法实例化对象
}
//制作主食的基类函数
class Foot
{
public:
//放水
virtual void water() = 0;
//洗米
virtual void wash() = 0;
//放入锅中
virtual void pot() = 0;
//加热
virtual void warm() = 0;
//开始做饭
void dowork()
{
water();
wash();
pot();
warm();
}
};
//做大米饭
class rice :public Foot
{
public:
virtual void water()
{
cout << "正在放水" << endl;
}
virtual void wash()
{
cout << "正在洗米" << endl;
}
virtual void pot()
{
cout << "放入锅中" << endl;
}
//加热
virtual void warm()
{
cout << "加热" << endl;
}
};
//做小米饭
class millet :public Foot
{
public:
virtual void water()
{
cout << "锅中正在放水" << endl;
}
virtual void wash()
{
cout << "正在洗小米" << endl;
}
virtual void pot()
{
cout << "小米放入锅中" << endl;
}
//加热
virtual void warm()
{
cout << "加热小米" << endl;
}
};
void cook(Foot *f)
{
f->dowork();
delete f;
}
void test08()
{
cout << "正在做米饭" << endl;
//做米饭
cook(new rice);
cout << "正在做稀饭" << endl;
//做稀饭
cook(new millet);
}
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,这时候我们就需要将父类中的析构函数改为虚析构或者纯虚析构。
虚析构和纯虚析构共性:
1、可以解决父类指针释放子类对象
2、都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名()= 0;
类名:: ~类名()
{
//函数体实现
}
具体实现样例如下:
class Animal
{
public:
virtual ~Animal()
{
cout << "这是父类的一个虚析构" << endl;
}
virtual void speak() = 0;
};
Animal:: ~Animal()
{
cout << "这是父类的一个纯虚析构" << endl;
}
class Sheep :public Animal
{
public:
Sheep(string name)
{
m_Name = new string(name);
}
virtual void speak()
{
cout << *m_Name << "小羊在说话" << endl;
}
~Sheep()
{
if (m_Name != NULL)
{
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
void test09()
{
Animal *a = new Sheep("duoli");
a->speak();
delete a;
}
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类
//抽象一个CPU
class CPU
{
public:
virtual void calaculate() = 0;
};
//抽象一个显卡
class VideoCard
{
public:
virtual void display() = 0;
};
//抽象一个内存条类
class Memory
{
public:
virtual void Mom() = 0;
};
//组装一个电脑
class Computer
{
public:
//初始化电脑
Computer(CPU * cpu, VideoCard * video, Memory * memory)
{
m_cpu = cpu;
m_video = video;
m_memory = memory;
}
void work()
{ //CPU开始工作
m_cpu->calaculate();
//显卡开始工作
m_video->display();
//内存条开始工作
m_memory->Mom();
}
Computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu == NULL;
}
if (m_video != NULL)
{
delete m_video;
m_video == NULL;
}
if (m_memory != NULL)
{
delete m_memory;
m_memory == NULL;
}
}
private:
CPU * m_cpu;
VideoCard * m_video;
Memory * m_memory;
};
//创建因特尔的CPU
class intercpu:public CPU
{
public:
void calaculate()
{
cout << "因特尔的CPU开始计算了" << endl;
}
};
//创建因特尔的显卡
class intervideocard :public VideoCard
{
public:
void display()
{
cout << "因特尔的显卡开始显示了" << endl;
}
};
//创建因特尔的内存条
class intermemory :public Memory
{
public:
void Mom()
{
cout << "因特尔的内存条开始存储了" << endl;
}
};
//创建英伟达的CPU
class nvidiacpu :public CPU
{
public:
void calaculate()
{
cout << "英伟达的CPU开始计算了" << endl;
}
};
//创建英伟达的显卡
class nvidiavideocard :public VideoCard
{
public:
void display()
{
cout << "英伟达的显卡开始显示了" << endl;
}
};
//创建英伟达的内存条
class nvidiamemory :public Memory
{
public:
void Mom()
{
cout << "英伟达的内存条开始存储了" << endl;
}
};
//开始组装电脑
void test10()
{
//第一台电脑
CPU * interc = new intercpu;
VideoCard * interv = new intervideocard;
Memory * interm = new intermemory;
Computer * c1 = new Computer(interc, interv, interm);
c1->work();
delete c1;
//第二台电脑
cout << "第二台电脑开始工作" << endl;
CPU * nvidiac = new nvidiacpu;
VideoCard * nvidiav = new nvidiavideocard;
Memory * nvidiam = new nvidiamemory;
Computer * c2 = new Computer(new nvidiacpu, new nvidiavideocard, new nvidiamemory);
c2->work();
delete c2;
}