C++類與對象(3)—多態 一個介面有多種形態,傳入的對象不一樣,同一個介面執行的操作不同 多態的基本概念 多態分為兩類 靜態多態:函數重載和運算符重載屬於靜態多態,復用函數名 動態多態:派生類和虛函數實現運行時多態 靜態多態胡動態多態的區別 靜態多態的函數地址早綁定,編譯階段搞定函數地址 動態多態 ...
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;
}