天天看點

C++核心程式設計---4.7 類和對象-多态【P135~P142】4.7 多态

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 多态的原理剖析

C++核心程式設計---4.7 類和對象-多态【P135~P142】4.7 多态

當 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-制作飲品

案例描述:

制作飲品的大緻流程為:煮水 - 沖泡 - 倒入杯中 - 加入輔料

利用多态技術實作本案例,提供抽象制作飲品基類,提供子類制作咖啡和茶葉

C++核心程式設計---4.7 類和對象-多态【P135~P142】4.7 多态
#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 廠商

建立電腦類提供讓電腦工作的函數,并且調用每個零件工作的接口

測試時組裝三台不同的電腦進行工作

C++核心程式設計---4.7 類和對象-多态【P135~P142】4.7 多态
#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;
}
           

繼續閱讀