1. 程式人生 > 其它 >c++進階知識

c++進階知識

本階段主要針對c++面向物件程式設計。

基礎知識請看:c++基礎知識

  1. 記憶體分割槽模型
  2. 引用
  3. 函式提高
  4. 類和物件
  5. 檔案操作

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個函式

  1. 預設建構函式(無參,函式體為空)
  2. 預設解構函式(無參,函式體為空)
  3. 預設拷貝建構函式,對屬性進行值拷貝
  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碼形式儲存
  • 二進位制檔案:以二進位制形式儲存

操作檔案的三大類

  1. ofstream:寫操作
  2. ifstream:讀操作
  3. 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));    

二進位制方式讀檔案主要利用流物件呼叫成員函式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;
}