多态是C++面向对象三大特性之一
多态分为两类
静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
静态多态的函数地址早绑定-编译阶段确定函数地址
动态多态的函数地址晚绑定-运行阶段确定函数地址
动态多态
1.有继承关系
2.子类要重写父类虚函数
这里的重写是要 函数名一样,函数返回类型一样,函数参数列表一样
动态多态使用
父类的指针或者引用,指向子类对象
注意:
1.虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候
2.虚函数是动态绑定(多态动态)的基础,虚函数必须是非静态成员函数的,虚函数经过派生之后,在类族中就可以实现运行过程中的多态。
3.动态多态由成员函数来调用或者是通过指针、引用来访问虚函数。
如果通过是使用对象名来访问虚函数,则绑定过程中进行的是静态绑定,而无须在运行过程中进行。
#include<iostream>
#include<string>
using namespace std;
//动物类
class Animal{
//如果不写public,那么成员变量会默认为私有属性
public:
void speak() {
cout << "动物在说话" << endl;
}
};
//羊类
class Cat: public Animal {
public:
void speak() {
cout << "小猫在说话" << endl;
}
};
//地址早绑定 在编译阶段确定函数地址
//如果想让猫说话,这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
//解决方法,在speak 函数前加virtual
void doSpeak(Animal&animal){
animal.speak();
}
void test01() {
Cat cat;
doSpeak(cat);
}
int main() {
test01();
return 0;
}
//地址早绑定 在编译阶段确定函数地址
//如果想让猫说话,这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
//解决方法,在猫类的speak 函数前加virtual
#include<iostream>
#include<string>
using namespace std;
//动物类
class Animal{
//如果不写public,那么成员变量会默认为私有属性
public:
virtual void speak() {
cout << "动物在说话" << endl;
}
};
//羊类a
class Cat: public Animal {
public:
void speak() {
cout << "小猫在说话" << endl;
}
};
//驼类
class Tuo :virtual public Animal {
public:
};
void doSpeak(Animal&animal){
animal.speak();
}
void test01() {
Cat cat;
doSpeak(cat);
//当出现菱形继承的时候,有两个父类具有相同的数据需要加作用域区分
}
int main() {
test01();
return 0;
}
多态的原理剖析
在以上代码种,类Animal的大小是4字节表示的是指针
vfptr 虚函数指针指向虚函数表
表的内部有记录虚函数地址
&Animal::speak()
cat类内
也是虚函数指针指向虚函数表然后
表内有继承过来的动物的虚函数的地址,但是如果发生函数重写了之后,重写后的函数就会覆盖之前继承过来的函数
当父类的指针或者引用指向子类对象的时候发生多态
Animal & animal =cat;
animal.speak();
案例1
分别利用普通写法和多态技术实现计算器
普通写法要扩展运算器的运算功能需要加代码
#include<iostream>
#include<string>
using namespace std;
//动物类
class Calculator{
friend void test01();
public:
int getResult(string oper) {
if (oper == "+") {
return m_Num1 + m_Num2;
}
else if (oper == "-") {
return m_Num1 + m_Num2;
}
}
private: int m_Num1, m_Num2;
};
void test01() {
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult("+") << endl;
}
int main() {
test01();
return 0;
}
而在真实的编程中提倡开闭原则
开闭原则,对扩展进行开放,对修改进行关闭!!
#include<iostream>
#include<string>
using namespace std;
//实现计算器抽象类
class AbstractCalculator {
public:
virtual int getResult() {
return 0;
}
int m_Nmu1;
int m_Num2;
};
//加法计算器类
class AddCal :public AbstractCalculator {
virtual int getResult() {
return m_Nmu1 + m_Num2;
}
};
//减法计算器类
class MulCal :public AbstractCalculator {
virtual int getResult() {
return m_Nmu1 - m_Num2;
}
};
void test02() {
//多态使用条件
//父类指针或引用指向子类对象
AbstractCalculator*abc = new AddCal;
abc->m_Num2 = 100;
abc->m_Nmu1 = 100;
cout << abc->m_Nmu1 << "+" << abc->m_Nmu1 << "=" << abc->getResult() << endl;
//堆区数据用完后销毁
delete abc;
}
int main() {
test02();
return 0;
}
纯虚函数和抽象类
父类中的虚函数好像没啥用
virtual 返回值类型 函数名(参数列表)=0;
当类中有了纯虚函数,这个类也成为抽象类
#include<iostream>
using namespace std;
class Base {
public:
//只要有一个纯虚函数这个类就叫抽象类
//抽象类无法实例化对象
//抽象类的子类必须要重写父类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;//纯虚函数
};
class Son :public Base {
public:
virtual void func() {
cout << "func函数调用" << endl;
}
};
void test01() {
Son s;
s.func();
Base *base = new Son;//这个才是动态多态
base->func();
delete base;
}
int main() {
test01();
return 0;
}
案例二 泡茶
#include<iostream>
#include<string>
using namespace std;
//实现计算器抽象类
class AbstractDrink {
public:
//煮水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//放入佐料
virtual void Pours() = 0;
void makedrink() {//建立一个能够调用所有函数的函数
Boil();
Brew();
PourInCup();
Pours();
}
};
class greentea:public AbstractDrink {
public:
virtual void Boil() {
cout << "煮矿泉水" << endl;
}
virtual void Brew() {
cout << "冲泡茶叶" << endl;
}
virtual void PourInCup() {
cout << "倒入杯中" << endl;
}
virtual void Pours() {
cout << "加入枸杞" << endl;
}
};
void dowork(AbstractDrink*abc) {//其实也可以直接在后面的函数里直接调用成员函数
abc->makedrink();
delete abc;
}
void test01() {
AbstractDrink *g = new greentea;
dowork(g);
//也可以直接在括号里新建对象
//dowork(new greentea);//这个有点类似于匿名对象,但是这个是在堆区开辟了数据的,需要手动释放。
}
int main() {
test01();
return 0;
}
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方法,将父类中的析构函数改为虚析构或纯虚析构
虚析构和纯虚析构的共性
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯虚析构区别
如果是纯虚析构,该类属于抽象类,无法实例化对象
virtual ~Animal()=0;
Animal::~Animal(){}
虚析构和纯虚析构都要有代码的实现
virtual~Animal(){cout<<}