C++核心程式設計---4.7 類和對象-多态【P135~P142】
- 4.7 多态
-
- 4.7.1 多态的基本概念
- 4.7.2 多态的原理剖析
- 4.7.3 案例1-計算機類
- 4.7.4 純虛函數和抽象類
- 4.7.5 案例2-制作飲品
- 4.7.6 虛析構和純虛析構
- 4.7.7 案例3-電腦組裝
4.7 多态
多态是C++面向對象三大特性之一
多态分為兩類:
- 靜态多态:函數重載 和 運算符重載屬于靜态多态,複用函數名
- 動态多态:派生類和虛函數實作運作時多态
靜态多态和動态多态差別:
- 靜态多态的函數位址早綁定 - 編譯階段确定函數位址
- 動态多态的函數位址晚綁定 - 運作階段确定函數位址
4.7.1 多态的基本概念
多态滿足條件:
1、有繼承關系
2、子類重寫父類中的虛函數
多态使用條件:
父類指針或引用指向子類對象
重寫概念:
函數傳回值類型 函數名 參數清單 完全一緻稱為重寫
#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
virtual void speak()//加上virtual ,虛函數
{
cout << "動物在說話" << endl;
}
};
class Cat : public Animal
{
public:
void speak()
{
cout << "小貓在說話" << endl;
}
};
//執行說話的函數
//位址早綁定 在編譯階段确定函數位址
//如果想執行讓貓說話,那麼這個函數的位址就不能提前綁定,需要在運作階段進行綁定,即位址晚綁定
void DoSpeak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
DoSpeak(cat);//父類的引用可以直接指向子類的對象
}
int main()
{
test01();
system("pause");
return 0;
}
4.7.2 多态的原理剖析
當 Animal 類的成員函數 void speak() 前加上關鍵字 virtual 後,Animal 類就占4個位元組,原因在于這時存放的是 vfptr (虛函數指針),指針變量的記憶體大小是4個位元組。
- v------virtual
- f------function
- ptr------pointer
該指針指向 vftable (虛函數清單),虛函數清單中存放的是 Animal 類的虛函數位址
Cat 類繼承了 Animal 類的成員函數,擁有和 Animal 類同樣的内部結構,即存在 vfptr 指針指向 vftable 。
然鵝當子類重寫父類的虛函數時,子類中的虛函數表内部會替換成子類的虛函數位址。是以,當父類的指針或者引用指向子類對象的時候,發生多态。
4.7.3 案例1-計算機類
案例描述:
分别利用普通寫法和多态技術,設計實作兩個操作數進行運算的計算機類。
多态的優點:
- 代碼組織結構清晰
- 可讀性強
- 利于前期和後期的擴充以及維護
普通寫法實作
#include<iostream>
#include<string>
using namespace std;
//普通寫法
class Calculator
{
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;
}
//如果想擴充新的功能,需要修改源碼
//在真實的開發中 提倡開閉原則
//開閉原則:對擴充進行開發,對修改進行關閉
else if(oper == "/")
{
return m_Num1 / m_Num2;
}
}
int m_Num1;
int m_Num2;
};
void test01()
{
//建立電腦對象
Calculator 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;
cout << c.m_Num1 << "*" << c.m_Num2 << "=" << c.getResult("*") << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
如果想擴充新的功能,需要修改源碼
在真實的開發中 提倡開閉原則
開閉原則:對擴充進行開放,對修改進行關閉
多态寫法實作
#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;
}
};
//乘法類
class MulCalculator : public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
//多态使用條件
//父類指針或者引用指向子類對象
//加法運算
AbstractCalculator * abs = new AddCalculator;
abs->m_Num1 = 10;
abs->m_Num2 = 20;
cout << abs->m_Num1 << "+" << abs->m_Num2 << "=" << abs->getResult() << endl;
//用完記得銷毀
delete abs;//這裡是把堆區的資料釋放了,但是 abs 依然是父類指針
//減法運算
abs = new SubCalculator;
abs->m_Num1 = 200;
abs->m_Num2 = 300;
cout << abs->m_Num1 << "-" << abs->m_Num2 << "=" << abs->getResult() << endl;
delete abs;
//乘法運算
abs = new MulCalculator;
abs->m_Num1 = 200;
abs->m_Num2 = 300;
cout << abs->m_Num1 << "*" << abs->m_Num2 << "=" << abs->getResult() << endl;
delete abs;
}
int main()
{
test02();
system("pause");
return 0;
}
多态帶來的好處:
- 組織結構清晰
- 可讀性強
總結:C++ 開發提倡利用多态設計程式架構,因為多态優點很多
4.7.4 純虛函數和抽象類
在多态中,通常父類中虛函數的實作是毫無意義的,主要都是調用子類重寫的内容。
是以可以将虛函數設定為純虛函數
純虛函數文法: virtual 傳回值卡類型 函數名 (參數清單)= 0
當類中出現了虛函數,這個類也被稱為抽象類
抽象類特點:
- 無法執行個體化對象
- 子類必須重寫抽象類中的純虛函數,否則也屬于抽象類
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
//純虛函數
//隻要有一個純虛函數,這個類就稱為抽象類
virtual void func() = 0;
};
class Son : public Base
{
public:
void func()
{
cout << "func函數調用" << endl;
};
};
void test01()
{
//Base b;//報錯,抽象類無法執行個體化對象
//new Base;//報錯,抽象類無法執行個體化對象
//Son s;//子類必須重寫父類中的純虛函數,否則無法執行個體化對象
Base * base = new Son;
base->func();
}
int main()
{
test01();
system("pause");
return 0;
}
4.7.5 案例2-制作飲品
案例描述:
制作飲品的大緻流程為:煮水 - 沖泡 - 倒入杯中 - 加入輔料
利用多态技術實作本案例,提供抽象制作飲品基類,提供子類制作咖啡和茶葉
#include<iostream>
#include<string>
using namespace std;
class AbstractDrinking
{
public:
//煮水
virtual void Boil() = 0;
//沖泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入輔料
virtual void PutSomething() = 0;
//制作飲品
void makeDrink()
{
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffee : public AbstractDrinking
{
public:
//煮水
virtual void Boil()
{
cout << "煮水" << endl;
}
//沖泡
virtual void Brew()
{
cout << "沖泡咖啡" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入咖啡杯中" << endl;
}
//加入輔料
virtual void PutSomething()
{
cout << "加入牛奶和糖" << endl;
}
};
//制作茶
//制作咖啡
class Tea : public AbstractDrinking
{
public:
//煮水
virtual void Boil()
{
cout << "煮水" << endl;
}
//沖泡
virtual void Brew()
{
cout << "沖泡茶葉" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入茶杯中" << endl;
}
//加入輔料
virtual void PutSomething()
{
cout << "加入輔料" << endl;
}
};
void doWork(AbstractDrinking *abs)
{
abs->makeDrink();
delete abs;
}
void test01()
{
//制作咖啡
doWork(new Coffee);
cout << "--------------" << endl;
doWork(new Tea);
}
int main()
{
test01();
system("pause");
return 0;
}
4.7.6 虛析構和純虛析構
多态使用時,父類指針會指向子類對象,父類指針是無法釋放子類中的析構代碼的,如果子類中有屬性開辟到堆區,那麼父類指針在釋放時無法調用到子類的析構代碼,堆區的資料會造成記憶體洩漏。
解決方式:将父類中的析構函數改為虛析構或者純虛析構
虛析構和純虛析構共性:
- 可以解決父類指針釋放子類對象
- 都需要有具體的函數實作(類内聲明,類外實作)
class Animal
{
public:
Animal()
{
cout << "Animal 的構造函數調用" << endl;
}
virtual ~Animal() = 0;//純虛析構
//純虛函數
virtual void speak() = 0;
};
Animal::~Animal()
{
cout << "Animal 的純虛析構函數調用" << endl;
}
虛析構和純虛析構差別:
- 如果是純虛析構,該類屬于抽象類,無法執行個體化對象
虛析構文法:
virtual ~類名(){}
純虛析構文法:
virtual ~類名()= 0;
類名::~類名(){}
#include<iostream>
#include<string>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "Animal 的構造函數調用" << endl;
}
//virtual ~Animal()//加關鍵字 virtual,虛析構
//{
// cout << "Animal 的虛析構函數調用" << endl;
//}
virtual ~Animal() = 0;//純虛析構
//純虛函數
virtual void speak() = 0;
};
Animal::~Animal()
{
cout << "Animal 的純虛析構函數調用" << endl;
}
class Cat : public Animal
{
public:
Cat(string name)
{
cout << "Cat 的構造函數調用" << endl;
m_Name = new string (name);//new傳回的是目前類型的指針
}
virtual void speak()
{
cout << *m_Name << "小貓在說話" << endl;
}
string *m_Name;//做成指針,讓它建立在堆區
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat 的析構函數調用" << endl;
delete m_Name;
m_Name = NULL;
}
}
};
void test01()
{
Animal * animal = new Cat("Tom");
animal->speak();
//父類指針在析構時候,不會調用子類中析構函數,導緻子類如果有堆區屬性,出現記憶體洩露
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
總結:
- 1、虛析構或純虛析構就是用來解決父類指針釋放子類對象
- 2、如果子類中沒有堆區資料,可以不寫為虛析構或純虛析構
- 3、擁有純虛析構函數的類也屬于抽象類
4.7.7 案例3-電腦組裝
案例描述:
電腦主要組成部件為CPU (用于計算),顯示卡(用于顯示),記憶體條(用于存儲)
将每個零件封裝出抽象基類,并且提供不同的廠商生産不同的零件,例如Intel廠商和 Lenovo 廠商
建立電腦類提供讓電腦工作的函數,并且調用每個零件工作的接口
測試時組裝三台不同的電腦進行工作
#include<iostream>
#include<string>
using namespace std;
//抽象出不同的零件類
//抽象出CPU類
class CPU
{
public:
virtual void calculator() = 0;
};
//抽象出顯示卡類
class VideoCard
{
public:
virtual void display() = 0;
};
//抽象出記憶體條類
class Memory
{
public:
virtual void storage() = 0;
};
//電腦類
class Computer//Computer 類就是一個接口類,所有類的調用、實作、釋放,都是通過他的指針來實作的
{
public:
Computer(CPU * cpu, VideoCard * vc, Memory * mem)
{
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
//提供工作的函數
void Work()
{
//讓零件工作起來,調用接口
m_cpu->calculator();
m_vc->display();
m_mem->storage();
}
//提供析構函數,釋放三個電腦的零件
~Computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
private://這裡為什麼要用指針?抽象類不能執行個體化對象,隻能建立指針,指向可以執行個體化的子類
CPU * m_cpu;//CPU的零件指針
VideoCard * m_vc; //顯示卡零件指針
Memory * m_mem;//記憶體條零件指針
};
//具體廠商
//Intel 廠商
class IntelCPU : public CPU
{
public:
virtual void calculator()
{
cout << "Intel 的 CPU 開始計算了" << endl;
}
};
class IntelVideoCard : public VideoCard
{
public:
virtual void display()
{
cout << "Intel 的 顯示卡 開始顯示了" << endl;
}
};
class IntelMemory : public Memory
{
public:
virtual void storage()
{
cout << "Intel 的 記憶體條 開始存儲了" << endl;
}
};
//Lenovo 廠商
class LenovoCPU : public CPU
{
public:
virtual void calculator()
{
cout << "Lenovo 的 CPU 開始計算了" << endl;
}
};
class LenovoVideoCard : public VideoCard
{
public:
virtual void display()
{
cout << "Lenovo 的 顯示卡 開始顯示了" << endl;
}
};
class LenovoMemory : public Memory
{
public:
virtual void storage()
{
cout << "Lenovo 的 記憶體條 開始存儲了" << endl;
}
};
//開始測試
void test01()
{
//第一台電腦零件
CPU * intelCpu = new IntelCPU;
VideoCard * intelCard = new IntelVideoCard;
Memory * intelMem = new IntelMemory;
Computer * computer1 = new Computer(intelCpu, intelCard, intelMem);
computer1->Work();
delete computer1;
cout << "--------------------" << endl;
//第二台電腦零件
Computer * computer2 = new Computer(new LenovoCPU, new LenovoVideoCard,new LenovoMemory );
computer2->Work();
delete computer2;
cout << "--------------------" << endl;
//第三台電腦零件
Computer * computer3 = new Computer(new IntelCPU, new LenovoVideoCard, new IntelMemory);
computer3->Work();
delete computer3;
}
int main()
{
test01();
system("pause");
return 0;
}