目录
计算机程序运行时,相同的消息可能会送给多个不同的类别之对象,而系统可依据对象所属类别,引发对应类别的方法,而有不同的行为。简单来说,所谓多态意指相同的消息给予不同的对象会引发不同的动作。
多态也可定义为“一种将不同的特殊行为和单个泛化记号相关联的能力”。
多态可分为变量多态与函数多态。变量多态是指:基类型的变量(对于C++是引用或指针)可以被赋值基类型对象,也可以被赋值派生类型的对象。函数多态是指,相同的函数调用界面(函数名与实参表),传送给一个对象变量,可以有不同的行为,这视该对象变量所指向的对象类型而定。因此,变量多态是函数多态的基础。?
多态是c++面向对象的三大特性之一
??????函数重载 和 运算符重载 属于静态多态,复用函数名
?????静态多态(static polymorphism):模板也允许将不同的特殊行为和单个泛化记号相关联,由于这种关联处理于编译期而非运行期,因此被称为“静态”。可以用来实现类型安全、运行高效的同质对象集合操作。C++STL不采用动态多态来实现就是个例子。
函数重载(Function Overloading)
运算符重载(Operator Overloading)
带变量的宏多态(macro polymorphism)
非参数化多态或译作特设多态(Ad-hoc polymorphism):
参数化多态(Parametric polymorphism):把类型作为参数的多态。在面向对象程序设计中,这被称作泛型编程。
????派生类 和 虚函数 实现运行时多态
? ?动态多态(dynamic polymorphism):通过类继承机制和虚函数机制生效于运行期。可以优雅地处理异质对象集合,只要其共同的基类定义了虚函数的接口。也被称为子类型多态(Subtype polymorphism)或包含多态(inclusion polymorphism)。在面向对象程序设计中,这被直接称为多态。
对于C++语言,带变量的宏和函数重载(function overload)机制也允许将不同的特殊行为和单个泛化记号相关联。然而,习惯上并不将这种函数多态(function polymorphism)、宏多态(macro polymorphism)展现出来的行为称为多态(或静态多态),否则就连C语言也具有宏多态了。谈及多态时,默认就是指动态多态,而静态多态则是指基于模板的多态。
????????1. 静态多态的函数地址早绑定,编译阶段确定函数地址
????????2. 动态多态的函数地址晚绑定,运行阶段确定函数地址
????????1.有继承关系
????????2.子类中重写父类的虚函数(子类中写不写virtual都可以)
????????函数返回值类型? 函数名? 参数列表都完全相同。
????????注意:与函数重载要区分。
????????父类的指针或引用? 执行子类对象
示例:
#include<iostream>
using namespace std;
// 多态
?
// 动态多态的满足条件
// 1. 有继承关系
// 2. 子类要重写父类的虚函数
?
// 动态多态的使用
// 父类的指针或引用? 指向子类对象
?
// 动物类
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 & animal? =? cat;
{
// 地址早绑定? 在编译阶段确定函数地址
?
// 如果要执行猫说话,那么这个函数地址就不能提前绑定
// 需要在运行阶段进行绑定,地址晚绑定
animal.speak();
}
?
// c++允许父类和子类的转化
void test01()
{
Cat cat;
// cat.speak();
dospeak(cat);? // 父类的引用,接收的是子类的对象
?
Dog dog;
dospeak(dog);
}
?
int main()
{
test01();
return 0;
}
没写 virtual 之前是地址早绑定
写入虚函数后,地址晚绑定运行结果:
?
图解示例:
示例:
#include<iostream>
using namespace std;
// 多态的底层逻辑
// 动物类
class Animal
{
public:
// 虚函数
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();
}
// c++允许父类和子类的转化
void test01()
{
Cat cat;
// cat.speak();
dospeak(cat);? // 父类的引用,接收的是子类的对象
Dog dog;
dospeak(dog);
}
void test02()
{
cout << "Animal 类的大小:" << sizeof(Animal) << endl;
}
int main()
{
test01();
test02();
return 0;
}
通过命令提示符查看
加入虚函数后
子类在没有重写的时候
子类重写虚函数后
没加virtual的类的大小,相当于空类,大小是1
变为虚函数后的类的大小为8/4(看你的编译器是多少位的),类的大小是一个指针的大小
关于命令提示符查看类可查询上一篇文章:命令提示符查看类
多态的优点:
示例1:
#include<iostream>
using namespace std;
// 分别利用普通写法和多态的技术实现计算器的功能
?
// 普通的写法
class Calculator
{
public:
int get_result(string oper)
{
if(oper == "+")
return m_num1+m_num2;
if(oper == "-")
return m_num1 - m_num2;
if(oper == "*")
return m_num1 * m_num2;
if(oper == "/")
return m_num1 / m_num2;
}
int m_num1;
int m_num2;
};
void test01()
{
Calculator c;
c.m_num1 = 40;
c.m_num2 = 20;
?
// 开闭原则,对扩展进行开放,对修改进行关闭
?
cout<<c.m_num1<<" + "<<c.m_num2<<" = "<<c.get_result("+")<<endl;
cout<<c.m_num1<<" - "<<c.m_num2<<" = "<<c.get_result("-")<<endl;
cout<<c.m_num1<<" * "<<c.m_num2<<" = "<<c.get_result("*")<<endl;
cout<<c.m_num1<<" / "<<c.m_num2<<" = "<<c.get_result("/")<<endl;
}
int main()
{
test01();
return 0;
}
运行结果:
示例2:
#include<iostream>
using namespace std;
// 利用多态实现计算器
class Abstract_Calculator
{
public:
virtual int get_result()
{
return 0;
}
int num1;
int num2;
};
?
// 设计一个加法计算器的类
class Add_Calculator:public Abstract_Calculator
{
public:
int get_result()
{
return num1+num2;
}
};
// 设计一个减法计算器的类
class Sub_Calculator:public Abstract_Calculator
{
public:
int get_result()
{
return num1-num2;
}
};
// 设计一个乘法计算器的类
class Mul_Calculator:public Abstract_Calculator
{
public:
int get_result()
{
return num1*num2;
}
};
?
void test02()
{
// 多态的使用条件
// 父类的指针或引用指向子类对象
// 加法运算
Abstract_Calculator *abc = new Add_Calculator;
abc->num1 = 100;
abc->num2 = 200;
cout<<abc->num1<<" + "<<abc->num2<<" = "<<abc->get_result()<<endl;
// 堆区数据用完记得销毁
delete abc;
?
// 减法运算
abc = new Sub_Calculator;
abc->num1 = 200;
abc->num2 = 100;
cout<<abc->num1<<" - "<<abc->num2<<" = "<<abc->get_result()<<endl;
delete abc;
?
// 乘法运算
abc = new Mul_Calculator;
abc->num1 = 200;
abc->num2 = 100;
cout<<abc->num1<<" * "<<abc->num2<<" = "<<abc->get_result()<<endl;
delete abc;
}
int main()
{
test02();
return 0;
}
运行结果:
在多态中,通常父类中虚函数的实现是毫无意义的,主要是调用子类的重写内容
因此可以将虚数改为纯虚数
纯虚数语法: virtual 返回值类型 函数名(参数列表) =? 0 ;
当类中有了纯虚函数,这个类称为抽象类
示例:
#include<iostream>
using namespace std;
// 纯虚数抽象类
class Base
{
public:
virtual void func() = 0; // 虚函数的基础上才可以等于0,纯虚函数
// 只要有一个纯虚数,这个类称为抽象类
};
?
// 抽象类的子类,必须要重写父类中的纯虚函数,否则也是抽象类,无法实例化对象
class Son:public Base
{
public:
void func()
{
cout<<"func函数调用"<<endl;
}
};
void test01()
{
// Base b; 无法实例化一个抽象类
// new 一个也不行
Base *base = new Son;
base->func();
}
int main()
{
test01();
return 0;
}
运行结果:
案例描述:利用多态技术,提供抽象类制作饮品基类,提供子类制作咖啡和茶叶
示例:
#include<iostream>
using namespace std;
class Abstract_Drinking
{
public:
??? // 煮水
??? virtual void Boil() = 0;
??? // 冲泡
??? virtual void Brew() = 0;
??? // 倒入杯中
??? virtual void Pour_Cup() = 0;
??? // 加入辅助佐料
??? virtual void Put_something() = 0;
??? // 制作饮品
??? void make_drink()
??? {
Boil();
??????? Brew();
??????? Pour_Cup();
??????? Put_something();
??? }
};
?
// 具体的制作
// 咖啡
class Coffee:public Abstract_Drinking
{
public:
??? // 煮水
??? virtual void Boil()
??? {
???? ????????cout<<"煮矿泉水"<<endl;
??? }
??? // 冲泡
??? virtual void Brew()
??? {
cout<<"冲泡咖啡"<<endl;
??? }
??? // 倒入杯中
??? virtual void Pour_Cup()
??? {
cout<<"倒入杯中"<<endl;
??? }
??? // 加入辅助佐料
??? virtual void Put_something()
??? {
cout<<"加入糖和牛奶"<<endl;
??? }
};
?
// 茶
class Tea:public Abstract_Drinking
{
public:
??? // 煮水
??? virtual void Boil()
??? {
???? ????????cout<<"煮矿泉水"<<endl;
??? }
??? // 冲泡
??? virtual void Brew()
??? {
cout<<"冲泡茶叶"<<endl;
??? }
??? // 倒入杯中
??? virtual void Pour_Cup()
??? {
cout<<"倒入杯中"<<endl;
??? }
??? // 加入辅助佐料
??? virtual void Put_something()
??? {
cout<<"加入柠檬,枸杞"<<endl;
??? }
};
?
// 制作
void do_work(Abstract_Drinking * abd)
??? // Abstract_Drinking *abd = new Coffee? 父类指针指向子类对象
{
abd->make_drink();
??? delete abd;// 在堆区制作完记得释放
}
?
void test01()
{
// 制作咖啡
??? do_work(new Coffee);
?
cout<<"-------------"<<endl;
??? // 制作茶
??? do_work(new Tea);
}
int main()
{
test01();
return 0;
}
运行结果:
问题:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
解决办法:将父类中的析构函数改为**虚析构**或者**纯虚析构**
**虚析构语法:**
virtual ~类名(){}
**纯虚析构语法:**
virtual ~类名()= 0;
类名::~类名(){} // 类内声明,类外实现
示例:
#include<iostream>
using namespace std;
// 虚析构和纯虚析构
class Animal
{
public:
Animal()
{
cout<<"Animal的构造函数的调用"<<endl;
}
virtual void speak() = 0;// 变为纯虚函数
?
// 利用虚析构可以解决? 父类指针释放子类对象时不干净的问题
/*
virtual ~Animal()
{
cout<<"Animal虚析构函数的调用"<<endl;
}
*/
?
virtual ~Animal() = 0;
// 纯虚析构
// 需要声明? 也需要实现
// 有了纯虚析构之后,那么这个类也属于抽象类,无法实现实例化对象
};
Animal::~Animal()
{
cout<<"Animal纯虚析构函数的调用"<<endl;
}
?
class Cat: public Animal
{
public:
Cat(string name)
{
cout<<"这是Cat的构造函数的调用"<<endl;
this->name = new string(name);
}
void speak()
{
cout<<*name<<"小猫在说话"<<endl;
}
~Cat()
{
if(this->name!=NULL)
{
cout<<"这是Cat的析构函数的调用"<<endl;
delete this->name;
this->name = NULL;
}
}
string *name;// 创建在堆区
};
?
void test01()
{
Animal *animal = new Cat("Tom");
animal->speak();
// 父类的指针在析构的时候不会调用子类的析构函数
// 导致子类如果在堆区有数据,会出现内存泄漏的情况
delete animal;
}
int main()
{
test01();
return 0;
}
运行结果:
案例说明:
电脑主要组成部件为CPU(用于计算),显卡〔用于显示),内存条〔用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的霉件,例如Intel厂商和Lenovo厂商
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台下同的电脑进行工作
示例:
#include<iostream>
using namespace std;
?
// 抽象不同零件的类
// 计算机类
class CPU
{
public:
// 抽象的计算函数
virtual void calculate() = 0;
};
// 显卡类
class Video_Card
{
public:
// 抽象的显示函数
virtual void display() = 0;
};
// 内存条类
class Memory
{
public:
// 抽象的存储函数
virtual void storage() = 0;
};
?
// 提供电脑类
class Computer
{
public:
Computer(CPU* cpu, Video_Card* vc, Memory* mem)
{
this->cpu = cpu;
this->vc = vc;
this->mem = mem;
}
?
// 提供工作函数
void work()
{
// 让零件工作起来,调用接口
this->cpu->calculate();
this->vc->display();
this->mem->storage();
}
?
// 提供析构函数? 释放在堆区创建的三个零件
~Computer()
{
if(cpu!=NULL){
delete cpu;
cpu = NULL;
}
if(vc!=NULL){
delete vc;
vc = NULL;
????????}
????????if(mem!=NULL){
???????? delete mem;
??????????? mem = NULL;
??????? }
}
?
private:
CPU* cpu;? // CPU的零件指针
Video_Card* vc;? // 显卡的零件指针
Memory* mem;? // 内存条的零件指针
};
?
// 具体厂商
// Intel厂商
// CPU
class Intel_CPU :public CPU
{
public:
void calculate()
{
cout << "这是Intel厂商的CPU,并且开始计算了" << endl;
}
};
// 显卡
class Intel_Video_Card :public Video_Card
{
public:
void display()
{
cout << "这是Intel厂商的显卡,并且开始显示了" << endl;
}
};
// 内存条
class Intel_Memory :public Memory
{
void storage()
{
cout << "这是Intel厂商的内存条,并且开始存储了" << endl;
}
};
?
?
// Lenovo厂商
// CPU
class Lenovo_CPU :public CPU
{
public:
void calculate()
{
cout << "这是Lenovo厂商的CPU,并且开始计算了" << endl;
}
};
// 显卡
class Lenovo_Video_Card :public Video_Card
{
public:
void display()
{
cout << "这是Lenovo厂商的显卡,并且开始显示了" << endl;
}
};
// 内存条
class Lenovo_Memory :public Memory
{
void storage()
{
cout << "这是Lenovo厂商的内存条,并且开始存储了" << endl;
}
};
?
?
void test01()
{
cout<<"------------------------"<<endl;
cout<<"第一台电脑开始工作"<<endl;
// 创建第一台电脑的零件
CPU* intel_CPU = new Intel_CPU;
Video_Card* intel_videocard = new Intel_Video_Card;
Memory* intel_memory = new Intel_Memory;
// 创建电脑零件的时候时在堆区创建的
?
// 创建第一台电脑
Computer* computer1 = new Computer(intel_CPU, intel_videocard, intel_memory);
computer1->work();
delete computer1;
?
cout<<"-----------------------"<<endl;
cout<<"第二台电脑开始工作"<<endl;
// 组装第二台电脑
Computer* computer2 = new Computer(new Lenovo_CPU, new Lenovo_Video_Card, new Lenovo_Memory);
computer2->work();
delete computer2;
?
cout<<"-----------------------"<<endl;
cout<<"第三台电脑开始工作"<<endl;
// 组装第三台电脑
Computer* computer3 = new Computer(new Lenovo_CPU, new Intel_Video_Card, new Lenovo_Memory);
computer3->work();
delete computer3;
}
int main()
{
test01();
return 0;
}
运行结果: