C++核心程式設計筆記

斐波那契數列~發表於2020-10-19

C++核心程式設計

本階段主要針對C++物件導向程式設計技術做詳細講解,探討C++中的核心和精髓。

1 記憶體分割槽模型

C++程式在執行時,將記憶體大方向劃分為4個區域

  • 程式碼區:存放函式體的二進位制程式碼,由作業系統進行管理的
  • 全域性區:存放全域性變數和靜態變數以及常量
  • 棧區:由編譯器自動分配釋放, 存放函式的引數值,區域性變數等
  • 堆區:由程式設計師分配和釋放,若程式設計師不釋放,程式結束時由作業系統回收

記憶體四區意義:

不同區域存放的資料,賦予不同的生命週期, 給我們更大的靈活程式設計

1.1 程式執行前

​ 在程式編譯後,生成了exe可執行程式,未執行該程式前分為兩個區域

程式碼區:

​ 存放 CPU 執行的機器指令

​ 程式碼區是共享的,共享的目的是對於頻繁被執行的程式,只需要在記憶體中有一份程式碼即可

​ 程式碼區是只讀的,使其只讀的原因是防止程式意外地修改了它的指令

全域性區:

​ 全域性變數和靜態變數存放在此.

​ 全域性區還包含了常量區, 字串常量和其他常量也存放在此.

該區域的資料在程式結束後由作業系統釋放.

示例:

//全域性變數
int g_a = 10;
int g_b = 10;

//全域性常量
const int c_g_a = 10;
const int c_g_b = 10;

int main() {

	//區域性變數
	int a = 10;
	int b = 10;

	//列印地址
	cout << "區域性變數a地址為: " << (int)&a << endl;
	cout << "區域性變數b地址為: " << (int)&b << endl;

	cout << "全域性變數g_a地址為: " <<  (int)&g_a << endl;
	cout << "全域性變數g_b地址為: " <<  (int)&g_b << endl;

	//靜態變數
	static int s_a = 10;
	static int s_b = 10;

	cout << "靜態變數s_a地址為: " << (int)&s_a << endl;
	cout << "靜態變數s_b地址為: " << (int)&s_b << endl;

	cout << "字串常量地址為: " << (int)&"hello world" << endl;
	cout << "字串常量地址為: " << (int)&"hello world1" << endl;

	cout << "全域性常量c_g_a地址為: " << (int)&c_g_a << endl;
	cout << "全域性常量c_g_b地址為: " << (int)&c_g_b << endl;

	const int c_l_a = 10;
	const int c_l_b = 10;
	cout << "區域性常量c_l_a地址為: " << (int)&c_l_a << endl;
	cout << "區域性常量c_l_b地址為: " << (int)&c_l_b << endl;

	system("pause");

	return 0;
}

列印結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-WwMjqOfv-1603073902170)(assets/1545017602518.png)]

總結:

  • C++中在程式執行前分為全域性區和程式碼區
  • 程式碼區特點是共享和只讀
  • 全域性區中存放全域性變數、靜態變數、常量
  • 常量區中存放 const修飾的全域性常量 和 字串常量

1.2 程式執行後

棧區:

​ 由編譯器自動分配釋放, 存放函式的引數值,區域性變數等

​ 注意事項:不要返回區域性變數的地址,棧區開闢的資料由編譯器自動釋放

示例:

int * func()
{
	int a = 10;
	return &a;
}

int main() {

	int *p = func();

	cout << *p << endl;
	cout << *p << endl;

	system("pause");

	return 0;
}

堆區:

​ 由程式設計師分配釋放,若程式設計師不釋放,程式結束時由作業系統回收

​ 在C++中主要利用new在堆區開闢記憶體

示例:

int* func()
{
	int* a = new int(10);
	return a;
}

int main() {

	int *p = func();

	cout << *p << endl;
	cout << *p << endl;
    
	system("pause");

	return 0;
}

總結:

堆區資料由程式設計師管理開闢和釋放

堆區資料利用new關鍵字進行開闢記憶體

1.3 new操作符

​ C++中利用new操作符在堆區開闢資料

​ 堆區開闢的資料,由程式設計師手動開闢,手動釋放,釋放利用操作符 delete

​ 語法:new 資料型別

​ 利用new建立的資料,會返回該資料對應的型別的指標

示例1: 基本語法

int* func()
{
	int* a = new int(10);
	return a;
}

int main() {

	int *p = func();

	cout << *p << endl;
	cout << *p << endl;

	//利用delete釋放堆區資料
	delete p;

	//cout << *p << endl; //報錯,釋放的空間不可訪問

	system("pause");

	return 0;
}

示例2:開闢陣列

//堆區開闢陣列
int main() {

	int* arr = new int[10];

	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;
	}

	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
	//釋放陣列 delete 後加 []
	delete[] arr;

	system("pause");

	return 0;
}

2 引用

2.1 引用的基本使用

**作用: **給變數起別名

語法: 資料型別 &別名 = 原名

示例:

int main() {

	int a = 10;
	int &b = a;

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	b = 100;

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	system("pause");

	return 0;
}

2.2 引用注意事項

  • 引用必須初始化
  • 引用在初始化後,不可以改變

示例:

int main() {

	int a = 10;
	int b = 20;
	//int &c; //錯誤,引用必須初始化
	int &c = a; //一旦初始化後,就不可以更改
	c = b; //這是賦值操作,不是更改引用

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;

	system("pause");

	return 0;
}

2.3 引用做函式引數

**作用:**函式傳參時,可以利用引用的技術讓形參修飾實參

**優點:**可以簡化指標修改實參

示例:

//1. 值傳遞
void mySwap01(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
}

//2. 地址傳遞
void mySwap02(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

//3. 引用傳遞
void mySwap03(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}

int main() {

	int a = 10;
	int b = 20;

	mySwap01(a, b);
	cout << "a:" << a << " b:" << b << endl;

	mySwap02(&a, &b);
	cout << "a:" << a << " b:" << b << endl;

	mySwap03(a, b);
	cout << "a:" << a << " b:" << b << endl;

	system("pause");

	return 0;
}

總結:通過引用引數產生的效果同按地址傳遞是一樣的。引用的語法更清楚簡單

2.4 引用做函式返回值

作用:引用是可以作為函式的返回值存在的

注意:不要返回區域性變數引用

用法:函式呼叫作為左值

示例:

//返回區域性變數引用
int& test01() {
	int a = 10; //區域性變數
	return a;
}

//返回靜態變數引用
int& test02() {
	static int a = 20;
	return a;
}

int main() {

	//不能返回區域性變數的引用
	int& ref = test01();
	cout << "ref = " << ref << endl;
	cout << "ref = " << ref << endl;

	//如果函式做左值,那麼必須返回引用
	int& ref2 = test02();
	cout << "ref2 = " << ref2 << endl;
	cout << "ref2 = " << ref2 << endl;

	test02() = 1000;

	cout << "ref2 = " << ref2 << endl;
	cout << "ref2 = " << ref2 << endl;

	system("pause");

	return 0;
}

2.5 引用的本質

本質:引用的本質在c++內部實現是一個指標常量.

講解示例:

//發現是引用,轉換為 int* const ref = &a;
void func(int& ref){
	ref = 100; // ref是引用,轉換為*ref = 100
}
int main(){
	int a = 10;
    
    //自動轉換為 int* const ref = &a; 指標常量是指標指向不可改,也說明為什麼引用不可更改
	int& ref = a; 
	ref = 20; //內部發現ref是引用,自動幫我們轉換為: *ref = 20;
    
	cout << "a:" << a << endl;
	cout << "ref:" << ref << endl;
    
	func(a);
	return 0;
}

結論:C++推薦用引用技術,因為語法方便,引用本質是指標常量,但是所有的指標操作編譯器都幫我們做了

2.6 常量引用

**作用:**常量引用主要用來修飾形參,防止誤操作

在函式形參列表中,可以加const修飾形參,防止形參改變實參

示例:

//引用使用的場景,通常用來修飾形參
void showValue(const int& v) {
	//v += 10;
	cout << v << endl;
}

int main() {

	//int& ref = 10;  引用本身需要一個合法的記憶體空間,因此這行錯誤
	//加入const就可以了,編譯器優化程式碼,int temp = 10; const int& ref = temp;
	const int& ref = 10;

	//ref = 100;  //加入const後不可以修改變數
	cout << ref << endl;

	//函式中利用常量引用防止誤操作修改實參
	int a = 10;
	showValue(a);

	system("pause");

	return 0;
}

3 函式提高

3.1 函式預設引數

在C++中,函式的形參列表中的形參是可以有預設值的。

語法:返回值型別 函式名 (引數= 預設值){}

示例:

int func(int a, int b = 10, int c = 10) {
	return a + b + c;
}

//1. 如果某個位置引數有預設值,那麼從這個位置往後,從左向右,必須都要有預設值
//2. 如果函式宣告有預設值,函式實現的時候就不能有預設引數
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
	return a + b;
}

int main() {

	cout << "ret = " << func(20, 20) << endl;
	cout << "ret = " << func(100) << endl;

	system("pause");

	return 0;
}

3.2 函式佔位引數

C++中函式的形參列表裡可以有佔位引數,用來做佔位,呼叫函式時必須填補該位置

語法: 返回值型別 函式名 (資料型別){}

在現階段函式的佔位引數存在意義不大,但是後面的課程中會用到該技術

示例:

//函式佔位引數 ,佔位引數也可以有預設引數
void func(int a, int) {
	cout << "this is func" << endl;
}

int main() {

	func(10,10); //佔位引數必須填補

	system("pause");

	return 0;
}

3.3 函式過載

3.3.1 函式過載概述

**作用:**函式名可以相同,提高複用性

函式過載滿足條件:

  • 同一個作用域下
  • 函式名稱相同
  • 函式引數型別不同 或者 個數不同 或者 順序不同

注意: 函式的返回值不可以作為函式過載的條件

示例:

//函式過載需要函式都在同一個作用域下
void func()
{
	cout << "func 的呼叫!" << endl;
}
void func(int a)
{
	cout << "func (int a) 的呼叫!" << endl;
}
void func(double a)
{
	cout << "func (double a)的呼叫!" << endl;
}
void func(int a ,double b)
{
	cout << "func (int a ,double b) 的呼叫!" << endl;
}
void func(double a ,int b)
{
	cout << "func (double a ,int b)的呼叫!" << endl;
}

//函式返回值不可以作為函式過載條件
//int func(double a, int b)
//{
//	cout << "func (double a ,int b)的呼叫!" << endl;
//}


int main() {

	func();
	func(10);
	func(3.14);
	func(10,3.14);
	func(3.14 , 10);
	
	system("pause");

	return 0;
}

3.3.2 函式過載注意事項

  • 引用作為過載條件
  • 函式過載碰到函式預設引數

示例:

//函式過載注意事項
//1、引用作為過載條件

void func(int &a)
{
	cout << "func (int &a) 呼叫 " << endl;
}

void func(const int &a)
{
	cout << "func (const int &a) 呼叫 " << endl;
}


//2、函式過載碰到函式預設引數

void func2(int a, int b = 10)
{
	cout << "func2(int a, int b = 10) 呼叫" << endl;
}

void func2(int a)
{
	cout << "func2(int a) 呼叫" << endl;
}

int main() {
	
	int a = 10;
	func(a); //呼叫無const
	func(10);//呼叫有const


	//func2(10); //碰到預設引數產生歧義,需要避免

	system("pause");

	return 0;
}

4 類和物件

C++物件導向的三大特性為:封裝、繼承、多型

C++認為萬事萬物都皆為物件,物件上有其屬性和行為

例如:

​ 人可以作為物件,屬性有姓名、年齡、身高、體重…,行為有走、跑、跳、吃飯、唱歌…

​ 車也可以作為物件,屬性有輪胎、方向盤、車燈…,行為有載人、放音樂、放空調…

​ 具有相同性質的物件,我們可以抽象稱為,人屬於人類,車屬於車類

4.1 封裝

4.1.1 封裝的意義

封裝是C++物件導向三大特性之一

封裝的意義:

  • 將屬性和行為作為一個整體,表現生活中的事物
  • 將屬性和行為加以許可權控制

封裝意義一:

​ 在設計類的時候,屬性和行為寫在一起,表現事物

語法: class 類名{ 訪問許可權: 屬性 / 行為 };

**示例1:**設計一個圓類,求圓的周長

示例程式碼:

//圓周率
const double PI = 3.14;

//1、封裝的意義
//將屬性和行為作為一個整體,用來表現生活中的事物

//封裝一個圓類,求圓的周長
//class代表設計一個類,後面跟著的是類名
class Circle
{
public:  //訪問許可權  公共的許可權

	//屬性
	int m_r;//半徑

	//行為
	//獲取到圓的周長
	double calculateZC()
	{
		//2 * pi  * r
		//獲取圓的周長
		return  2 * PI * m_r;
	}
};

int main() {

	//通過圓類,建立圓的物件
	// c1就是一個具體的圓
	Circle c1;
	c1.m_r = 10; //給圓物件的半徑 進行賦值操作

	//2 * pi * 10 = = 62.8
	cout << "圓的周長為: " << c1.calculateZC() << endl;

	system("pause");

	return 0;
}

**示例2:**設計一個學生類,屬性有姓名和學號,可以給姓名和學號賦值,可以顯示學生的姓名和學號

示例2程式碼:

//學生類
class Student {
public:
	void setName(string name) {
		m_name = name;
	}
	void setID(int id) {
		m_id = id;
	}

	void showStudent() {
		cout << "name:" << m_name << " ID:" << m_id << endl;
	}
public:
	string m_name;
	int m_id;
};

int main() {

	Student stu;
	stu.setName("德瑪西亞");
	stu.setID(250);
	stu.showStudent();

	system("pause");

	return 0;
}

封裝意義二:

類在設計時,可以把屬性和行為放在不同的許可權下,加以控制

訪問許可權有三種:

  1. public 公共許可權
  2. protected 保護許可權
  3. private 私有許可權

示例:

//三種許可權
//公共許可權  public     類內可以訪問  類外可以訪問
//保護許可權  protected  類內可以訪問  類外不可以訪問
//私有許可權  private    類內可以訪問  類外不可以訪問

class Person
{
	//姓名  公共許可權
public:
	string m_Name;

	//汽車  保護許可權
protected:
	string m_Car;

	//銀行卡密碼  私有許可權
private:
	int m_Password;

public:
	void func()
	{
		m_Name = "張三";
		m_Car = "拖拉機";
		m_Password = 123456;
	}
};

int main() {

	Person p;
	p.m_Name = "李四";
	//p.m_Car = "賓士";  //保護許可權類外訪問不到
	//p.m_Password = 123; //私有許可權類外訪問不到

	system("pause");

	return 0;
}

4.1.2 struct和class區別

在C++中 struct和class唯一的區別就在於 預設的訪問許可權不同

區別:

  • struct 預設許可權為公共
  • class 預設許可權為私有
class C1
{
	int  m_A; //預設是私有許可權
};

struct C2
{
	int m_A;  //預設是公共許可權
};

int main() {

	C1 c1;
	c1.m_A = 10; //錯誤,訪問許可權是私有

	C2 c2;
	c2.m_A = 10; //正確,訪問許可權是公共

	system("pause");

	return 0;
}

4.1.3 成員屬性設定為私有

**優點1:**將所有成員屬性設定為私有,可以自己控制讀寫許可權

**優點2:**對於寫許可權,我們可以檢測資料的有效性

示例:

class Person {
public:

	//姓名設定可讀可寫
	void setName(string name) {
		m_Name = name;
	}
	string getName()
	{
		return m_Name;
	}


	//獲取年齡 
	int getAge() {
		return m_Age;
	}
	//設定年齡
	void setAge(int age) {
		if (age < 0 || age > 150) {
			cout << "你個老妖精!" << endl;
			return;
		}
		m_Age = age;
	}

	//情人設定為只寫
	void setLover(string lover) {
		m_Lover = lover;
	}

private:
	string m_Name; //可讀可寫  姓名
	
	int m_Age; //只讀  年齡

	string m_Lover; //只寫  情人
};


int main() {

	Person p;
	//姓名設定
	p.setName("張三");
	cout << "姓名: " << p.getName() << endl;

	//年齡設定
	p.setAge(50);
	cout << "年齡: " << p.getAge() << endl;

	//情人設定
	p.setLover("蒼井");
	//cout << "情人: " << p.m_Lover << endl;  //只寫屬性,不可以讀取

	system("pause");

	return 0;
}

4.2 物件的初始化和清理

  • 生活中我們買的電子產品都基本會有出廠設定,在某一天我們不用時候也會刪除一些自己資訊資料保證安全
  • C++中的物件導向來源於生活,每個物件也都會有初始設定以及 物件銷燬前的清理資料的設定。

4.2.1 建構函式和解構函式

物件的初始化和清理也是兩個非常重要的安全問題

​ 一個物件或者變數沒有初始狀態,對其使用後果是未知

​ 同樣的使用完一個物件或變數,沒有及時清理,也會造成一定的安全問題

c++利用了建構函式解構函式解決上述問題,這兩個函式將會被編譯器自動呼叫,完成物件初始化和清理工作。

物件的初始化和清理工作是編譯器強制要我們做的事情,因此如果我們不提供構造和析構,編譯器會提供

編譯器提供的建構函式和解構函式是空實現。

  • 建構函式:主要作用在於建立物件時為物件的成員屬性賦值,建構函式由編譯器自動呼叫,無須手動呼叫。
  • 解構函式:主要作用在於物件銷燬前系統自動呼叫,執行一些清理工作。

建構函式語法:類名(){}

  1. 建構函式,沒有返回值也不寫void
  2. 函式名稱與類名相同
  3. 建構函式可以有引數,因此可以發生過載
  4. 程式在呼叫物件時候會自動呼叫構造,無須手動呼叫,而且只會呼叫一次

解構函式語法: ~類名(){}

  1. 解構函式,沒有返回值也不寫void
  2. 函式名稱與類名相同,在名稱前加上符號 ~
  3. 解構函式不可以有引數,因此不可以發生過載
  4. 程式在物件銷燬前會自動呼叫析構,無須手動呼叫,而且只會呼叫一次
class Person
{
public:
	//建構函式
	Person()
	{
		cout << "Person的建構函式呼叫" << endl;
	}
	//解構函式
	~Person()
	{
		cout << "Person的解構函式呼叫" << endl;
	}

};

void test01()
{
	Person p;
}

int main() {
	
	test01();

	system("pause");

	return 0;
}

4.2.2 建構函式的分類及呼叫

兩種分類方式:

​ 按引數分為: 有參構造和無參構造

​ 按型別分為: 普通構造和拷貝構造

三種呼叫方式:

​ 括號法

​ 顯示法

​ 隱式轉換法

示例:

//1、建構函式分類
// 按照引數分類分為 有參和無參構造   無參又稱為預設建構函式
// 按照型別分類分為 普通構造和拷貝構造

class Person {
public:
	//無參(預設)建構函式
	Person() {
		cout << "無參建構函式!" << endl;
	}
	//有參建構函式
	Person(int a) {
		age = a;
		cout << "有參建構函式!" << endl;
	}
	//拷貝建構函式
	Person(const Person& p) {
		age = p.age;
		cout << "拷貝建構函式!" << endl;
	}
	//解構函式
	~Person() {
		cout << "解構函式!" << endl;
	}
public:
	int age;
};

//2、建構函式的呼叫
//呼叫無參建構函式
void test01() {
	Person p; //呼叫無參建構函式
}

//呼叫有參的建構函式
void test02() {

	//2.1  括號法,常用
	Person p1(10);
	//注意1:呼叫無參建構函式不能加括號,如果加了編譯器認為這是一個函式宣告
	//Person p2();

	//2.2 顯式法
	Person p2 = Person(10); 
	Person p3 = Person(p2);
	//Person(10)單獨寫就是匿名物件  當前行結束之後,馬上析構

	//2.3 隱式轉換法
	Person p4 = 10; // Person p4 = Person(10); 
	Person p5 = p4; // Person p5 = Person(p4); 

	//注意2:不能利用 拷貝建構函式 初始化匿名物件 編譯器認為是物件宣告
	//Person p5(p4);
}

int main() {

	test01();
	//test02();

	system("pause");

	return 0;
}

4.2.3 拷貝建構函式呼叫時機

C++中拷貝建構函式呼叫時機通常有三種情況

  • 使用一個已經建立完畢的物件來初始化一個新物件
  • 值傳遞的方式給函式引數傳值
  • 以值方式返回區域性物件

示例:

class Person {
public:
	Person() {
		cout << "無參建構函式!" << endl;
		mAge = 0;
	}
	Person(int age) {
		cout << "有參建構函式!" << endl;
		mAge = age;
	}
	Person(const Person& p) {
		cout << "拷貝建構函式!" << endl;
		mAge = p.mAge;
	}
	//解構函式在釋放記憶體之前呼叫
	~Person() {
		cout << "解構函式!" << endl;
	}
public:
	int mAge;
};

//1. 使用一個已經建立完畢的物件來初始化一個新物件
void test01() {

	Person man(100); //p物件已經建立完畢
	Person newman(man); //呼叫拷貝建構函式
	Person newman2 = man; //拷貝構造

	//Person newman3;
	//newman3 = man; //不是呼叫拷貝建構函式,賦值操作
}

//2. 值傳遞的方式給函式引數傳值
//相當於Person p1 = p;
void doWork(Person p1) {}
void test02() {
	Person p; //無參建構函式
	doWork(p);
}

//3. 以值方式返回區域性物件
Person doWork2()
{
	Person p1;
	cout << (int *)&p1 << endl;
	return p1;
}

void test03()
{
	Person p = doWork2();
	cout << (int *)&p << endl;
}


int main() {

	//test01();
	//test02();
	test03();

	system("pause");

	return 0;
}

4.2.4 建構函式呼叫規則

預設情況下,c++編譯器至少給一個類新增3個函式

1.預設建構函式(無參,函式體為空)

2.預設解構函式(無參,函式體為空)

3.預設拷貝建構函式,對屬性進行值拷貝

建構函式呼叫規則如下:

  • 如果使用者定義有參建構函式,c++不在提供預設無參構造,但是會提供預設拷貝構造

  • 如果使用者定義拷貝建構函式,c++不會再提供其他建構函式

示例:

class Person {
public:
	//無參(預設)建構函式
	Person() {
		cout << "無參建構函式!" << endl;
	}
	//有參建構函式
	Person(int a) {
		age = a;
		cout << "有參建構函式!" << endl;
	}
	//拷貝建構函式
	Person(const Person& p) {
		age = p.age;
		cout << "拷貝建構函式!" << endl;
	}
	//解構函式
	~Person() {
		cout << "解構函式!" << endl;
	}
public:
	int age;
};

void test01()
{
	Person p1(18);
	//如果不寫拷貝構造,編譯器會自動新增拷貝構造,並且做淺拷貝操作
	Person p2(p1);

	cout << "p2的年齡為: " << p2.age << endl;
}

void test02()
{
	//如果使用者提供有參構造,編譯器不會提供預設構造,會提供拷貝構造
	Person p1; //此時如果使用者自己沒有提供預設構造,會出錯
	Person p2(10); //使用者提供的有參
	Person p3(p2); //此時如果使用者沒有提供拷貝構造,編譯器會提供

	//如果使用者提供拷貝構造,編譯器不會提供其他建構函式
	Person p4; //此時如果使用者自己沒有提供預設構造,會出錯
	Person p5(10); //此時如果使用者自己沒有提供有參,會出錯
	Person p6(p5); //使用者自己提供拷貝構造
}

int main() {

	test01();

	system("pause");

	return 0;
}

4.2.5 深拷貝與淺拷貝

深淺拷貝是面試經典問題,也是常見的一個坑

淺拷貝:簡單的賦值拷貝操作

深拷貝:在堆區重新申請空間,進行拷貝操作

示例:

class Person {
public:
	//無參(預設)建構函式
	Person() {
		cout << "無參建構函式!" << endl;
	}
	//有參建構函式
	Person(int age ,int height) {
		
		cout << "有參建構函式!" << endl;

		m_age = age;
		m_height = new int(height);
		
	}
	//拷貝建構函式  
	Person(const Person& p) {
		cout << "拷貝建構函式!" << endl;
		//如果不利用深拷貝在堆區建立新記憶體,會導致淺拷貝帶來的重複釋放堆區問題
		m_age = p.m_age;
		m_height = new int(*p.m_height);
		
	}

	//解構函式
	~Person() {
		cout << "解構函式!" << endl;
		if (m_height != NULL)
		{
			delete m_height;
		}
	}
public:
	int m_age;
	int* m_height;
};

void test01()
{
	Person p1(18, 180);

	Person p2(p1);

	cout << "p1的年齡: " << p1.m_age << " 身高: " << *p1.m_height << endl;

	cout << "p2的年齡: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:如果屬性有在堆區開闢的,一定要自己提供拷貝建構函式,防止淺拷貝帶來的問題

4.2.6 初始化列表

作用:

C++提供了初始化列表語法,用來初始化屬性

語法:建構函式():屬性1(值1),屬性2(值2)... {}

示例:

class Person {
public:

	傳統方式初始化
	//Person(int a, int b, int c) {
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}

	//初始化列表方式初始化
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
	void PrintPerson() {
		cout << "mA:" << m_A << endl;
		cout << "mB:" << m_B << endl;
		cout << "mC:" << m_C << endl;
	}
private:
	int m_A;
	int m_B;
	int m_C;
};

int main() {

	Person p(1, 2, 3);
	p.PrintPerson();


	system("pause");

	return 0;
}

4.2.7 類物件作為類成員

C++類中的成員可以是另一個類的物件,我們稱該成員為 物件成員

例如:

class A {}
class B
{
    A a;
}

B類中有物件A作為成員,A為物件成員

那麼當建立B物件時,A與B的構造和析構的順序是誰先誰後?

示例:

class Phone
{
public:
	Phone(string name)
	{
		m_PhoneName = name;
		cout << "Phone構造" << endl;
	}

	~Phone()
	{
		cout << "Phone析構" << endl;
	}

	string m_PhoneName;

};


class Person
{
public:

	//初始化列表可以告訴編譯器呼叫哪一個建構函式
	Person(string name, string pName) :m_Name(name), m_Phone(pName)
	{
		cout << "Person構造" << endl;
	}

	~Person()
	{
		cout << "Person析構" << endl;
	}

	void playGame()
	{
		cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手機! " << endl;
	}

	string m_Name;
	Phone m_Phone;

};
void test01()
{
	//當類中成員是其他類物件時,我們稱該成員為 物件成員
	//構造的順序是 :先呼叫物件成員的構造,再呼叫本類構造
	//析構順序與構造相反
	Person p("張三" , "蘋果X");
	p.playGame();

}


int main() {

	test01();

	system("pause");

	return 0;
}

4.2.8 靜態成員

靜態成員就是在成員變數和成員函式前加上關鍵字static,稱為靜態成員

靜態成員分為:

  • 靜態成員變數
    • 所有物件共享同一份資料
    • 在編譯階段分配記憶體
    • 類內宣告,類外初始化
  • 靜態成員函式
    • 所有物件共享同一個函式
    • 靜態成員函式只能訪問靜態成員變數

**示例1 :**靜態成員變數

class Person
{
	
public:

	static int m_A; //靜態成員變數

	//靜態成員變數特點:
	//1 在編譯階段分配記憶體
	//2 類內宣告,類外初始化
	//3 所有物件共享同一份資料

private:
	static int m_B; //靜態成員變數也是有訪問許可權的
};
int Person::m_A = 10;
int Person::m_B = 10;

void test01()
{
	//靜態成員變數兩種訪問方式

	//1、通過物件
	Person p1;
	p1.m_A = 100;
	cout << "p1.m_A = " << p1.m_A << endl;

	Person p2;
	p2.m_A = 200;
	cout << "p1.m_A = " << p1.m_A << endl; //共享同一份資料
	cout << "p2.m_A = " << p2.m_A << endl;

	//2、通過類名
	cout << "m_A = " << Person::m_A << endl;


	//cout << "m_B = " << Person::m_B << endl; //私有許可權訪問不到
}

int main() {

	test01();

	system("pause");

	return 0;
}

**示例2:**靜態成員函式

class Person
{

public:

	//靜態成員函式特點:
	//1 程式共享一個函式
	//2 靜態成員函式只能訪問靜態成員變數
	
	static void func()
	{
		cout << "func呼叫" << endl;
		m_A = 100;
		//m_B = 100; //錯誤,不可以訪問非靜態成員變數
	}

	static int m_A; //靜態成員變數
	int m_B; // 
private:

	//靜態成員函式也是有訪問許可權的
	static void func2()
	{
		cout << "func2呼叫" << endl;
	}
};
int Person::m_A = 10;


void test01()
{
	//靜態成員變數兩種訪問方式

	//1、通過物件
	Person p1;
	p1.func();

	//2、通過類名
	Person::func();


	//Person::func2(); //私有許可權訪問不到
}

int main() {

	test01();

	system("pause");

	return 0;
}

4.3 C++物件模型和this指標

4.3.1 成員變數和成員函式分開儲存

在C++中,類內的成員變數和成員函式分開儲存

只有非靜態成員變數才屬於類的物件上

class Person {
public:
	Person() {
		mA = 0;
	}
	//非靜態成員變數佔物件空間
	int mA;
	//靜態成員變數不佔物件空間
	static int mB; 
	//函式也不佔物件空間,所有函式共享一個函式例項
	void func() {
		cout << "mA:" << this->mA << endl;
	}
	//靜態成員函式也不佔物件空間
	static void sfunc() {
	}
};

int main() {

	cout << sizeof(Person) << endl;

	system("pause");

	return 0;
}

4.3.2 this指標概念

通過4.3.1我們知道在C++中成員變數和成員函式是分開儲存的

每一個非靜態成員函式只會誕生一份函式例項,也就是說多個同型別的物件會共用一塊程式碼

那麼問題是:這一塊程式碼是如何區分那個物件呼叫自己的呢?

c++通過提供特殊的物件指標,this指標,解決上述問題。this指標指向被呼叫的成員函式所屬的物件

this指標是隱含每一個非靜態成員函式內的一種指標

this指標不需要定義,直接使用即可

this指標的用途:

  • 當形參和成員變數同名時,可用this指標來區分
  • 在類的非靜態成員函式中返回物件本身,可使用return *this
class Person
{
public:

	Person(int age)
	{
		//1、當形參和成員變數同名時,可用this指標來區分
		this->age = age;
	}

	Person& PersonAddPerson(Person p)
	{
		this->age += p.age;
		//返回物件本身
		return *this;
	}

	int age;
};

void test01()
{
	Person p1(10);
	cout << "p1.age = " << p1.age << endl;

	Person p2(10);
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
	cout << "p2.age = " << p2.age << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

4.3.3 空指標訪問成員函式

C++中空指標也是可以呼叫成員函式的,但是也要注意有沒有用到this指標

如果用到this指標,需要加以判斷保證程式碼的健壯性

示例:

//空指標訪問成員函式
class Person {
public:

	void ShowClassName() {
		cout << "我是Person類!" << endl;
	}

	void ShowPerson() {
		if (this == NULL) {
			return;
		}
		cout << mAge << endl;
	}

public:
	int mAge;
};

void test01()
{
	Person * p = NULL;
	p->ShowClassName(); //空指標,可以呼叫成員函式
	p->ShowPerson();  //但是如果成員函式中用到了this指標,就不可以了
}

int main() {

	test01();

	system("pause");

	return 0;
}

4.3.4 const修飾成員函式

常函式:

  • 成員函式後加const後我們稱為這個函式為常函式
  • 常函式內不可以修改成員屬性
  • 成員屬性宣告時加關鍵字mutable後,在常函式中依然可以修改

常物件:

  • 宣告物件前加const稱該物件為常物件
  • 常物件只能呼叫常函式

示例:

class Person {
public:
	Person() {
		m_A = 0;
		m_B = 0;
	}

	//this指標的本質是一個指標常量,指標的指向不可修改
	//如果想讓指標指向的值也不可以修改,需要宣告常函式
	void ShowPerson() const {
		//const Type* const pointer;
		//this = NULL; //不能修改指標的指向 Person* const this;
		//this->mA = 100; //但是this指標指向的物件的資料是可以修改的

		//const修飾成員函式,表示指標指向的記憶體空間的資料不能修改,除了mutable修飾的變數
		this->m_B = 100;
	}

	void MyFunc() const {
		//mA = 10000;
	}

public:
	int m_A;
	mutable int m_B; //可修改 可變的
};


//const修飾物件  常物件
void test01() {

	const Person person; //常量物件  
	cout << person.m_A << endl;
	//person.mA = 100; //常物件不能修改成員變數的值,但是可以訪問
	person.m_B = 100; //但是常物件可以修改mutable修飾成員變數

	//常物件訪問成員函式
	person.MyFunc(); //常物件不能呼叫const的函式

}

int main() {

	test01();

	system("pause");

	return 0;
}

4.4 友元

生活中你的家有客廳(Public),有你的臥室(Private)

客廳所有來的客人都可以進去,但是你的臥室是私有的,也就是說只有你能進去

但是呢,你也可以允許你的好閨蜜好基友進去。

在程式裡,有些私有屬性 也想讓類外特殊的一些函式或者類進行訪問,就需要用到友元的技術

友元的目的就是讓一個函式或者類 訪問另一個類中私有成員

友元的關鍵字為 friend

友元的三種實現

  • 全域性函式做友元
  • 類做友元
  • 成員函式做友元

4.4.1 全域性函式做友元

class Building
{
	//告訴編譯器 goodGay全域性函式 是 Building類的好朋友,可以訪問類中的私有內容
	friend void goodGay(Building * building);

public:

	Building()
	{
		this->m_SittingRoom = "客廳";
		this->m_BedRoom = "臥室";
	}


public:
	string m_SittingRoom; //客廳

private:
	string m_BedRoom; //臥室
};


void goodGay(Building * building)
{
	cout << "好基友正在訪問: " << building->m_SittingRoom << endl;
	cout << "好基友正在訪問: " << building->m_BedRoom << endl;
}


void test01()
{
	Building b;
	goodGay(&b);
}

int main(){

	test01();

	system("pause");
	return 0;
}

4.4.2 類做友元

class Building;
class goodGay
{
public:

	goodGay();
	void visit();

private:
	Building *building;
};


class Building
{
	//告訴編譯器 goodGay類是Building類的好朋友,可以訪問到Building類中私有內容
	friend class goodGay;

public:
	Building();

public:
	string m_SittingRoom; //客廳
private:
	string m_BedRoom;//臥室
};

Building::Building()
{
	this->m_SittingRoom = "客廳";
	this->m_BedRoom = "臥室";
}

goodGay::goodGay()
{
	building = new Building;
}

void goodGay::visit()
{
	cout << "好基友正在訪問" << building->m_SittingRoom << endl;
	cout << "好基友正在訪問" << building->m_BedRoom << endl;
}

void test01()
{
	goodGay gg;
	gg.visit();

}

int main(){

	test01();

	system("pause");
	return 0;
}

4.4.3 成員函式做友元


class Building;
class goodGay
{
public:

	goodGay();
	void visit(); //只讓visit函式作為Building的好朋友,可以發訪問Building中私有內容
	void visit2(); 

private:
	Building *building;
};


class Building
{
	//告訴編譯器  goodGay類中的visit成員函式 是Building好朋友,可以訪問私有內容
	friend void goodGay::visit();

public:
	Building();

public:
	string m_SittingRoom; //客廳
private:
	string m_BedRoom;//臥室
};

Building::Building()
{
	this->m_SittingRoom = "客廳";
	this->m_BedRoom = "臥室";
}

goodGay::goodGay()
{
	building = new Building;
}

void goodGay::visit()
{
	cout << "好基友正在訪問" << building->m_SittingRoom << endl;
	cout << "好基友正在訪問" << building->m_BedRoom << endl;
}

void goodGay::visit2()
{
	cout << "好基友正在訪問" << building->m_SittingRoom << endl;
	//cout << "好基友正在訪問" << building->m_BedRoom << endl;
}

void test01()
{
	goodGay  gg;
	gg.visit();

}

int main(){
    
	test01();

	system("pause");
	return 0;
}

4.5 運算子過載

運算子過載概念:對已有的運算子重新進行定義,賦予其另一種功能,以適應不同的資料型別

4.5.1 加號運算子過載

作用:實現兩個自定義資料型別相加的運算

class Person {
public:
	Person() {};
	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}
	//成員函式實現 + 號運算子過載
	Person operator+(const Person& p) {
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	}


public:
	int m_A;
	int m_B;
};

//全域性函式實現 + 號運算子過載
//Person operator+(const Person& p1, const Person& p2) {
//	Person temp(0, 0);
//	temp.m_A = p1.m_A + p2.m_A;
//	temp.m_B = p1.m_B + p2.m_B;
//	return temp;
//}

//運算子過載 可以發生函式過載 
Person operator+(const Person& p2, int val)  
{
	Person temp;
	temp.m_A = p2.m_A + val;
	temp.m_B = p2.m_B + val;
	return temp;
}

void test() {

	Person p1(10, 10);
	Person p2(20, 20);

	//成員函式方式
	Person p3 = p2 + p1;  //相當於 p2.operaor+(p1)
	cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;


	Person p4 = p3 + 10; //相當於 operator+(p3,10)
	cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;

}

int main() {

	test();

	system("pause");

	return 0;
}

總結1:對於內建的資料型別的表示式的的運算子是不可能改變的

總結2:不要濫用運算子過載

4.5.2 左移運算子過載

作用:可以輸出自定義資料型別

class Person {
	friend ostream& operator<<(ostream& out, Person& p);

public:

	Person(int a, int b)
	{
		this->m_A = a;
		this->m_B = b;
	}

	//成員函式 實現不了  p << cout 不是我們想要的效果
	//void operator<<(Person& p){
	//}

private:
	int m_A;
	int m_B;
};

//全域性函式實現左移過載
//ostream物件只能有一個
ostream& operator<<(ostream& out, Person& p) {
	out << "a:" << p.m_A << " b:" << p.m_B;
	return out;
}

void test() {

	Person p1(10, 20);

	cout << p1 << "hello world" << endl; //鏈式程式設計
}

int main() {

	test();

	system("pause");

	return 0;
}

總結:過載左移運算子配合友元可以實現輸出自定義資料型別

4.5.3 遞增運算子過載

作用: 通過過載遞增運算子,實現自己的整型資料


class MyInteger {

	friend ostream& operator<<(ostream& out, MyInteger myint);

public:
	MyInteger() {
		m_Num = 0;
	}
	//前置++
	MyInteger& operator++() {
		//先++
		m_Num++;
		//再返回
		return *this;
	}

	//後置++
	MyInteger operator++(int) {
		//先返回
		MyInteger temp = *this; //記錄當前本身的值,然後讓本身的值加1,但是返回的是以前的值,達到先返回後++;
		m_Num++;
		return temp;
	}

private:
	int m_Num;
};


ostream& operator<<(ostream& out, MyInteger myint) {
	out << myint.m_Num;
	return out;
}


//前置++ 先++ 再返回
void test01() {
	MyInteger myInt;
	cout << ++myInt << endl;
	cout << myInt << endl;
}

//後置++ 先返回 再++
void test02() {

	MyInteger myInt;
	cout << myInt++ << endl;
	cout << myInt << endl;
}

int main() {

	test01();
	//test02();

	system("pause");

	return 0;
}

總結: 前置遞增返回引用,後置遞增返回值

4.5.4 賦值運算子過載

c++編譯器至少給一個類新增4個函式

  1. 預設建構函式(無參,函式體為空)
  2. 預設解構函式(無參,函式體為空)
  3. 預設拷貝建構函式,對屬性進行值拷貝
  4. 賦值運算子 operator=, 對屬性進行值拷貝

如果類中有屬性指向堆區,做賦值操作時也會出現深淺拷貝問題

示例:

class Person
{
public:

	Person(int age)
	{
		//將年齡資料開闢到堆區
		m_Age = new int(age);
	}

	//過載賦值運算子 
	Person& operator=(Person &p)
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		//編譯器提供的程式碼是淺拷貝
		//m_Age = p.m_Age;

		//提供深拷貝 解決淺拷貝的問題
		m_Age = new int(*p.m_Age);

		//返回自身
		return *this;
	}


	~Person()
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
	}

	//年齡的指標
	int *m_Age;

};


void test01()
{
	Person p1(18);

	Person p2(20);

	Person p3(30);

	p3 = p2 = p1; //賦值操作

	cout << "p1的年齡為:" << *p1.m_Age << endl;

	cout << "p2的年齡為:" << *p2.m_Age << endl;

	cout << "p3的年齡為:" << *p3.m_Age << endl;
}

int main() {

	test01();

	//int a = 10;
	//int b = 20;
	//int c = 30;

	//c = b = a;
	//cout << "a = " << a << endl;
	//cout << "b = " << b << endl;
	//cout << "c = " << c << endl;

	system("pause");

	return 0;
}

4.5.5 關係運算子過載

**作用:**過載關係運算子,可以讓兩個自定義型別物件進行對比操作

示例:

class Person
{
public:
	Person(string name, int age)
	{
		this->m_Name = name;
		this->m_Age = age;
	};

	bool operator==(Person & p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	bool operator!=(Person & p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	string m_Name;
	int m_Age;
};

void test01()
{
	//int a = 0;
	//int b = 0;

	Person a("孫悟空", 18);
	Person b("孫悟空", 18);

	if (a == b)
	{
		cout << "a和b相等" << endl;
	}
	else
	{
		cout << "a和b不相等" << endl;
	}

	if (a != b)
	{
		cout << "a和b不相等" << endl;
	}
	else
	{
		cout << "a和b相等" << endl;
	}
}


int main() {

	test01();

	system("pause");

	return 0;
}

4.5.6 函式呼叫運算子過載

  • 函式呼叫運算子 () 也可以過載
  • 由於過載後使用的方式非常像函式的呼叫,因此稱為仿函式
  • 仿函式沒有固定寫法,非常靈活

示例:

class MyPrint
{
public:
	void operator()(string text)
	{
		cout << text << endl;
	}

};
void test01()
{
	//過載的()操作符 也稱為仿函式
	MyPrint myFunc;
	myFunc("hello world");
}


class MyAdd
{
public:
	int operator()(int v1, int v2)
	{
		return v1 + v2;
	}
};

void test02()
{
	MyAdd add;
	int ret = add(10, 10);
	cout << "ret = " << ret << endl;

	//匿名物件呼叫  
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

int main() {

	test01();
	test02();

	system("pause");

	return 0;
}

4.6 繼承

繼承是物件導向三大特性之一

下級別的成員除了擁有上一級的共性,還有自己的特性。

這個時候我們就可以考慮利用繼承的技術,減少重複程式碼

4.6.1 繼承的基本語法

例如我們看到很多網站中,都有公共的頭部,公共的底部,甚至公共的左側列表,只有中心內容不同

接下來我們分別利用普通寫法和繼承的寫法來實現網頁中的內容,看一下繼承存在的意義以及好處

普通實現:

//Java頁面
class Java 
{
public:
	void header()
	{
		cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
	}
	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分類列表)" << endl;
	}
	void content()
	{
		cout << "JAVA學科視訊" << endl;
	}
};
//Python頁面
class Python
{
public:
	void header()
	{
		cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
	}
	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分類列表)" << endl;
	}
	void content()
	{
		cout << "Python學科視訊" << endl;
	}
};
//C++頁面
class CPP 
{
public:
	void header()
	{
		cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
	}
	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分類列表)" << endl;
	}
	void content()
	{
		cout << "C++學科視訊" << endl;
	}
};

void test01()
{
	//Java頁面
	cout << "Java下載視訊頁面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python頁面
	cout << "Python下載視訊頁面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++頁面
	cout << "C++下載視訊頁面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();

}

int main() {

	test01();

	system("pause");

	return 0;
}

繼承實現:

//公共頁面
class BasePage
{
public:
	void header()
	{
		cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
	}

	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java,Python,C++...(公共分類列表)" << endl;
	}

};

//Java頁面
class Java : public BasePage
{
public:
	void content()
	{
		cout << "JAVA學科視訊" << endl;
	}
};
//Python頁面
class Python : public BasePage
{
public:
	void content()
	{
		cout << "Python學科視訊" << endl;
	}
};
//C++頁面
class CPP : public BasePage
{
public:
	void content()
	{
		cout << "C++學科視訊" << endl;
	}
};

void test01()
{
	//Java頁面
	cout << "Java下載視訊頁面如下: " << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "--------------------" << endl;

	//Python頁面
	cout << "Python下載視訊頁面如下: " << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "--------------------" << endl;

	//C++頁面
	cout << "C++下載視訊頁面如下: " << endl;
	CPP cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();


}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:

繼承的好處:可以減少重複的程式碼

class A : public B;

A 類稱為子類 或 派生類

B 類稱為父類 或 基類

派生類中的成員,包含兩大部分

一類是從基類繼承過來的,一類是自己增加的成員。

從基類繼承過過來的表現其共性,而新增的成員體現了其個性。

4.6.2 繼承方式

繼承的語法:class 子類 : 繼承方式 父類

繼承方式一共有三種:

  • 公共繼承
  • 保護繼承
  • 私有繼承

示例:

class Base1
{
public: 
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

//公共繼承
class Son1 :public Base1
{
public:
	void func()
	{
		m_A; //可訪問 public許可權
		m_B; //可訪問 protected許可權
		//m_C; //不可訪問
	}
};

void myClass()
{
	Son1 s1;
	s1.m_A; //其他類只能訪問到公共許可權
}

//保護繼承
class Base2
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son2:protected Base2
{
public:
	void func()
	{
		m_A; //可訪問 protected許可權
		m_B; //可訪問 protected許可權
		//m_C; //不可訪問
	}
};
void myClass2()
{
	Son2 s;
	//s.m_A; //不可訪問
}

//私有繼承
class Base3
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
class Son3:private Base3
{
public:
	void func()
	{
		m_A; //可訪問 private許可權
		m_B; //可訪問 private許可權
		//m_C; //不可訪問
	}
};
class GrandSon3 :public Son3
{
public:
	void func()
	{
		//Son3是私有繼承,所以繼承Son3的屬性在GrandSon3中都無法訪問到
		//m_A;
		//m_B;
		//m_C;
	}
};

4.6.3 繼承中的物件模型

**問題:**從父類繼承過來的成員,哪些屬於子類物件中?

示例:

class Base
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C; //私有成員只是被隱藏了,但是還是會繼承下去
};

//公共繼承
class Son :public Base
{
public:
	int m_D;
};

void test01()
{
	cout << "sizeof Son = " << sizeof(Son) << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

4.6.4 繼承中構造和析構順序

子類繼承父類後,當建立子類物件,也會呼叫父類的建構函式

問題:父類和子類的構造和析構順序是誰先誰後?

示例:

class Base 
{
public:
	Base()
	{
		cout << "Base建構函式!" << endl;
	}
	~Base()
	{
		cout << "Base解構函式!" << endl;
	}
};

class Son : public Base
{
public:
	Son()
	{
		cout << "Son建構函式!" << endl;
	}
	~Son()
	{
		cout << "Son解構函式!" << endl;
	}

};


void test01()
{
	//繼承中 先呼叫父類建構函式,再呼叫子類建構函式,析構順序與構造相反
	Son s;
}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:繼承中 先呼叫父類建構函式,再呼叫子類建構函式,析構順序與構造相反

4.6.5 繼承同名成員處理方式

問題:當子類與父類出現同名的成員,如何通過子類物件,訪問到子類或父類中同名的資料呢?

  • 訪問子類同名成員 直接訪問即可
  • 訪問父類同名成員 需要加作用域

示例:

class Base {
public:
	Base()
	{
		m_A = 100;
	}

	void func()
	{
		cout << "Base - func()呼叫" << endl;
	}

	void func(int a)
	{
		cout << "Base - func(int a)呼叫" << endl;
	}

public:
	int m_A;
};


class Son : public Base {
public:
	Son()
	{
		m_A = 200;
	}

	//當子類與父類擁有同名的成員函式,子類會隱藏父類中所有版本的同名成員函式
	//如果想訪問父類中被隱藏的同名成員函式,需要加父類的作用域
	void func()
	{
		cout << "Son - func()呼叫" << endl;
	}
public:
	int m_A;
};

void test01()
{
	Son s;

	cout << "Son下的m_A = " << s.m_A << endl;
	cout << "Base下的m_A = " << s.Base::m_A << endl;

	s.func();
	s.Base::func();
	s.Base::func(10);

}
int main() {

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

總結:

  1. 子類物件可以直接訪問到子類中同名成員
  2. 子類物件加作用域可以訪問到父類同名成員
  3. 當子類與父類擁有同名的成員函式,子類會隱藏父類中同名成員函式,加作用域可以訪問到父類中同名函式

4.6.6 繼承同名靜態成員處理方式

問題:繼承中同名的靜態成員在子類物件上如何進行訪問?

靜態成員和非靜態成員出現同名,處理方式一致

  • 訪問子類同名成員 直接訪問即可
  • 訪問父類同名成員 需要加作用域

示例:

class Base {
public:
	static void func()
	{
		cout << "Base - static void func()" << endl;
	}
	static void func(int a)
	{
		cout << "Base - static void func(int a)" << endl;
	}

	static int m_A;
};

int Base::m_A = 100;

class Son : public Base {
public:
	static void func()
	{
		cout << "Son - static void func()" << endl;
	}
	static int m_A;
};

int Son::m_A = 200;

//同名成員屬性
void test01()
{
	//通過物件訪問
	cout << "通過物件訪問: " << endl;
	Son s;
	cout << "Son  下 m_A = " << s.m_A << endl;
	cout << "Base 下 m_A = " << s.Base::m_A << endl;

	//通過類名訪問
	cout << "通過類名訪問: " << endl;
	cout << "Son  下 m_A = " << Son::m_A << endl;
	cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}

//同名成員函式
void test02()
{
	//通過物件訪問
	cout << "通過物件訪問: " << endl;
	Son s;
	s.func();
	s.Base::func();

	cout << "通過類名訪問: " << endl;
	Son::func();
	Son::Base::func();
	//出現同名,子類會隱藏掉父類中所有同名成員函式,需要加作作用域訪問
	Son::Base::func(100);
}
int main() {

	//test01();
	test02();

	system("pause");

	return 0;
}

總結:同名靜態成員處理方式和非靜態處理方式一樣,只不過有兩種訪問的方式(通過物件 和 通過類名)

4.6.7 多繼承語法

C++允許一個類繼承多個類

語法:class 子類 :繼承方式 父類1 , 繼承方式 父類2...

多繼承可能會引發父類中有同名成員出現,需要加作用域區分

C++實際開發中不建議用多繼承

示例:

class Base1 {
public:
	Base1()
	{
		m_A = 100;
	}
public:
	int m_A;
};

class Base2 {
public:
	Base2()
	{
		m_A = 200;  //開始是m_B 不會出問題,但是改為mA就會出現不明確
	}
public:
	int m_A;
};

//語法:class 子類:繼承方式 父類1 ,繼承方式 父類2 
class Son : public Base2, public Base1 
{
public:
	Son()
	{
		m_C = 300;
		m_D = 400;
	}
public:
	int m_C;
	int m_D;
};


//多繼承容易產生成員同名的情況
//通過使用類名作用域可以區分呼叫哪一個基類的成員
void test01()
{
	Son s;
	cout << "sizeof Son = " << sizeof(s) << endl;
	cout << s.Base1::m_A << endl;
	cout << s.Base2::m_A << endl;
}

int main() {

	test01();

	system("pause");

	return 0;
}

總結: 多繼承中如果父類中出現了同名情況,子類使用時候要加作用域

4.6.8 菱形繼承

菱形繼承概念:

​ 兩個派生類繼承同一個基類

​ 又有某個類同時繼承者兩個派生類

​ 這種繼承被稱為菱形繼承,或者鑽石繼承

菱形繼承問題:

  1. 羊繼承了動物的資料,駝同樣繼承了動物的資料,當草泥馬使用資料時,就會產生二義性。
    
  2. 草泥馬繼承自動物的資料繼承了兩份,其實我們應該清楚,這份資料我們只需要一份就可以。

示例:

class Animal
{
public:
	int m_Age;
};

//繼承前加virtual關鍵字後,變為虛繼承
//此時公共的父類Animal稱為虛基類
class Sheep : virtual public Animal {};
class Tuo   : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};

void test01()
{
	SheepTuo st;
	st.Sheep::m_Age = 100;
	st.Tuo::m_Age = 200;

	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Tuo::m_Age = " <<  st.Tuo::m_Age << endl;
	cout << "st.m_Age = " << st.m_Age << endl;
}


int main() {

	test01();

	system("pause");

	return 0;
}

總結:

  • 菱形繼承帶來的主要問題是子類繼承兩份相同的資料,導致資源浪費以及毫無意義
  • 利用虛繼承可以解決菱形繼承問題

4.7 多型

4.7.1 多型的基本概念

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

多型分為兩類

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

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

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

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

class Animal
{
public:
	//Speak函式就是虛擬函式
	//函式前面加上virtual關鍵字,變成虛擬函式,那麼編譯器在編譯的時候就不能確定函式呼叫了。
	virtual void speak()
	{
		cout << "動物在說話" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小貓在說話" << endl;
	}
};

class Dog :public Animal
{
public:

	void speak()
	{
		cout << "小狗在說話" << endl;
	}

};
//我們希望傳入什麼物件,那麼就呼叫什麼物件的函式
//如果函式地址在編譯階段就能確定,那麼靜態聯編
//如果函式地址在執行階段才能確定,就是動態聯編

void DoSpeak(Animal & animal)
{
	animal.speak();
}
//
//多型滿足條件: 
//1、有繼承關係
//2、子類重寫父類中的虛擬函式
//多型使用:
//父類指標或引用指向子類物件

void test01()
{
	Cat cat;
	DoSpeak(cat);


	Dog dog;
	DoSpeak(dog);
}


int main() {

	test01();

	system("pause");

	return 0;
}

總結:

多型滿足條件

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

多型使用條件

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

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

4.7.2 多型案例一-計算器類

案例描述:

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

多型的優點:

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

示例:

//普通實現
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;
		}
		//如果要提供新的運算,需要修改原始碼
	}
public:
	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;
}



//多型實現
//抽象計算器類
//多型優點:程式碼組織結構清晰,可讀性強,利於前期和後期的擴充套件以及維護
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 = 10;
	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  //用完了記得銷燬

	//建立減法計算器
	abc = new SubCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
	delete abc;  

	//建立乘法計算器
	abc = new MulCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	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 ;

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

抽象類特點

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

示例:

class Base
{
public:
	//純虛擬函式
	//類中只要有一個純虛擬函式就稱為抽象類
	//抽象類無法例項化物件
	//子類必須重寫父類中的純虛擬函式,否則也屬於抽象類
	virtual void func() = 0;
};

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

void test01()
{
	Base * base = NULL;
	//base = new Base; // 錯誤,抽象類無法例項化物件
	base = new Son;
	base->func();
	delete base;//記得銷燬
}

int main() {

	test01();

	system("pause");

	return 0;
}

4.7.5 虛析構和純虛析構

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

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

虛析構和純虛析構共性:

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

虛析構和純虛析構區別:

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

虛析構語法:

virtual ~類名(){}

純虛析構語法:

virtual ~類名() = 0;

類名::~類名(){}

示例:

class Animal {
public:

	Animal()
	{
		cout << "Animal 建構函式呼叫!" << endl;
	}
	virtual void Speak() = 0;

	//解構函式加上virtual關鍵字,變成虛解構函式
	//virtual ~Animal()
	//{
	//	cout << "Animal虛解構函式呼叫!" << endl;
	//}


	virtual ~Animal() = 0;
};

Animal::~Animal()
{
	cout << "Animal 純虛解構函式呼叫!" << endl;
}

//和包含普通純虛擬函式的類一樣,包含了純虛解構函式的類也是一個抽象類。不能夠被例項化。

class Cat : public Animal {
public:
	Cat(string name)
	{
		cout << "Cat建構函式呼叫!" << endl;
		m_Name = new string(name);
	}
	virtual void Speak()
	{
		cout << *m_Name <<  "小貓在說話!" << endl;
	}
	~Cat()
	{
		cout << "Cat解構函式呼叫!" << endl;
		if (this->m_Name != NULL) {
			delete m_Name;
			m_Name = NULL;
		}
	}

public:
	string *m_Name;
};

void test01()
{
	Animal *animal = new Cat("Tom");
	animal->Speak();

	//通過父類指標去釋放,會導致子類物件可能清理不乾淨,造成記憶體洩漏
	//怎麼解決?給基類增加一個虛解構函式
	//虛解構函式就是用來解決通過父類指標釋放子類物件
	delete animal;
}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:

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

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

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

5 檔案操作

程式執行時產生的資料都屬於臨時資料,程式一旦執行結束都會被釋放

通過檔案可以將資料持久化

C++中對檔案操作需要包含標頭檔案 < fstream >

檔案型別分為兩種:

  1. 文字檔案 - 檔案以文字的ASCII碼形式儲存在計算機中
  2. 二進位制檔案 - 檔案以文字的二進位制形式儲存在計算機中,使用者一般不能直接讀懂它們

操作檔案的三大類:

  1. ofstream:寫操作
  2. ifstream: 讀操作
  3. fstream : 讀寫操作

5.1文字檔案

5.1.1寫檔案

寫檔案步驟如下:

  1. 包含標頭檔案

    #include <fstream>

  2. 建立流物件

    ofstream ofs;

  3. 開啟檔案

    ofs.open(“檔案路徑”,開啟方式);

  4. 寫資料

    ofs << “寫入的資料”;

  5. 關閉檔案

    ofs.close();

檔案開啟方式:

開啟方式解釋
ios::in為讀檔案而開啟檔案
ios::out為寫檔案而開啟檔案
ios::ate初始位置:檔案尾
ios::app追加方式寫檔案
ios::trunc如果檔案存在先刪除,再建立
ios::binary二進位制方式

注意: 檔案開啟方式可以配合使用,利用|操作符

**例如:**用二進位制方式寫檔案 ios::binary | ios:: out

示例:

#include <fstream>

void test01()
{
	ofstream ofs;
	ofs.open("test.txt", ios::out);

	ofs << "姓名:張三" << endl;
	ofs << "性別:男" << endl;
	ofs << "年齡:18" << endl;

	ofs.close();
}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:

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

5.1.2讀檔案

讀檔案與寫檔案步驟相似,但是讀取方式相對於比較多

讀檔案步驟如下:

  1. 包含標頭檔案

    #include <fstream>

  2. 建立流物件

    ifstream ifs;

  3. 開啟檔案並判斷檔案是否開啟成功

    ifs.open(“檔案路徑”,開啟方式);

  4. 讀資料

    四種方式讀取

  5. 關閉檔案

    ifs.close();

示例:

#include <fstream>
#include <string>
void test01()
{
	ifstream ifs;
	ifs.open("test.txt", ios::in);

	if (!ifs.is_open())
	{
		cout << "檔案開啟失敗" << endl;
		return;
	}

	//第一種方式
	//char buf[1024] = { 0 };
	//while (ifs >> buf)
	//{
	//	cout << buf << endl;
	//}

	//第二種
	//char buf[1024] = { 0 };
	//while (ifs.getline(buf,sizeof(buf)))
	//{
	//	cout << buf << endl;
	//}

	//第三種
	//string buf;
	//while (getline(ifs, buf))
	//{
	//	cout << buf << endl;
	//}

	char c;
	while ((c = ifs.get()) != EOF)
	{
		cout << c;
	}

	ifs.close();


}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:

  • 讀檔案可以利用 ifstream ,或者fstream類
  • 利用is_open函式可以判斷檔案是否開啟成功
  • close 關閉檔案

5.2 二進位制檔案

以二進位制的方式對檔案進行讀寫操作

開啟方式要指定為 ios::binary

5.2.1 寫檔案

二進位制方式寫檔案主要利用流物件呼叫成員函式write

函式原型 :ostream& write(const char * buffer,int len);

引數解釋:字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數

示例:

#include <fstream>
#include <string>

class Person
{
public:
	char m_Name[64];
	int m_Age;
};

//二進位制檔案  寫檔案
void test01()
{
	//1、包含標頭檔案

	//2、建立輸出流物件
	ofstream ofs("person.txt", ios::out | ios::binary);
	
	//3、開啟檔案
	//ofs.open("person.txt", ios::out | ios::binary);

	Person p = {"張三"  , 18};

	//4、寫檔案
	ofs.write((const char *)&p, sizeof(p));

	//5、關閉檔案
	ofs.close();
}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:

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

5.2.2 讀檔案

二進位制方式讀檔案主要利用流物件呼叫成員函式read

函式原型:istream& read(char *buffer,int len);

引數解釋:字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數

示例:

#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;
}
  • 檔案輸入流物件 可以通過read函式,以二進位制方式讀資料

cout << “Animal 純虛解構函式呼叫!” << endl;
}

//和包含普通純虛擬函式的類一樣,包含了純虛解構函式的類也是一個抽象類。不能夠被例項化。

class Cat : public Animal {
public:
Cat(string name)
{
cout << “Cat建構函式呼叫!” << endl;
m_Name = new string(name);
}
virtual void Speak()
{
cout << *m_Name << “小貓在說話!” << endl;
}
~Cat()
{
cout << “Cat解構函式呼叫!” << endl;
if (this->m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}

public:
string *m_Name;
};

void test01()
{
Animal *animal = new Cat(“Tom”);
animal->Speak();

//通過父類指標去釋放,會導致子類物件可能清理不乾淨,造成記憶體洩漏
//怎麼解決?給基類增加一個虛解構函式
//虛解構函式就是用來解決通過父類指標釋放子類物件
delete animal;

}

int main() {

test01();

system("pause");

return 0;

}




總結:

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

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

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









## 5 檔案操作



程式執行時產生的資料都屬於臨時資料,程式一旦執行結束都會被釋放

通過**檔案可以將資料持久化**

C++中對檔案操作需要包含標頭檔案 ==&lt; fstream &gt;==



檔案型別分為兩種:

1. **文字檔案**     -  檔案以文字的**ASCII碼**形式儲存在計算機中
2. **二進位制檔案** -  檔案以文字的**二進位制**形式儲存在計算機中,使用者一般不能直接讀懂它們



操作檔案的三大類:

1. ofstream:寫操作
2. ifstream: 讀操作
3. fstream : 讀寫操作



### 5.1文字檔案

#### 5.1.1寫檔案

   寫檔案步驟如下:

1. 包含標頭檔案   

     \#include <fstream\>

2. 建立流物件  

   ofstream ofs;

3. 開啟檔案

   ofs.open("檔案路徑",開啟方式);

4. 寫資料

   ofs << "寫入的資料";

5. 關閉檔案

   ofs.close();

   

檔案開啟方式:

| 開啟方式    | 解釋                       |
| ----------- | -------------------------- |
| ios::in     | 為讀檔案而開啟檔案         |
| ios::out    | 為寫檔案而開啟檔案         |
| ios::ate    | 初始位置:檔案尾           |
| ios::app    | 追加方式寫檔案             |
| ios::trunc  | 如果檔案存在先刪除,再建立 |
| ios::binary | 二進位制方式                 |

**注意:** 檔案開啟方式可以配合使用,利用|操作符

**例如:**用二進位制方式寫檔案 `ios::binary |  ios:: out`





**示例:**

```C++
#include <fstream>

void test01()
{
	ofstream ofs;
	ofs.open("test.txt", ios::out);

	ofs << "姓名:張三" << endl;
	ofs << "性別:男" << endl;
	ofs << "年齡:18" << endl;

	ofs.close();
}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:

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

5.1.2讀檔案

讀檔案與寫檔案步驟相似,但是讀取方式相對於比較多

讀檔案步驟如下:

  1. 包含標頭檔案

    #include <fstream>

  2. 建立流物件

    ifstream ifs;

  3. 開啟檔案並判斷檔案是否開啟成功

    ifs.open(“檔案路徑”,開啟方式);

  4. 讀資料

    四種方式讀取

  5. 關閉檔案

    ifs.close();

示例:

#include <fstream>
#include <string>
void test01()
{
	ifstream ifs;
	ifs.open("test.txt", ios::in);

	if (!ifs.is_open())
	{
		cout << "檔案開啟失敗" << endl;
		return;
	}

	//第一種方式
	//char buf[1024] = { 0 };
	//while (ifs >> buf)
	//{
	//	cout << buf << endl;
	//}

	//第二種
	//char buf[1024] = { 0 };
	//while (ifs.getline(buf,sizeof(buf)))
	//{
	//	cout << buf << endl;
	//}

	//第三種
	//string buf;
	//while (getline(ifs, buf))
	//{
	//	cout << buf << endl;
	//}

	char c;
	while ((c = ifs.get()) != EOF)
	{
		cout << c;
	}

	ifs.close();


}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:

  • 讀檔案可以利用 ifstream ,或者fstream類
  • 利用is_open函式可以判斷檔案是否開啟成功
  • close 關閉檔案

5.2 二進位制檔案

以二進位制的方式對檔案進行讀寫操作

開啟方式要指定為 ios::binary

5.2.1 寫檔案

二進位制方式寫檔案主要利用流物件呼叫成員函式write

函式原型 :ostream& write(const char * buffer,int len);

引數解釋:字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數

示例:

#include <fstream>
#include <string>

class Person
{
public:
	char m_Name[64];
	int m_Age;
};

//二進位制檔案  寫檔案
void test01()
{
	//1、包含標頭檔案

	//2、建立輸出流物件
	ofstream ofs("person.txt", ios::out | ios::binary);
	
	//3、開啟檔案
	//ofs.open("person.txt", ios::out | ios::binary);

	Person p = {"張三"  , 18};

	//4、寫檔案
	ofs.write((const char *)&p, sizeof(p));

	//5、關閉檔案
	ofs.close();
}

int main() {

	test01();

	system("pause");

	return 0;
}

總結:

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

5.2.2 讀檔案

二進位制方式讀檔案主要利用流物件呼叫成員函式read

函式原型:istream& read(char *buffer,int len);

引數解釋:字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數

示例:

#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;
}
  • 檔案輸入流物件 可以通過read函式,以二進位制方式讀資料

相關文章