1. 程式人生 > 其它 >C++ 類和物件--多型

C++ 類和物件--多型

4.7 多型

4.7.1 多型的基本概念

多型是C++面向物件三大特性之一

多型分為兩類

  • 靜態多型: 函式過載 和 運算子過載屬於靜態多型,複用函式名
  • 動態多型: 派生類和虛擬函式實現執行時多型

靜態多型和動態多型區別:

  • 靜態多型的函式地址早繫結 - 編譯階段確定函式地址
  • 動態多型的函式地址晚繫結 - 執行階段確定函式地址

下面通過案例進行講解多型

#include <iostream>
using namespace std;

// 多型

// 動物類
class Animal
{
public:
	// 虛擬函式
	virtual void speak()
	{
		cout << "動物說話" << endl;
	}
};

// 貓類
class Cat : public Animal
{
public:
	// 重寫  函式返回值型別  函式名 函式列表 完全相同
	virtual void speak()
	{
		cout << "小貓在說話" << endl;
	}
};

// 狗類
class Dog : public Animal
{
public:
	void speak()
	{
		cout << "小狗在說話" << endl;
	}
};

// 執行說話的函式
// 地址早繫結  在編譯階段確定函式地址	
// 如果想執行讓貓說話,那麼這個函式地址就不能提前繫結,需要在執行階段繫結,地址晚繫結

// 動態多型滿足條件
// 1、有繼承關係
// 2、子類重寫父類的虛擬函式

// 動態多型使用
// 父類的指標或者引用 指向子類物件

void doSpeak(Animal* animal) // Animal &animal = cat;
{
	animal->speak();
}

void test01()
{
	Cat cat;
	doSpeak(&cat);

	Dog dog;
	doSpeak(&dog);
}

int main()
{
	test01();

	system("pause");

	return 0;
}

總結:

多型滿足條件

  • 有繼承關係
  • 子類重寫父類中的虛擬函式

多型使用條件

  • 父類指標或引用指向子類物件

重寫:函式返回值型別 函式名 引數列表 完全一致稱為重寫

4.7.2 多型案例一-計算器類

案例描述:

分別利用普通寫法和多型技術,設計實現兩個運算元進行運算的計算器類

多型的優點:

  • 程式碼組織結構清晰
  • 可讀性強
  • 利於前期和後期的擴充套件以及維護

示例:

 #include <iostream>
using namespace std;

// 分別利用普通寫法和多型技術實現計算器

// 普通寫法
class Calculator
{
public:

	int getResult(string oper)
	{
		if (oper == "+")
		{
			return m_Numl + m_Num2;
		}
		else if (oper == "-")
		{
			return m_Numl - m_Num2;
		}
		else if (oper == "*")
		{
			return m_Numl * m_Num2;
		}
		else
		{
			return m_Numl / m_Num2;
		}
		// 如果想擴充套件新的功能,需求改原始碼
		// 在真實開發中  提倡  開閉原則
		// 開閉原則:對擴充套件進行開放,對修改進行關閉 
	}
	int m_Numl; // 運算元1
	int m_Num2; // 運算元2

};

void test01()
{
	// 建立計算器物件
	Calculator c;
	c.m_Numl = 10;
	c.m_Num2 = 10;

	cout << c.m_Numl << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
	cout << c.m_Numl << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
	cout << c.m_Numl << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
	cout << c.m_Numl << " / " << c.m_Num2 << " = " << c.getResult("/") << endl;

}

// 利用多型實現計算器

// 實現計算器抽象類
// 多型好處:
// 1、組織結構清晰
// 2、可讀性強
// 3、對於前期和後期擴充套件以及維護性高


// 實現計算器抽象類
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* abc = new AddCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 20;

	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	// 用完後記得銷燬
	delete abc;

	// 減法運算
	abc = new SubCalculator;
	abc->m_Num1 = 100;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	// 用完後記得銷燬
	delete abc;

	// 乘法法運算
	abc = new MulCalculator;
	abc->m_Num1 = 100;
	abc->m_Num2 = 20;
	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
	// 用完後記得銷燬
	delete abc;
	 
}

int main()
{

	// test01();

	test02();

	system("pause");

	return 0;
}

總結:C++開發提倡利用多型設計程式架構,因為多型優點很多

4.7.3 純虛擬函式和抽象類

在多型中,通常父類中虛擬函式的實現是毫無意義的,主要都是呼叫子類重寫的內容

因此可以將虛擬函式改為純虛擬函式

純虛擬函式語法:virtual 返回值型別 函式名 (引數列表)= 0 ;

當類中有了純虛擬函式,這個類也稱為抽象類

抽象類特點

  • 無法例項化物件
  • 子類必須重寫抽象類中的純虛擬函式,否則也屬於抽象類

示例:

#include <iostream>
using namespace std;

// 純虛擬函式和抽象類
class Base
{
public:

	// 純虛擬函式
	// 只要有一個純虛擬函式,這個類稱為抽象類
	// 抽象類特點:
	// 1、無法例項化物件
	// 2、抽象類的子類  必須要重新父類中純虛擬函式,否則也屬於抽象類
	virtual void func() = 0;
};

class Son : public Base
{
public:
	virtual void func() 
	{
		cout << "func函式呼叫" << endl;
	}

};

void test01()
{
	// Base b; // 抽象類無法例項化物件
	// new Base; // 抽象類無法例項化物件

	Son s; // 子類必須重寫父類中的純虛擬函式,否則無法例項化物件
	Base* base = new Son;
	base->func();
	// 記得銷燬
	delete base;
}

int main()
{

	test01();

	system("pause");

	return 0;
}

4.7.4 多型案例二-製作飲品

案例描述:

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

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

示例:

#include <iostream>
using namespace std;

// 多型案例2 製作飲品
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
{
	// 煮水
	virtual void Boil()
	{
		cout << "煮農夫山泉" << endl;
	}

	// 沖泡
	virtual void Brew()
	{
		cout << "沖泡咖啡" << endl;
	}

	// 倒入杯中
	virtual void PourInCup()
	{
		cout << "倒入杯中" << endl;
	}

	// 加入輔料
	virtual void PutSomething()
	{
		cout << "加入糖和牛奶" << endl;
	}
};

// 製作茶葉
class Tea : public AbstractDrinking
{
	// 煮水
	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.5 虛析構和純虛析構

多型使用時,如果子類中有屬性開闢到堆區,那麼父類指標在釋放時無法呼叫到子類的析構程式碼

解決方式:將父類中的解構函式改為虛析構或者純虛析構

虛析構和純虛析構共性:

  • 可以解決父類指標釋放子類物件
  • 都需要有具體的函式實現

虛析構和純虛析構區別:

  • 如果是純虛析構,該類屬於抽象類,無法例項化物件

虛析構語法:

virtual ~類名(){}

純虛析構語法:

virtual ~類名() = 0;

類名::~類名(){}

示例:

#include <iostream>
using namespace std;

// 虛析構和純虛析構

class Animal
{
public:

	Animal()
	{
		cout << "Animal 建構函式呼叫" << endl;
	}

	// 利用虛析構可以解決 父類指標釋放子類物件時不乾淨的問題
	//virtual ~Animal()
	//{
	//	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);
	}

	~Cat()
	{
		if (m_Name != NULL)
		{
			cout << "Cat 解構函式呼叫" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}

	virtual void speak()
	{
		cout << *m_Name << "小貓在說話" << endl;
	}

	string* m_Name;

};

void test01()
{
	Animal* animal = new Cat("Tom");
	animal->speak();
	// 父類指標在析構時候 不會呼叫子類中解構函式,導致子類如果有堆區屬性,出現記憶體洩漏
	delete animal;
}

int main()
{

	test01();

	system("pause");

	return 0;
}

總結:

​ 1. 虛析構或純虛析構就是用來解決通過父類指標釋放子類物件

​ 2. 如果子類中沒有堆區資料,可以不寫為虛析構或純虛析構

3. 擁有純虛解構函式的類也屬於抽象類

4.7.6 多型案例三-電腦組裝

案例描述:

電腦主要組成部件為 CPU(用於計算),顯示卡(用於顯示),記憶體條(用於儲存)

將每個零件封裝出抽象基類,並且提供不同的廠商生產不同的零件,例如Intel廠商和Lenovo廠商

建立電腦類提供讓電腦工作的函式,並且呼叫每個零件工作的介面

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

示例:

#include <iostream>
using namespace std;

// 抽象 CPU 類
class CPU
{
public:
	// 抽象的計算函式
	virtual void calculate() = 0;
};

// 抽象顯示卡類
class VideCard
{
public:
	// 抽象的顯示函式
	virtual void display() = 0;
};

// 抽象記憶體條類
class Memory
{
public:
	// 抽象的儲存函式
	virtual void storage() = 0;
};


// 電腦類
class Conputer
{
public:
	Conputer(CPU* cpu, VideCard* vc, Memory* men)
	{
		this->cpu = cpu;
		this->vc = vc;
		this->men = men;
	}

	// 提供工作函式
	void work()
	{
		// 讓零件工作起來,呼叫介面
		cpu->calculate();

		vc->display();

		men->storage();
	}

	// 提供解構函式 釋放 3 個電腦零件
	~Conputer()
	{
		// 釋放 cpu
		if (cpu != NULL)
		{
			delete cpu;
			cpu = NULL;
		}
		// 實現顯示卡
		if (vc != NULL)
		{
			delete vc;
			vc = NULL;
		}
		// 實現記憶體條
		if (men != NULL)
		{
			delete men;
			men = NULL;
		}
	}
private:

	CPU* cpu; // CPU的零件指標
	VideCard* vc; // 顯示卡零件指標
	Memory* men; // 記憶體條零件指標
};

// Inter 廠商
class InterCpu : public CPU
{
public:
	virtual void calculate()
	{
		cout << "Inter 的 Cpu開始計算了!" << endl;
	}
};

class InterVideCard : public VideCard
{
public:
	virtual void display()
	{
		cout << "Inter 的顯示卡開始顯示了!" << endl;
	}
};

class InterMemory : public Memory
{
public:
	virtual void storage()
	{
		cout << "Inter 的記憶體條開始儲存了!" << endl;
	}
};

// Lenovo 廠商
class LenovoCpu : public CPU
{
public:
	virtual void calculate()
	{
		cout << "Lenovo 的 Cpu開始計算了!" << endl;
	}
};

class LenovoVideCard : public VideCard
{
public:
	virtual void display()
	{
		cout << "Lenovo 的顯示卡開始顯示了!" << endl;
	}
};

class LenovoMemory : public Memory
{
public:
	virtual void storage()
	{
		cout << "Lenovo 的記憶體條開始儲存了!" << endl;
	}
};

void test01()
{
	// 第一臺的電腦零件
	cout << "第一臺電腦開始工作" << endl;
	CPU* interCpu = new InterCpu;
	VideCard* interVc = new InterVideCard;
	Memory* interMen = new InterMemory;

	Conputer* compute1 = new Conputer(interCpu, interVc, interMen);
	compute1->work();
	delete compute1;

	cout << "-----------------------------------" << endl;

	cout << "第二臺電腦開始工作" << endl;
	// 第二臺電腦組裝
	Conputer* compute2 = new Conputer(new LenovoCpu, new LenovoVideCard, new LenovoMemory);
	compute2->work();
	delete compute2;

	cout << "-----------------------------------" << endl;

	cout << "第三臺電腦開始工作" << endl;
	// 第三臺電腦組裝
	Conputer* compute3 = new Conputer(new InterCpu, new LenovoVideCard, new LenovoMemory);
	compute3->work();
	delete compute3;
}

int main()
{

	test01();


	system("pause");

	return 0;
}