C++類與物件(3)—多型
一個介面有多種形態,傳入的物件不一樣,同一個介面執行的操作不同
多型的基本概念
多型分為兩類
- 靜態多型:函式多載和運算子多載屬于靜態多型,復用函式名
- 動態多型:派生類和虛函式實作運行時多型
靜態多型胡動態多型的區別
- 靜態多型的函式地址早系結,編譯階段搞定函式地址
- 動態多型的函式地址晚系結,運行階段確定函式地址
例子
早系結
#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;
}

晚系結(只要在父類的函式前面加一個virtual)
virtual void speak(){
cout << "動物在說話" << endl;
}

動態多型的滿足條件
- 有繼承關系
- 子類要重寫父類中的虛函式(可以不寫virtual,其余完全一樣)
動態多型的使用
- 父類的指標或參考 指向子類物件
多型的原理剖析
在使用virtual后父類結構改變,多列一個指標(虛函式表指標),指向一個虛函式表,在虛函式表中寫著虛函式函式入口地址
子類重寫虛函式時,會把原來的虛函式替換成子類新寫的虛函式(如果不重寫,則虛函式表中的資料和父類是一樣的)
所以用父類的參考去指向子類物件時,當呼叫公共的介面(虛函式)時,會從子類中去尋找確實的函式地址,

多型案例(1)-計算機類
多型的優點:
- 代碼組織結構清晰
- 可讀性強
- 利于前期和后期的拓展和保護
基礎實作
#include<iostream>
using namespace std;
class Calculator{//計算器類
public:
int getResult(string oper){
if(oper == "+") return m_a + m_b;
if(oper == "-") return m_a - m_b;
if(oper == "*") return m_a * m_b;
}
int m_a;
int m_b;
};
void test01(){
Calculator C;
C.m_a = 10;
C.m_b = 10;
cout << C.m_a << "+" << C.m_b << "=" << C.getResult("+") << endl;
cout << C.m_a << "-" << C.m_b << "=" << C.getResult("-") << endl;
cout << C.m_a << "*" << C.m_b << "=" << C.getResult("*") << endl;
}
int main(){
test01();
return 0;
}

但是我們發現,這個計算器是沒有除法的,要想添加除法,必須去原始碼中修改,但是
在真實開發中,提倡:開閉原則(對拓張進行開放,對修改進行關閉)
所以我們來用多型的方式來寫一下吧
多型實作
//乘法、除法的計算器就不寫了
#include<iostream>
using namespace std;
class Calculator{ //虛基類
public:
virtual int getResult(){ //只要個名字,沒有任何功能
return 0;
}
int m_a;
int m_b;
};
class SonAdd:public Calculator{ //加法類
public:
int getResult(){
return m_a + m_b;
}
};
class SonSub:public Calculator{ //減法類
public:
int getResult(){
return m_a - m_b;
}
};
class SonMul:public Calculator{ //乘法類
public:
int getResult(){
return m_a * m_b;
}
};
class SonDiv:public Calculator{ //除法類
public:
int getResult(){
return m_a / m_b;
}
};
void test01(){//這次我們使用指標的方法來指向子類物件
Calculator * C = new SonAdd;//加法計算器
C->m_a = 10;
C->m_b = 10;
cout << C->m_a << "+" << C->m_b << "=" << C->getResult() << endl;
delete C;//new在堆區,要記得釋放哦
C = new SonSub; //減法計算器
C->m_a = 20;
C->m_b = 20;
cout << C->m_a << "-" << C->m_b << "=" << C->getResult() << endl;
delete C;
}
int main(){
test01();
return 0;
}

純虛函式和抽象類
在多型中,通常父類中虛函式的實作是沒有意義的,主要都是呼叫子類中重寫的內容
因此可以將虛函式改為純虛函式
當類中出現了純虛函式,這個類就叫做抽象類
語法
virtual 回傳值型別 函式名 (引數串列) = 0;
特點
- 無法實體化物件
- 子類必須重寫抽象類(父類)中的純虛函式,否則也屬于抽象類了
例子
#include<iostream>
using namespace std;
class Base{//抽象類
public:
virtual void func() = 0;
};
class Son:public Base{ //子類
public:
void func(){ //重寫函式是
cout << "子類重寫成功" <<endl;
}
};
void use(Base &base){//參考來實作
base.func();
}
void test01(){
Son son;
use(son);
cout << "=========================" << endl;
Base * base = new Son;//指標來實作
base->func();
}
int main(){
test01();
return 0;
}

多型案例(2)-制作飲品
在原本的代碼上稍稍優化了一下
#include<iostream>
using namespace std;
class Base{ //抽象類
public:
virtual void Boll() = 0;
virtual void Brew() = 0;
virtual void Pour() = 0;
virtual void AddSome() = 0;
void make(){//呼叫制作飲品的函式
Boll();
Brew();
Pour();
AddSome();
}
};
class Tea:public Base{//茶類
public:
void Boll(){
cout << "1.煮茶水" << endl;
}
void Brew(){
cout << "2.沖泡茶葉" << endl;
}
void Pour(){
cout << "3.倒入茶杯中" << endl;
}
void AddSome(){
cout << "4.加入輔料" << endl;
}
};
class Coffee:public Base{ //咖啡類
public:
void Boll(){
cout << "1.煮水" << endl;
}
void Brew(){
cout << "2.沖泡咖啡" << endl;
}
void Pour(){
cout << "3.倒入咖啡杯中" << endl;
}
void AddSome(){
cout << "4.加入輔料(牛奶/放糖)" << endl;
}
};
void domake(Base *base){ //多型的實作
base->make();
delete base;
}
void test01(){ //制作茶
domake(new Tea);
}
void test02(){ //制作咖啡
//domake(new Coffee);
Base * base = new Coffee;
base->make();
delete base;
}
void select(){ //選擇要制作的飲品
int i = 0;
const int j = 3; //最大的制作次數
while(1){
cout << "請選擇所需要的飲品(您現在最多可以選" << j-i << "次)" << endl;
string drink;
cin >> drink;
if(drink == "茶"){
test01();
}else if(drink == "咖啡"){
test02();
}else{
cout << "對不起,您所選的飲品目前沒有..." << endl;
}
i += 1;
if(i == j) break;//達到3次后結束
}
return;
}
int main(){
select();
return 0;
}

虛析構和純虛析構
多型使用時,父類的指標或參考子類時,如果子類中有屬性開辟到堆區,那么父類指標在釋放時無法呼叫子類的解構式
所以,我們將父類中的解構式改為(純)虛析構
析構和純虛析構的共性
- 都可以解決父類指標釋放子類物件
- 都需要具體函式實作
析構和純虛析構的區別
- 如果時純虛析構,該類為抽象類,不可以實體化物件
語法
虛析構:
virtual ~類名(){}
純虛析構:
virtual ~類名() = 0;//類內
類名::~類名(){}//類外
虛析構
#include<iostream>
using namespace std;
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;
m_Name = new string(name);
}
~Cat(){
if(m_Name != NULL){
cout << "Cat析構呼叫" << endl;
delete m_Name;
m_Name = NULL;
}
}
void speak(){
cout << *m_Name << "小貓會說話" << endl;
}
string *m_Name;//要創建在堆區
};
void test01(){
Animal * animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main(){
test01();
return 0;
}

沒有呼叫Cat的解構式,資料沒有清楚干凈,可能會造成記憶體泄露
原因:父類指標指向子類,delete父類時,不會呼叫子類的析構代碼
解決:使用虛解構式
virtual ~Animal(){
cout << "Animal析構呼叫" << endl;
}
結果
300
純虛析構
內部要有函式的實作,不能只是下面這樣
virtual ~Animal() = 0; //這是錯的
看看正確的吧
{//類內宣告
virtual ~Animal() = 0;
};
//類外定義
Animal::~Animal(){
cout << "Animal純虛析構呼叫" << endl;
};

案例(3)-電腦組裝
案例描述:
主要零件時CPU(用于計算),顯卡(用于顯示),記憶體條(用于存盤)
每個零件封裝成抽象基類,并且用于不同的廠商生產不同的零件
創建電腦類提供讓電腦作業的函式,并且呼叫每個零件作業的介面
測驗時組裝3臺不同的電腦作業
#include<iostream>
using namespace std;
class CPU{ //CPU類
public:
virtual void calculate() = 0;
};
class Card{ //顯卡類
public:
virtual void display() = 0;
};
class Memory{ //記憶體條類
public:
virtual void storage() = 0;
};
class Com{ //電腦類
private:
CPU *m_cpu; //用指標來傳入三個零件資料(要用多型嘛)
Card *m_card;
Memory *m_memory;
public:
Com(CPU *cpu,Card *card,Memory *memory){ //有參構造
m_cpu = cpu;
m_card = card;
m_memory = memory;
}
void work(){ //呼叫不同零件的函式
m_cpu->calculate();
m_card->display();
m_memory->storage();
}
//提供解構式來釋放三個零件
~Com(){
if(m_cpu != NULL){
delete m_cpu;
m_cpu = NULL;
}
if(m_card!= NULL){
delete m_card;
m_card = NULL;
}
if(m_memory != NULL){
delete m_memory;
m_memory = NULL;
}
}
};
//聯想廠商
class Lenovo:public CPU,public Card,public Memory{//方法一:三個子類再整合成一個大類,親測不太好用哎(我不知道該在怎么析構比較好)
public:
void calculate(){
cout << "聯想的CPU開始計算" << endl;
}
void display(){
cout << "聯想的顯卡開始顯示" << endl;
}
void storage(){
cout << "聯想的記憶體條開始存盤" << endl;
}
};
//蘋果廠商
//方法二:把三個子類分開
class AppleCPU:public CPU{ //簡單的繼承和函式的重寫
public:
void calculate(){
cout << "蘋果的CPU開始計算" << endl;
}
};
class AppleCard:public Card{
public:
void display(){
cout << "蘋果的顯卡開始顯示" << endl;
}
};
class AppleMemory:public Memory{
public:
void storage(){
cout << "蘋果的記憶體條開始顯示" << endl;
}
};
//戴爾廠商
class DellCPU:public CPU{
public:
void calculate(){
cout << "戴爾的CPU開始計算" << endl;
}
};
class DellCard:public Card{
public:
void display(){
cout << "戴爾的顯卡開始顯示" << endl;
}
};
class DellMemory:public Memory{
public:
void storage(){
cout << "戴爾的記憶體條開始顯示" << endl;
}
};
void test01(){
// Lenovo *lenovo = new Lenovo; 不寫那個析構的內容的化,我感覺是對的,但有一點怪怪的
// Com *com = new Com(lenovo,lenovo,lenovo);
// com->work();
// //delete com;
// delete lenovo;
Com *com1 = new Com(new AppleCPU,new AppleCard,new AppleMemory); //使用了第二中的廠商法
com1->work();
delete com1;//delete的時候就呼叫解構式啦
cout << "=======================" << endl;
Com *com2 = new Com(new DellCPU,new AppleCard,new DellMemory); //使用了第二中的廠商法
com2->work();
delete com2;
}
int main(){
test01();
return 0;
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/464971.html
標籤:C++
上一篇:倒數第N個字符
下一篇:muduo原始碼分析之回呼模塊
