目录
一、基本概念
多态的使用:
案例一——计算机类
多态的优点:
二、纯虚函数与抽象类
特点:
①无法实例化对象
②子类必须重写父类中的纯虚函数,否则也属于抽象类
案例二——制作饮品
三、虚析构与纯虚析构
因为父类指针在析构时,不会调用子类中的析构函数,从而导致堆区属性未释放
解决:将父类虚构改为虚析构
案例三——电脑组装
一、基本概念
多态是C++三大特性之一
分为、作用与区别:
作用 | 区别 | |
静态多态 | 函数重载 和 运算符重载,复用函数名 | 函数地址早绑定,编译阶段确定函数地址 |
动态多态 | 派生类 和 虚函数 实现运行时多态 | 函数地址晚绑定,运行阶段确定函数地址 |
动态多态需要满足:
①有继承关系
②子类重写父类的虚函数
重写:子类的 返回值 函数名 形参列表 要与父类完全相同,但virtual可写可不写
多态的使用:
父类指针或引用子类对象
例:
创建Animal与Cat与Dog类,其中每个都有dodif函数,最后单独实现dodif函数,创建测试函数并调用dodif函数
class Animal
{
public:
void dodif()
{
cout << "动物" << endl;
}
};
class Cat : public Animal
{
public:
void dodif()
{
cout << "小猫" << endl;
}
};
class Dog : public Animal
{
public:
void dodif()
{
cout << "小狗" << endl;
}
};
void dodif(Animal &animal)// Animal &animal = 子类传来的对象; 多态的使用
{
animal.dodif();
}
void test01()
{
Cat cat;
dodif(cat);
Cat dog;
dodif(dog);
}
可以发现,无论我传参是什么,结果总是输出动物,也就是调用父类中的函数
这是因为dodif函数是静态多态,地址早绑定,在编译阶段就确定了函数地址,因此后面我怎么传参都是访问已经确定的函数地址
因此需要改为动态多态:父类中,函数前加virtual,使其变成虚函数,地址晚绑定
class Animal
{
public:
virtual void dodif() // 虚函数
{
cout << "动物" << endl;
}
};
接下来深度分析一下上面代码的实现逻辑:
首先,没有加virtual时,我们输出一下父类的大小
结果是1,说明是个空对象
加上virtual,再次输出
变成8,其实是变成了一个指针,即 虚函数指针cfptr
接下来,画出Animal类的内部结构
使用开发人员命令提示符工具查看
然后是,继承的Cat类的内部结构,此时并无重写
虚函数表内仍然是&Animal::dodif
而当我们加上重写后
虚函数表内就改成了&Cat::dodif
因为重写后,子类中的虚函数表会被替换成子类的虚函数地址
案例一——计算机类
使用普通方法和多态2种方法实现计算器类
①普通方法
class caculator
{
public:
int getResult()
{
cin >> num1 >> oper >> num2;
if (oper == "+")
{
return num1 + num2;
}
else if (oper == "-")
{
return num1 - num2;
}
else if (oper == "*")
{
return num1 * num2;
}
}
string oper;
int num1;
int num2;
};
void test01()
{
caculator c;
int ret = c.getResult();
cout << ret << endl;
}
现在实现了加法、减法和乘法三种功能,但是如果想要增加功能,则需要修改源码
实际开发中提倡开闭原则:对扩展进行开放,对修改进行关闭
②多态的方法
class caculator_abstract // 计算器抽象类
{
public:
virtual int getResult()
{
return 0;
}
int num1;
int num2;
};
class Add:public caculator_abstract // 加法 Addition
{
int getResult()
{
return num1 + num2;
}
};
class Sub :public caculator_abstract // 减法 subtraction
{
int getResult()
{
return num1 - num2;
}
};
class Mul :public caculator_abstract // 乘法 multiplication
{
int getResult()
{
return num1 * num2;
}
};
class Div :public caculator_abstract // 除法 division
{
int getResult()
{
return num1 / num2;
}
};
调用
void test02()
{
// 多态的使用方法:
//父类指针或者引用子类对象
caculator_abstract* c = new Add; // 加法调用
c->num1 = 10;
c->num2 = 5;
cout << c->getResult() << endl;
delete c;// 堆区的数据,记得销毁
c = new Sub;
c->num1 = 10;
c->num2 = 5;
cout << c->getResult() << endl; // 减法调用
delete c;
c = new Mul;
c->num1 = 10;
c->num2 = 5;
cout << c->getResult() << endl; // 乘法调用
delete c;
c = new Div;
c->num1 = 10;
c->num2 = 5;
cout << c->getResult() << endl; // 除法调用
delete c;
}
多态的优点:
①组织结构清晰
②可读性强
③对于扩展以及维护能力高
二、纯虚函数与抽象类
在多态中,通常父类中虚函数的实现没有什么意义,主要是调用子类重写的内容
因此可将父类中的虚函数改为纯虚函数
纯虚函数:写一个虚函数,使其 = 0,即为纯虚函数
语法:virtual 返回值类型 函数名 (参数列表)= 0;
而当类中有了纯虚函数,这个类也称为抽象类
特点:
①无法实例化对象
class Base
{
public:
virtual void func() = 0;
};
void test01()
{
Base b;
new Base;
}
无论是栈上还是堆区都无法实例化对象
②子类必须重写父类中的纯虚函数,否则也属于抽象类
首先,不重写:
class Son :public Base
{
public:
};
void test01()
{
Son s;
new Son;
}
一样的无法实例化对象
加上重写
class Son :public Base
{
public:
virtual void func() {}; // 重写
};
即使重写是空实现,仍然可以实例化对象
简单修改一下
class Son :public Base
{
public:
virtual void func()
{
cout << "Son_func的调用" << endl;
}; // 重写
};
void test01()
{
Son s;
s.func();
// 多态的方法:
Base* base = new Son;
base->func();
}
案例二——制作饮品
简单制作一份饮品,咖啡or沏茶
咖啡:①煮水②冲泡咖啡③倒入杯中④加糖与牛奶
沏茶:①煮水②冲泡茶叶③倒入杯中④加柠檬
流程大致相同:①煮水②冲泡③倒入杯中④加佐料
class AbstactDrinking // 抽象类的饮品
{
public:
virtual void Boil() = 0;
virtual void Brew() = 0;
virtual void Pour() = 0;
virtual void Put() = 0;
void makeDriking()
{
Boil();
Brew();
Pour();
Put();
}
};
class tea :public AbstactDrinking // 茶的具体实现
{
public:
virtual void Boil()
{
cout << "煮水" << endl;
}
virtual void Brew()
{
cout << "冲泡茶叶" << endl;
}
virtual void Pour()
{
cout << "倒入杯中" << endl;
}
virtual void Put()
{
cout << "加柠檬" << endl;
}
};
class coffee :public AbstactDrinking // 咖啡的具体实现
{
public:
virtual void Boil()
{
cout << "煮水" << endl;
}
virtual void Brew()
{
cout << "冲泡咖啡" << endl;
}
virtual void Pour()
{
cout << "倒入杯中" << endl;
}
virtual void Put()
{
cout << "加糖与牛奶" << endl;
}
};
void doDrinking(AbstactDrinking* abd)
{
abd->makeDriking();
delete abd; // new出的堆区数据,记得销毁
}
void test01()
{
doDrinking(new tea);
cout << "-----------------" << endl;
doDrinking(new coffee);
}
没啥说的
三、虚析构与纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,导致子类的堆区属性未被释放,引起内存泄漏
解决方法:将父类中的析构函数改为虚析构或纯虚析构
共同点 | 不同点 | |
虚析构 | 可以解决父类指针释放子类对象 | \ |
纯虚析构 | 都需要有具体的函数实现 | 属于抽象类,无法实例化对象 |
语法:
虚析构 | virtual~类名() {}; |
纯虚析构 | virtual~类名() = 0 ; 声明 类名 : : 类名() {} ;实现 |
接下来创建父类Animal类与子类Cat类
class Animal
{
public:
virtual void speak() = 0;
};
class Cat:public Animal
{
public:
virtual void speak()
{
cout << "小猫" << endl;
}
};
void test01()
{
Animal* animal = new Cat;
animal->speak();
delete animal;
}
接下来在Cat类中增加string属性c_name;
class Cat:public Animal
{
public:
Cat(string name)
{
c_name = new string(name); // 堆区创建,应有释放
}
virtual void speak()
{
cout << *c_name << "小猫" << endl;
}
string *c_name;
};
void test01()
{
Animal* animal = new Cat("yomi");
animal->speak();
delete animal;
}
而c_name是堆区开辟的内存,应有释放,补全Cat的析构:
~Cat()
{
if (c_name != NULL)
{
cout << "Cat的析构" << endl;
delete c_name;
c_name = NULL;
}
}
同时,我们把父类Animal的析构和构造以及子类Cat的构造也补全:
class Animal
{
public:
Animal()
{
cout << "Animal的构造" << endl;
}
~Animal()
{
cout << "Animal的析构" << endl;
}
virtual void speak() = 0;
};
class Cat:public Animal
{
public:
Cat(string name)
{
cout << "Cat的构造" << endl;
c_name = new string(name); // 堆区创建,应有释放
}
virtual void speak()
{
cout << *c_name << "小猫" << endl;
}
string *c_name;
~Cat()
{
if (c_name != NULL)
{
cout << "Cat的析构" << endl;
delete c_name;
c_name = NULL;
}
}
};
运行可以发现,没有子类Cat的析构,说明堆区new出的c_name属性并未释放
因为父类指针在析构时,不会调用子类中的析构函数,从而导致堆区属性未释放
解决:将父类虚构改为虚析构
virtual ~Animal()
{
cout << "Animal的析构" << endl;
}
就走了子类的析构
接下来实现纯虚析构
class Animal
{
public:
Animal()
{
cout << "Animal的构造" << endl;
}
virtual ~Animal() = 0; // 纯虚析构
virtual void speak() = 0; // 虚函数
};
如果只在父类中写 virtual ~Animal() = 0; 运行将报错
因为这个相当于声明,没有实现
接下来在类外实现纯虚析构的实现
Animal::~Animal() // 纯虚析构的实现
{
cout << "Animal的纯虚析构" << endl;
}
因为如果父类有开辟堆区的属性,也需要释放,因此需要纯虚析构的实现
同时,当类中有了纯虚析构,这个类也属于抽象类,无法实例化对象
总结:
①虚析构或纯虚析构是用来解决通过父类指针释放子类对象
②如果子类没有堆区数据,可以不写虚析构或纯虚析构
③纯虚析构的类也属于抽象类,无法实例化对象
案例三——电脑组装
电脑主要组成部件是处理器(计算)+显卡(显示)+内存条(存储)
将每个零件封装成抽象基类,并提供不同的厂商生产不同的零件
class CPU
{
public:
// 抽象计算函数
virtual void calculate() = 0;
};
class GraphicsCard
{
public:
// 抽象显示函数
virtual void display() = 0;
};
class Memory
{
public:
// 抽象存储函数
virtual void storage() = 0;
};
具体厂商零件:
// 具体零件厂商
// 1、Inter
class InterCPU :public CPU
{
virtual void calculate()
{
cout << "Inter_CPU" << endl;
}
};
class InterGPU :public GraphicsCard
{
virtual void display()
{
cout << "Inter_GPU" << endl;
}
};
class InterRAM :public Memory
{
virtual void storage()
{
cout << "Inter_RAM" << endl;
}
};
// 2、lenovo
class LenovoCPU :public CPU
{
void calculate()
{
cout << "Lenovo_CPU" << endl;
}
};
class LenovoGPU :public GraphicsCard
{
void display()
{
cout << "Lenovo_GPU" << endl;
}
};
class LenovoRAM :public Memory
{
void storage()
{
cout << "Lenovo_RAM" << endl;
}
};
创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
// 电脑类的实现
class Computer
{
public:
Computer(CPU* cpu, GraphicsCard* gpu, Memory* ram)
{
c_cpu = cpu;
c_gpu = gpu;
c_ram = ram;
}
void dowork() // 工作接口
{
c_cpu->calculate();
c_gpu->display();
c_ram->storage();
}
private:
CPU* c_cpu;
GraphicsCard* c_gpu;
Memory* c_ram;
};
测试时组装3种不同的电脑进行工作
void test01()
{
// 创建第一台电脑的零件
CPU* interCpu = new InterCPU;
GraphicsCard* interGpu = new InterGPU;
Memory* interRam = new InterRAM;
// 创建第一台电脑
Computer* computer1 = new Computer(interCpu, interGpu, interRam);
computer1->dowork(); // 调用工作函数
delete computer1;
}
在computer1工作完后,我们将其delete,但仍有一个问题:
上面new出的3个零件尚未delete,下面有2个方法
①在最后delete computer后,接着delete3个零件
②在电脑类中,提供析构函数,释放3个零件,下面实现方法②
~Computer()
{
if (c_cpu != NULL) // 释放cpu
{
delete c_cpu;
c_cpu = NULL;
}
if (c_gpu != NULL) // 释放gpu
{
delete c_gpu;
c_gpu = NULL;
}
if (c_ram!= NULL) // 释放ram
{
delete c_ram;
c_ram = NULL;
}
}
这样就解决了堆区的问题
创建另外2台电脑
2:
void test01()
{
// 创建第二台电脑的零件
CPU* lenovoCpu = new LenovoCPU;
GraphicsCard* lenovoGpu = new LenovoGPU;
Memory* lenovoRam = new LenovoRAM;
// 创建第二台电脑
Computer* computer2 = new Computer(lenovoCpu, lenovoGpu, lenovoRam);
computer2->dowork(); // 调用工作函数
delete computer2;
}
3:
// 创建第三台电脑的零件
CPU* InterCpu = new InterCPU;
GraphicsCard* InterGpu = new InterGPU;
Memory* lenovoRam = new LenovoRAM;
// 创建第三台电脑
Computer* computer3 = new Computer(InterCpu, InterGpu, lenovoRam);
computer3->dowork(); // 调用工作函数
delete computer3;