c++學習心得第二期 多型+檔案操作

CodeCong發表於2020-10-06

一、多型

1、多型的基本概念

多型是C++物件導向三大特性之一
多型分為兩類
靜態多型: 函式過載 和 運算子過載屬於靜態多型,複用函式名
動態多型: 派生類和虛擬函式實現執行時多型
靜態多型和動態多型區別:
靜態多型的函式地址早繫結 - 編譯階段確定函式地址
動態多型的函式地址晚繫結 - 執行階段確定函式地址

#include<iostream>
#include<string>
using namespace std;
//因為早繫結,所以地址已經是animal的地址了  想執行小貓說話但結果是動物說話
class Animal{

public:
	void speak()
	{
		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;
}

在這裡插入圖片描述

#include<iostream>
#include<string>
using namespace std;
class Animal{
//在函式前加 virtual 變成動態繫結。
public:
	virtual void   speak()
	{
		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;
}

在這裡插入圖片描述

多型滿足條件
有繼承關係
子類重寫父類中的虛擬函式
多型使用條件
父類指標或引用指向子類物件
重寫:函式返回值型別 函式名 引數列表 完全一致稱為重寫
在這裡插入圖片描述

2、多型案例一-計算器類

分別利用普通寫法和多型技術,設計實現兩個運算元進行運算的計算器類
多型的優點:
程式碼組織結構清晰
可讀性強
利於前期和後期的擴充套件以及維護

#include<iostream>
#include<string>
using namespace std;

class AbstractCalculator{
	
public :
	virtual int result() = 0;
public:
	int num1;
	int num2;
};
class AddCalculator:public AbstractCalculator{

public:
	AddCalculator(int num1,int num2)
	{
		this->num1 = num1;
		this->num2 = num2;
	}
	int result()
	{
		return num1 + num2;
	}
};
class SubstractorCalculator :public AbstractCalculator{

public:
	SubstractorCalculator(int num1, int num2)
	{
		this->num1 = num1;
		this->num2 = num2;
	}
	int result()
	{
		return num1 - num2;
	}
};
class MultiplyrCalculator :public AbstractCalculator{

public:
	MultiplyrCalculator(int num1, int num2)
	{
		this->num1 = num1;
		this->num2 = num2;
	}
	int result()
	{
		return num1*num2;
	}
};
void test01()
{
	AbstractCalculator *abc = new AddCalculator(10, 10);
	cout << abc->result() << endl;
	delete abc;

	abc = new SubstractorCalculator(20,10);
	cout << abc->result() << endl;
	delete abc;

	abc = new MultiplyrCalculator(20, 10);
	cout << abc->result() << endl;
	delete abc;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

3、純虛擬函式和抽象類

在多型中,通常父類中虛擬函式的實現是毫無意義的,主要都是呼叫子類重寫的內容
因此可以將虛擬函式改為純虛擬函式
純虛擬函式語法: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 *base;
	//設定了純虛擬函式即為抽象函式,因此抽象函式不能例項化
	//base = new Base;
	base = new Son();
	base->func();
	delete base;//分配到堆區的記憶體要刪除
}
int main()
{
	test01();
	system("pause");
	return 0;
}

4、多型案例二-製作飲品

製作飲品的大致流程為:煮水 - 沖泡 - 倒入杯中 - 加入輔料
利用多型技術實現本案例,提供抽象製作飲品基類,提供子類製作咖啡和茶葉

#include<iostream>
#include<string>
using namespace std;
class AbstractDrinking
{
public:
	virtual void Boil() = 0;
	virtual void Brew() = 0;
	virtual void PourInCup() = 0;
	virtual void PusSomething() = 0;

	void MakeDrink()
	{
		Boil();
		Brew();
		PourInCup();
		PusSomething();
	}
};

class Coffee :public AbstractDrinking
{
	void Boil()
	{
		cout <<"礦泉水" << endl;
	}
	void Brew()
	{
		cout <<"泡" << endl;
	}
	void PourInCup()
	{
		cout <<"倒" << endl;
	}
	void PusSomething()
	{
		cout <<"加牛奶" << endl;
	}


};
class Tea :public AbstractDrinking
{
	void Boil()
	{
		cout << "自來水" << endl;
	}
	void Brew()
	{
		cout << "加熱" << endl;
	}
	void PourInCup()
	{
		cout << "潑" << endl;
	}
	void PusSomething()
	{
		cout << "加樹葉" << endl;
	}
};
//使用引用呼叫虛擬函式和多型
void DoWork(AbstractDrinking& drinking)
{
	drinking.MakeDrink();
}
//使用指標呼叫虛擬函式和多型
void DoWorkByPoint(AbstractDrinking *dringing)
{
	dringing->MakeDrink();
}
void test01()
{
	//使用引用呼叫虛擬函式和多型
	Tea tea;
	DoWork(tea);
	Coffee coffee;
	DoWork(coffee);
	cout <<"------------------>" << endl;
	//使用指標呼叫虛擬函式和多型
	AbstractDrinking *drinking = new Tea;
	DoWorkByPoint(drinking);
	delete drinking;
	drinking = new Coffee;
	DoWorkByPoint(drinking);
	delete drinking;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

5、虛析構和純虛析構

多型使用時,如果子類中有屬性開闢到堆區,那麼父類指標在釋放時無法呼叫到子類的析構程式碼
解決方式:將父類中的解構函式改為虛析構或者純虛析構
虛析構和純虛析構共性:
可以解決父類指標釋放子類物件
都需要有具體的函式實現
虛析構和純虛析構區別:
如果是純虛析構,該類屬於抽象類,無法例項化物件
虛析構語法:
virtual ~類名(){}
純虛析構語法:
virtual ~類名() = 0;
類名::~類名(){}

總結:

  1. 虛析構或純虛析構就是用來解決通過父類指標釋放子類物件
  2. 如果子類中沒有堆區資料,可以不寫為虛析構或純虛析構
  3. 擁有純虛解構函式的類也屬於抽象類
#include<iostream>
#include<string>
using namespace std;

class Animal{
public:
	Animal()
	{
		cout <<"Animal 建構函式的呼叫" << endl;
	}
	~Animal()
	{
		cout <<"Animal 解構函式的呼叫" << endl;
	}
	virtual void speak() = 0;

};

class Cat :public Animal
{
public:
	Cat()
	{
		cout <<"小貓的建構函式在呼叫" << endl;
	}
	~Cat()
	{
		cout <<"小貓的解構函式在呼叫" << endl;
	}
	void speak()
	{
		cout <<"小貓在叫" << endl;
	}
};

void test01()
{
	Animal* animal = new Cat;
	animal->speak();
	delete animal;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

在這裡插入圖片描述

小貓的解構函式沒有呼叫,如果小貓中存在堆區的分配,有開闢的記憶體空間沒有釋放會造成記憶體洩漏。因此要在父類中使用虛析構和純虛析構,可以是子類中的解構函式被呼叫。

#include<iostream>
#include<string>
using namespace std;

class Animal{
public:
	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 <<"小貓的建構函式在呼叫" << endl;
		m_Name = new string(name);
	}
	~Cat()
	{
		if (m_Name != NULL)
		{
			delete m_Name;
			m_Name = NULL;
		}
		cout <<"小貓的解構函式在呼叫" << endl;
	}
	void speak()
	{
		cout <<*m_Name<<"小貓在叫" << endl;
	}
	string *m_Name;
};

void test01()
{
	Animal* animal = new Cat("Jack");
	animal->speak();
	delete animal;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

在這裡插入圖片描述

6、多型案例三-電腦組裝

電腦主要組成部件為 CPU(用於計算),顯示卡(用於顯示),記憶體條(用於儲存)
將每個零件封裝出抽象基類,並且提供不同的廠商生產不同的零件,例如Intel廠商和Lenovo廠商
建立電腦類提供讓電腦工作的函式,並且呼叫每個零件工作的介面
測試時組裝三臺不同的電腦進行工作

#include<iostream>
#include<string>
using namespace std;

//建立 cpu memory videocard 虛擬函式  建立Inter和Lenovo對虛擬函式進行實現。 
//建立 computer 作為cpu memory 和 videocard的集合類  進行組裝
class Cpu{
public:
	virtual void calculate() = 0;
};
class VideoCard
{
public:
	virtual void display() = 0;
};
class Memory
{
public:
	virtual void storage() = 0;
};
class Computer
{
public:
	Computer(Cpu* c,Memory *m,VideoCard *v)
	{
		cpu = c;
		memory = m;
		videocard = v;
	}
	~Computer()
	{
		if (cpu != NULL)
		{
			delete cpu;
			cpu = NULL;
		}
		if (memory != NULL)
		{
			delete cpu;
			memory = NULL;
		}
		if (videocard != NULL)
		{
			delete cpu;
			videocard = NULL;
		}
	}
	void work()
	{
		cpu->calculate();
		memory->storage();
		videocard->display();
	}
private:
	Cpu *cpu;
	Memory *memory;
	VideoCard *videocard;
};

//具體廠商
//Intel廠商
class IntelCPU :public Cpu
{
public:
	virtual void calculate()
	{
		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 calculate()
	{
		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;

	cout << "第一臺電腦開始工作:" << endl;
	//建立第一臺電腦
	Computer * computer1 = new Computer(intelCpu, intelMem, intelCard);
	computer1->work();
	delete computer1;

	cout << "-----------------------" << endl;
	cout << "第二臺電腦開始工作:" << endl;
	//第二臺電腦組裝
	Computer * computer2 = new Computer(new LenovoCPU, new LenovoMemory,new LenovoVideoCard);
	computer2->work();
	delete computer2;

	cout << "-----------------------" << endl;
	cout << "第三臺電腦開始工作:" << endl;
	//第三臺電腦組裝
	Computer * computer3 = new Computer(new LenovoCPU, new LenovoMemory, new LenovoVideoCard);
	computer3->work();
	delete computer3;

}
int main()
{
	test01();
	system("pause");
	return 0;
}

二、檔案操作

程式執行時產生的資料都屬於臨時資料,程式一旦執行結束都會被釋放
通過檔案可以將資料持久化
C++中對檔案操作需要包含標頭檔案 < fstream >
檔案型別分為兩種:
文字檔案 - 檔案以文字的ASCII碼形式儲存在計算機中
二進位制檔案 - 檔案以文字的二進位制形式儲存在計算機中,使用者一般不能直接讀懂它們
操作檔案的三大類:
ofstream:寫操作
ifstream: 讀操作
fstream : 讀寫操作

1.文字寫檔案

寫檔案步驟如下:
包含標頭檔案
#include
建立流物件
ofstream ofs;
開啟檔案
ofs.open(“檔案路徑”,開啟方式);
寫資料
ofs << “寫入的資料”;
關閉檔案
ofs.close();

在這裡插入圖片描述

注意: 檔案開啟方式可以配合使用,利用|操作符
**例如:**用二進位制方式寫檔案 ios::binary | ios:: out

#include<iostream>
using namespace std;
#include<fstream>

void test01()
{
	ofstream ofs;
	ofs.open("text.txt",ios::out);
	ofs << "姓名:張三" << endl;
	ofs << "性別:男" << endl;
	ofs.close();
}

int main()
{
	test01();
	system("pause");
	return 0;
}

在這裡插入圖片描述在這裡插入圖片描述

檔案操作必須包含標頭檔案 fstream
讀檔案可以利用 ofstream ,或者fstream類
開啟檔案時候需要指定操作檔案的路徑,以及開啟方式
利用<<可以向檔案中寫資料
操作完畢,要關閉檔案

2.文字讀檔案

讀檔案與寫檔案步驟相似,但是讀取方式相對於比較多
讀檔案步驟如下:
包含標頭檔案
#include
建立流物件
ifstream ifs;
開啟檔案並判斷檔案是否開啟成功
ifs.open(“檔案路徑”,開啟方式);
讀資料
四種方式讀取
關閉檔案
ifs.close();

#include<iostream>
using namespace std;
#include<fstream>
#include<string>

void test01()
{
	ifstream ifs;
	ifs.open("text.txt",ios::in);
	if (!ifs.is_open())
	{
		cout <<"檔案開啟失敗" << endl;
		return;
	}

	string buf;
	while (getline(ifs,buf))
	{
		cout <<buf << endl;

	}

}

int main()
{
	test01();
	system("pause");
	return 0;
}

3、二進位制檔案

以二進位制的方式對檔案進行讀寫操作
開啟方式要指定為 ios::binary

4、二進位制寫檔案

二進位制方式寫檔案主要利用流物件呼叫成員函式write
函式原型 :ostream& write(const char * buffer,int len);
引數解釋:字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數

檔案輸出流物件 可以通過write函式,以二進位制方式寫資料

#include<iostream>
using namespace std;
#include<fstream>
#include<string>

class Person{
public:
	char m_Name[64];
	int m_Age;
};
//二進位制寫檔案
void test01()
{
	//建立輸出流物件
	ofstream ofs("person.txt",ios::out|ios::binary);
	Person p = {"張三",18};
	ofs.write((const char*)&p,sizeof(p));
	ofs.close();
}

int main()
{
	test01();
	system("pause");
	return 0;
}

5、二進位制讀檔案

二進位制方式讀檔案主要利用流物件呼叫成員函式read
函式原型:istream& read(char *buffer,int len);
引數解釋:字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數

檔案輸入流物件 可以通過read函式,以二進位制方式讀資料

#include<iostream>
using namespace std;
#include<fstream>
#include<string>

class Person{
public:
	char m_Name[64];
	int m_Age;
};
//二進位制寫檔案
void test01()
{
	ifstream ifs("person.txt",ios::in|ios::binary);
	if (!ifs.is_open())
	{
		cout <<"檔案開啟失敗" << endl;
	}
	Person p;
	ifs.read((char*)&p,sizeof(p));
	cout <<"姓名  " <<p.m_Name <<"  年齡  " <<p.m_Age<< endl;
}

int main()
{
	test01();
	system("pause");
	return 0;
}

在這裡插入圖片描述

相關文章