c++進階知識
本階段主要針對c++面向物件程式設計。
基礎知識請看:c++基礎知識
- 記憶體分割槽模型
- 引用
- 函式提高
- 類和物件
- 檔案操作
1、記憶體分割槽模型
c++程式在執行時,將記憶體大方向劃分為4個區域
- 程式碼區:存放函式體的二進位制程式碼,由作業系統進行管理
- 全域性區:存放全域性變數,靜態變數,常量
- 棧區:由編譯器自動分配釋放,存放函式的引數值,區域性變數
- 堆區:由程式設計師分配和釋放,若程式設計師不釋放,程式結束時由作業系統回收
目的:不同區域存放的資料,其生命週期不同,方便我們靈活程式設計
1.1 程式執行前
在程式編譯後,生成了exe可執行檔案,未執行該程式前分為兩個區域
程式碼區:
- 存放CPU執行的機器指令
- 程式碼區是共享的,共享的目的是對於頻繁被執行的程式,只需要在記憶體中有一份程式碼即可
- 程式碼區是只讀的,只讀的原因是防止程式意外修改了它的指令
全域性區:
- 全域性變數和靜態變數存放在此
- 全域性區還包含了常量區,字串常量和全域性常量也存放在此(區域性常量,區域性變數在棧區)
- 該區域的資料在程式結束後由作業系統釋放
總結:
- c++程式在執行前分為全域性區和程式碼區
- 程式碼區的特點是共享和只讀
- 全域性區中存放全域性變數、靜態變數、常量
- 常量區中存放了const修飾的全域性常量 和 字串常量
1.2 程式執行後
棧區:
由編譯器自動分配和釋放,存放函式的引數值,區域性變數等
注意事項:函式不要返回區域性變數的地址:棧區開闢的資料空間,在函式執行完後,由編譯器自動釋放
堆區:
由程式設計師分配釋放,若程式設計師不釋放,程式結束時由作業系統回收
c++中主要利用new在堆區開闢記憶體
1.3 new操作符
堆區開闢用new,釋放用delete。利用new建立的資料,會返回該資料對應的型別的指標。
int * a = new int(10);//建立變數
delete a;
int * arr = new int[10]; //建立陣列
delete[] arr;
2、引用
作用:給變數起別名
資料型別 &別名 = 原名
注意事項:
- 必須初始化
- 初始化之後不可改變
函式傳參時,可以利用引用的技術,讓形參修飾實參
int swap(int &a, int&b)
引用可以作為函式的返回值(1.不要返回區域性變數 2.函式的呼叫可以作為左值)
常量引用的作用主要是來修飾形參,防止誤操作:在函式形參列表中,可以加const修飾形參,防止形參改變實參。
引用的本質:指標常量
3.函式提高
函式的形參列表是可以有預設值的。預設值只能設定一次,如:函式宣告時設定了,函式定義時就不能設定了。
函式的形參列表中可以有佔位引數,用來做佔位,呼叫函式時必須填補該位置。
返回值型別 函式名(資料型別){}
函式過載:函式名可以相同,提高複用性。
函式過載滿足條件:
- 同一作用域下
- 函式名稱相同
- 函式引數型別不同 || 個數不同 || 順序不同
注意事項:
- 返回值不可以作為過載條件。
4.類和物件
三大特性:封裝、繼承、多型
萬事萬物皆可為物件,物件上有其屬性和行為
4.1 封裝
封裝的意義:
- 將屬性和行為作為一個整體,表現生活中的事物
- 將屬性和行為加以許可權控制
- public(公共許可權):類內可以訪問,類外可以
- protected(保護許可權):類內可以;類外不可以,子類可以訪問
- private(私有許可權):類內可以;類外不可以,子類不可以訪問
class 類名{訪問許可權:屬性 / 行為};
類中的屬性和行為統一稱為 成員
屬性:成員屬性,成員變數
行為:成員函式,成員方法
struct 和 class的區別
- struct 預設許可權為公共
- class 預設許可權為私有
將成員變數設定為私有之後,可以通過成員方法:1.控制讀寫許可權 2.驗檢測資料的有效性。
標頭檔案中宣告,原始檔中實現
4.2 物件的初始化和清理
4.2.1 建構函式和解構函式
建構函式:初始化,在建立物件時為物件的成員變數賦值,建構函式由編譯器自動呼叫。
類名(){}
- 沒有返回值,且不寫void
- 函式名稱與類名相同
- 可以有引數,因此可以過載
- 建立物件時會自動呼叫一次
解構函式:在物件銷燬前,系統自動呼叫,執行一些清理工作。
~類名(){}
- 沒有返回值 和 void
- ~
- 沒有引數,因此不可以過載
- 銷燬前自動呼叫一次
當未提供構造和解構函式時,編譯器會提供,且其是空實現。只有預設拷貝函式是值拷貝。
4.2.2 建構函式的分類及呼叫
兩種分類方式
- 按引數:有參構造和無參構造
- 按型別:普通構造和拷貝構造(引數是引用的物件)
三種呼叫方式:括號法;顯示法;隱式轉換法
拷貝建構函式呼叫時機
- 使用一個已經建立完畢的物件來初始化一個新物件
- 值傳遞的方式給函式引數傳值(傳參)
- 以值方式返回區域性物件(返回值)
拷貝函式的呼叫規則
- 如果使用者定義有參建構函式,c++不再提供預設無參構造,但是會提供預設拷貝構造
- 如果使用者定義拷貝建構函式,c++不再提供其他建構函式
深拷貝和淺拷貝
- 淺拷貝:簡單的賦值拷貝操作(會帶來問題:堆區的記憶體重複釋放)
- 深拷貝:在堆區重新申請空間,進行拷貝操作(解決了淺拷貝的問題)
總結:如果屬性有在堆區開闢的,需要自己提供拷貝建構函式,防止淺拷貝帶來的問題。
初始化列表:用於初始化屬性
建構函式(資料結構,值1,資料結構 值2 ···):屬性1(值2),屬性1(值2),···{}
類物件可以作為類成員,構造時,先構造物件成員,再構造本類。析構順序相反。
- 靜態成員變數
- 所有物件共享同一份資料
- 在編譯階段分配記憶體
- 類內宣告,類外初始化
- 靜態成員函式
- 所有物件共享同一個函式
- 靜態成員函式只能訪問靜態成員變數
c++編譯器給類新增4個函式
- 預設建構函式(無參,函式體為空)
- 預設解構函式(無參,函式體為空)
- 預設拷貝建構函式,對屬性進行值拷貝
- 賦值運算子 operator= ,對屬性進行值拷貝
4.3 c++物件模型和 this 指標
成員變數和成員函式分開儲存。只有非靜態成員變數在類上。
每一個非靜態成員函式只會誕生一份函式例項,也就是說,多個同類型的物件會共用一塊程式碼。
c++通過提供 this 指標來幫助函式例項區分是哪一個物件呼叫自己。this 指標的本質是一個指標常量。
this 指標指向被呼叫的成員函式所屬的物件。
- 當形參和成員變數同名時,可用 this 指標來區分(this.age = age)
- 在類的非靜態成員函式中返回物件本身,可使用 return *this
空指標呼叫成員函式,如果用到this,需要加以判斷保持程式碼的健壯性。
if(this == NULL) return;
const 修飾成員函式
常函式
- 成員函式後加 const 後我們稱這個函式為常函式
- 常函式內的成員屬性無法被修改,若想修改,需要在成員屬性宣告時加關鍵字 mutable 。
常物件
- 宣告物件前加 const 稱該物件為常物件
- 常物件只能呼叫常函式(防止普通成員函式修改成員屬性)
友元:讓一個函式或者類 訪問 另一個類中的私有成員。關鍵字為 friend
三種實現
- 全域性函式做友元(在類內宣告為友元)
- 類做友元
- 成員函式做友元
4.4 運算子過載:
對已有的運算子重新進行定義,賦予其另一種功能,以適應不同的資料型別
可以通過成員函式過載,也可以通過全域性函式過載。運算子過載也可以發生函式過載。
- 加法運算子過載:實現兩個自定義資料型別相加的運算
- 左移運算子過載:輸出自定義的資料型別
- 遞增運算子過載:實現自己的整型資料(用佔位引數區分前置遞增和後置遞增,前置遞增返回引用,後置遞增返回值)
- 賦值運算子過載:解決淺拷貝的問題
- 關係運算符過載:可以讓兩個自定義物件進行對比操作
- 函式呼叫運算子過載:函式呼叫運算子()也可以過載,過載後也被稱為仿函式
4.5 繼承:減少重複程式碼
class 子類:繼承方式 父類
子類,也成為 派生類(表示個性)
父類,也稱為 基類(表示共性)
繼承方式:
- 公共繼承
- 保護繼承
- 私有繼承
繼承中的物件模型:從父類繼承過來的成員,哪些屬於子類物件?
所有非靜態成員屬性都會被繼承。私有成員屬性被編譯器隱藏了,無法訪問。
//利用開發人員命令提示工具檢視物件模型 //跳轉碟符 //cd 具體路徑下 cl /d1 reportSingleClassLayout類名 檔名
繼承同名成員處理方式
- 訪問子類同名成員 直接訪問即可
- 訪問父類童名成員 需要加作用域
- 當子類和父類擁有同名的成員函式,子類會隱藏父類中同名成員函式,加作用域可以訪問到父類中同名函式
c++允許多繼承,但實際開發中不建議使用
菱形繼承,又稱鑽石繼承:利用虛繼承可以解決菱形繼承問題
虛繼承後,子類繼承了虛基類指標,指標指向虛基類表,表中是實際資料的偏移量。
4.6 多型
- 靜態多型(編譯時確定函式地址):函式過載 和 運算子過載屬於靜態多型,複用函式名
- 動態多型(執行時確定函式地址):派生類 和 虛擬函式實現執行時多型
多型滿足條件
- 有繼承關係
- 子類重寫父類中的虛擬函式
多型使用條件:父類指標 或 引用指向子類物件
重寫:函式返回值型別 函式名 引數列表 完全一致稱為重寫
多型的優點:
- 程式碼組織結構清晰
- 可讀性強
- 利於前期和後期的擴充套件以及維護
c++開發提倡利用多型設計程式結構,因為多型優點很多
多型中,父類的虛擬函式實現通常毫無意義,因此可以改成純虛擬函式。
當類中有了純虛擬函式,就變成了抽象類
抽象類特點
- 無法例項 寫抽象類中的純虛擬函式,否則也屬於抽象類
虛析構和純虛析構
多型使用時,如果子類中有屬性開闢到堆區,那麼父類指標釋放 子類物件時不乾淨,因此,需要走將父類中的解構函式改為 虛析構 或者 純虛析構。
虛析構 和 純虛析構共性:
- 可以解決父類指標釋放子類物件
- 都需要有具體的函式實現
虛析構和純虛析構 區別:如果是純虛析構,該類屬於抽象類,無法例項化物件
//虛析構 virtual ~類名(){} //純虛析構 virtual ~類名()=0; 類名::~類名(){}
5. 檔案操作
通過檔案可以將資料持久化
c++中對檔案操作需要包含標頭檔案<fstream>
- 文字檔案:以ASCII碼形式儲存
- 二進位制檔案:以二進位制形式儲存
操作檔案的三大類
- ofstream:寫操作
- ifstream:讀操作
- fstream:讀寫操作
寫檔案
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
讀檔案
1. 包含標頭檔案 \#include <fstream\> 2. 建立流物件 ifstream ifs; 3. 開啟檔案並判斷檔案是否開啟成功 ifs.open("檔案路徑",開啟方式); 4. 讀資料 四種方式讀取 //第一種方式 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; } 5. 關閉檔案 ifs.close();
二進位制方式寫檔案主要利用流物件呼叫成員函式write
函式原型 :ostream& write(const char * buffer,int len);
引數解釋:字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數
ofstream ofs("person.txt", ios::out | ios::binary); //ofs.open("person.txt", ios::out | ios::binary); Person p = {"張三" , 18}; //4、寫檔案 ofs.write((const char *)&p, sizeof(p));
函式原型: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; }