C++核心程式設計
主要針對C++物件導向程式設計技術,探討C++中的核心和精髓
1 記憶體分割槽模型
C++程式在執行時,將記憶體大方向劃分為4個區域
- 程式碼區:存放函式體的二進位制程式碼,由作業系統進行管理的
- 全域性區:存放全域性變數和靜態變數及常量
- 棧區:由編譯器自動分配釋放,存放函式的引數值,區域性變數等
- 堆區:由程式設計師分配和釋放,若程式設計師不釋放,程式結束時由作業系統回收
記憶體四區的意義:
不同區域存放的資料,賦予不同生命週期,給我們更大的靈活程式設計
1.1 程式執行前
在程式編譯後,生成了exe可執行程式,未執行該程式前分為兩個區域
程式碼區:
存放CPU執行的機器指令
程式碼是共享的,共享的目的是對於頻繁被執行多的程式,只需要在記憶體中有一份程式碼即可
程式碼是隻讀的,使其只讀的原因是防止程式意外地修改了它的指令
全域性區:
全域性變數和靜態變數存放在此
全域性變數還包含了常量區,字串常量和其他常量也存放在此
該區域的資料在程式結束後由作業系統釋放
示例
#include <iostream>
#include <string>
using namespace std;
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;
}
列印結果
總結
- C++中在程式執行前分為全域性區和程式碼區
- 程式碼區特點是共享和只讀
- 全域性區中存放全域性變數、靜態變數、常量
- 常量區中存放
const
修飾的全域性常量
和字串常量
1.2 程式執行後
棧區
由編譯器自動分配釋放,存放函式的引數值,區域性變數等
注意事項:不要返回區域性變數的地址,棧區開闢的資料由編譯器自動釋放
示例
#include <iostream>
using namespace std;
//棧區資料注意事項 ---- 不要返回區域性變數的地址
//棧區的資料由編譯器管理開闢和釋放
int *func(/*int a 形引數據也會放在棧區*/)
{
int a = 10;//區域性變數 存放在棧區,棧區的資料在函式執行完後自動釋放
return &a;//返回區域性變數的地址
}
int main()
{
//接受func函式的返回值
int * p = func();
cout << *p << endl;//第一次可以列印正確的數字,是因為編譯器做了保留
cout << *p << endl;//第二這個資料就不再保留了
system("pause");
return 0;
}
堆區
由程式設計師分配釋放,若程式設計師不釋放,程式結束時由作業系統回收
在C++中主要利用new在堆區中開闢記憶體
1.3 new運算子
C++中利用new
運算子在堆區開闢資料
堆區開闢的資料,由程式設計師手動開闢,手動釋放,釋放利用運算子delete
語法new資料型別
利用new建立的資料,會返回該資料對應的型別的指標
示例
#include <iostream>
using namespace std;
//1、new的基本語法
int * func()
{
//在堆區建立整型資料
//new返回是 該資料型別的指標
int *p = new int(10);
return p;
}
void test01()
{
int* p = func();
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
//堆區的資料 由程式設計師管理開闢,程式設計師管理釋放
//如果想釋放堆區中的資料,利用關鍵字delete
delete p;
//cout << *p << endl; //記憶體已經被釋放,再次訪問就是非法操作,會報錯
}
//2、在堆區利用new開闢陣列
void test02()
{
//建立10整形資料的陣列,在堆區
int *arr = new int[10]; //10代表陣列由10個元素
for(int i = 0; i < 10; i++)
{
arr[i] = i + 100;//給10個元素賦值 100 ~ 109
}
for(int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
//釋放堆區陣列
//釋放陣列的時候 要加一個[]才可以
delete[] arr;
}
int main()
{
//test01();
//test02();
system("pause");
}
2 引用
2.1 引用的基本使用
作用:給變數起別名
語法:資料型別 &別名 原名
示例
#include <iostream>
using namespace std;
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");
}
2.2 引用注意事項
- 引用必須初始化
- 引用在初始化後,不可改變
示例
#include <iostream>
using namespace std;
int main()
{
int a = 10;
//1、引用必須初始化
//int &b; //錯誤,必須要初始化
int &b = a;
//2、引用在初始化後,不可以改變
int c = 20;
b = c;//賦值操作,而不是更改引用
cout << "a =" << a << endl;
cout << "b =" << b << endl;
cout << "c =" << c << endl;
system("pause");
}
2.3 引用做函式引數
作用:函式傳參時,可以利用引用的技術讓形參修飾實參
優點:可以簡化指標修改實參
示例:
#include <iostream>
using namespace std;
//1、值傳遞
void mySwap01(int a,int b)
{
int temp = a;
a = b;
b = temp;
/*cout << "Swap01 a =" << a << endl;
cout << "Swap01 b =" << b << endl;*/
}
//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);//值傳遞,形參不會修飾實參
// mySwap02(&a, &b);//地址傳遞,形參會修飾實參
mySwap03(a, b);//引用傳遞,形參也會修飾實參
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0
}
總結:透過引用產生的效果同按地址傳遞是一樣的,引用的語法更清楚簡單
2.4 引用做函式返回值
作用:引用是可以作為函式的返回值存在的
注意:不要返回區域性變數引用
用法:函式呼叫作為左值
示例
#include <iostream>
using namespace std;
//引用做函式的返回值
//1、不要返回區域性變數的引用
int& test01()
{
int a = 10;//區域性變數存放在四區中的 棧區
return a;
}
//2、函式的呼叫可以作為左值
int& test02()
{
static int a = 10;//靜態變數,存放在全域性區,全域性上的資料在程式結束後釋放
return a;
}
int main()
{
int &ref = test01();
cout << "ref = " << ref << endl;//第一次結果正確,是因為編譯器做了保留
cout << "ref = " << ref << endl;//第二次結果錯誤,是因為a的記憶體已經釋放
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++內部實現是一個指標常量
引用一旦初始化,就不可以改變
講解示例:
#include <iostream>
using namespace std;
//發現是引用,轉換為 int* const ref = 100
void func(int& ref)
{
ref = 100;//ref是引用,轉換為*ref = 100
}
int main()
{
int a;
//自動轉換為 int* const ref = &a;指標常量是指標指向不改變,也說明為什麼引用不可更改
int& ref = a;
ref = 20;//內部發現ref是引用,自動幫我們轉換為: *ref = 20
cout << "a:" << a << endl;
cout << "ref:" << ref << endl;
func(a);
system("pause");
return 0;
}
結論:C++推薦使用引用技術,因為語法方便,引用本質是指標常量,但是所有的指標操作編譯器都幫我們做了
2.6 常量引用
作用:常量引用主要用來修飾形參,防止誤操作
在函式形參列表中,可以加const
修飾形參,防止形參改變實參
示例:
#include <iostream>
using namespace std;
//列印資料函式
void ShowValue(const int & val)
{
//val = 1000;
cout << "val = " << val << endl;
}
int main()
{
//常量引用
//使用場景:用來修飾形參防止誤操作
//int a = 10;
//加上const之後 編譯器將程式碼修改 int temp = 10; const int &ref = temp;
//const int & ref = 10;//引用必須引一塊合法的記憶體空間
//ref = 20; //加入const之後變為只讀,不可以修改
int a = 100;
ShowValue(a);
cout << "a = " << a <<endl;
system("pause");
return 0;
}
3 函式提高
3.1 函式預設引數
在C++中,函式的形參列表中的形參是可以有預設值的。
語法:返回值型別 函式名 (引數 =預設值){ }
示例:
#include <iostream>
using namespace std;
//函式預設引數
//如果我們自己傳入資料,就用自己的資料,如果沒有,那麼用預設值
int func(int a, int b = 20, int c = 30)
{
return a + b + c;
}
//注意事項
//1、如果某個位置已經有了預設引數,那麼從這個位置往後,從左到右都必須有預設值
//int func2(int a = 10, int b, int c, int d)
//{
// return a + b + c;
//}
//2、如果函式宣告有預設引數,函式實現就不能有預設引數
//宣告和實現只能有一個有預設引數
int func2(int a = 10, int b = 10);
int func2(int a, int b)
{
return a + b;
}
int main()
{
cout << func(10) << endl;
cout << func2() << endl;
system("pause");
return 0;
}
3.2 函式佔位引數
C++中函式的形參列表裡可以有佔位引數,用來做佔位,呼叫函式時必須填補該位置
語法:返回值型別 函式名 (資料型別) {}
在現階段函式的佔位引數存在意義不大,但是後面會擁到該技術
示例:
#include <iostream>
using namespace std;
//佔位引數
//返回值型別 函式名(資料型別)
void func(int a, int)
{
cout << "this is func " << endl;
}
int main()
{
func(10, 10);
system("pause");
}
3.3 函式過載
3.3.1 函式過載概述
作用:函式名可以相同,提高複用性
函式重灌載滿足條件:
- 同意作用域下
- 函式名稱相同
- 函式引數型別不同或者個數不同或者順序不同
注意:函式的返回值不可以作為函式過載的條件
示例:
#include <iostream>
using namespace std;
//函式過載
//可以讓函式名相同,提高複用性
//函式過載的滿足條件
//1、同一個作用域下
//2、函式名稱相同
//3、函式引數型別不同,或者個數不同,或者順序不同
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(double a, int b)
{
cout << "func(double a, int b) 的呼叫" << endl;
}
void func(int a, double b)
{
cout << "func(int a, double b) 的呼叫" << endl;
}
//注意事項
//函式的返回值不可以作為函式過載的條件
//int func(int a, double b)
//{
// cout << "func(int a, double b) 的呼叫" << endl;
//}
int main()
{
//func();
//func(10);
//func(3.14);
//func(10, 3.14);
func(3.14, 10);
system("pause");
}
3.3.2 函式過載注意事項
- 引用作為過載條件
- 函式過載碰到函式預設引數
示例:
#include <iostream>
using namespace std;
//函式過載的注意事項
//1、引用作為過載的條件
void func(int &a) //int &a = 10; 不合法
{
cout << "func(int &a) 的呼叫" << endl;
}
void func(const int &a) //const int &a = 10; 合法
{
cout << "func(const int &a) 的呼叫" << endl;
}
//2、函式過載碰到預設引數
void func2(int a, int b = 10)
{
cout << "func2(int a, int b) 的呼叫" << endl;
}
void func2(int a)
{
cout << "func2(int a) 的呼叫" << endl;
}
int main()
{
//int a = 10;
//func(a);
//func(10);
//func2(10);//當函式過載碰到預設引數,出現二義性,報錯,儘量避免這種情況
//func(10, 10);合法
system("pause");
return 0;
}
4 類和物件
C++物件導向的三大特性:封裝、繼承、多型
C++認為萬事萬物都皆為物件,物件上有其屬性和行為
例如:
人可以作為物件,屬性有姓名、身高、體重...,行為有走、跑、跳、吃飯、唱歌
車也可以作為物件,屬性有輪胎,方向盤,車燈...,行為有載人,放音樂,放空調
具體相同性質的物件,我們可以抽象為類,人屬於人類,車屬於車類
4.1 封裝
4.1.1 封裝的意義
封裝是C++物件導向三大特性之一
封裝的意義:
- 將屬性和行為作為一個整體,表現生活中的事務
- 將屬性和行為加以許可權控制
封裝的意義一:
在設計類的時候,屬性和行為寫在一起,表現事物
語法:class 類名{ 訪問許可權:屬性 /行為};
示例1:設計一個圓類,求圓的周長
#include <iostream>
using namespace std;
//圓周率
const double PI = 3.14;
//設計一個圓類,求圓的周長
//圓求周長的公式:2 * PI * 半徑
class Circle
{
//訪問許可權
//公共許可權
public:
//屬性
//半徑
int m_r;
//行為
//獲取圓的周長
double calculateZC()
{
return 2 * PI * m_r;
}
};
int main()
{
//透過圓類建立一個具體的圓(物件)
//例項化 (透過一個類 建立一個物件的過程)
Circle cl;
//給圓物件 的屬性進行賦值
cl.m_r = 10;
//2 * PI * 10 = 62.8
cout << "圓的周長為:" << cl.calculateZC() << endl;
system("pause");
return 0;
}
示例2:設計一個學生類,屬性有姓名和學號,可以給姓名和學號賦值,可以顯示學生的姓名和學號
#include <iostream>
#include <string>
using namespace std;
//設計一個學生類,屬性有姓名和學號
//可以給姓名和學號賦值,可以顯示學生的姓名和學號
//設計學生類
class student
{
public: //公共許可權
//類中的屬性和行為 我們統一稱為 成員
//屬性 成員屬性 成員變數
//行為 成員函式 成員方法
//屬性
string m_Name; //姓名
int m_Id; //學號
//行為
//顯示姓名和學號
void showStudent()
{
cout << "姓名: " << m_Name << " 學號: " << m_Id << endl;
}
//給姓名賦值
void SetName(string name)
{
m_Name = name;
}
//給學號賦值
void SetId(int id)
{
m_Id = id;
}
};
int main()
{
//建立一個具體學生 例項化物件
student s1;
//給s1物件 進行屬性賦值操作
//s1.m_Name = "張三";
s1.SetName("張三");
//s1.m_Id = 1;
s1.SetId(1);
//顯示學生資訊
s1.showStudent();
student s2;
//給s2物件 進行屬性賦值操作
s2.m_Name = "李四";
s2.m_Id = 2;
//顯示學生資訊
s2.showStudent();
system("pause");
return 0;
}
封裝意義二:
類在設計時,可以把屬性和行為放在不同的許可權下,加以控制
訪問許可權有以下三種:
- punlic 公共許可權
- protected 保護許可權
- private 私有許可權
示例:
#include <iostream>
using namespace std;
//訪問許可權
//三種
//公共許可權 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 p1;
p1.m_Name = "李四";
//p1.m_Car = "賓士"; //保護許可權內容,在類外訪問不到
//p1.m_Password = 123;//私有許可權內容,在類外訪問不到
p1.func();
system("pause");
return 0;
}
4.1.2 struct 和 class 區別
在C++中struct和class唯一的區別就在於預設的訪問許可權不同
區別:
- struct 預設許可權為公共
- class 預設許可權為私有
#include <iostream>
using namespace std;
class C1
{
int m_A; //預設許可權 是私有
};
struct C2
{
int m_A; //預設許可權 是公共
};
int main()
{
//struct 和class區別
//struct 預設許可權是 公共 public
//class 預設許可權是 私有 private
C1 c1;
//c1.m_A = 100; //在class中預設許可權為私有,因此不可以訪問
C2 c2;
c2.m_A = 100; //在struct中預設許可權為公共,因此可以訪問
system("pause");
return 0;
}
4.1.3 成員屬性設定為私有
優點1:將所有成員屬性設定為私有,可以自己控制讀寫許可權
優點2:對於寫許可權,我們可以檢測資料的有效性
#include <iostream>
using namespace std;
//成員屬性設定私有
//1、可以自己控制讀寫許可權
//2、對於寫可以檢測資料的有效性
//人類
class Person
{
public:
//設定姓名
void setName(string name)
{
m_Name = name;
}
//獲取姓名
string getName()
{
return m_Name;
}
//獲取年齡
int getAge()
{
return m_Age;
}
//設定年齡(0~150)
void setAge(int age)
{
if (age < 0 || age > 150)
{
cout << "年齡" << age << "輸入有誤,賦值失敗" << endl;
return;
}
m_Age = age;
}
//設定偶像
void setm_Idol(string idol)
{
m_Idol = idol;
}
private:
string m_Name; //姓名 可讀可寫
int m_Age = 18; //年齡 只讀 也可以寫(年齡必須在0~150之間)
string m_Idol; //偶像 只寫
};
int main()
{
Person p;
//姓名設定
p.setName("張三");
//獲取姓名
cout << "姓名:" << p.getName() << endl;
//年齡設定
p.setAge(160);
//p.m_Age = 20;
//獲取年齡
cout << "年齡:" << p.getAge() << endl;
//偶像設定
p.setm_Idol("小明");//只寫
//cout << "偶像" << p.getIdol() << endl;//只寫狀態,外界讀不到
system("pause");
return 0;
}
4.2 物件的初始化和清理
- 生活中我們買的電子產品都基本會有出廠設定,在某一天我們不用時也會刪除一些自己資訊資料保證安全
- C++中的物件導向來源於生活,每個物件也都會有初始設定以及物件銷燬的清理資料的設定
4.2.1 建構函式和解構函式
物件的初始化和清理也是兩個非常重要的安全問題
一個物件或者變數沒有初始狀態,對其使用後果是未知的
同樣的使用完一個物件或變數,沒有及時清理,也會造成一定的安全問題
C++利用了建構函式和解構函式解決上述問題,這兩個函式將會被編譯器自動呼叫,完成物件初始化和清理工作。
物件的初始化和清理工作是編譯器強制要我們做的事情,因此如果我們不提供構造和析構,編譯器會提供
編譯器提供的建構函式和解構函式的空實現
- 建構函式:主要作用在於建立物件時為物件的成員屬性賦值,建構函式由編譯器自動呼叫,無須手動呼叫。
- 解構函式:主要作用在於物件銷燬前系統自動呼叫,執行一些清理工作
建構函式:類名(){}
- 建構函式,沒有返回值也不寫void
- 函式名稱與類名相同
- 建構函式可以有引數,因此可以發生過載
- 程式在呼叫物件時候會自動呼叫構造,無須手動呼叫,而且只會呼叫一次
解構函式:~類名(){}
- 解構函式,沒有返回值也不寫void
- 函式名稱與類相同,在名稱前加符號
~
- 解構函式不可以有引數,因此不可以發生過載
- 程式在呼叫物件時候會自動呼叫構造,無須手動呼叫,而且只會呼叫一次
示例:
#include <iostream>
using namespace std;
//物件的初始化和清理
//1、建構函式 進行初始化操作
class Person
{
public:
//1.1、建構函式
//沒有返回值 不用寫void
//函式名 與類名相同
//建構函式可以有引數,可以發生過載
//建立物件的時候,建構函式會自動呼叫,而且只呼叫一次
Person()
{
cout << "Person 建構函式的呼叫" << endl;
}
//2、解構函式,進行清理操作
//沒有返回值 不用寫void
//函式名 與類名相同 在名稱前加~
//建構函式不可以有引數,因此不可以發生過載
//物件在銷燬前會自動呼叫解構函式,而且只呼叫一次
~Person()
{
cout << "Person 解構函式的呼叫" << endl;
}
};
//構造和析構都是必須有的實現,如果我們自己不提供,編譯器會提供一個空實現的構造和析構
void test01()
{
Person P;//在棧上的資料,test01執行完畢後,釋放這個物件
}
int main()
{
//test01();
Person P;
system("pause");
return 0;
}
4.2.2 建構函式的分類和呼叫
兩種分類方式:
按引數分為:有參構造和無參構造
按型別分為:普通構造和複製構造
三種呼叫方式:
括號法
顯示法
隱式轉換法
示例:
#include <iostream>
using namespace std;
//1、建構函式的分類及呼叫
//分類
// 按照引數分類 無參構造(預設構造)和 有參構造
// 按照型別分類 普通構造 複製構造
class Person
{
public:
//建構函式
Person()
{
cout << "Person的無參建構函式呼叫" << endl;
}
Person(int a)
{
age = a;
cout << "Person的有參建構函式呼叫" << endl;
}
//複製建構函式
Person(const Person& p)
{
//將傳入的人身上的所有屬性,複製到我身上
age = p.age;
cout << "Person的複製建構函式呼叫" << endl;
}
~Person()
{
cout << "Person的解構函式呼叫" << endl;
}
int age;
};
//呼叫
void test01()
{
//1、括號法
//Person p1;//預設建構函式呼叫
//Person p2(10);//有參建構函式
//Person p3(p2);//複製建構函式
//注意事項1
//呼叫預設建構函式時候,你要加()
//因為下面這行程式碼,編譯器會認為是一個函式宣告,不會認為在建立物件
//Person p1();
//cout << "p2的年齡為: " << p2.age << endl;
//cout << "p3的年齡為: " << p3.age << endl;
//2、顯示法
//Person p1;
//Person p2 = Person(10);//有參構造
//Person p3 = Person(p2);//複製構造
//Person(10); //匿名物件 特點:當前執結束後,系統會立即回收掉匿名物件
//cout << "aaaaa" << endl;
//注意事項2
//不要利用複製建構函式 初始化匿名物件 編譯器會認為 Person(p3); 等價於 Person p3; 物件宣告
//Person(p3);
//3、隱式轉換法
Person p4 = 10;//相當於 寫了Person p4 = Person(10); 有參構造
Person p5 = p4;//複製構造
}
int main()
{
test01();
system("pause");
return 0;
}
4.2.3 複製建構函式呼叫時機
C++中複製建構函式呼叫時機通常有三種情況
- 使用一個已經建立完畢的物件來初始化一個新物件
- 值傳遞的方式給函式引數傳值
- 以值方式返回區域性物件
示例:
#include <iostream>
using namespace std;
//複製建構函式的呼叫時機
class Person
{
public:
Person()
{
cout << "Person 預設建構函式呼叫" << endl;
}
Person(int age)
{
cout << "Person 有參建構函式呼叫" << endl;
m_age = age;
}
Person(const Person &p)
{
cout << "Person 複製建構函式呼叫" << endl;
m_age = p.m_age;
}
~Person()
{
cout << "Person 解構函式呼叫" << endl;
}
int m_age;
};
//1、使用一個已經建立完畢的物件來初始化一個新物件
void test01()
{
Person p1(20);
Person p2(p1);
cout << "p2的年齡為:" << p2.m_age << endl;
}
//2、值傳遞的方式給函式引數傳值
void doWork(Person p)
{
}
void test02()
{
Person p;
doWork(p);
}
//3、以值方式返回區域性物件
Person doWork3()
{
Person p1;
cout << (int*)&p1 << endl;
return p1;
}
void test03()
{
Person p = doWork3();
cout << (int*)&p << endl;
}
int main()
{
//test01();
//test02();
test03();
system("pause");
return 0;
}
4.2.4 建構函式呼叫規則
預設情況下,C++編譯器至少給一個類新增三個函式
- 預設建構函式(無參,函式體為空)
- 預設解構函式(無參,函式體為空)
- 預設複製函式,對屬性進行值複製
建構函式呼叫規則如下:
- 如果使用者定義有參建構函式,C++不再提供預設無參構造,但是會提供預設複製構造
- 如果使用者定義複製建構函式,C++不會再提供其他建構函式
示例:
#include <iostream>
using namespace std;
//建構函式呼叫規則
// 1、建立一個類,C++編譯器會給每個類都新增至少3個函式
// 預設構造 (空實現)
// 解構函式 (空實現)
// 複製函式 (值複製)
//2、(依次註釋測試)
// 如果我們寫了有參建構函式,編譯器就不再提供預設構造,依然提供複製構造
// 如果我們寫了複製建構函式,編譯器就不再提供其他建構函式
class Person
{
public:
//Person()
//{
// cout << "Person的預設建構函式呼叫" << endl;
//}
Person(int age)
{
m_Age = age;
cout << "Person的有參建構函式呼叫" << endl;
}
//Person(const Person &p)
//{
// m_Age = p.m_Age;
// cout << "Person的複製建構函式呼叫" << endl;
//}
~Person()
{
cout << "Person的解構函式呼叫" << endl;
}
int m_Age;
};
/*
void test01()
{
Person p;
p.m_Age = 18;
Person p2(p);
cout << "p2的年齡為: " << p2.m_Age << endl;
}*/
void test02()
{
Person p(28);
Person p2(p);
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
4.4.5 深複製與淺複製
深淺複製是面試經典問題,也是常見的一個坑
淺複製:簡單的賦值複製操作
深複製:在堆區重新申請空間,進行複製操作
示例:
#include <iostream>
using namespace std;
//深複製與淺複製
class Person
{
public:
Person()
{
cout << "Person的預設建構函式呼叫" << endl;
}
Person(int age,int height)
{
m_Age = age;
m_Height = new int(height);
cout << "Person的有參建構函式呼叫" << endl;
}
~Person()
{
//析構程式碼,將堆區開闢資料做釋放操作
if (m_Height != NULL)
{
delete m_Height;
m_Height = NULL;
}
cout << "Person的解構函式呼叫" << endl;
}
//自己實現複製建構函式 解決淺複製帶來的問題
Person(const Person& p)
{
cout << "Person 複製建構函式呼叫" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height; //編譯器預設實現的就是這行程式碼
//深複製操作
m_Height = new int(*p.m_Height);
}
int m_Age;//年齡
int* m_Height;//身高
};
void test01()
{
Person p1(18,160);
cout << "p1的年齡為: " << p1.m_Age << "身高為:" << *p1.m_Height << endl;
Person p2(p1);
cout << "p2的年齡為: " << p2.m_Age << "身高為:" << *p2.m_Height << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
總結:如果屬性有在堆區開闢的,一定要自己提供複製建構函式,防止淺複製帶來的問題
4.2.6 初始化列表
作用:
C++提供了初始化列表語法,用來初始化屬性
語法:建構函式():屬性1(值1),屬性2(值2)...{}
示例:
#include <iostream>
using namespace std;
//初始化列表
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)
{
}
int m_A;
int m_B;
int m_C;
};
void test01()
{
//Person p(10, 20, 30);
Person p(30, 20, 10);
cout << "m_A = " << p.m_A << endl;
cout << "m_B = " << p.m_B << endl;
cout << "m_C = " << p.m_C << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
4.4.7 類物件作為類成員
C++類中的成員可以是另一個類的物件,我們稱該成員為 物件成員
例如:
class A {}
class B
{
A a;
}
B類中有物件A作為成員,A為物件成員
那麼當建立B物件時,A與B的構造和析構的順序是誰先誰後?
示例:
#include <iostream>
#include<string>
using namespace std;
//類物件作為類成員
//手機類
class Phone
{
public:
Phone(string PName)
{
cout << "Phone 的建構函式呼叫" << endl;
m_PName = PName;
}
~Phone()
{
cout << "Phone 的解構函式呼叫" << endl;
}
//手機品牌名稱
string m_PName;
};
//人類
class Person
{
public:
//Phone m_Phone = pName;//隱式轉換法
Person(string Name, string pName):m_Name(Name),m_Phone(pName)
{
cout << "Person 的建構函式呼叫" << endl;
}
~Person()
{
cout << "Person 的解構函式呼叫" << endl;
}
//姓名
string m_Name;
//手機
Phone m_Phone;
};
//當其他類物件作為本類成員,構造時先構造類物件,再構造自身 析構的順序與構造相反
void test01()
{
Person p("張三", "蘋果MAX");
cout << p.m_Name << "拿著" << p.m_Phone.m_PName << "手機" << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
執行結果
Phone 的建構函式呼叫
Person 的建構函式呼叫
張三拿著蘋果MAX手機
Person 的解構函式呼叫
Phone 的解構函式呼叫
先構造類物件,再構造自身 析構的順序與構造相反
4.2.8 靜態成員
靜態成員就是在成員變數和成員函式前加上關鍵字static,稱為靜態成員
靜態成員分為:
- 靜態成員變數
- 所有物件共享同一份資料
- 在編譯階段分配記憶體
- 類內宣告,類外初始化
- 靜態成員函式
- 所有物件共享同一個函式
- 靜態成員函式只能訪問靜態成員變數
示例1:靜態成員變數
#include <iostream>
#include<string>
using namespace std;
//靜態成員變數
class Person
{
public:
//1、所有物件都共享同一份資料
//2、編譯階段就分配了記憶體
//3、類內宣告,類外初始化操作
static int m_A;
//靜態成員變數也是有訪問許可權的
private:
static int m_B;
};
int Person::m_A = 100;
int Person::m_B = 200;
void test01()
{
Person p;
//100
cout << p.m_A << endl;
Person p2;
p2.m_A = 200;
//200
cout << p.m_A << endl;
}
void test02()
{
//靜態成員變數 不屬於某個物件上,所有物件都共享同一份資料
//因此靜態成員變數有兩種訪問方式
//1、透過物件進行訪問
//Person p;
//cout << p.m_A << endl;
//2、透過類名進行訪問
cout << Person::m_A << endl;
cout << Person::m_B << endl; //類外訪問不到私有靜態成員變數
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
示例2:靜態成員函式
#include <iostream>
#include<string>
using namespace std;
//靜態成員函式
//1、所有物件共享同一個函式
//2、靜態成員函式只能訪問靜態成員變數
class Person
{
public:
//靜態成員函式
static void func()
{
m_A = 100; //靜態成員函式可以訪問 靜態成員變數
//m_B = 200; //靜態成員函式不可以訪問 非靜態成員變數 因為無法區分是哪個物件的m_B
cout << "static void func 呼叫" << endl;
}
static int m_A; //靜態成員變數
int m_B; //非靜態成員變數
//靜態成員函式也是有訪問許可權的
private:
static void func2()
{
cout << "static void func2 呼叫" << endl;
}
};
int Person::m_A = 0;
//有兩種訪問方式
void test01()
{
//1、透過物件訪問
//Person p;
//p.func();
//2、透過類名訪問
Person::func();
//Person::func2(); //類外訪問不到私有靜態成員函式
}
int main()
{
test01();
system("pause");
return 0;
}
4.3 C++物件模型和this指標
4.3.1 成員變數和成員函式分開儲存
在C++中,類內的成員變數和成員函式分開儲存
只有非靜態成員變數才屬於類的物件上
示例:
#include <iostream>
using namespace std;
//成員變數 和 成員函式 分開儲存的
class Person
{
int m_A; //非靜態成員變數 屬於類的物件上
static int m_B; //靜態成員變數 不屬於類的物件上
void func() {} //非靜態成員函式 不屬於類的物件上
static void func2() {} //靜態成員函式 不屬於類的物件上
};
int Person::m_B = 0;
void test01()
{
Person p;
//空物件佔用的記憶體空間為:1位元組
//C++編譯器會給每個空物件也分配一個位元組空間,是為了區分空物件佔記憶體的位置
//每個空物件也應該有一個獨一無二的記憶體地址
cout << "sizeof p = " << sizeof(p) << endl;
}
void test02()
{
Person p;
cout << "sizeof p = " << sizeof(p) << endl;
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
4.3.2 this
指標概念
透過4.3.1我們還知道在C++中成員變數和成員函式是分開儲存的
每一個非靜態成員函式只會誕生一份函式例項,也就是說多個同型別的物件會共用一塊程式碼
那麼問題是:這一塊程式碼是如何區分那個物件呼叫的自己呢?
C++透過提供特殊的物件指標,this指標,解決上述問題。this
指標指向被呼叫的成員函式所屬的物件
this
指標是隱含每一個非靜態成員函式內的一種指標
this
指標不需要定義,直接使用即可
this
指標的用途:
- 當形參和成員變數同名時,可用
this
指標來區分 - 在類的非靜態成員函式返回物件本身,可使用
return *this
示例:
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//this指標指向 被呼叫的成員函式 所屬的物件
this->age = age;
}
Person& PersonAddAge(Person &p)
{
this->age += p.age;
// this指向p2的指標,而*this指向的就是p2這個物件本體
return *this;
}
int age;
};
//1、解決名稱衝突
void test01()
{
Person p1(18);
cout << "p1的年齡為:" << p1.age << endl;
}
//2、返回物件本身用 *this
void test02()
{
Person p1(10);
Person p2(10);
//鏈式程式設計思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << "p2的年齡為: " << p2.age << endl;
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
4.3.3 空指標訪問成員函式
C++中空指標也可以是呼叫成員函式的,但是也要注意有沒有用到this指標
如果用到this指標,需要加以判斷保證程式碼的健壯性
示例:
#include <iostream>
using namespace std;
//空指標呼叫成員函式
class Person
{
public:
void showClassName()
{
cout << "this is Person class" << endl;
}
void showName()
{
//報錯的原因是因為傳入的指標為NULL
if (this == NULL)
{
return;
}
cout << "Age = " << m_Age << endl;
}
int m_Age;
};
void test01()
{
Person * p = NULL;
p->showClassName();
//p->showName();
}
int main()
{
test01();
system("pause");
return 0;
}
4.3.4 const修飾成員函式
常函式:
- 成員函式後加
const
後我們稱為這個函式為常函式 - 常函式內不可以修改成員屬性
- 成員屬性宣告時加關鍵字
mutable
後,在常函式中依然可以修改
常物件:
- 宣告物件前加
const
稱該物件為常物件 - 常物件只能呼叫常函式
示例:
#include <iostream>
using namespace std;
//常函式
class Person
{
public:
//this指標的本質 是指標常量 指標的指向是不可以修改的
//const Person * const this;(第一個const與函式最後的const一致)
//在成員函式後面加const,修飾的是this指向,讓指標指向的值也不可以修改
void showPerson() const
{
this->m_B = 100;
//this->m_A = 100;
//this = NULL;//this指標不可以修改指標的指向
}
void func()
{
}
int m_A;
mutable int m_B; //特殊變數,即使在常函式中,也可以修改這個值,加上關鍵字mutable
};
void test01()
{
Person p;
p.showPerson();
}
//常物件
void test02()
{
const Person p;//在物件前加const,變為常物件
//p.m_A = 100;
p.m_B = 100; //m_B是特殊值,在常物件下也可以修改
//常物件只能呼叫常函式
//p.func(); //常物件 不可以呼叫普通成員函式,因為普通成員函式可以修改屬性
}
int main()
{
test01();
system("pause");
return 0;
}
4.4 友元
生活中你的家有客廳(public),有你的臥室(private)
客廳所有來的客人都可以進去,但你的臥室是私有的,也就是說只有你能進去
但是呢,你也可以允許你的好閨蜜好基友進去。
在程式裡,有些私有屬性 也想讓類外特殊的一些函式或者類進行訪問,就需要用到友元的技術
友元的目的就是讓一個函式或類 訪問另一個類中私有成員
友元的關鍵字為 friend
友元的三種實現
- 全域性函式做友元
- 類做友元
- 成員函式做友元
4.4.1 全域性函式做友元
示例:
#include <iostream>
#include<string>
using namespace std;
class Building
{
//goodGay全域性函式是 Building好朋友,可以訪問Building中私有成員
friend void goodGay(Building* building);
public:
Building()
{
m_SittingRoom = "客廳";
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 building;
goodGay(&building);
}
int main()
{
test01();
system("pause");
return 0;
}
4.4.2 類做友元
示例:
#include <iostream>
#include<string>
using namespace std;
//類做友元
class Building;
class GoodGay
{
public:
GoodGay();
void visit(); //參觀函式 訪問Building中的屬性
Building* building;
};
class Building
{
//GoodGay是本類的好朋友,可以訪問本類中私有成員
friend class GoodGay;
public:
Building();
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 成員函式做友元
示例:
#include <iostream>
#include<string>
using namespace std;
//成員函式做友元
class Building;
class GoodGay
{
public:
GoodGay();
void visit(); //讓visit函式可以訪問Building中私有成員
void visit2(); //讓visit函式不可以訪問Building中私有成員
Building* building;
};
class Building
{
//告訴編譯器 GoodGay類下的visit成員函式作為本類的好朋友,可以訪問私有成員
friend void GoodGay::visit();
public:
Building();
string m_SittingRoom; //客廳
private:
string m_BedRoom; //臥室
};
//類外寫成員函式
Building::Building()
{
this->m_SittingRoom = "客廳";
this->m_BedRoom = "臥室";
}
GoodGay::GoodGay()
{
//建立建築物物件
building = new Building;
}
void GoodGay::visit()
{
cout << "visit 函式 正在訪問:" << building->m_SittingRoom << endl;
cout << "visit 函式 正在訪問:" << building->m_BedRoom << endl;
}
void GoodGay::visit2()
{
cout << "visit2 函式 正在訪問:" << building->m_SittingRoom << endl;
//cout << "visit2 函式 正在訪問:" << building->m_BedRoom << endl;
}
void test01()
{
GoodGay gg;
gg.visit();
gg.visit2();
}
int main()
{
test01();
system("pause");
return 0;
}
4.5 運算過載符
運算子過載概念:對已有的運算子重新進行定義,賦予其另一種功能,以適應不同的資料型別 關鍵字operator
4.5.1 加號運算子過載
作用:實現兩個自定義資料型別相加的運算
示例:
#include <iostream>
#include<string>
using namespace std;
//加號運算子過載
class Person
{
public:
//1、成員函式過載+號
//Person operator+(Person& p)
//{
// Person temp;
// temp.m_A = this->m_A + p.m_A;
// temp.m_B = this->m_B + p.m_B;
// return temp;
//}
int m_A;
int m_B;
};
//2、全域性函式過載+號
Person operator+(Person& p1, Person& p2)
{
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
//函式過載版本
Person operator+(Person& p1, int num)
{
Person temp;
temp.m_A = p1.m_A + num;
temp.m_B = p1.m_B + num;
return temp;
}
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
//成員函式過載本質呼叫
//Person p3 = p1.operator+(p2);
//全域性函式過載本質呼叫
//Person p3 = operator+(p1, p2);
//運算子過載 也可以發生函式過載
Person p3 = p1 + 20; //Person+int
//Person p3 = p1 + p2;
cout << "p3.m_A = " << p3.m_A << " p3.m_B = " << p3.m_B << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
總結1:對於內建的資料型別的表示式的運算是不可能改變的
總結2:不要濫用運算子過載
4.5.2 左移運算過載符
作用:可以輸出自定義資料型別
示例:
#include <iostream>
#include<string>
using namespace std;
//左移運算子過載
class Person
{
friend ostream& operator<<(ostream& cout, Person& p);
public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
private:
//利用成員函式過載 左移運算子 p.operator<<(cout) 簡化版本 p<<cout
//不會利用成員函式過載<<運算子,因為無法實現cout在左側
//void operator<<(cout)
//{
//
//}
int m_A;
int m_B;
};
//只能利用全域性函式過載左移運算子
ostream & operator<<(ostream &cout, Person &p) //本質 opeator<<(cout, p) 簡化 cout<<p
{
cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
return cout;
}
void test01()
{
Person p(10, 10);
//p.m_A = 10;
//p.m_B = 10;
cout << p << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
總結:過載左移運算子配合友元可以實現輸出自定義資料型別
4.5.3 遞增運算過載符
作用:透過過載遞增運算子,實現自己的整型資料
示例:
#include<iostream>
using namespace std;
//過載遞增運算子
//自定義整型
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
//過載前置++運算子 返回引用是為了一直對一個資料進行遞增操作
MyInteger& operator++()
{
//先進行++運算
m_Num++;
//再將自身做返回
return *this;
}
//過載後置++運算子
//void operator++(int)代表佔位引數,可以用於區分前置和後置
MyInteger operator++(int)
{
//先 記錄一下當時結果
MyInteger temp = *this;
//後 遞增
m_Num++;
//將記錄結果做返回操作
return temp;
}
private:
int m_Num;
};
//過載<<運算子
ostream& operator<<(ostream& cout, MyInteger myint)
{
cout << myint.m_Num;
return cout;
}
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個函式
- 預設建構函式(無參,函式體為空)
- 預設解構函式(無參,函式體為空)
- 預設複製建構函式,對屬性進行值複製
- 賦值運算子
operator=
,對屬性進行值複製
如果類中有屬性指向堆區,做賦值操作時也會出現深複製問題
示例:
#include<iostream>
using namespace std;
//賦值運算子過載
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//過載 賦值運算子
Person & operator=(Person& p)
{
//編譯器是提供淺複製
//m_Age = p.m_Age;
//應該先判斷是否有屬性在堆區,如果有釋放乾淨,然後再深複製
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//深複製
m_Age = new int(*p.m_Age);
//返回物件本身
return *this;
}
int* m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(20);
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 關係運算子過載
作用:過載關係運算子,可以讓兩個自定義型別物件進行對比操作
示例:
#include<iostream>
using namespace std;
//過載關係運算子
class Person
{
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
//過載 ==
bool operator==(Person &p)
{
if (this->m_Age == p.m_Age && this->m_Name == p.m_Name)
{
return true;
}
return false;
}
//過載 !=
bool operator!=(Person& p)
{
if (this->m_Age == p.m_Age && this->m_Name == p.m_Name)
{
return false;
}
return true;
}
string m_Name;
int m_Age;
};
void test01()
{
Person p1("Tom", 18);
Person p2("Tom", 18);
if (p1 == p2)
{
cout << "p1 和 p2 是相等的!" << endl;
}
else
{
cout << "p1 和 p2 是不相等的!" << endl;
}
if (p1 != p2)
{
cout << "p1 和 p2 是不相等的!" << endl;
}
else
{
cout << "p1 和 p2 是相等的!" << endl;
}
}
int main()
{
test01();
system("pause");
return 0;
}
4.5.6 函式呼叫運算子過載
- 函式呼叫運算子()也可以過載
- 由於過載後使用的方式非常像函式的呼叫,因此稱為仿函式
- 仿函式沒有固定寫法,非常靈活
示例:
#include<iostream>
#include<string>
using namespace std;
//函式呼叫運算子過載
//列印輸出類
class MyPrint
{
public:
//過載函式呼叫運算
void operator()(string test)
{
cout << test << endl;
}
};
void myPrint02(string test)
{
cout << test << endl;
}
void test01()
{
MyPrint myPrint;
myPrint("hello world"); //由於使用起來非常類似於函式呼叫,因此稱為仿函式
myPrint02("hello world");//函式呼叫
}
//仿函式非常靈活,沒有固定的寫法
//加法類
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test02()
{
MyAdd myadd;
int ret = myadd(100, 100);
cout << "ret = " << ret << endl;
//匿名函式物件 (過載了仿函式的沒有名字的物件)
cout << MyAdd()(100, 100) << endl;
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
4.6 繼承
繼承是物件導向三大特性之一
有些類與類之間存在特殊關係,例如下圖中:
我們發現,定義這些類時,下級別的成員除了擁有上一級的共性,還有自己的特性。
這個時候我們就要考慮利用繼承的技術,減少重複程式碼
4.6.1 繼承的基本語法
例如我們看到很多網站中,都有公共的頭部,公共的底部,甚至公共的左側列表欄,只有中心內容不同
接下來我們分別利用普通寫法和繼承的寫法來實現網頁中的內容,看一下繼承存在的意義以及好處
示例:
#include<iostream>
#include<string>
using namespace std;
//普通實現頁面
//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++頁面
//Python頁面
class Cpp
{
public:
void header()
{
cout << "首頁、公開課、登入、註冊...(公共)" << endl;
}
void footer()
{
cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、...(公共分類列表)" << endl;
}
void content()
{
cout << "C++學科影片" << endl;
}
};
*/
//繼承實現頁面
class BasePage
{
public:
void header()
{
cout << "首頁、公開課、登入、註冊...(公共)" << endl;
}
void footer()
{
cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、...(公共分類列表)" << endl;
}
};
//繼承的好處:減少重複程式碼
//語法:class 子類 :繼承方式 父類
//子類 也成為 派生類
//父類 也成為 基類
//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()
{
cout << "Java下載影片頁面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "----------------------------" << endl;
cout << "Python下載影片頁面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "----------------------------" << endl;
cout << "C++下載影片頁面如下:" << endl;
Cpp cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
int main()
{
test01();
system("pause");
return 0;
}
總結:
繼承的好處:可以減少程式碼的重複量
class A : public B
A類稱為 子類
或 派生類
B類稱為 父類
或 基類
派生類中成員,包含兩大部分:
一類是從基類繼承過來的,一類是自己增加的成員
從基類繼承過來的表現其共性,而新增的成員體現了其個性
4.6.2 繼承方式
繼承的語法:class 子類 :繼承方式 父類
繼承方式一共有三種:
- 共有繼承
- 保護繼承
- 私有繼承
示例:
#include<iostream>
#include<string>
using namespace std;
//繼承方式
//公共繼承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son1 :public Base1
{
public:
void func1()
{
m_A = 10;//父類中的公共許可權成員 到子類中依然是公共許可權
m_B = 10;//父類中的保護許可權成員 到子類中依然是保護許可權
//m_C = 10;//父類中的私有許可權成員 子類訪問不到
}
};
void test01()
{
Son1 s1;
s1.m_A = 100;
//s1.m_B = 100;//到Son1中 m_B是保護許可權 類外訪問不到
}
//保護繼承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2 :protected Base2
{
public:
void func()
{
m_A = 100; //父類中公共成員,到子類中變為保護許可權
m_B = 100; //父類中保護成員,到子類中變為保護許可權
//m_C = 100; //父類中私有成員 子類訪問不到
}
};
void test02()
{
Son2 s1;
//s1.m_A = 100; //在Son2中 m_A變為保護許可權,因此類外訪問不到
//s1.m_B = 100; //在Son2中 m_B保護許可權 不可以訪問
}
//私有繼承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3 :private Base3
{
void func()
{
m_A = 100; //父類中公共成員,到子類中變為私有許可權
m_B = 100; //父類中保護成員,到子類中變為私有許可權
//m_C = 100; //父類中私有成員 子類訪問不到
}
};
void test03()
{
Son3 s1;
//s1.m_A = 1000; //到Son3中變為了私有成員,類外訪問不到
//s1.m_B = 1000; //到Son3中變為了私有成員,類外訪問不到
}
class GrandSon3 :public Son3
{
public:
void func()
{
//m_A = 1000; //到了Son3 m_A變為了私有,即使是兒子,也是訪問不到
//m_B = 1000; //到了Son3 m_B變為了私有,即使是兒子,也是訪問不到
}
};
int main()
{
system("pause");
return 0;
}
4.6.3 繼承中的物件模型
問題:從父類繼承過來的成員,哪些屬於子類物件中
示例:
#include<iostream>
#include<string>
using namespace std;
//繼承中的物件模型
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son :public Base
{
public:
int m_D;
};
//利用開發人員命令提示工具檢視物件模型
//跳轉磁碟機代號 F:
//跳轉檔案路徑 cd 具體路徑如下 E:\projects\C++Code\helloworld
//檢視命令
//cl /dl reportsingleClassLayout 類名 檔名
void test01()
{
//16
//父類中所有非靜態成員屬性都會被子類繼承下去
//父類中的私有成員屬性 是被編譯器給隱藏了,因此訪問不到,但是確實被繼承了
cout << "size of Son = " << sizeof(Son) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
命令操作:
4.6.4 繼承中構造和析構順序
子類繼承父類後,當建立子類物件,也會呼叫父類中的建構函式
問題:父類和子類的構造和析構的順序是誰先誰後?
示例:
#include<iostream>
#include<string>
using namespace std;
//繼承中的物件模型
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()
{
//Base b;
//繼承中的構造和析構順序如下:
//先構造父類,在構造子類,析構的順序和構造的順序相反
Son s;
}
int main()
{
test01();
system("pause");
return 0;
}
總結:繼承中先呼叫父類建構函式,再呼叫子類建構函式,析構順序與構造順序相反
4.6.5 繼承同名成員處理方式
問題:當子類和父類出現同名的成員,如何透過子類物件,訪問到子類或者父類中的同名的資料呢?
- 訪問子類同名成員,直接訪問即可
- 訪問父類同名成員,需要加作用域
示例:
#include<iostream>
#include<string>
using namespace std;
//繼承中的同名成員處理方式
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base - func()呼叫" << endl;
}
void func(int a)
{
cout << "Base - func(int)呼叫" << endl;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "Son - func()呼叫" << endl;
}
int m_A;
};
//同名成員屬性處理方式
void test01()
{
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
//如果透過子類物件 訪問父類中同名成員,需要加作用域
cout << "Base 下 m_A = " << s.Base::m_A << endl;
}
//同名成員函式處理方式
void test02()
{
Son s;
s.func(); //直接呼叫 呼叫的是子類中的同名成員
//如何呼叫到父類中的同名成員
s.Base::func();
//如果子類中出現和父類同名的成員函式,子類的同名成員會隱藏掉父類中所有同名成員函式(包括過載的函式)
//如果想訪問到父類中被隱藏的同名函式,需要加作用域
s.Base::func(100);
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
總結:
- 子類物件可以直接訪問到子類中同名成員
- 子類物件加作用域可以訪問到父類同名成員
- 當子類與父類擁有同名的成員函式,子類會隱藏父類中的同名成員函式,加作用域可以訪問到父類中的同名函式
4.6.6 繼承同名靜態成員處理方式
問題:繼承中同名的靜態成員在子類物件上如何進行訪問?
靜態成員和非靜態成員出現重名,處理方式一致
- 訪問子類同名成員,直接訪問即可
- 訪問父類同名成員,需要加作用域
示例:
#include<iostream>
#include<string>
using namespace std;
//繼承中的同名靜態成員處理方式
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int)" << endl;
}
};
int Base::m_A = 100;
class Son :public Base
{
public:
static int m_A;
static void func()
{
cout << "Son - static void func()" << endl;
}
};
int Son::m_A = 200;
//同名靜態成員屬性
void test01()
{
//1、透過物件訪問
cout << "透過物件訪問:" << endl;
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
//2、透過類名訪問
cout << "透過類名訪問:" << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
//第一個::代表透過類名方式訪問 第二個::代表訪問父類作用域下
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
//同名靜態成員函式
void test02()
{
//1、透過物件訪問
cout << "透過物件訪問:" << endl;
Son s;
s.func();
s.Base::func();
//2、透過類名訪問
cout << "透過類名訪問:" << endl;
Son::func();
Son::Base::func();
//子類出現和父類同名靜態成員函式,也會隱藏父類中所有同名成員函式
//如果想訪問父類中被隱藏同名成員,需要加作用域
Son::Base::func(10);
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
總結:同名靜態成員處理方式和非靜態處理方式一樣,只不過有兩種訪問的方式(透過物件 和 透過類名)
4.6.7 多繼承語法
C++允許一個類繼承多個類
語法:class 子類 : 繼承方式 父類1 , 繼承方式 父類2...
多繼承可能會引發父類中有同名成員出現,需加作用域區分
C++實際開發中不建議使用多繼承
示例:
#include<iostream>
#include<string>
using namespace std;
//多繼承語法
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};
//子類需要繼承 Base1 和 Base2
//語法:class 子類 : 繼承方式 父類1 , 繼承方式 父類2...
class Son :public Base1, public Base2
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
void test01()
{
Son s;
cout << "sizeof Son = " << sizeof(s) << endl;
//當父類中出現同名成員,需要加作用域區分
cout << "Base1::m_A = " << s.Base1::m_A << endl;
cout << "Base2::m_A = " << s.Base2::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
總結:多繼承中如果父類中出現了同名情況,子類使用的時候要加作用域
4.7.8 菱形繼承
菱形繼承概念:
兩個派生類繼承同一個基類
又有某個類同時繼承兩個派生類
這種繼承被稱為菱形繼承,或者鑽石繼承
典型的菱形繼承案例:
菱形繼承問題:
- 羊繼承了動物的資料,駝同樣繼承了動物的資料,當草泥馬使用資料時,就會產生二義性
- 草泥馬繼承自動物的資料繼承了兩份,其實我們應該清楚,這份資料我們只需要一份即可
示例:
#include<iostream>
#include<string>
using namespace std;
//動物類
class Animal
{
public:
int m_Age;
};
//利用虛繼承 解決菱形繼承的問題
//在繼承之前 加上關鍵字 virtual 變為虛繼承
// Animal 類稱為虛基類
//羊類
class Sheep :virtual public Animal {}; //vfptr v-virtual f-function ptr-pointer (虛擬函式指標)
//駝類
class Tuo :virtual public Animal {};
//羊駝類
class SheepTuo :public Sheep, public Tuo {};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
//當菱形繼承,兩個父類擁有相同資料,需要加以作用域區分
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;
}
總結:
- 菱形繼承帶來的主要問題是子類繼承兩份相同的資料,導致資源浪費以及毫無意義
- 利用虛繼承可以解決菱形繼承問題 關鍵字
virtual
4.7 多型
4.7.1 多型基本概念
多型是C++物件導向三大特性之一
多型分為兩類:
- 靜態多型:函式過載 和 運算子過載屬於靜態多型,複用函式名
- 動態多型:派生類和虛擬函式實現執行時多型
靜態多型和動態多型的區別:
- 靜態多型的函式地址早繫結 - 編譯階段確定函式地址
- 動態多型的函式地址晚繫結 - 執行階段確定函式地址
示例:
#include<iostream>
#include<string>
using namespace std;
//多型
//動物類
class Animal
{
public:
//虛擬函式
virtual void speak()
{
cout << "動物在說話" << endl;
}
};
//貓類
class Cat :public Animal
{
public:
void speak()
{
cout << "小貓在說話" << endl;
}
};
//執行說話的 函式
//地址早繫結 在編譯階段確定函式地址
//如果想執行讓貓說話,那麼這個函式地址就不能提前繫結,需要在執行階段進行繫結,地址晚繫結(加virtual)
//動態多型滿足條件
//1、有繼承關係
//2、子類要重寫父類的虛擬函式
//動態多型使用
//父類的指標或引用 指向子類物件
void doSpeak(Animal& animal) //Animal & animal = cat;
{
animal.speak();
}
//狗類
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在說話" << endl;
}
};
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main()
{
test01();
system("pause");
return 0;
}
總結:
多型滿足條件
- 有繼承關係
- 子類重寫父類中的虛擬函式
多型使用條件
- 父類指標或引用指向子類物件
重寫:函式返回值型別 函式名 引數列表 完全一致稱為重寫
4.7.2 多型案例一:計算器類
案例描述:
分別利用普通寫法和多型技術,設計實現兩個運算元進行運算的計算器類
多型的優點:
- 程式碼組織結構清晰
- 可讀性強
- 利於前期後期的擴充套件以及維護
示例:
#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;
}
//如果想擴充套件新的功能,需要修改原始碼
//在真實的開發中 提倡 開閉原則
//開閉原則:對擴充套件進行開放,對修改進行關閉
}
int m_Num1; //運算元1
int m_Num2; //運算元2
};
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;
}
//利用多型實現計算器
//多型好處:
//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 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << " + " << abc->m_Num2 << "=" << abc->getResult() << endl;
//用完後記得銷燬
delete abc;
//減法運算
abc = new SubCalculator;
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << " - " << abc->m_Num2 << "=" << abc->getResult() << endl;
//用完後記得銷燬
delete abc;
//乘法運算
abc = new MulCalculator;
abc->m_Num1 = 100;
abc->m_Num2 = 100;
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>
#include<string>
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();
}
int main()
{
test01();
system("pause");
return 0;
}
4.7.4 多型案例二:製作飲品
案例描述:
製做飲品的大致流程為:煮水 - 沖泡 - 倒入杯中 - 加入輔料
利用多型技術實現本案例,提供抽象製作飲品基類,提供子類製作咖啡和茶葉
示例:
#include<iostream>
#include<string>
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
{
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) //AbstractDrinking * abs = new Coffee
{
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>
#include<string>
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);
}
virtual void speak()
{
cout << *m_Name << "小貓在說話" << endl;
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat的解構函式呼叫" << endl;
delete m_Name;
m_Name = NULL;
}
}
string* m_Name;
};
void test01()
{
Animal* animal = new Cat("Tom");
animal->speak();
//父類指標在析構的時候 不會呼叫子類中的解構函式,導致子類如果有堆區屬性,會出現記憶體洩漏情況
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}
總結:
- 虛析構或純虛析構就是用來解決透過父類指標釋放子類物件
- 如果子類沒有堆區資料,可以不寫為虛析構或純虛析構
- 擁有純虛析構的類也屬於抽象類
4.7.6 多型案例三:電腦組裝
案例描述:
電腦主要組成部件為CPU(用於計算),顯示卡(用於顯示),記憶體條(用於儲存)
將每個零件封裝出抽象基類,並且提供不同的廠商生產不同的零件,例如 intel 廠商和 lenovo 廠商
建立電腦類提供讓電腦工作的函式,並且呼叫每個零件工作的介面
測試時組裝三臺不同的電腦進行工作
示例:
#include<iostream>
#include<string>
using namespace std;
//抽象不同零件類
//抽象CPU類
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* cpu, VideoCard* vc, Memory* mem)
{
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
//提供工作函式
void work()
{
//讓零件工作起來,呼叫介面
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
//提供虛構函式 釋放3個電腦零件
~Computer()
{
//釋放CPU零件
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 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, intelCard, intelMem);
computer1->work();
delete computer1;
cout << "--------------------------" << endl;
cout << "第二臺電腦開始工作:" << endl;
//第二臺電腦組裝
Computer* computer2 = new Computer(new lenovoCPU, new lenovoVideoCard, new lenovoMemory);
computer2->work();
delete computer2;
cout << "--------------------------" << endl;
cout << "第三臺電腦開始工作:" << endl;
//第二臺電腦組裝
Computer* computer3 = new Computer(new IntelCPU, new lenovoVideoCard, new IntelMemory);
computer3->work();
delete computer3;
}
int main()
{
test01();
system("pause");
return 0;
}
5 檔案操作
程式執行時產生的資料都屬於臨時資料,程式一旦執行結束都會被釋放
透過檔案可以將資料持久化
C++中對檔案操作需要包含標頭檔案<fstream>
檔案型別分為兩種:
- 文字檔案 -檔案以文字的ASCII碼形式儲存在計算機中
- 二進位制檔案 -檔案以文字的二進位制形式儲存在計算機中,使用者一般不能直接讀懂它們
操作檔案三大類:
- ofstream:寫操作
- ifstream:讀操作
- fstream:讀寫操作
5.1 文字檔案
5.1.1 寫檔案
寫檔案步驟如下:
-
包含標頭檔案
#include <fstream>
-
建立流資料
ofstream ofs;
-
開啟檔案
ofs.open("檔案路徑", 開啟方式)
-
寫資料
ofs << "寫入資料";
-
關閉檔案
ofs.close();
檔案開啟方式:
開啟方式 | 解釋 |
---|---|
ios::in |
為讀檔案而開啟資料夾 |
ios::out |
為寫檔案而開啟資料夾 |
ios::ate |
初始位置:檔案尾 |
ios::app |
追加方式寫檔案 |
ios::trunc |
如果檔案存在先刪除,再建立 |
ios::binary |
二進位制方式 |
注意:檔案開啟方式可以配合使用|
運算子
例如:用二進位制方式寫檔案ios::binary | ios:out
示例:
#include<iostream>
#include<fstream>
using namespace std;
//文字檔案 寫檔案
void test01()
{
//1、包含標頭檔案 fstream
//2、建立流物件
ofstream ofs;
//3、指定開啟方式
ofs.open("test.txt", ios::out);
//4、寫內容
ofs << "姓名:張三" << endl;
ofs << "性別:男" << endl;
ofs << "年齡:18" << endl;
//5、關閉檔案
ofs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
總結:
- 檔案操作必須包含標頭檔案 fstream
- 讀檔案可以利用ofstream,或者fstream類
- 開啟檔案時候需要指定操作檔案的路徑,以及開啟方式
- 利用 << 可以向檔案中寫入資料
- 操作完畢需要關閉檔案
5.1.2 讀檔案
讀檔案與寫檔案類似,但是讀取方式相對於比較多
讀檔案包含步驟如下:
-
包含標頭檔案
#include <fstream>
-
建立流物件
ifstream ifs;
-
開啟檔案並判斷檔案是否開啟成功
ifs.open("檔案路徑", 開啟方式)
-
讀資料
四種方式讀取
-
關閉檔案
ifs.close();
示例:
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
//文字檔案 讀檔案
void test01()
{
//1、包含標頭檔案 fstream
//2、建立流物件
ifstream ifs;
//3、開啟檔案 並且判斷是否開啟成功
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "檔案開啟失敗" << endl;
return;
}
//4、讀資料
//第一種
//char Buffer[1024] = { 0 };
//while (ifs >> Buffer)
//{
// cout << Buffer << endl;
//}
//第二種
//char Buffer[1024] = { 0 };
//while (ifs.getline(Buffer, sizeof(Buffer)))
//{
// cout << Buffer << endl;
//}
//第三種
//string Buffer;
//while (getline(ifs, Buffer))
//{
// cout << Buffer << endl;
//}
//第四種
char c;
while ((c = ifs.get()) != EOF) //EOF end of file
{
cout << c;
}
//5、關閉檔案
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<iostream>
#include<fstream>
#include<string>
using namespace std;
//二進位制檔案 寫檔案
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);
//4、寫檔案
Person p = { "張三",18 }; //初始化物件
ofs.write((const char*)&p, sizeof(Person));
//5、關閉檔案
ofs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
總結:
- 檔案輸出流物件可以透過write函式,以二進位制方式寫資料
5.3.3 讀檔案
二進位制方式讀檔案主要利用流物件呼叫成員函式read
函式原型:istream& read(char *buffer, int len);
引數解釋:字元指標 buffer 指向記憶體中一段儲存空間,len 是讀寫的位元組數
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
//二進位制檔案 讀檔案
class Person
{
public:
char m_Name[64]; //姓名
int m_Age; //年齡
};
void test01()
{
//1、包含標頭檔案
//
//2、建立流物件
ifstream ifs;
//3、開啟檔案 判斷檔案是否開啟成功
ifs.open("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "檔案開啟失敗" << endl;
}
//4、讀檔案
Person p;
ifs.read((char*)&p, sizeof(Person));
cout << "姓名: " << p.m_Name << " 年齡: " << p.m_Age << endl;
//5、關閉檔案
ifs.close();
}
int main()
{
test01();
system("pause");
return 0;
}
總結:
- 檔案輸入流物件 可以透過 read 函式,以二進位制方式讀資料