(135) 多态
多态分为两类
- 静态多态:函数重载 和 运算符重载 ,复用函数名
- 动态多态:子类和
虚函数
实现运行时多态
静态多态 和 动态多态 区别:
- 静态多态的地址早绑定-- 编译阶段确定函数地址
- 动态多态的地址晚绑定-- 运行阶段确定函数地址
#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 &animal = cat;
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat); // 走动物在说话还是猫在说话?
}
int main()
{
test01();
return 0;
}
问题:
结果是动物在说话还是猫在说话?
结果是动物在说话。????
2、如果想执行猫在说话,怎么做?
答:animal 类中的 speak函数 前加 virtual
#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 &animal = cat;
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat); // 走动物在说话还是猫在说话?
}
int main()
{
test01();
return 0;
}
运行结果:
小猫在说话
3、加了狗类
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
// 执行说话的函数
// 地址早绑定,在编译阶段确定函数地址
// 如果想执行猫在说话,那么这个函数地址不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(Animal &animal) // Animal &animal = cat;
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat); // 走动物在说话还是猫在说话?
Dog dog;
doSpeak(dog);
}
运行结果:
小猫在说话
小狗在说话
动态多态满足条件:
- 有继承关系
- 子类重写父类虚函数 (本题speak函数)
重写:函数返回值类型 函数名 参数列表完全相同
动态多态使用
父类的 指针或引用 指向 子类对象
,本题就是
多态的目的:
封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了“接口重用”。也即,不论传递过来的究竟是类的哪个对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
多态:不同对象接收相同消息时产生不同的动作
虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。
(136) 多态原理分析
#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 &animal = cat;
{
animal.speak();
}
void test02()
{
cout << "sizeof Animal = " << sizeof(Animal) << endl;
}
int main()
{
test02();
return 0;
}
运行结果:
sizeof Animal = 1
为什么?
加了关键字 virtual后,即
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
运行结果:
sizeof Animal = 4
为什么?
这4个字节是指针
当子类重写父类的虚函数后,子类中的虚函数表 内部 会替换成 子类的虚函数地址
在父类 Animal 的内部结构中,虚函数指针(vfptr)指向 虚函数表(vftable),在虚函数表中存储 父类虚函数的地址(&Animal::speak())
当发生继承时,子类(Cat)虚函数表中存储 父类虚函数的地址(&Animal::speak())
当发生多态时,子类的虚函数表中的 父类虚函数的地址 将被替换成 子类自己的虚函数地址(&Cat::speak())
注: 当父类的指针 或 引用 指向 子类的对象时,就发生了多态
替换后
子类替换自己的 虚函数表
,即 &Animal::speak 替换成 &Cat:: speak ,不会替换父类的 虚函数表
`
当父类指针或引用 指向 子类对象时,发生多态`。即
Animal & animal = cat;
animal.speak();
当子类重写虚函数时,子类会将 自身虚函数表中 父类的虚函数地址 替换成 子类的虚函数地址
调用cat后,到子类的虚函数表,确定函数入口,
(137) 多态案例-- 计算器类
分别利用 普通写法和多态 技术,实现两个操作数进行运算的 计算器类
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期扩展以及维护
普通写法:
#include <iostream>
#include<string>
using namespace std;
// 普通写法
class Calculaator
{
public:
int getResult(string oper)
{
if (oper == "+")
{
return m_num1 + m_num2;
}
else if (oper=="-")
{
return m_num1 - m_num2;
}
else if (oper == "*")
{
return m_num1 * m_num2;
}
}
int m_num1;
int m_num2;
};
void test01()
{
Calculaator c;
c.m_num1 = 10;
c.m_num2 = 10;
cout << c.m_num1 << " + " << c.m_num2 << " = " << c.getResult("+") << endl;
cout << c.m_num1 << " - " << c.m_num2 << " = " << c.getResult("-") << endl;
}
int main()
{
test01();
return 0;
}
运行结果:
10 + 10 = 20
10 - 10 = 0
10 * 10 = 100
存在的问题:
如果想扩展新的功能(比如增加除法),需要修改源码(本题改getResult函数)
在真实开发中,提倡 开闭原则,即 对扩展进行开放,对修改进行关闭
用多态改写:
#include <iostream>
#include<string>
using namespace std;
// 多态实现
class AbstractCalculator // 类名后没有括号
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
// 加法计算机类
class AddCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 + m_Num2;
}
};
// 减法计算器类
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
void test02()
{
AbstractCalculator* abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
// 用完后记得销毁
delete abc;
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
}
int main()
{
test02();
return 0;
}
运行结果:
10 + 10 = 20
10 - 10 = 0
多态实现的好处:
1、组织结构清晰;都是一个个 独立出的小块,便于改写;
2、对于前期和后期 扩展和维护性高
138 纯虚函数 和 抽象类
在多态中,通常父类中 虚函数的实现是毫无意义的,主要是 调用子类重写的内容
因此可以将虚函数 改为纯虚函数
。
纯虚函数语法: virtual 返回值类型 函数名 (参数列表)=0;
当类中有纯虚函数,这个类称为 抽象类
。
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的虚函数,否则属于抽象类
// 138 纯虚函数 和 抽象类
#include <iostream>
using namespace std;
class Base
{
public:
// 纯虚函数
// 抽象类特点
// 1、无法实例化对象
virtual void func() = 0;
};
int main()
{
Base b; // 在栈上 报错
new Base; // 在堆上 报错
return 0;
}
2、子类不重写父类的纯虚函数,也是抽象类,无法实例化
class Base
{
public:
// 纯虚函数
// 抽象类特点
// 1、无法实例化对象
// 2、抽象类的子类 必须重写父类的纯虚函数,否则也属于 抽象类
virtual void func() = 0;
};
class Son :public Base
{
public:
};
int main()
{
Son a;
return 0;
}
3、 抽象类的子类 必须重写父类的纯虚函数,否则也属于 抽象类
#include <iostream>
using namespace std;
class Base
{
public:
// 纯虚函数
// 抽象类特点
// 1、无法实例化对象
// 2、抽象类的子类 必须重写父类的纯虚函数,否则也属于 抽象类
virtual void func() = 0;
};
class Son :public Base
{
public:
virtual void func() {
cout << "func函数调用" << endl;
}
};
void test01()
{
Base* base = new Son;
base->func();
}
int main()
{
test01();
return 0;
}
运行结果:
func函数调用