日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

笔记②:牛客校招冲刺集训营---C++工程师(面向对象(友元、运算符重载、继承、多态) -- 内存管理 -- 名称空间、模板(类模板/函数模板) -- STL)

發(fā)布時間:2023/12/8 c/c++ 32 豆豆

0618

C++工程師

  • 第5章 高頻考點與真題精講
    • 5.1 指針 & 5.2 函數(shù)
    • 5.3 面向?qū)ο?#xff08;和5.4、5.5共三次直播課)
      • 5.3.1 - 5.3.11
      • 5.3.12-14 友元
        • 友元全局函數(shù)
        • 友元類
        • 友元成員函數(shù)
        • 友元的注意事項
      • 5.3.15-23 運(yùn)算符重載 -->靜態(tài)多態(tài)
        • 運(yùn)算符 + = == != ++ () << 重載
        • ☆☆☆指針運(yùn)算符重載(* 和 ->)(寫個智能指針類)
        • 運(yùn)算符重載的應(yīng)用:封裝字符串類
      • 5.3.24 類的自動類型轉(zhuǎn)換和強(qiáng)制類型轉(zhuǎn)換(關(guān)鍵字`explicit`)
      • 5.3.25-34 繼承
        • 繼承的概念、好處、弊端
        • 繼承的基本語法
        • protected訪問權(quán)限
        • 繼承中的對象模型(成員變量和成員函數(shù)分開存儲)
        • 繼承中的構(gòu)造和析構(gòu)順序
        • 訪問繼承中的 非靜態(tài) 同名 成員
        • 訪問繼承中的 靜態(tài) 同名 成員
        • 總結(jié):如何訪問繼承中的同名成員(靜態(tài)成員和非靜態(tài)成員)
        • 多繼承/多重繼承
        • 菱形繼承-->虛繼承、虛基類☆☆☆
        • 虛繼承的實現(xiàn)原理
      • 5.3.35-38 多態(tài)(一般是指動態(tài)多態(tài))
        • 靜態(tài)聯(lián)編和動態(tài)聯(lián)編(靜態(tài)多態(tài)和動態(tài)多態(tài))
        • 虛函數(shù)的原理(多態(tài)的底層原理)
        • 多態(tài)案例(開閉原則:對擴(kuò)展進(jìn)行開放,對修改進(jìn)行關(guān)閉)
        • 純虛函數(shù) --> 抽象類
        • 虛析構(gòu)和純虛析構(gòu)(解決 調(diào)用不到子類析構(gòu)函數(shù) 的問題)
      • C++面試寶典-->第一章-->1.3 面向?qū)ο?/li>
    • 5.6 內(nèi)存管理①(結(jié)合計算機(jī)操作系統(tǒng)筆記)
      • 1.重定位(地址轉(zhuǎn)換:邏輯地址-->物理地址)
      • 2.交換技術(shù)
      • 3.分段(將各段分別放入內(nèi)存)
      • 4.分頁(從連續(xù)到離散)
    • 5.7 內(nèi)存管理②(結(jié)合計算機(jī)操作系統(tǒng)筆記)
      • 5.段頁式管理 和 虛擬內(nèi)存(虛擬地址空間/虛擬內(nèi)存地址)
      • 6.虛擬內(nèi)存和段頁式存儲管理知識補(bǔ)充
        • 參考鏈接1
        • 參考鏈接2
        • 參考鏈接3
          • 1.如何將段和頁結(jié)合在一起
          • 2.段頁結(jié)合時進(jìn)程對內(nèi)存的使用
          • 看個例子(邏輯地址、虛擬地址、物理地址)
      • 虛擬內(nèi)存和虛擬存儲器的區(qū)別?
        • 參考鏈接4、5
      • 7.(5和6的)總結(jié)☆☆☆☆☆
        • 7.1 虛擬內(nèi)存的提出是為了解決什么問題?
        • 7.2 虛擬內(nèi)存的原理解釋
        • 7.3 分頁和頁表
        • 7.4 虛擬內(nèi)存地址(邏輯地址)-->物理地址 (快表、缺頁中斷)
        • 7.5 虛擬內(nèi)存的功能
        • 7.6 段頁式內(nèi)存管理與虛擬內(nèi)存☆☆☆
      • C++面試寶典--> 1.2 C++內(nèi)存 和 第2章 操作系統(tǒng)
    • 5.7 名稱空間、模板
      • 補(bǔ)充:內(nèi)存對齊/字節(jié)對齊
      • 名稱空間
        • 作用域解析運(yùn)算符(兩個冒號`::`)
        • 名稱空間
        • using聲明和using編譯指令
      • 模板
        • 函數(shù)模板
          • ①函數(shù)模板語法
          • ②函數(shù)模板和普通函數(shù)的區(qū)別
          • ③函數(shù)模板調(diào)用規(guī)則
          • ④模板實現(xiàn)機(jī)制及局限性
        • 類模板
          • ①類模板基礎(chǔ)語法
          • ②類模板中成員函數(shù)的創(chuàng)建時機(jī)
          • ③類模板做函數(shù)參數(shù)
          • ④類模板派生類
          • ⑤類模板成員函數(shù)類內(nèi)實現(xiàn)
          • ⑥類模板成員函數(shù)類外實現(xiàn)
          • ⑦類模板分文件編寫(類模板文件 .hpp)
          • ⑧模板類碰到友元函數(shù)
          • ⑨模板案例---數(shù)組類封裝
    • 5.8 STL(標(biāo)準(zhǔn)模板庫)
      • 總結(jié) 常用容器的**排序**的區(qū)別:
        • 0.自定義排序規(guī)則的實現(xiàn)方式
        • 1.vector容器,deque容器;&& 2.list容器
        • 3.set容器,map容器:
        • 4.匯總
      • 函數(shù)對象 & 謂詞
        • 內(nèi)建函數(shù)對象
      • STL—常用算法
    • 5.9 C++新特性

第5章 高頻考點與真題精講

5.1 指針 & 5.2 函數(shù)

5.3 面向?qū)ο?#xff08;和5.4、5.5共三次直播課)

5.3.1 - 5.3.11

見筆記①:牛客校招沖刺集訓(xùn)營—C++工程師

5.3.12-14 友元

友元分為三種:

  • 友元全局函數(shù)
  • 友元成員函數(shù)
  • 友元類

友元的目的就是讓一個 函數(shù)或者類 能夠訪問另一個類中私有成員,關(guān)鍵字是 friend。重載<<運(yùn)算符時,會用到友元。

友元全局函數(shù)

一個全局函數(shù)想訪問一個類中的私有成員變量,那么就把這個全局函數(shù)設(shè)置為這個類的友元(friend),就可以訪問了。

友元類

一個好朋友類想訪問Building類中的私有成員變量,那么就把好朋友類設(shè)置為Building類的友元(friend),就可以訪問了。

友元成員函數(shù)

好朋友類的成員函數(shù)visit()訪問另一類的對象的私有成員變量:

成員函數(shù)visit()在類外實現(xiàn):

Building類中把GoodFriend類的成員函數(shù)visit()設(shè)置為友元,就可以訪問自己的m_BedRoom屬性;
但GoodFriend類的成員函數(shù)visit1()沒被設(shè)置為友元,所以還是無法訪問到自己的m_BedRoom屬性。

友元的注意事項

友元關(guān)系:
1.不能被繼承; 2.是單向的; 3.不具有傳遞性。

5.3.15-23 運(yùn)算符重載 -->靜態(tài)多態(tài)

運(yùn)算符 + = == != ++ () << 重載

C++筆記10:運(yùn)算符重載
概念:
對已有的運(yùn)算符重新進(jìn)行定義,賦予其另一種功能,以適應(yīng)不同的數(shù)據(jù)類型。
例如:加號運(yùn)算符重載的作用就是實現(xiàn)兩個自定義數(shù)據(jù)類型(類)相加的運(yùn)算

實現(xiàn)的方式有兩種:

  • 利用成員函數(shù)重載
  • 利用全局函數(shù)重載

C++筆記10:運(yùn)算符重載中實現(xiàn)了:

  • 加號運(yùn)算符(+)重載
  • 左移運(yùn)算符(<<)重載
    改進(jìn)
  • 遞增運(yùn)算符(++)重載(前置++ 和 后置++)
  • 賦值運(yùn)算符(=)重載
    如果類的成員變量在堆區(qū),做賦值操作時就會出現(xiàn)深拷貝與淺拷貝問題。
  • 關(guān)系運(yùn)算符重載(== 和 !=)
  • 函數(shù)調(diào)用運(yùn)算符()重載—仿函數(shù)(在STL中用的比較多)
運(yùn)算符成員函數(shù)實現(xiàn)全局函數(shù)實現(xiàn)備注
加號運(yùn)算符(+)重載Students operator+(const Students& stu){}friend Students operator+(const Students& stu1, const Students& stu2){}
左移運(yùn)算符(<<)重載friend ostream& operator<<(ostream& cout, const Students& stu){}//可以實現(xiàn)連續(xù)cout輸出
遞增運(yùn)算符(++)重載前置++:Students& operator++(){}
后置++:Students operator++(int){}//這個int是為了和前置++形成重載,以通過編譯,int本身沒啥用
前置++:friend Students& operator++(Students& stu){}
后置++:friend Students operator++(Students& stu,int){}//這個int是為了和前置++形成重載,以通過編譯,int本身沒啥用
關(guān)系運(yùn)算符(== 和 !=)重載bool operator==(Students& stu) {}friend bool operator==(const Students& stu1, const Students& stu2){}
賦值運(yùn)算符(=)重載void operator=(const Person& p){}
升級版:Person& operator=(const Person& p){}//可以實現(xiàn)連續(xù)賦值
函數(shù)調(diào)用運(yùn)算符()重載也叫仿函數(shù)

(以上是筆記中復(fù)制來的內(nèi)容)

☆☆☆指針運(yùn)算符重載(* 和 ->)(寫個智能指針類)

(視頻課從01:32:10開始)
指針的操作就是解引用*和箭頭->,所以就是重載這兩個運(yùn)算符。

示例:

#include<iostream> using namespace std;class Person{ public:Person(int age){cout << "Person的構(gòu)造函數(shù)" << endl;m_Age = age;}//輸出成員信息:void showAge(){cout << "m_Age = " << this->m_Age << endl;}~Person(){cout << "Person的析構(gòu)函數(shù)" << endl;} private:int m_Age; };//智能指針類: class SmartPointer{ public://重載箭頭運(yùn)算符->Person* operator->(){return this->m_Person;}//重載解引用運(yùn)算符*Person& operator*(){return *m_Person;}SmartPointer(Person* p){cout << "SmartPointer的構(gòu)造函數(shù)" << endl;m_Person = p;}~SmartPointer(){cout << "SmartPointer析構(gòu)函數(shù)" << endl;if(this->m_Person != nullptr){delete this->m_Person;this->m_Person == nullptr;}} //private:Person* m_Person;//內(nèi)部維護(hù)的Person的指針 };int main(){Person* p = new Person(30);p->showAge();(*p).showAge();//正常應(yīng)該執(zhí)行下面這行,如果忘了就會造成內(nèi)存泄漏,所以就用下面的智能指針類//delete p;cout << "=================================" << endl;//智能指針類就是把p替換成了sp,并且在其析構(gòu)函數(shù)中將new的內(nèi)容delete掉,這樣就解決了可能造成的內(nèi)存泄漏問題SmartPointer sp(p);sp->showAge();//相當(dāng)于sp.operator->()->showAge(); 其中sp.operator->()就相當(dāng)于psp.operator->()->showAge();//編譯器會把sp.operator->()->showAge()優(yōu)化成sp->showAge();(*sp).showAge();//相當(dāng)于sp.operator*().showAge();其中sp.operator*()相當(dāng)于*psp.operator*().showAge();//編譯器會把sp.operator*().showAge()優(yōu)化成(*sp).showAge();return 0;}

編譯運(yùn)行:

Person的構(gòu)造函數(shù) m_Age = 30 m_Age = 30 ================================= SmartPointer的構(gòu)造函數(shù) m_Age = 30 m_Age = 30 m_Age = 30 m_Age = 30 SmartPointer析構(gòu)函數(shù) Person的析構(gòu)函數(shù)

運(yùn)算符重載的應(yīng)用:封裝字符串類

視頻課:點這里

5.3.24 類的自動類型轉(zhuǎn)換和強(qiáng)制類型轉(zhuǎn)換(關(guān)鍵字explicit)

被explicit修飾的構(gòu)造函數(shù)不能用于自動類型轉(zhuǎn)換(隱式類型轉(zhuǎn)換)。

示例:

#include <iostream> using namespace std;class MyString { public:// 被explicit修飾的構(gòu)造函數(shù)不能用于自動類型轉(zhuǎn)換(隱式類型轉(zhuǎn)換)explicit MyString(int len) {cout << "MyString有參構(gòu)造MyString(int len)..." << endl;}//explicit MyString(const char* str) {cout << "MyString有參構(gòu)造MyString(const char* str)..." << endl;}// 轉(zhuǎn)換函數(shù):轉(zhuǎn)換成doubleoperator double() {// 業(yè)務(wù)邏輯return 20.1;}};// 類的自動類型轉(zhuǎn)換和強(qiáng)制類型轉(zhuǎn)換 /*如果我們想讓類對象轉(zhuǎn)換成基本類型的數(shù)據(jù),我們需要在類中添加轉(zhuǎn)換函數(shù)1.轉(zhuǎn)換函數(shù)必須是類方法2.轉(zhuǎn)換函數(shù)不能指定返回類型3.轉(zhuǎn)換函數(shù)不能有參數(shù) */ int main() {// 基本內(nèi)置數(shù)據(jù)類型的自動類型轉(zhuǎn)換和強(qiáng)制類型轉(zhuǎn)換long count = 8;double time = 10;int size = 3.14;cout << count << endl; //8cout << time << endl; //10cout << size << endl; //3double num = 20.3;cout << num << endl; //20.3// 強(qiáng)制轉(zhuǎn)換成int類型的數(shù)據(jù)int num1 = (int)num; int num2 = int(num);cout << num1 << endl; //20cout << num2 << endl; //20MyString str = "hello"; //// MyString str = MyString("hello");// MyString str1 = 10; //不能通過隱式類型轉(zhuǎn)換創(chuàng)建對象了MyString str1 = MyString(10);// 類的強(qiáng)制類型轉(zhuǎn)換double d = str1;cout << d << endl; //20.1double d1 = double(str);double d2 = (double)str;cout << d1 << endl; //20.1cout << d2 << endl; //20.1return 0; }

結(jié)果:

8 10 3 20.3 20 20 MyString有參構(gòu)造MyString(const char* str)... MyString有參構(gòu)造MyString(int len)... 20.1 20.1 20.1

5.3.25-34 繼承

繼承的概念、好處、弊端

繼承是面向?qū)ο蟮囊淮筇卣鳌?/p>

繼承好處:

  • 提高代碼的復(fù)用性;
  • 提高代碼的維護(hù)性(方便維護(hù));
  • 讓類與類之間產(chǎn)生了關(guān)系,是(動態(tài))多態(tài)的前提。

繼承的弊端:

  • 類的耦合性增加了

開發(fā)的原則是:高內(nèi)聚,低耦合
內(nèi)聚:自己完成一件事的能力;
耦合:類和類之間的關(guān)系。

繼承的基本語法

繼承的語法:

class 子類:繼承方式 父類{ ... };例如: class dogs: public Animals { ... };

繼承方式分為public,protected,private,即公共繼承,保護(hù)繼承,私有繼承

注意:
如果不寫繼承方式,默認(rèn)是私有繼承

protected訪問權(quán)限

三種權(quán)限:

  • public: 公共的訪問權(quán)限,被修飾的成員在類內(nèi)和類外都能夠被訪問;
  • protected: 受保護(hù)的訪問權(quán)限,如果沒有繼承關(guān)系,就和private的特點一樣;
  • privated: 私有的訪問權(quán)限,被修飾的成員只能在類內(nèi)被訪問,在類外不能夠被訪問;

在繼承關(guān)系中,父類中的protected修飾的成員,子類中可以直接訪問,但在類外的其他地方不能訪問。

成員變量一般使用privated私有訪問控制,不要使用protected受保護(hù)的訪問控制;
成員方法如果想要讓子類訪問,但是不想讓外界訪問,就可以使用protected受保護(hù)的訪問控制。

(下面的內(nèi)容來自C++筆記3:C++核心編程中的4.6.2 繼承方式

總結(jié):
1.父類中的私有內(nèi)容(private)任何一種繼承方式都訪問不到,即無法被訪問/被繼承
2.公共繼承:父類中的各訪問權(quán)限不變
3.保護(hù)繼承:父類中的各訪問權(quán)限都變成protected保護(hù)權(quán)限
4.私有繼承:父類中的各訪問權(quán)限都變成private私有權(quán)限

無繼承關(guān)系:

父類中的三個成員變量父類類內(nèi)訪問類外通過父類對象訪問
public修飾的num1可以訪問可以訪問
protected修飾的num2可以訪問不能訪問
private修飾的num3可以訪問不能訪問

子類公共繼承父類:class Zi : public Fu{ ... };

父類中的三個成員變量子類中的三個成員變量父類類內(nèi)訪問子類類內(nèi)訪問類外通過父類對象訪問類外通過子類對象訪問
public修飾的num1public修飾的num1可以訪問可以訪問可以訪問可以訪問
protected修飾的num2protected修飾的num2可以訪問(因為有繼承關(guān)系,所以)
可以訪問
不能訪問不能訪問
private修飾的num3private修飾的num3可以訪問不能訪問不能訪問不能訪問

子類保護(hù)繼承父類:class Zi : protected Fu{ ... };

父類中的三個成員變量子類中的三個成員變量父類類內(nèi)訪問子類類內(nèi)訪問類外通過父類對象訪問類外通過子類對象訪問
public修飾的num1protected修飾的num1可以訪問可以訪問可以訪問不能訪問
protected修飾的num2protected修飾的num2可以訪問(因為有繼承關(guān)系,所以)
可以訪問
不能訪問不能訪問
private修飾的num3private修飾的num3可以訪問不能訪問不能訪問不能訪問

子類私有繼承父類:class Zi : private Fu{ ... };

父類中的三個成員變量子類中的三個成員變量父類類內(nèi)訪問子類類內(nèi)訪問類外通過父類對象訪問類外通過子類對象訪問
public修飾的num1private修飾的num1可以訪問可以訪問可以訪問不能訪問
protected修飾的num2private修飾的num2可以訪問(因為有繼承關(guān)系,所以)
可以訪問
不能訪問不能訪問
private修飾的num3private修飾的num3可以訪問不能訪問不能訪問不能訪問

示例:

#include<iostream> using namespace std;class Fu{ public:int num1; protected:int num2; private:int num3;public:void func(){num1 = 10;num2 = 20;num3 = 30;} }; class Zi : protected Fu{ //protected保護(hù)繼承 public公共繼承 private私有繼承 public:void func1(){num1 = 10;num2 = 20;num3 = 30;} };int main(){Fu fu;cout << fu.num1 << endl;cout << fu.num2 << endl;cout << fu.num3 << endl;Zi zi;cout << zi.num1 << endl;cout << zi.num2 << endl;cout << zi.num3 << endl; }

繼承中的對象模型(成員變量和成員函數(shù)分開存儲)

類中的成員變量和成員函數(shù)是分開存儲的。
在對象中,只保存了(非靜態(tài))成員變量的信息;
子類將父類中的所有成員都繼承了過來,包括私有的成員(變量和方法)。

(筆記①:牛客校招沖刺集訓(xùn)營—C++工程師中的5.3.8 靜態(tài)成員(靜態(tài)成員變量和靜態(tài)成員函數(shù))補(bǔ)充:成員變量和成員函數(shù)分開存儲 中也有講到)

普通成員變量(非靜態(tài)成員變量)屬于類的對象
普通成員函數(shù)(非靜態(tài)成員函數(shù))、靜態(tài)成員變量、靜態(tài)成員函數(shù)屬于類本身

空對象占1個字節(jié)。

示例:

#include<iostream> using namespace std;class Fu{ public:int num1;//4個字節(jié) 非靜態(tài)成員變量 protected:int* num2;//8個字節(jié) 非靜態(tài)成員變量 private:long num3;//8個字節(jié) 非靜態(tài)成員變量 public:static int num4;//靜態(tài)成員變量void func(){}//非靜態(tài)成員函數(shù)static void func1(){}//靜態(tài)成員函數(shù) }; class Zi : public Fu{ public:int num5;//4個字節(jié) 非靜態(tài)成員變量 protected: int* num6;//8個字節(jié) 非靜態(tài)成員變量 private: long num7;//8個字節(jié) 非靜態(tài)成員變量 public:static int num8; //靜態(tài)成員變量void func2(){}//非靜態(tài)成員函數(shù)static void func3(){} //靜態(tài)成員函數(shù) };int main(){Fu fu;cout << "一個具體的對象的大小:" << sizeof(fu) << endl;//24cout << "一個類的大小:" << sizeof(Fu) << endl;//48Zi zi;cout << "一個具體的對象的大小:" << sizeof(zi) << endl;cout << "一個類的大小:" << sizeof(Zi) << endl;return 0; }

結(jié)果:
(為什么是24不是20,因為字節(jié)對齊(內(nèi)存對齊)

一個具體的對象的大小:24 一個類的大小:24 一個具體的對象的大小:48 一個類的大小:48

如果把父類中的內(nèi)容都屏蔽掉,結(jié)果如下:(空對象占1個字節(jié))

class Fu{ public://int num1;//4個字節(jié) 非靜態(tài)成員變量//int* num2;//8個字節(jié) 非靜態(tài)成員變量//long num3;//8個字節(jié) 非靜態(tài)成員變量//static int num4; //靜態(tài)成員變量//void func(){}//非靜態(tài)成員函數(shù)//static void func1(){} //靜態(tài)成員函數(shù) };結(jié)果: 一個具體的對象的大小:1 一個類的大小:1 一個具體的對象的大小:24 一個類的大小:24

如果只屏蔽父類中的非靜態(tài)成員變量,結(jié)果如下:(空對象占1個字節(jié))

class Fu{ public://int num1;//4個字節(jié) 非靜態(tài)成員變量//int* num2;//8個字節(jié) 非靜態(tài)成員變量//long num3;//8個字節(jié) 非靜態(tài)成員變量static int num4; //靜態(tài)成員變量void func(){}//非靜態(tài)成員函數(shù)static void func1(){} //靜態(tài)成員函數(shù) };結(jié)果: 一個具體的對象的大小:1 一個類的大小:1 一個具體的對象的大小:24 一個類的大小:24

所以說:
普通成員變量(非靜態(tài)成員變量)屬于類的對象
普通成員函數(shù)(非靜態(tài)成員函數(shù))、靜態(tài)成員變量、靜態(tài)成員函數(shù)屬于類本身

(這里第一次用到了對象模型來輔助理解)
具體是通過Visual Studio的工具查看一個類的內(nèi)存分布:
1.打開這個工具;
2.切換到當(dāng)前原文件的目錄;
3.cl /d1 reportSingleClassLayout類名 文件名

繼承中的構(gòu)造和析構(gòu)順序

(見筆記①:牛客校招沖刺集訓(xùn)營—C++工程師中的5.3.7 類對象作為類成員(構(gòu)造和析構(gòu)的順序)
(見C++筆記3:C++核心編程中的4.6.4 繼承中的構(gòu)造和析構(gòu)順序
繼承中 先調(diào)用父類構(gòu)造函數(shù),再調(diào)用子類構(gòu)造函數(shù),析構(gòu)順序與構(gòu)造相反。

視頻課中的筆記:

訪問繼承中的 非靜態(tài) 同名 成員

出現(xiàn)同名成員,就會出現(xiàn)二義性的現(xiàn)象,一般是通過加作用域的方式來避免出現(xiàn)二義性。

(見C++筆記3:C++核心編程中的4.6.5 繼承同名成員處理方式

問題:當(dāng)子類與父類出現(xiàn)同名的成員,如何通過子類對象,訪問到子類或父類中同名的數(shù)據(jù)呢
答:
子類對象可以直接訪問到子類中同名成員;
子類對象加作用域可以訪問到父類同名成員;
當(dāng)子類與父類擁有同名的成員函數(shù),子類會隱藏父類中所有同名成員函數(shù),加作用域可以訪問到父類中同名函數(shù)。

//子類與父類出現(xiàn)相同的成員(變量/函數(shù))時,子類對象如何訪問到同名的數(shù)據(jù)?Son1 son;//同名成員變量cout << "子類中的a = " << son.a << endl;//直接訪問cout << "父類中的a = " << son.Base1::a << endl;//加父類的作用域//同名成員函數(shù)son.func();//直接訪問son.Base1::func();//加父類的作用域son.Base1::func(10);//加父類的作用域

視頻課里的例子:(視頻課從19:28開始)

訪問繼承中的 靜態(tài) 同名 成員

當(dāng)子類與父類出現(xiàn)同名的成員,如何通過子類對象,訪問到子類或父類中同名的數(shù)據(jù)呢
對于同名的靜態(tài)成員,也是加上作用域

但是,由于靜態(tài)成員(變量和函數(shù))被所有對象共享,所以一般不通過子類對象來訪問這些同名成員,而是直接加上父類類名的作用域來訪問

總結(jié):如何訪問繼承中的同名成員(靜態(tài)成員和非靜態(tài)成員)

訪問繼承中的同名成員有一下幾種方式:

  • 通過父類對象訪問父類中的非靜態(tài)成員和靜態(tài)成員;
  • 通過子類對象訪問子類中的非靜態(tài)成員和靜態(tài)成員;
  • 通過子類對象訪問父類中的非靜態(tài)成員和靜態(tài)成員;
  • 不通過對象來訪問,直接加個作用域行不行?
    答:靜態(tài)成員可以直接加個作用域來訪問,非靜態(tài)成員必須通過特定的對象來訪問
    方式4的理由:
    由于靜態(tài)成員(變量和函數(shù))被所有對象共享,所以一般不通過子類對象來訪問這些同名成員,而是直接加上父類類名的作用域來訪問
    非靜態(tài)成員就必須要通過具體的對象來訪問了。
  • 代碼:

    #include<iostream> using namespace std;class Fu{ public:int num1;//非靜態(tài)成員變量static int num4;//靜態(tài)成員變量void func(){}//非靜態(tài)成員函數(shù)static void func1(){}//靜態(tài)成員函數(shù) }; class Zi : public Fu{ public:int num1;//非靜態(tài)成員變量static int num4;//靜態(tài)成員變量void func(){}//非靜態(tài)成員函數(shù)static void func1(){}//靜態(tài)成員函數(shù) };int main(){//通過父類對象訪問父類中的非靜態(tài)成員和靜態(tài)成員:Fu fu;cout << fu.num1 << endl;cout << fu.num4 << endl;fu.func();fu.func1();//通過子類對象訪問子類中的非靜態(tài)成員和靜態(tài)成員:Zi zi;cout << zi.num1 << zi.num4 << endl;zi.func();zi.func1();//通過子類對象訪問父類中的非靜態(tài)成員和靜態(tài)成員:cout << zi.Fu::num1 << endl;cout << zi.Fu::num4 << endl;zi.Fu::func();zi.Fu::func1();//不通過對象來訪問,直接加個作用域行不行? 靜態(tài)成員可以直接加個作用域來訪問,非靜態(tài)成員必須通過特定的對象來訪問cout << Fu::num1 << endl;//非靜態(tài)成員引用必須與特定對象相對cout << Fu::num4 << endl;Fu::func();//非靜態(tài)成員引用必須與特定對象相對Fu::func1();cout << Zi::num1 << endl;//非靜態(tài)成員引用必須與特定對象相對cout << Zi::num4 << endl;Zi::func();//非靜態(tài)成員引用必須與特定對象相對Zi::func1();return 0; }

    多繼承/多重繼承

    多繼承
    概念:一個類繼承多個類
    語法:class 子類: 繼承方式 父類1, 繼承方式 父類2...
    注意:繼承方式不要省略,否則就默認(rèn)是私有繼承

    C++實際開發(fā)中不建議用多繼承,從多個類繼承可能導(dǎo)致成員方法或成員變量同名產(chǎn)生較多的歧義


    看個示例:
    基類Base1:

    基類Base2:

    子類多繼承Base1和Base2:

    main函數(shù):

    輸出m_A的時候兩個父類都有,就沒辦法直接訪問了,要加上作用域;
    輸出m_B的時候就可以直接輸出,或者也可以加上作用域。

    菱形繼承–>虛繼承、虛基類☆☆☆

    什么是菱形繼承?
    菱形繼承會帶來什么問題?
    怎么解決?


    示例:
    Person類:

    Singer類和Waiter類都繼承自Person類,然后SingerWaiter類多繼承Singer類和Waiter類:

    main函數(shù):

    解決方法:給Singer類和Waiter類的繼承方式后面加上virtual關(guān)鍵字,就成了虛繼承,此時Person類被稱為虛基類

    結(jié)果:

    虛繼承的實現(xiàn)原理

    vbptr:virtual base pointer(虛基類指針)
    原理:
    只有一個唯一的成員,通過保存虛基類指針,這個指針指向一張表(虛基類表),這個表中保存了當(dāng)前獲取到唯一的數(shù)據(jù)的偏移量offset。


    (第二次用了對象模型來輔助理解)

    5.3.35-38 多態(tài)(一般是指動態(tài)多態(tài))

    靜態(tài)聯(lián)編和動態(tài)聯(lián)編(靜態(tài)多態(tài)和動態(tài)多態(tài))

    以下的內(nèi)容來自c++筆記3的4.7.1 多態(tài)的基本概念

    ①多態(tài)分為兩類:

    • 靜態(tài)多態(tài): 函數(shù)重載運(yùn)算符重載 屬于靜態(tài)多態(tài),復(fù)用函數(shù)名;
    • 動態(tài)多態(tài): 派生類和虛函數(shù)實現(xiàn)運(yùn)行時多態(tài),一般我們說多態(tài)指的都是動態(tài)多態(tài)

    ②靜態(tài)多態(tài)和動態(tài)多態(tài)區(qū)別:

    • 靜態(tài)多態(tài)的函數(shù)地址早綁定 - 編譯階段確定函數(shù)地址
    • 動態(tài)多態(tài)的函數(shù)地址晚綁定 - 運(yùn)行階段確定函數(shù)地址

    ③動態(tài)多態(tài)滿足條件:

    • 繼承關(guān)系
    • 子類重寫父類中的虛函數(shù)(virtual + 函數(shù)名)

    ④動態(tài)多態(tài)使用條件

    • 父類指針或引用指向子類對象

    ⑤重寫和重載的區(qū)別
    重寫:

    • 函數(shù)返回值類型、函數(shù)名、參數(shù)列表完全一致稱為重寫
    • 重寫也叫覆蓋、覆寫。

    重載:
    ①同一個作用域下;
    ②函數(shù)名稱相同;
    ③函數(shù)參數(shù)的 類型不同、個數(shù)不同 、順序不同;
    ④和返回值類型、函數(shù)形參名無關(guān);

    示例:
    關(guān)鍵在于父類中成員函數(shù)前的virtual關(guān)鍵字

    #include<iostream> using namespace std;//動物類 class Animals { public://函數(shù)前面加上virtual關(guān)鍵字,speak函數(shù)就是虛函數(shù)virtual void speak() {cout << "動物在說話" << endl;} }; //貓類 class Cats: public Animals{ public:void speak() {cout << "小貓在說話" << endl;} }; class Dogs:public Animals { public:void speak() {cout << "小狗在說話" << endl;} }; //全局函數(shù):父類引用指向子類對象 // void doSpeak(Animals& ani) { // ani.speak(); // } //全局函數(shù):父類指針指向子類對象 void doSpeak(Animals* ani) {//ani->speak();(*ani).speak(); }int main(){cout << "sizeof Animals類 = " << sizeof(Animals) << endl;//有virtual關(guān)鍵字的Animals類占4字節(jié)//沒有virtual關(guān)鍵字的Animals類占1字節(jié),空類,并且非靜態(tài)成員函數(shù)不屬于類的內(nèi)存(見4.3.1 成員變量和成員函數(shù)分開存儲)//加了virtual關(guān)鍵字后Animals類占4字節(jié),不再是空類,而是多了一個指針,叫vfptr(虛函數(shù)表指針),表內(nèi)記錄虛函數(shù)的地址Cats cat;//當(dāng)子類 重寫 父類的 虛函數(shù) ,子類中的虛函數(shù)表內(nèi)部會替換成子類的虛函數(shù)地址//子類重寫父類的虛函數(shù),即子類也有個vfptr(虛函數(shù)表指針),表內(nèi)記錄虛函數(shù)的地址cout << "sizeof Cats類 = " << sizeof(Cats) << endl;//4 //當(dāng)父類的 指針或者引用 指向子類對象的時候,就發(fā)生了多態(tài)//doSpeak(cat);//小貓在說話 父類引用指向子類對象doSpeak(&cat);//小貓在說話 父類指針指向子類對象Dogs dog;//doSpeak(dog);//小狗在說話 父類引用指向子類對象doSpeak(&dog);//小狗在說話 父類指針指向子類對象system("pause");return 0; }

    父類的speak函數(shù)前不加關(guān)鍵字virtual,結(jié)果如下:(靜態(tài)多態(tài))

    sizeof Animals類 = 1 //空類(非靜態(tài)成員函數(shù)不屬于對象的內(nèi)存) sizeof Cats類 = 1 動物在說話 動物在說話

    結(jié)果是調(diào)用了父類的speak函數(shù),這屬于地址早綁定,也叫靜態(tài)聯(lián)編,因為在編譯期間就知道父類speak函數(shù)的地址了。
    而我們想要的結(jié)果是如果傳進(jìn)來是貓類的對象,就執(zhí)行貓的speak函數(shù);傳進(jìn)來是狗類的對象,就執(zhí)行狗的speak函數(shù);這屬于地址晚綁定,也叫動態(tài)聯(lián)編。具體做法就是

    在父類的speak函數(shù)前加關(guān)鍵字virtual,結(jié)果如下:(動態(tài)多態(tài))

    sizeof Animals類 = 8 //虛函數(shù)表指針vfptr的大小 sizeof Cats類 = 8 小貓在說話 小狗在說話

    虛函數(shù)的原理(多態(tài)的底層原理)

    main函數(shù)中的部分代碼:

    cout << "sizeof Animals類 = " << sizeof(Animals) << endl;//有virtual關(guān)鍵字的Animals類占4字節(jié)//沒有virtual關(guān)鍵字的Animals類占1字節(jié),空類,并且非靜態(tài)成員函數(shù)不屬于類的內(nèi)存(見4.3.1 成員變量和成員函數(shù)分開存儲)//加了virtual關(guān)鍵字后Animals類占4字節(jié),不再是空類,而是多了一個指針,叫vfptr(虛函數(shù)表指針),表內(nèi)記錄虛函數(shù)的地址Cats cat;//當(dāng)子類 重寫 父類的 虛函數(shù) ,子類中的虛函數(shù)表內(nèi)部會替換成子類的虛函數(shù)地址//子類重寫父類的虛函數(shù),即子類也有個vfptr(虛函數(shù)表指針),表內(nèi)記錄虛函數(shù)的地址cout << "sizeof Cats類 = " << sizeof(Cats) << endl;//4

    解釋:
    ①沒有virtual關(guān)鍵字的Animals類占1字節(jié),空類,因為非靜態(tài)成員函數(shù)不屬于對象的內(nèi)存(見繼承中的對象模型(成員變量和成員函數(shù)分開存儲));
    ②加了virtual關(guān)鍵字后Animals類占4字節(jié)(32位操作系統(tǒng))/8個字節(jié)(64位操作系統(tǒng)),不再是空類,而是多了一個指針,叫vfptr(虛函數(shù)表指針),表內(nèi)記錄虛函數(shù)的地址;虛函數(shù)表指針指向虛函數(shù)表vftable;
    ③由于子類重寫父類的虛函數(shù),因此子類也有個vfptr(虛函數(shù)表指針),表內(nèi)記錄虛函數(shù)的地址;
    ④在子類重寫父類的虛函數(shù)時,子類中的虛函數(shù)表內(nèi)部會替換成子類的虛函數(shù)地址

    多態(tài)的底層原理:

    • 首先在父類中的虛函數(shù)(virtual void Speak(){}),使得父類占4個字節(jié)(32位操作系統(tǒng))/8個字節(jié)(64位操作系統(tǒng)),這4個字節(jié)是個vfptr(虛函數(shù)表指針),它指向虛函數(shù)表(vftable),表內(nèi)記錄虛函數(shù)的地址(&Animal::speak);
    • 然后是子類重寫父類中的虛函數(shù),因此子類也占4個字節(jié),這4個字節(jié)也是個vfptr(虛函數(shù)表指針),它也指向虛函數(shù)表(vftable),表內(nèi)也記錄虛函數(shù)的地址;在子類重寫父類中的虛函數(shù)后,子類中的虛函數(shù)表內(nèi)部會替換成子類的虛函數(shù)地址(&Cats::speak)。
    • 最后是當(dāng)父類的指針或者引用指向子類對象時,就發(fā)生了多態(tài)

    派生類虛表:
    1.先將基類的虛表中的內(nèi)容拷貝一份;
    2.如果派生類對基類中的虛函數(shù)進(jìn)行重寫,使用派生類的虛函數(shù)替換相同偏移量位置的基類虛函數(shù);
    3.如果派生類中新增加自己的虛函數(shù),按照其在派生類中的聲明次序,放在上述虛函數(shù)之后。
    原文鏈接:https://blog.csdn.net/qq_39412582/article/details/81628254

    (視頻課中從01:22:00開始)

    靜態(tài)多態(tài):

    動態(tài)多態(tài):

    (第三次用了對象模型來輔助理解)
    ①父類中的speak函數(shù)前沒加virtual,查看Animal類的內(nèi)存分布:

    ②父類中的speak函數(shù)前加了virtual:
    查看Animal類的內(nèi)存分布:

    然后再查看Cats類的內(nèi)存分布:


    再查看Dog類的內(nèi)存分布:

    如果Cats類不重寫父類的speak函數(shù),它的內(nèi)存分布就變成了:

    多態(tài)案例(開閉原則:對擴(kuò)展進(jìn)行開放,對修改進(jìn)行關(guān)閉)

    視頻課中的案例和c++筆記3的4.7.2 多態(tài)案例1—計算器類中的案例一樣。

    如果想擴(kuò)展新的功能,需要修改源碼。
    在真實開發(fā)中提倡開閉原則
    開閉原則:對擴(kuò)展進(jìn)行開放,對修改進(jìn)行關(guān)閉。

    多態(tài)技術(shù):
    ①繼承②父類有虛函數(shù)③子類重寫父類的虛函數(shù)④父類的指針或引用指向子類對象

    重寫:函數(shù)返回值類型 函數(shù)名 參數(shù)列表 完全一致稱為重寫

    純虛函數(shù) --> 抽象類

    在多態(tài)中,通常父類中虛函數(shù)的現(xiàn)實無意義的,主要都是調(diào)用子類重寫的內(nèi)容,所以可以將虛函數(shù)改為“純虛函數(shù)”。

    純虛函數(shù)語法:virtual 返回值類型 函數(shù)名 (形參列表) = 0;

    父類中有了純虛函數(shù),這個類就被稱為抽象類

    抽象類特點:

    • 無法實例化對象;
    • ②子類必須重寫抽象類中的純虛函數(shù)
      如果子類沒有重寫父類的純虛函數(shù),那么子類也是一個抽象類。

    示例:

    //父類:抽象計算器 class abstractCalculator { public:int a = 0, b = 0;//virtual int getResult() {//②虛函數(shù)// return 0;//}virtual int getResult() = 0;//純虛函數(shù) };

    虛析構(gòu)和純虛析構(gòu)(解決 調(diào)用不到子類析構(gòu)函數(shù) 的問題)

    多態(tài)使用時,如果子類中有屬性開辟到堆區(qū),那么父類指針在釋放時無法調(diào)用到子類的析構(gòu)代碼

    解決方式:將父類中的析構(gòu)函數(shù)改為虛析構(gòu)或者純虛析構(gòu)(☆☆☆推薦用虛析構(gòu))。

    總結(jié):

  • 虛析構(gòu)或純虛析構(gòu)就是用來解決通過父類指針釋放子類對象;
  • 如果子類中沒有堆區(qū)數(shù)據(jù),可以不寫為虛析構(gòu)或純虛析構(gòu);
  • 擁有純虛析構(gòu)函數(shù)的類也屬于抽象類,
    純虛析構(gòu)的目的只有一個:讓類成為抽象類
  • 純虛析構(gòu)需要有聲明,也要有實現(xiàn)
    (在類內(nèi)聲明)virtual ~Animal() = 0;
    (在類外實現(xiàn))Animal::~Animal(){}

    注意:
    純虛函數(shù)不用實現(xiàn),但子類必須重寫純虛函數(shù),否則子類也是抽象類;
    純虛析構(gòu)必須要實現(xiàn),而且只能在類外實現(xiàn)

    class Animal{ public:Animal(){cout << "Animal類的構(gòu)造" << endl;}virtual void speak() = 0;//純虛函數(shù) 子類必須重寫純虛函數(shù)//純虛析構(gòu):要聲明,也要實現(xiàn)virtual ~Animal() = 0;//在類內(nèi)聲明 };//純虛析構(gòu):在類外實現(xiàn) Animal::~Animal(){cout << "Animal類的析構(gòu)" << endl; }

    示例:

    #include<iostream> #include<string.h> using namespace std;class Animal{ public:Animal(){cout << "Animal類的構(gòu)造" << endl;}virtual void speak() = 0;//純虛函數(shù)//常規(guī)的析構(gòu)函數(shù): 會帶來 無法調(diào)用子類析構(gòu)函數(shù) 的問題~Animal(){cout << "Animal類的析構(gòu)" << endl; }//虛析構(gòu): // virtual ~Animal(){ // cout << "Animal類的析構(gòu)" << endl; // }//純虛析構(gòu):要聲明,也要實現(xiàn) // virtual ~Animal() = 0;//在類內(nèi)聲明 };//純虛析構(gòu):在類外實現(xiàn) // Animal::~Animal(){ // cout << "Animal類的析構(gòu)" << endl; // }class Cats : public Animal{ public: Cats(const char* name){cout << "Cats類的有參構(gòu)造" << endl;this->m_Name = new char(strlen(name) + 1);strcpy(this->m_Name, name);}void speak(){cout << this->m_Name << "小貓在說話..." << endl;}~Cats(){cout << "Cats類的析構(gòu)" << endl; if(this->m_Name != nullptr){delete[] this->m_Name;this->m_Name = nullptr;}} private:char* m_Name; };int main(){Animal* ani = new Cats("Tom");ani->speak();delete ani;return 0; }

    結(jié)果:

    Animal類的構(gòu)造 Cats類的有參構(gòu)造 Tom小貓在說話... Animal類的析構(gòu)

    沒有調(diào)用子類的析構(gòu)函數(shù),解決辦法是在父類的析構(gòu)函數(shù)前面加個virtual,將其變?yōu)樘撐鰳?gòu)

    class Animal{ public:Animal(){cout << "Animal類的構(gòu)造" << endl;}virtual void speak() = 0;//純虛函數(shù)//虛析構(gòu):virtual ~Animal(){cout << "Animal類的析構(gòu)" << endl; } };

    然后再編譯運(yùn)行:

    Animal類的構(gòu)造 Cats類的有參構(gòu)造 Tom小貓在說話... Cats類的析構(gòu) Animal類的析構(gòu)

    或者寫成純虛析構(gòu)的形式:

    class Animal{ public:Animal(){cout << "Animal類的構(gòu)造" << endl;}virtual void speak() = 0;//純虛函數(shù)//純虛析構(gòu):要聲明,也要實現(xiàn)virtual ~Animal() = 0;//在類內(nèi)聲明 };//純虛析構(gòu):在類外實現(xiàn) Animal::~Animal(){cout << "Animal類的析構(gòu)" << endl; }

    那么當(dāng)子類中有屬性開辟到堆區(qū),為什么會出現(xiàn)無法調(diào)用到子類的析構(gòu)代碼的問題呢?
    可以聯(lián)想到上面的靜態(tài)聯(lián)編和動態(tài)聯(lián)編中說的:

    父類的speak函數(shù)前如果不加關(guān)鍵字virtual,最終的結(jié)果就是調(diào)用了父類的speak函數(shù)(即輸出"動物在說話"),這里也一樣,父類的析構(gòu)函數(shù)前沒有關(guān)鍵字virtual,最終的結(jié)果就是調(diào)用了父類的析構(gòu)函數(shù),所以就沒有調(diào)用子類的析構(gòu)函數(shù)

    那么怎么解決呢?
    方法和上圖中的內(nèi)容類似,就是在父類的析構(gòu)函數(shù)前加上關(guān)鍵字virtual,這樣的話就可以調(diào)用到子類的析構(gòu)函數(shù)了。具體解釋見下圖:
    (視頻課中從01:56:40開始)
    (第四次用了對象模型)
    Animal類的析構(gòu)函數(shù)前加了關(guān)鍵字virtual,查看Cat類的內(nèi)存分布:

    Cat類的析構(gòu)函數(shù)就會加入到虛函數(shù)表中,這樣就可以調(diào)用到了:

    析構(gòu)函數(shù):destructor
    構(gòu)造函數(shù):constructor

    C++面試寶典–>第一章–>1.3 面向?qū)ο?/h3>

    E:\找工作\C++八股文\C面試寶典完整版最最最新.pdf
    視頻課中從02:00:39開始

    5.6 內(nèi)存管理①(結(jié)合計算機(jī)操作系統(tǒng)筆記)

    (這部分內(nèi)容可以看計算機(jī)操作系統(tǒng)筆記 的0620開始一直到第三章結(jié)束)

    程序就是文件;
    運(yùn)行起來的程序被稱為進(jìn)程

    01:00:35開始回顧第一節(jié)課的內(nèi)容:

    1.重定位(地址轉(zhuǎn)換:邏輯地址–>物理地址)

    重定位:修改該程序中的地址(相對地址)

    什么時候完成重定位?編譯時?還是載入時?
    都不對:
    編譯時重定位的程序只能放在內(nèi)存固定位置;
    載入時重定位的程序一旦載入內(nèi)存就不能動了;
    答案是在運(yùn)行時(執(zhí)行每條指令時才)完成重定位,找到真正的物理地址。
    (分別對應(yīng)操作系統(tǒng)筆記中的絕對裝入(載入)、靜態(tài)重定位、動態(tài)重定位

    也可以叫地址翻譯:基地址(起始地址) + 邏輯地址(偏移量)–> 物理地址

    2.交換技術(shù)

    (對應(yīng)操作系統(tǒng)筆記中的內(nèi)存空間的擴(kuò)充:交換技術(shù),是指內(nèi)存和外存之間交換進(jìn)程,以緩解內(nèi)存吃緊的問題)

    交換技術(shù):
    (要運(yùn)行進(jìn)程2,但內(nèi)存不夠了)

    (進(jìn)程1處于睡眠狀態(tài),就把進(jìn)程1換出到磁盤中,把進(jìn)程2換入到內(nèi)存中)

    (然后進(jìn)程3頁進(jìn)入睡眠狀態(tài),把進(jìn)程3換出磁盤,把剛剛換出的進(jìn)程1再換入到內(nèi)存中)

    3.分段(將各段分別放入內(nèi)存)

    程序是分段管理的;
    程序運(yùn)行時是分段加載的,而不是將整個程序一次性全部載入內(nèi)存

    (對應(yīng)操作系統(tǒng)筆記中的分段存儲管理的邏輯地址結(jié)構(gòu):<段號/段名,段內(nèi)地址/段內(nèi)偏移量>

    分段:將各段程序分別放入內(nèi)存。
    (對應(yīng)操作系統(tǒng)比較中的段表中記錄的內(nèi)容:段號、段的起始地址、段的長度

    具體怎么分段?
    有固定分區(qū)和可變分區(qū)(動態(tài)分區(qū)):
    (對應(yīng)操作系統(tǒng)筆記中的連續(xù)分配中的固定分區(qū)分配和可變分區(qū)分配(動態(tài)分區(qū)分配)

    可變分區(qū)算法:首先適配、最佳適配、最差適配。
    (對應(yīng)操作系統(tǒng)筆記中的動態(tài)分區(qū)分配算法:首次適應(yīng)、最佳時應(yīng)、最壞適應(yīng)、臨近時應(yīng)

    內(nèi)存緊縮 & 內(nèi)存碎片
    (對應(yīng)操作系統(tǒng)筆記中的內(nèi)存緊縮技術(shù),用來解決外部碎片的問題。

    4.分頁(從連續(xù)到離散)

    分頁:頁表的內(nèi)容(頁號、內(nèi)存塊號/頁框號) 邏輯地址結(jié)構(gòu):<頁號,頁內(nèi)地址偏移量>

    為了提高內(nèi)存空間利用率,頁的大小應(yīng)該足夠小,但頁表就大了,所以就有了二級頁表,即頁表的頁表,稱為頁目錄表

    二級頁表的邏輯地址結(jié)構(gòu):頁目錄號、頁號、頁內(nèi)偏移量

    二級頁表的出現(xiàn)是因為沒必要把所有的頁表項都放在內(nèi)存中,很占內(nèi)存,所以就弄成二級頁表,把用的頁表放到內(nèi)存中,沒用到的先放外存中,這樣就提高了內(nèi)存的利用率。

    通過二級頁表訪問一個邏輯地址需要三次訪存
    第一次訪問內(nèi)存中的頁目錄表,第二次訪問內(nèi)存中的頁表,第三次訪問目標(biāo)內(nèi)存單元。

    因為需要三次訪存,所以引入快表,可以讓訪存次數(shù)降低一次。快表的查詢速度非常快。

    5.7 內(nèi)存管理②(結(jié)合計算機(jī)操作系統(tǒng)筆記)

    5.段頁式管理 和 虛擬內(nèi)存(虛擬地址空間/虛擬內(nèi)存地址)

    (視頻課中從32:52開始)

    這里面的段就是虛擬內(nèi)存,那虛擬內(nèi)存中具體是怎么分段的,都分為哪些分區(qū)?就是下面的虛擬地址空間,或者叫虛擬內(nèi)存地址。

    C++筆記3:C++核心編程 --> 1、內(nèi)存分區(qū)模型
    C++ Primer Plus(嵌入式公開課)—第4章 復(fù)合類型–>4.8.5 自動存儲、靜態(tài)存儲和動態(tài)存儲

    C++的內(nèi)存分區(qū)/內(nèi)存模型:(下圖的虛擬內(nèi)存地址中的用戶區(qū)
    全局區(qū)(靜態(tài)存儲)、棧區(qū)(自動存儲)、堆區(qū)/自由存儲區(qū)(動態(tài)存儲)

    每個進(jìn)程都有一個虛擬地址空間;
    同一個進(jìn)程下的不同線程共用一個虛擬地址空間。


    共享庫:靜態(tài)庫、共享內(nèi)存;
    棧:局部變量、返回值(自動釋放)后進(jìn)先出
    堆區(qū):malloc或者new的數(shù)據(jù),要手動釋放
    全局區(qū):.bss(未初始化或初始化為0的全局變量)、.data(已初始化全局變量)、.text(代碼段、二進(jìn)制機(jī)器指令)

    內(nèi)存分區(qū)權(quán)限
    共享庫靜態(tài)庫、共享內(nèi)存
    棧區(qū)局部變量、返回值(自動釋放)后進(jìn)先出可讀可寫
    堆區(qū)malloc或者new的數(shù)據(jù)要手動釋放free或者delete可讀可寫
    全局區(qū):
    .bss未初始化或初始化為0的全局變量
    .data已初始化的全局變量
    .text代碼段只讀
    常量(全局常量+字符串常量)只讀
    靜態(tài)變量(static)

    缺頁中斷

    6.虛擬內(nèi)存和段頁式存儲管理知識補(bǔ)充

    參考鏈接1

    (以下內(nèi)容來自電子發(fā)燒友的文章)
    什么是虛擬內(nèi)存?
    在現(xiàn)代操作系統(tǒng)中,多任務(wù)已是標(biāo)配。多任務(wù)并行,大大提升了 CPU 利用率,但卻引出了多個進(jìn)程對內(nèi)存操作的沖突問題,虛擬內(nèi)存概念的提出就是為了解決這個問題

    操作系統(tǒng)有一塊物理內(nèi)存(中間的部分),有兩個進(jìn)程(實際會更多)P1 和 P2,操作系統(tǒng)偷偷地分別告訴 P1 和 P2,我的整個內(nèi)存都是你的,隨便用,管夠。可事實上呢,操作系統(tǒng)只是給它們畫了個大餅,這些內(nèi)存說是都給了 P1 和 P2,實際上只給了它們一個序號而已。只有當(dāng) P1 和 P2 真正開始使用這些內(nèi)存時,系統(tǒng)才開始使用輾轉(zhuǎn)挪移,拼湊出各個塊給進(jìn)程用,P2 以為自己在用 A 內(nèi)存,實際上已經(jīng)被系統(tǒng)悄悄重定向到真正的 B 去了,甚至,當(dāng) P1 和 P2 共用了 C 內(nèi)存,他們也不知道。(確保了進(jìn)程之間互不影響,也可以讓兩個進(jìn)程共享同一個內(nèi)存的內(nèi)容

    操作系統(tǒng)的這種欺騙進(jìn)程的手段,就是虛擬內(nèi)存
    對 P1 和 P2 等進(jìn)程來說,它們都以為自己占用了整個內(nèi)存,而自己使用的物理內(nèi)存的哪段地址,它們并不知道也無需關(guān)心

    分頁和頁表?
    虛擬內(nèi)存是操作系統(tǒng)里的概念,對操作系統(tǒng)來說,虛擬內(nèi)存就是一張張的對照表,P1 獲取 A 內(nèi)存里的數(shù)據(jù)時應(yīng)該去物理內(nèi)存的 A 地址找,而找 B 內(nèi)存里的數(shù)據(jù)應(yīng)該去物理內(nèi)存的 C 地址。

    我們知道系統(tǒng)里的基本單位都是 Byte 字節(jié),如果將每一個虛擬內(nèi)存的 Byte 都對應(yīng)到物理內(nèi)存的地址,每個條目最少需要 8字節(jié)(32位虛擬地址->32位物理地址),在 4G 內(nèi)存的情況下,就需要 32GB 的空間來存放對照表,那么這張表就大得真正的物理地址也放不下了,于是操作系統(tǒng)引入了(Page)的概念。

    在系統(tǒng)啟動時,操作系統(tǒng)將整個物理內(nèi)存以 4K 為單位,劃分為各個。之后進(jìn)行內(nèi)存分配時,都以為單位,那么虛擬內(nèi)存頁對應(yīng)物理內(nèi)存頁映射表就大大減小了。4G 內(nèi)存,只需要 8M 的映射表即可,一些進(jìn)程沒有使用到的虛擬內(nèi)存,也并不需要保存映射關(guān)系,而且Linux 還為大內(nèi)存設(shè)計了多級頁表,可以進(jìn)一頁減少了內(nèi)存消耗。
    操作系統(tǒng)虛擬內(nèi)存到物理內(nèi)存的映射表,就被稱為頁表。

    內(nèi)存尋址和內(nèi)存分配?
    我們知道通過虛擬內(nèi)存機(jī)制每個進(jìn)程都以為自己占用了全部內(nèi)存,進(jìn)程訪問內(nèi)存時,操作系統(tǒng)都會把進(jìn)程提供的虛擬內(nèi)存地址轉(zhuǎn)換為物理地址,再去對應(yīng)的物理地址上獲取數(shù)據(jù)。CPU 中有一種硬件,內(nèi)存管理單元 MMU(Memory Management Unit)專門用來將翻譯虛擬內(nèi)存地址。CPU 還為頁表尋址設(shè)置了緩存策略(快表),由于程序的局部性原理,其緩存命中率能達(dá)到 98%。(快表其實就是一種特殊的高速緩沖寄存器Cache,高速緩存

    以上情況是頁表內(nèi)存在虛擬地址到物理地址的映射,而如果進(jìn)程訪問的物理地址還沒有被分配,系統(tǒng)則會產(chǎn)生一個缺頁中斷,在中斷處理時,系統(tǒng)切到內(nèi)核態(tài)為進(jìn)程虛擬地址分配物理地址。

    虛擬內(nèi)存的功能:

    • 虛擬內(nèi)存不僅通過內(nèi)存地址轉(zhuǎn)換解決了多個進(jìn)程訪問內(nèi)存沖突的問題,還帶來更多的益處。
    • 它有助于進(jìn)程內(nèi)存管理,主要體現(xiàn)在:
      內(nèi)存完整性:由于虛擬內(nèi)存對進(jìn)程的”欺騙”,每個進(jìn)程都認(rèn)為自己獲取的內(nèi)存是一塊連續(xù)的地址。我們在編寫應(yīng)用程序時,就不用考慮大塊地址的分配,總是認(rèn)為系統(tǒng)有足夠的大塊內(nèi)存即可。
      安全:由于進(jìn)程訪問內(nèi)存時,都要通過頁表來尋址,操作系統(tǒng)在頁表的各個項目上添加各種訪問權(quán)限標(biāo)識位,就可以實現(xiàn)內(nèi)存的權(quán)限控制。
    • 通過虛擬內(nèi)存更容易實現(xiàn)內(nèi)存和數(shù)據(jù)的共享
      在進(jìn)程加載系統(tǒng)庫時,總是先分配一塊內(nèi)存,將磁盤中的庫文件加載到這塊內(nèi)存中,在直接使用物理內(nèi)存時,由于物理內(nèi)存地址唯一,即使系統(tǒng)發(fā)現(xiàn)同一個庫在系統(tǒng)內(nèi)加載了兩次,但每個進(jìn)程指定的加載內(nèi)存不一樣,系統(tǒng)也無能為力。
      而在使用虛擬內(nèi)存時,系統(tǒng)只需要將進(jìn)程的虛擬內(nèi)存地址指向庫文件所在的物理內(nèi)存地址即可。如上文圖中所示,進(jìn)程 P1 和 P2 的 B 地址都指向了物理地址 C。
      而通過使用虛擬內(nèi)存使用共享內(nèi)存也很簡單,系統(tǒng)只需要將各個進(jìn)程的虛擬內(nèi)存地址指向系統(tǒng)分配的共享內(nèi)存地址即可。
    • 虛擬內(nèi)存可以幫進(jìn)程”擴(kuò)充”內(nèi)存
      我們前文提到了虛擬內(nèi)存通過缺頁中斷為進(jìn)程分配物理內(nèi)存,內(nèi)存總是有限的,如果所有的物理內(nèi)存都被占用了怎么辦呢?
      Linux 提出 SWAP 的概念,Linux 中可以使用 SWAP 分區(qū),在分配物理內(nèi)存,但可用內(nèi)存不足時,將暫時不用的內(nèi)存數(shù)據(jù)先放到磁盤上,讓有需要的進(jìn)程先使用,等進(jìn)程再需要使用這些數(shù)據(jù)時,再將這些數(shù)據(jù)加載到內(nèi)存中,通過這種”交換”技術(shù),Linux 可以讓進(jìn)程使用更多的內(nèi)存。

    參考鏈接2

    (下面內(nèi)容來自電子發(fā)燒友的文章)
    各個進(jìn)程的虛擬內(nèi)存地址相互獨(dú)立。因此,兩個進(jìn)程空間可以有相同的虛擬內(nèi)存地址,如0x10001000。虛擬內(nèi)存地址和物理內(nèi)存地址又有一定的對應(yīng)關(guān)系,如圖1所示。對進(jìn)程某個虛擬內(nèi)存地址的操作,會被CPU翻譯成對某個具體內(nèi)存地址的操作。

    應(yīng)用程序?qū)?strong>物理內(nèi)存地址一無所知。它只可能通過虛擬內(nèi)存地址來進(jìn)行數(shù)據(jù)讀寫。程序中表達(dá)的內(nèi)存地址,也都是虛擬內(nèi)存地址。進(jìn)程對虛擬內(nèi)存地址的操作,會被操作系統(tǒng)翻譯成對某個物理內(nèi)存地址的操作。由于翻譯的過程由操作系統(tǒng)全權(quán)負(fù)責(zé),所以應(yīng)用程序可以在全過程中對物理內(nèi)存地址一無所知。

    本質(zhì)上說,虛擬內(nèi)存地址剝奪了應(yīng)用程序自由訪問物理內(nèi)存地址的權(quán)利。進(jìn)程對物理內(nèi)存的訪問,必須經(jīng)過操作系統(tǒng)的審查。因此,掌握著內(nèi)存對應(yīng)關(guān)系的操作系統(tǒng),也掌握了應(yīng)用程序訪問內(nèi)存的閘門。借助虛擬內(nèi)存地址,操作系統(tǒng)可以保障進(jìn)程空間的獨(dú)立性。只要操作系統(tǒng)把兩個進(jìn)程的進(jìn)程空間對應(yīng)到不同的內(nèi)存區(qū)域,就讓兩個進(jìn)程空間成為“老死不相往來”的兩個小王國。兩個進(jìn)程就不可能相互篡改對方的數(shù)據(jù),進(jìn)程出錯的可能性就大為減少。

    另一方面,有了虛擬內(nèi)存地址,內(nèi)存共享也變得簡單。操作系統(tǒng)可以把同一物理內(nèi)存區(qū)域?qū)?yīng)到多個進(jìn)程空間。這樣,不需要任何的數(shù)據(jù)復(fù)制,多個進(jìn)程就可以看到相同的數(shù)據(jù)。內(nèi)核和共享庫的映射,就是通過這種方式進(jìn)行的。每個進(jìn)程空間中,最初一部分的虛擬內(nèi)存地址,都對應(yīng)到物理內(nèi)存中預(yù)留給內(nèi)核的空間。這樣,所有的進(jìn)程就可以共享同一套內(nèi)核數(shù)據(jù)。共享庫的情況也是類似。對于任何一個共享庫,計算機(jī)只需要往物理內(nèi)存中加載一次,就可以通過操縱對應(yīng)關(guān)系,來讓多個進(jìn)程共同使用。IPO中的共享內(nèi)存,也有賴于虛擬內(nèi)存地址。

    虛擬內(nèi)存地址和物理內(nèi)存地址的分離,給進(jìn)程帶來便利性和安全性。但虛擬內(nèi)存地址和物理內(nèi)存地址的翻譯,又會額外耗費(fèi)計算機(jī)資源。在多任務(wù)的現(xiàn)代計算機(jī)中,虛擬內(nèi)存地址已經(jīng)成為必備的設(shè)計。那么,操作系統(tǒng)必須要考慮清楚,如何能高效地翻譯虛擬內(nèi)存地址

    記錄對應(yīng)關(guān)系最簡單的辦法,就是把對應(yīng)關(guān)系記錄在一張表中。為了讓翻譯速度足夠地快,這個表必須加載在內(nèi)存中。不過,這種記錄方式驚人地浪費(fèi)。如果樹莓派1GB物理內(nèi)存的每個字節(jié)都有一個對應(yīng)記錄的話,那么光是對應(yīng)關(guān)系就要遠(yuǎn)遠(yuǎn)超過內(nèi)存的空間。由于對應(yīng)關(guān)系的條目眾多,搜索到一個對應(yīng)關(guān)系所需的時間也很長。這樣的話,會讓樹莓派陷入癱瘓。

    因此,Linux采用了分頁(paging)的方式來記錄對應(yīng)關(guān)系。所謂的分頁,就是以更大尺寸的單位頁(page)來管理內(nèi)存。在Linux中,通常每頁大小為4KB。如果想要獲取當(dāng)前樹莓派的內(nèi)存頁大小,可以使用命令:

    getconf PAGE_SIZE

    得到結(jié)果,即內(nèi)存分頁的字節(jié)數(shù):

    4096

    返回的4096代表每個內(nèi)存頁可以存放4096個字節(jié),即4KB
    Linux把物理內(nèi)存和進(jìn)程空間都分割成頁。

    內(nèi)存分頁,可以極大地減少所要記錄的內(nèi)存對應(yīng)關(guān)系。我們已經(jīng)看到,以字節(jié)為單位的對應(yīng)記錄實在太多。如果把物理內(nèi)存和進(jìn)程空間的地址都分成頁,內(nèi)核只需要記錄頁的對應(yīng)關(guān)系,相關(guān)的工作量就會大為減少。由于每頁的大小是每個字節(jié)的4096倍。因此,內(nèi)存中的總頁數(shù)只是總字節(jié)數(shù)的四千分之一。對應(yīng)關(guān)系也縮減為原始策略的四千分之一。分頁讓虛擬內(nèi)存地址的設(shè)計有了實現(xiàn)的可能。

    也就是說,分頁其實分的就是虛擬內(nèi)存地址和物理內(nèi)存地址的對應(yīng)關(guān)系

    無論是虛擬頁,還是物理頁,一頁之內(nèi)的地址都是連續(xù)的。這樣的話,一個虛擬頁和一個物理頁對應(yīng)起來,頁內(nèi)的數(shù)據(jù)就可以按順序一一對應(yīng)。這意味著,虛擬內(nèi)存地址和物理內(nèi)存地址的末尾部分應(yīng)該完全相同。大多數(shù)情況下,每一頁有4096個字節(jié)。由于4096是2的12次方,所以地址最后12位的對應(yīng)關(guān)系天然成立。我們把地址的這一部分稱為偏移量(offset)。偏移量實際上表達(dá)了該字節(jié)在頁內(nèi)的位置。地址的前一部分則是頁編號操作系統(tǒng)只需要記錄頁編號的對應(yīng)關(guān)系(用的頁表頁號對應(yīng)虛擬頁,頁框號/內(nèi)存塊號對應(yīng)物理頁)。

    內(nèi)存分頁制度的關(guān)鍵,在于管理進(jìn)程空間頁(虛擬頁)和物理頁的對應(yīng)關(guān)系。操作系統(tǒng)把對應(yīng)關(guān)系記錄在分頁表(page table)(即頁表)中。這種對應(yīng)關(guān)系讓上層的抽象內(nèi)存和下層的物理內(nèi)存分離,從而讓Linux能靈活地進(jìn)行內(nèi)存管理。由于每個進(jìn)程會有一套虛擬內(nèi)存地址,那么每個進(jìn)程都會有一個分頁表。

    參考鏈接3

    操作系統(tǒng)——段頁式內(nèi)存管理與虛擬內(nèi)存

    在虛擬內(nèi)存中分段、建立段表、將虛擬頁映射到空閑物理頁框,建立頁表。

    1.如何將段和頁結(jié)合在一起

    在對內(nèi)存進(jìn)行使用的過程中,用戶更希望程序以段的形式被放入內(nèi)存,這樣符合用戶的使用習(xí)慣,譬如找內(nèi)存中“代碼段的第40條指令”。而內(nèi)存更希望將自己等分成若干頁,可以避免因內(nèi)存碎片導(dǎo)致的內(nèi)存利用效率的降低

    為了同時滿足用戶和內(nèi)存的要求,操作系統(tǒng)需要一種中間結(jié)構(gòu)來完成段與頁機(jī)制的統(tǒng)一,這就是虛擬內(nèi)存

    虛擬內(nèi)存是一個抽象的概念,本身并不存在,它是一連串的虛擬地址構(gòu)成的。
    當(dāng)程序分段后,從虛擬內(nèi)存上分割出相應(yīng)的分區(qū)與各段建立映射關(guān)系,完成分段機(jī)制;(段表:程序段號段在虛擬內(nèi)存中的起始地址段的長度
    再將虛擬內(nèi)存分割成頁,將這些頁放在頁框中,并建立頁和頁框的映射,完成分頁機(jī)制(頁表:頁號頁框號/內(nèi)存塊號)。

    2.段頁結(jié)合時進(jìn)程對內(nèi)存的使用

    提出了虛擬內(nèi)存的概念后,我們已經(jīng)能夠?qū)⒎侄螜C(jī)制和分頁機(jī)制有機(jī)地結(jié)合在一起了。現(xiàn)在又要引出兩個問題?程序又是如何放置到內(nèi)存中去的了?又是如何得以正確執(zhí)行的了?

    當(dāng)一個程序想要放入內(nèi)存,會經(jīng)歷以下的步驟:

    (1)在虛擬內(nèi)存中劃分區(qū)域,將程序分段載入到虛擬內(nèi)存中,其實就是建立了程序段虛擬內(nèi)存各個分區(qū)間的映射關(guān)系,將這種映射關(guān)系放到段表中,記錄各段與虛擬內(nèi)存的映射關(guān)系。

    (2)將虛擬內(nèi)存中的各段打散分成頁,然后建立頁表,記錄虛擬頁號物理頁框號/內(nèi)存塊號之間的映射關(guān)系。

    看個例子(邏輯地址、虛擬地址、物理地址)

    以指令“call 40”為例,邏輯地址40。設(shè)代碼段為第一個代碼段,頁面大小為100(頁面大小和頁框大小相同)。
    段號為0,找到該段在虛擬內(nèi)存中的起始地址為1000,偏移量(邏輯地址)是40,1000+40=1040,這是在虛擬內(nèi)存中的地址
    再用1040除以頁面大小100,得到虛擬頁號為10,查找頁表發(fā)現(xiàn)對應(yīng)的物理頁框號為5,那么實際內(nèi)存地址為5*100+40=540,就是“mov 1, [300]”指令。

    只要將特定的寄存器(LDTR、CR3)的值設(shè)置為正確段表初始地址頁表初始地址,執(zhí)行每條指令時MMU都會自動完成上述地址轉(zhuǎn)換過程。

    虛擬內(nèi)存和虛擬存儲器的區(qū)別?

    參考鏈接4、5

    參考鏈接
    電子發(fā)燒友的文章

    一、虛擬內(nèi)存(操作系統(tǒng)筆記中的內(nèi)容)
    windows下的虛擬內(nèi)存其實是借用磁盤空間假裝它是內(nèi)存,當(dāng)應(yīng)用訪問虛擬內(nèi)存地址的時候,如果內(nèi)存管理器發(fā)現(xiàn)對應(yīng)的物理地址在磁盤中,那內(nèi)存管理器就會將這部分信息從磁盤中加載回內(nèi)存中。

    (以下的內(nèi)容來自操作系統(tǒng)筆記中的3.4.3 虛擬存儲技術(shù)
    虛擬內(nèi)存:
    在程序裝入(載入)時,可以將程序中很快會用到的部分裝入內(nèi)存,暫時用不到的部分留在外存,就可以讓程序開始執(zhí)行了;
    在程序執(zhí)行過程中,當(dāng)所訪問的信息不在內(nèi)存時,由操作系統(tǒng)負(fù)責(zé)將所需信息從外存調(diào)入內(nèi)存,然后繼續(xù)執(zhí)行程序;(外存–>內(nèi)存)
    若內(nèi)存空間不夠,由操作系統(tǒng)負(fù)責(zé)將內(nèi)存中暫時用不到的信息換出到外存;(內(nèi)存–>外存)
    在操作系統(tǒng)的管理下,在用戶看來似乎有一個比實際內(nèi)存大得多的存儲器,這就是虛擬存儲器

    如何實現(xiàn)虛擬內(nèi)存技術(shù)?
    要用到操作系統(tǒng)提供的兩個功能:請求調(diào)頁功能頁面置換功能

    二、虛擬存儲器(也叫虛擬內(nèi)存???)(牛客的視頻課中的內(nèi)容)
    更像是一種機(jī)制,這種機(jī)制在有的書稱為虛擬內(nèi)存,有的書稱為虛擬存儲器,但是這不重要,重要的是其中的原理、核心。

    虛擬內(nèi)存是硬件異常、硬件地址翻譯、主存、磁盤文件和內(nèi)核文件的完美交互,它為每個進(jìn)程提供了一個大的、一致的、私有的地址空間

    通過一個很清晰的機(jī)制,虛擬內(nèi)存提供了三個重要的能力:
    1)它將主存看成是一個存儲在磁盤上的地址空間的高速緩存,在主存中只保存活動區(qū)域,并根據(jù)需要在磁盤和主存之間來回傳送數(shù)據(jù),通過這種方式,它高效地使用了主存;
    2)它為每個進(jìn)程提供了一致的地址空間,從而簡化了內(nèi)存管理;
    3)它為每個進(jìn)程提供了私有的地址空間,從而保護(hù)了每個進(jìn)程的地址空間不被其他進(jìn)程破壞。

    虛擬內(nèi)存是計算機(jī)系統(tǒng)內(nèi)存管理的一種技術(shù)。 它使得應(yīng)用程序認(rèn)為它擁有連續(xù)可用的內(nèi)存(一個連續(xù)完整的地址空間),而實際上物理內(nèi)存通常被分隔成多個內(nèi)存碎片,還有部分暫時存儲在外部磁盤存儲器上,在需要時進(jìn)行數(shù)據(jù)交換。

    三、總結(jié)
    虛擬存儲技術(shù):
    借用磁盤空間假裝它是內(nèi)存,當(dāng)應(yīng)用訪問虛擬內(nèi)存地址的時候,如果內(nèi)存管理器發(fā)現(xiàn)對應(yīng)的物理地址在磁盤中,那內(nèi)存管理器就會將這部分信息從磁盤(外存)中加載回內(nèi)存中。

    虛擬內(nèi)存:
    操作系統(tǒng)為每個進(jìn)程提供了一個大的、一致的、私有的地址空間,叫虛擬內(nèi)存地址,或者虛擬地址空間
    1)在主存中只保存活動區(qū)域,并根據(jù)需要在磁盤和主存之間來回傳送數(shù)據(jù),通過這種方式,它高效地使用了主存;
    2)它為每個進(jìn)程提供了一致的地址空間,從而簡化了內(nèi)存管理;
    3)它為每個進(jìn)程提供了私有的地址空間,從而保護(hù)了每個進(jìn)程的地址空間不被其他進(jìn)程破壞。

    7.(5和6的)總結(jié)☆☆☆☆☆

    7.1 虛擬內(nèi)存的提出是為了解決什么問題?

    操作系統(tǒng)中有個概念叫并行,就是多核處理器中每個核都處理一個進(jìn)程,這些進(jìn)程是同時進(jìn)行的,那么就會有多個進(jìn)程對內(nèi)存操作的沖突問題,而虛擬內(nèi)存概念的提出就是為了解決這個問題。

    7.2 虛擬內(nèi)存的原理解釋

    首先,每個進(jìn)程都有一個虛擬地址空間(但同一個進(jìn)程下的不同線程共用一個虛擬地址空間),這樣就確保了進(jìn)程之間互不影響
    程序中表達(dá)的內(nèi)存地址,也都是虛擬內(nèi)存地址。進(jìn)程對虛擬內(nèi)存地址的操作,會被操作系統(tǒng)翻譯成對某個物理內(nèi)存地址的操作。由于翻譯的過程由操作系統(tǒng)全權(quán)負(fù)責(zé),所以應(yīng)用程序可以在全過程中對物理內(nèi)存地址一無所知

    本質(zhì)上說,虛擬內(nèi)存地址剝奪了應(yīng)用程序自由訪問物理內(nèi)存地址的權(quán)利。進(jìn)程對物理內(nèi)存的訪問,必須經(jīng)過操作系統(tǒng)的審查。因此,掌握著內(nèi)存對應(yīng)關(guān)系的操作系統(tǒng),也掌握了應(yīng)用程序訪問內(nèi)存的閘門。借助虛擬內(nèi)存地址,操作系統(tǒng)可以保障進(jìn)程空間的獨(dú)立性。只要操作系統(tǒng)把兩個進(jìn)程的進(jìn)程空間對應(yīng)到不同的內(nèi)存區(qū)域,就讓兩個進(jìn)程空間成為“老死不相往來”的兩個小王國。兩個進(jìn)程就不可能相互篡改對方的數(shù)據(jù),進(jìn)程出錯的可能性就大為減少。

    操作系統(tǒng)有一塊物理內(nèi)存(中間的部分),有兩個進(jìn)程(實際會更多)P1 和 P2,操作系統(tǒng)偷偷地分別告訴 P1 和 P2,我的整個內(nèi)存都是你的,隨便用,管夠。可事實上呢,操作系統(tǒng)只是給它們畫了個大餅,這些內(nèi)存說是都給了 P1 和 P2,實際上只給了它們一個序號而已。只有當(dāng) P1 和 P2 真正開始使用這些內(nèi)存時,系統(tǒng)才開始使用輾轉(zhuǎn)挪移,拼湊出各個塊給進(jìn)程用,P2 以為自己在用 A 內(nèi)存,實際上已經(jīng)被系統(tǒng)悄悄重定向到真正的 B 去了;甚至,當(dāng) P1 和 P2 共用了 C 內(nèi)存,他們也不知道。

    操作系統(tǒng)的這種欺騙進(jìn)程的手段,就是虛擬內(nèi)存。對 P1 和 P2 進(jìn)程來說,它們都以為自己占用了整個內(nèi)存,而自己使用的物理內(nèi)存的哪段地址,它們并不知道,也無需關(guān)心。

    虛擬內(nèi)存是操作系統(tǒng)里的概念,對操作系統(tǒng)來說,虛擬內(nèi)存就是一張張的對照表
    P1 獲取 A 內(nèi)存里的數(shù)據(jù)時應(yīng)該去物理內(nèi)存的 A 地址找,而找 B 內(nèi)存里的數(shù)據(jù)應(yīng)該去物理內(nèi)存的 C 地址。(這就是邏輯地址和物理地址的一個對應(yīng)關(guān)系

    此外,由于每個進(jìn)程都一個虛擬地址空間,所以兩個進(jìn)程可以有相同的虛擬內(nèi)存地址,但經(jīng)過地址轉(zhuǎn)換后不一定指向同一塊物理內(nèi)存

    7.3 分頁和頁表

    我們知道系統(tǒng)里的基本單位都是 Byte 字節(jié),如果將每一個虛擬內(nèi)存的 Byte 都對應(yīng)到物理內(nèi)存的地址,每個條目最少需要 8字節(jié)(32位虛擬地址->32位物理地址),在 4G 內(nèi)存的情況下,就需要 32GB 的空間來存放對照表,那么這張表就大得真正的物理地址也放不下了,于是操作系統(tǒng)引入了頁(Page)的概念。

    在系統(tǒng)啟動時,操作系統(tǒng)將整個物理內(nèi)存以 4K 為單位,劃分為各個頁。之后進(jìn)行內(nèi)存分配時,都以頁為單位,那么虛擬內(nèi)存頁 和 物理內(nèi)存頁 的映射表就大大減小了。
    4G 內(nèi)存,只需要 8M 的映射表即可
    32位系統(tǒng)的內(nèi)存是4G = 2^32
    每頁是4K = 2^12
    所以一共有2^20個頁,每個頁最少占8個字節(jié)B
    所以就是8M = 2^3 * 2^20

    并且一些進(jìn)程沒有使用到的虛擬內(nèi)存,也并不需要保存映射關(guān)系,此外Linux 還為大內(nèi)存設(shè)計了多級頁表,可以進(jìn)一頁減少了內(nèi)存消耗。

    虛擬內(nèi)存物理內(nèi)存的映射表,就被稱為頁表,即頁表記錄的是虛擬頁號頁框號/內(nèi)存塊號的對應(yīng)關(guān)系。

    無論是虛擬頁,還是物理頁,一頁之內(nèi)的地址都是連續(xù)的。這樣的話,一個虛擬頁和一個物理頁對應(yīng)起來,頁內(nèi)的數(shù)據(jù)就可以按順序一一對應(yīng)。

    由于每個進(jìn)程會有一套虛擬內(nèi)存地址,那么每個進(jìn)程都會有一個分頁表

    7.4 虛擬內(nèi)存地址(邏輯地址)–>物理地址 (快表、缺頁中斷)

    我們知道通過虛擬內(nèi)存機(jī)制,每個進(jìn)程都以為自己占用了全部內(nèi)存,進(jìn)程訪問內(nèi)存時,操作系統(tǒng)都會把進(jìn)程提供的虛擬內(nèi)存地址轉(zhuǎn)換為物理地址,再去對應(yīng)的物理地址上獲取數(shù)據(jù)。CPU 中有一種硬件,內(nèi)存管理單元 MMU(Memory Management Unit)專門用來翻譯虛擬內(nèi)存地址

    CPU 還為頁表尋址設(shè)置了緩存策略(快表),由于程序的局部性原理,其緩存命中率能達(dá)到 98%。(快表其實就是一種特殊的高速緩沖寄存器Cache,高速緩存)

    以上情況是頁表內(nèi)存在虛擬地址到物理地址的映射,而如果進(jìn)程訪問的物理地址還沒有被分配,系統(tǒng)則會產(chǎn)生一個缺頁中斷,在中斷處理時,系統(tǒng)切到內(nèi)核態(tài)為進(jìn)程提供的虛擬地址分配物理地址。

    7.5 虛擬內(nèi)存的功能

    • 解決了多個進(jìn)程訪問內(nèi)存沖突的問題;
    • 內(nèi)存完整性:由于虛擬內(nèi)存對進(jìn)程的”欺騙”,讓每個進(jìn)程都認(rèn)為自己獲取的內(nèi)存是一塊連續(xù)的地址,并且內(nèi)存足夠大;
    • 安全:由于進(jìn)程訪問內(nèi)存時,都要通過頁表來尋址,操作系統(tǒng)在頁表的各個項目上添加各種訪問權(quán)限標(biāo)識位,就可以實現(xiàn)內(nèi)存的訪問權(quán)限控制
      虛擬內(nèi)存地址和物理內(nèi)存地址的分離,給進(jìn)程帶來便利性和安全性
    • 更容易實現(xiàn)內(nèi)存和數(shù)據(jù)的共享,如上文圖中所示,進(jìn)程 P1 和 P2 的 B 地址都指向了物理地址 C。(分段的優(yōu)點:實現(xiàn)信息的共享和保護(hù)
      操作系統(tǒng)可以把同一物理內(nèi)存區(qū)域?qū)?yīng)到多個進(jìn)程空間。這樣,不需要任何的數(shù)據(jù)復(fù)制,多個進(jìn)程就可以看到相同的數(shù)據(jù)
    • 可以幫進(jìn)程”擴(kuò)充”內(nèi)存,利用交換技術(shù),在內(nèi)存吃緊的時候把暫時用不到的數(shù)據(jù)換出到外存中;(裝Linux系統(tǒng)的時候有一步是設(shè)置交換分區(qū)的大小)

    7.6 段頁式內(nèi)存管理與虛擬內(nèi)存☆☆☆

    直接看上面參考鏈接3的全部內(nèi)容,主要配合例子來理解一下。

    C++面試寶典–> 1.2 C++內(nèi)存 和 第2章 操作系統(tǒng)

    E:\找工作\C++八股文\C面試寶典完整版最最最新.pdf
    視頻課中從01:10:39開始

    5.7 名稱空間、模板

    補(bǔ)充:內(nèi)存對齊/字節(jié)對齊

    (視頻課中從01:28:45開始)

    為什么要有內(nèi)存對齊,因為加入CPU每次固定的讀4個字節(jié),這樣可以用空間來換取時間。

    對齊模數(shù)必須是2的整數(shù)次冪。

    內(nèi)存對齊的規(guī)則:

  • 第一個成員必須是從0位置開始偏移;
  • 下面的成員從 成員的大小 和 對齊模數(shù)相比 較小的那個數(shù)的整數(shù)倍 的地方開始;
    如果成員是結(jié)構(gòu)體變量,就把它里面最大的成員拿出來和對齊模數(shù)作比較,取小的那個的整數(shù)倍;
  • 最后要對結(jié)構(gòu)體整體進(jìn)行對齊,整個結(jié)構(gòu)體的大小應(yīng)該是:成員中最大的那個(成員中如果有結(jié)構(gòu)體變量,依然是把它里面最大的成員拿出來)和對齊模數(shù)相比,取小的那個的整數(shù)倍。
  • 示例:

    #include<iostream> using namespace std; //#pragma pack(show) //默認(rèn)的對齊模數(shù)是8 //#pragma pack(1) //如果把對齊模數(shù)改為1,就相當(dāng)于不存在內(nèi)存對齊了,結(jié)果就是所有的字節(jié)數(shù)加在一起的總和struct Student{//對齊模數(shù)是8int a; //0 ~ 3float b; //4 ~ 7 float大小是4,4和8相比,4小,從4的整數(shù)倍開始char c1; //8 ~ 8 15 char大小是1,1和8相比,1小,從1的整數(shù)倍開始double d; //9 16 ~ 23 double大小是8,8和8相比,8小,從8的整數(shù)倍開始(所以把9改成16,上面的8改成15//最后,最大的8和對齊模數(shù)8相比,8小,所以整個結(jié)構(gòu)體的大小是8的整數(shù)倍,結(jié)果是24 }; struct Student2{//對齊模數(shù)是8int a; //0 ~ 3 7Student stu;//4 8 ~ 31 結(jié)構(gòu)體中最大的是8,8和8相比,8小,所以從8的整數(shù)倍開始(把4改成8,上面的3改成7double d; //32 ~ 39 double大小是8,8和8相比,8小,所以從8的整數(shù)倍開始char e; //40 ~ 40 47 char的大小是1,1和8相比,1小,所以從1的整數(shù)倍開始//最后,最大的8和對齊模數(shù)8相比,8小,所以整個結(jié)構(gòu)體的大小是8的整數(shù)倍,結(jié)果是48(所以上面的40改成47 }; int main(){cout << "float的大小:" << sizeof(float) << endl;//4cout << "double的大小:" << sizeof(double) << endl;//8cout << "int的大小:" << sizeof(int) << endl;//4cout << "char的大小:" << sizeof(char) << endl;//1cout << "結(jié)構(gòu)體Student的大小:" << sizeof(Student) << endl;//24 如果把對齊模數(shù)改為1,結(jié)果是17cout << "結(jié)構(gòu)體Student2的大小:" << sizeof(Student2) << endl;//48 如果把對齊模數(shù)改為1,結(jié)果是30return 0; }

    名稱空間

    (視頻課中從02:03:00開始)
    C++ Primer Plus(嵌入式公開課)—第九章 內(nèi)存模型和名稱空間

    作用域解析運(yùn)算符(兩個冒號::)

    它的優(yōu)先級是運(yùn)算符中等級最高的,例如cat.Animals::name
    它有三個作用:1.全局作用域符;2.類作用域符;3名稱空間作用域符

    ::前面沒有任何內(nèi)容,代表全局作用域符。

    名稱空間

    名稱空間中可以寫什么?(變量、函數(shù)、結(jié)構(gòu)體、類…)

    using聲明和using編譯指令

    using聲明和using編譯指令,是用來簡化對名稱空間中名稱的使用

    using聲明:使特定的標(biāo)識符可用;using std::cout; using std::endl;
    using編譯指令:讓整個名稱空間中的名稱可用;using namespace std;
    也可以不用using聲明,也不用using編譯指令:std::cout << ""<< std::endl;

    有了using聲明或者using編譯指令,下面再使用時就不需要再加作用域

    注意:
    using聲明:如果局部變量和using聲明同時使用會出現(xiàn)問題
    using編譯指令:如果出現(xiàn)同名的變量,不會報錯,使用就近原則。(局部名稱隱藏名稱空間名)

    示例:

    #include<iostream> using namespace std;//讓std這個名稱空間下的所有名稱都可用namespace fun{int num = 1;double d = 1.1; }namespace fun1{int num = 1;double d = 1.1; }int main(){cout << fun::num << endl;//1int num = 2; using namespace fun; //using編譯:讓fun名稱空間下的所有名稱都可用 如果出現(xiàn)同名的變量,不會報錯,使用就近原則。//using fun::num; //using聲明:只讓fun名稱空間下的變量num可用 如果和局部變量一起用會出錯//有了上面的using聲明或者using編譯指令,下面再使用時就不需要再加作用域了cout << num << endl; //2cout << num << endl; //有了上面的using聲明,下面再使用時就不需要再加作用域了cout << d << endl;return 0; }

    模板

    (視頻課中從02:16:45開始)

    C++筆記7:C++提高編程1:模板—[函數(shù)模板和類模板]

    C++除了面向?qū)ο缶幊?/strong>思想,還有泛型編程思想。
    泛型編程主要是利用模板技術(shù)來實現(xiàn)的。

    函數(shù)模板

    ①函數(shù)模板語法

    語法:

    template<typename T> 函數(shù)聲明或定義

    其中:
    template — 聲明創(chuàng)建模板
    typename — 表面其后面的符號是一種數(shù)據(jù)類型,可以用class代替
    T — 通用的數(shù)據(jù)類型,名稱可以替換,通常為大寫字母

    ②函數(shù)模板和普通函數(shù)的區(qū)別

    函數(shù)模板不允許自動類型轉(zhuǎn)換;
    普通函數(shù)能夠自動類型轉(zhuǎn)換;(char --> int)

    ③函數(shù)模板調(diào)用規(guī)則

    C++編譯器優(yōu)先考慮普通函數(shù);
    可以通過空模板實例參數(shù)列表的語法限定編譯器只能通過模板匹配;
    函數(shù)模板可以重載;
    如果函數(shù)模板可以產(chǎn)生一個更好的匹配,那么優(yōu)先選擇模板。

    ④模板實現(xiàn)機(jī)制及局限性

    函數(shù)模板通過具體類型產(chǎn)生不同的函數(shù)。

    通過函數(shù)模板產(chǎn)生的函數(shù)稱為模板函數(shù)

    類模板

    ①類模板基礎(chǔ)語法
    template<typename T>//typename可以用class代替
    ②類模板中成員函數(shù)的創(chuàng)建時機(jī)

    類模板中成員函數(shù)并不是一開始創(chuàng)建,而是在使用的時候才生成,在替換T后生成。

    #include<iostream> using namespace std;template<class T> class testClass{ public:void func1(){obj.show1();}void fun2(){obj.show2();}T obj; }; class Person1{ public:void show1(){cout << "調(diào)用show1()函數(shù)" << endl;} }; class Person2{ public:void show2(){cout << "調(diào)用show2()函數(shù)" << endl;} }; int main(){testClass<Person1> tc;//testClass<Person2> tc;//編譯錯誤tc.func1();return 0; }
    ③類模板做函數(shù)參數(shù)
    ④類模板派生類
    ⑤類模板成員函數(shù)類內(nèi)實現(xiàn)
    ⑥類模板成員函數(shù)類外實現(xiàn)
    ⑦類模板分文件編寫(類模板文件 .hpp)

    把類的聲明(.h)和實現(xiàn)(.cpp)寫在一起放到.hpp文件中,一看.hpp文件就知道是類模板文件

    ⑧模板類碰到友元函數(shù)
    ⑨模板案例—數(shù)組類封裝

    5.8 STL(標(biāo)準(zhǔn)模板庫)

    C++筆記8:C++提高編程2:STL—標(biāo)準(zhǔn)模板庫


    SRL六大組件:容器、算法、迭代器、仿函數(shù)、適配器、空間配置器
    容器:各種數(shù)據(jù)結(jié)構(gòu),vector、list、deque、set、map,用來存放數(shù)據(jù),是一種類模板
    算法:各種常用的算法,sort、find、copy、for_each,是一種函數(shù)模板
    迭代器:相當(dāng)于指針,是一種將operator*,operator->,operator++,operator–等指針相關(guān)操作予以重載的類模板(運(yùn)算符重載?);
    仿函數(shù):重載函數(shù)調(diào)用運(yùn)算符()的類或者類模板;
    適配器:用來修飾容器、仿函數(shù)、迭代器的接口
    空間配置器:負(fù)責(zé)空間的配置與管理;比如vector容器的擴(kuò)容就是這個空間配置器來完成的。

    容器:序列式容器(vector、deque、list)、關(guān)聯(lián)式容器(map、set);
    算法:質(zhì)變算法(拷貝、替換、刪除)、非質(zhì)變算法(查找、計數(shù)、遍歷);
    迭代器:輸入迭代器(只讀)、輸出迭代器(只寫)、前向迭代器、雙向迭代器、隨機(jī)訪問迭代器。

    vector擴(kuò)容:

    size()是容器中的元素個數(shù)、capacity()是容器的容量;
    reserve(int len);//預(yù)留len個元素長度;
    vector與普通數(shù)組區(qū)別:數(shù)組是靜態(tài)空間,而vector可以動態(tài)擴(kuò)展。
    (補(bǔ)充:動態(tài)擴(kuò)展并不是在原空間之后續(xù)接新空間,而是找更大的內(nèi)存空間,然后將原數(shù)據(jù)拷貝至新空間,釋放原空間;并且原有的迭代器會失效。
    另外,vector容器的迭代器是支持隨機(jī)訪問的迭代器!!!

    總結(jié) 常用容器的排序的區(qū)別:


    主要看下面兩個地方的總結(jié):
    我的C++八股文中的筆記9最大的收獲
    C++筆記8:C++提高編程2:STL—標(biāo)準(zhǔn)模板庫中的☆☆☆總結(jié) 常用容器的排序的區(qū)別

    總結(jié):

    0.自定義排序規(guī)則的實現(xiàn)方式

    自定義排序規(guī)則:(全局函數(shù))

    • mySort0()//內(nèi)置數(shù)據(jù)類型
    • mySort0()//自定義數(shù)據(jù)類型

    自定義排序規(guī)則:(仿函數(shù))

    • mySort2()//內(nèi)置數(shù)據(jù)類型和自定義數(shù)據(jù)類型

    具體代碼:

    //自定義排序規(guī)則:(全局函數(shù)) bool mySort0(int a, int b){//降序if(a > b)return 1;else return 0; } bool C(const Person& p1, const Person& p2){//降序if(p1.age > p2.age)//按年齡降序return 1;else if(p1.age == p2.age){//如果年齡相等,就按身高升序return p1.height < p2.height;}else return 0; } //自定義排序規(guī)則:(仿函數(shù)) class mySort2{ public://重載函數(shù)調(diào)用運(yùn)算符()bool operator()(int a, int b){return a > b;}bool operator()(const Person& p1, const Person& p2) {//constif(p1.age > p2.age)//按年齡降序return 1;else if(p1.age == p2.age){//如果年齡相等,就按身高升序return p1.height < p2.height;}else return 0;} };

    1.vector容器,deque容器;&& 2.list容器

    對于內(nèi)置數(shù)據(jù)類型:
    如果想自定義排序規(guī)則,可以使用全局函數(shù)和仿函數(shù)和內(nèi)建仿函數(shù)來實現(xiàn):

  • 如果用全局函數(shù),那么參數(shù)就寫全局函數(shù)名
  • 如果用仿函數(shù),參數(shù)就是類名后面加個括號,相當(dāng)于是個利用無參構(gòu)造創(chuàng)建的匿名類對象;
  • 也可以直接使用內(nèi)建仿函數(shù)(大于仿函數(shù)greater<>())直接實現(xiàn)降序排列,需要包含頭文件#include<functional>。
  • 1.vector容器,deque容器: vector<int> v1; sort(v1.begin(), v1.end());//默認(rèn)升序排序 sort(v1.begin(), v1.end(), mySort0);//降序排列 mySort0是一個全局函數(shù) sort(v1.begin(), v1.end(), mySort2());//降序排列 mySort2()是一個類名,仿函數(shù) sort(v1.begin(), v1.end(), greater<>());//降序排列,使用內(nèi)建函數(shù)對象(仿函數(shù)),就不用自己寫排序規(guī)則了2.list容器: list<int> L; L.sort();//默認(rèn)升序排序 L.sort(greater<>()); //降序排列,使用內(nèi)建函數(shù)對象(仿函數(shù)),就不用自己寫排序規(guī)則了 L.sort(mySort0);//降序排列 L.sort(mySort2());//降序排列

    對于自定義數(shù)據(jù)類型:
    如果容器中存放的是自定義數(shù)據(jù)類型,就必須要指明自定義的排序規(guī)則,不能使用默認(rèn)的排序規(guī)則

    1.vector容器,deque容器: vector<Person> v2; //sort(v2.begin(), v2.end());//報錯!!!沒有指定排序規(guī)則 //sort(v2.begin(), v2.end(), greater<>());//會報錯!!!不能使用默認(rèn)的排序規(guī)則 sort(v2.begin(), v2.end(), mySort1);//默認(rèn)排序不能用,必須指明排序規(guī)則,mySort1是一個全局函數(shù) sort(v2.begin(), v2.end(), mySort2());//默認(rèn)排序不能用,必須指明排序規(guī)則,mySort2()是一個類名,仿函數(shù)2.list容器: list<Person> L2; //L2.sort();//報錯!!!沒有指定排序規(guī)則 //L2.sort(greater<>());//會報錯!!!不能使用默認(rèn)的排序規(guī)則 L2.sort(mySort1);//默認(rèn)排序不能用,必須指明排序規(guī)則,mySort1是一個全局函數(shù) L2.sort(mySort2());//默認(rèn)排序不能用,必須指明排序規(guī)則,mySort2()是一個類名,仿函數(shù)

    3.set容器,map容器:

    沒有sort(),因為此容器會自動排序,默認(rèn)是升序,因此可以利用仿函數(shù)實現(xiàn)自定義排序規(guī)則,不能通過全局函數(shù)來實現(xiàn)。

    對于內(nèi)置數(shù)據(jù)類型:
    如果想自定義排序規(guī)則,可以使用仿函數(shù)和內(nèi)建仿函數(shù)來實現(xiàn):

  • 可以使用仿函數(shù),參數(shù)就是類名,后面不加括號
  • 也可以直接使用內(nèi)建仿函數(shù)(大于仿函數(shù)greater<>,后面也沒有括號)直接實現(xiàn)降序排列,需要包含頭文件#include<functional>。
  • 對于自定義數(shù)據(jù)類型:
    如果容器中存放的是自定義數(shù)據(jù)類型,在創(chuàng)建容器的時候就要指明自定義的排序規(guī)則,不能使用默認(rèn)的排序規(guī)則

    注意:這里的排序規(guī)則寫的是類名,后面不用加括號,跟上面的vector、deque、list不一樣,注意區(qū)分!!!
    set容器:

    內(nèi)置數(shù)據(jù)類型: set<int> s1;//默認(rèn)升序排序 //set<int,mySort0> s2;//錯誤,這里不能用全局函數(shù),只能用仿函數(shù)實現(xiàn) //set<int,mySort2()> s2;//錯誤,這里寫的是類名,后面不用加括號 //set<int, greater<>()> s2;//錯誤,后面不用加括號 注意:仿函數(shù)和內(nèi)建仿函數(shù)后面都不用加括號,跟上面的vector、deque、list不一樣,注意區(qū)分: set<int,mySort2> s2;//降序排列 mySort2是一個類名,后面不用加括號,仿函數(shù) set<int, greater<>> s2;//降序排列,使用內(nèi)建函數(shù)對象(仿函數(shù)),這里也沒有括號,跟上面不一樣自定義數(shù)據(jù)類型: //set<Person> s5;//報錯!!!沒有指定排序規(guī)則 //set<Person, greater<>> s3;//會報錯!!!不能使用默認(rèn)的排序規(guī)則 //set<Person,mySort1> s3;//錯誤,這里不能用全局函數(shù),只能用仿函數(shù)實現(xiàn) //set<Person,mySort2()> s2;//錯誤,這里寫的是類名,后面不用加括號 set<Person,mySort2> s3;//如果容器中存儲的是自定義數(shù)據(jù)類型,在創(chuàng)建容器的時候就要指明自定義的排序規(guī)則 //注意:這里mySort2后面也沒有括號!!!

    map容器:

    map<double, double> m1;//默認(rèn)排序規(guī)則---按照Key值從小到大排 map<double, double,MySort2> m2;//自定義排序規(guī)則---在創(chuàng)建容器的時候就指明排序規(guī)則(按Key值降序) map<double, double, greater<>> m5;//使用內(nèi)建仿函數(shù)(大于仿函數(shù)greater<>()):按Key值降序map<double, Person> m3;//默認(rèn)排序規(guī)則:按Key值升序 map<double, Person,MySort2> m6;//利用仿函數(shù)自定義排序規(guī)則:按Key值降序 map<double, Person, greater<>> m7;//內(nèi)建仿函數(shù)(大于仿函數(shù)greater<>()):按Key值降序

    4.匯總

    默認(rèn)(升序)排序規(guī)則內(nèi)建仿函數(shù)
    默認(rèn)的降序排序規(guī)則全局函數(shù)實現(xiàn)降序排序規(guī)則仿函數(shù)實現(xiàn)降序排序規(guī)則寫參數(shù)時加不加括號
    vector容器、deque容器、list容器
    (內(nèi)置數(shù)據(jù)類型)
    全局函數(shù)后不加括號;類名后面加括號
    vector容器、deque容器、list容器
    (自定義數(shù)據(jù)類型)
    ××全局函數(shù)后不加括號;類名后面加括號
    set容器
    (內(nèi)置數(shù)據(jù)類型)
    ×不能用全局函數(shù)實現(xiàn),只能用仿函數(shù)實現(xiàn)
    并且是用仿函數(shù)時類名后面不加括號
    set容器
    (自定義數(shù)據(jù)類型)
    ×××只能用仿函數(shù)實現(xiàn)
    并且是用仿函數(shù)時類名后面不加括號
    map容器
    (內(nèi)置數(shù)據(jù)類型)

    按照key值從小到大排序

    按照key值從小到大排序
    ×不能用全局函數(shù)實現(xiàn),只能用仿函數(shù)實現(xiàn)
    并且是用仿函數(shù)時類名后面不加括號
    map容器
    (自定義數(shù)據(jù)類型)

    按照key值從小到大排序

    按照key值從小到大排序
    ×不能用全局函數(shù)實現(xiàn),只能用仿函數(shù)實現(xiàn)
    并且是用仿函數(shù)時類名后面不加括號

    函數(shù)對象 & 謂詞

    函數(shù)對象基本概念:
    重載函數(shù)調(diào)用操作符()的類,其對象常稱為函數(shù)對象
    函數(shù)對象在使用重載的函數(shù)調(diào)用操作符()時,行為類似函數(shù)調(diào)用,所以也叫仿函數(shù)

    注意:函數(shù)對象(仿函數(shù))是一個類,不是一個函數(shù)。(和普通函數(shù)類似,又超出普通函數(shù)的概念)

    謂詞基本概念:
    返回bool類型的仿函數(shù)稱為謂詞
    如果operator()接受一個參數(shù),那么叫做一元謂詞
    如果operator()接受兩個參數(shù),那么叫做二元謂詞

    內(nèi)建函數(shù)對象

    需要引入頭文件:

    #include<functional>

    關(guān)系仿函數(shù)中最常用的就是greater<>大于,
    因為默認(rèn)排序規(guī)則都時升序,想要實現(xiàn)降序排序規(guī)則,就要是用大于仿函數(shù)greater<>()

    STL—常用算法

    可以看C++筆記9:C++提高編程3:STL—函數(shù)對象&標(biāo)準(zhǔn)算法中的5、STL—常用算法

    5.9 C++新特性

    見筆記③:牛客校招沖刺集訓(xùn)營—C++工程師

    總結(jié)

    以上是生活随笔為你收集整理的笔记②:牛客校招冲刺集训营---C++工程师(面向对象(友元、运算符重载、继承、多态) -- 内存管理 -- 名称空间、模板(类模板/函数模板) -- STL)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。