<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          C++ 八股文(一)

          共 12049字,需瀏覽 25分鐘

           ·

          2021-09-14 02:59


          星標(biāo)/置頂 公眾號(hào)??硬核文章第一時(shí)間送達(dá)

          鏈接 | https://www.zhihu.com/question/400543720

          多態(tài)

          什么是多態(tài),有什么用

          C++ 多態(tài)有兩種:靜態(tài)多態(tài)(早綁定)、動(dòng)態(tài)多態(tài)(晚綁定)。靜態(tài)多態(tài)是通過函數(shù)重載實(shí)現(xiàn)的;動(dòng)態(tài)多態(tài)是通過虛函數(shù)實(shí)現(xiàn)的。

          • 定義:“一個(gè)接口,多種方法”,程序在運(yùn)行時(shí)才決定要調(diào)用的函數(shù)。
          • 實(shí)現(xiàn):C++ 多態(tài)性主要是通過虛函數(shù)實(shí)現(xiàn)的,虛函數(shù)允許子類重寫 override(注意和 overload 的區(qū)別,overload 是重載,是允許同名函數(shù)的表現(xiàn),這些函數(shù)參數(shù)列表/類型不同)。

          注:多態(tài)與非多態(tài)的實(shí)質(zhì)區(qū)別就是函數(shù)地址是靜態(tài)綁定還是動(dòng)態(tài)綁定。如果函數(shù)的調(diào)用在編譯器編譯期間就可以確定函數(shù)的調(diào)用地址,并產(chǎn)生代碼,說明地址是靜態(tài)綁定的;如果函數(shù)調(diào)用的地址是需要在運(yùn)行期間才確定,屬于動(dòng)態(tài)綁定。

          • 目的:接口重用。封裝可以使得代碼模塊化,繼承可以擴(kuò)展已存在的代碼,他們的目的都是為了代碼重用。而多態(tài)的目的則是為了接口重用。
          • 用法:聲明基類的指針,利用該指針指向任意一個(gè)子類對(duì)象,調(diào)用相應(yīng)的虛函數(shù),可以根據(jù)指向的子類的不同而實(shí)現(xiàn)不同的方法。

          用一句話概括:在基類的函數(shù)前加上 virtual 關(guān)鍵字,在派生類中重寫該函數(shù),運(yùn)行時(shí)將會(huì)根據(jù)對(duì)象的實(shí)際類型來調(diào)用相應(yīng)的函數(shù)。如果對(duì)象類型是派生類,就調(diào)用派生類的函數(shù);如果對(duì)象類型是基類,就調(diào)用基類的函數(shù)。

          重寫、重載與隱藏的區(qū)別

          Overload 重載

          在 C++ 程序中,可以將語義、功能相似的幾個(gè)函數(shù)用同一個(gè)名字表示,但參數(shù)或返回值不同(包括類型、順序不同),即函數(shù)重載。

          • 相同的范圍(在同一個(gè)類中);
          • 函數(shù)名字相同;
          • 參數(shù)不同;
          • virtual 關(guān)鍵字可有可無;

          Override(覆蓋或重寫)

          是指派生類函數(shù)覆蓋基類函數(shù),特征是:

          • 不同的范圍(分別位于派生類與基類);
          • 函數(shù)名字相同;參數(shù)相同;
          • 基類函數(shù)必須有 virtual 關(guān)鍵字。

          注:重寫基類虛函數(shù)的時(shí)候,會(huì)自動(dòng)轉(zhuǎn)換這個(gè)函數(shù)為 virtual 函數(shù),不管有沒有加 virtual,因此重寫的時(shí)候不加 virtual 也是可以的,不過為了易讀性,還是加上比較好。

          Overwrite(重寫)隱藏,

          是指派生類的函數(shù)屏蔽了與其同名的基類函數(shù),規(guī)則如下:

          • 如果派生類的函數(shù)與基類的函數(shù)同名,但是參數(shù)不同。此時(shí),不論有無 virtual 關(guān)鍵字,基類的函數(shù)將被隱藏(注意別與重載混淆)。
          • 如果派生類的函數(shù)與基類的函數(shù)同名,并且參數(shù)也相同,但是基類函數(shù)沒有 virtual 關(guān)鍵字。此時(shí),基類的函數(shù)被隱藏(注意別與覆蓋混淆)。

          虛函數(shù)和純虛函數(shù)

          • 虛函數(shù):為了實(shí)現(xiàn)動(dòng)態(tài)綁定。使用基類的引用或指針調(diào)用虛函數(shù)的時(shí)候會(huì)發(fā)生動(dòng)態(tài)綁定。
          • 純虛函數(shù):抽象類
          • 構(gòu)造函數(shù)可以重載,但不能是虛函數(shù),析構(gòu)函數(shù)可以是虛函數(shù)。

          基類為什么需要虛析構(gòu)函數(shù)?

          防止內(nèi)存泄漏。想去借助父類指針去銷毀子類對(duì)象的時(shí)候,不能去銷毀子類對(duì)象。假如沒有虛析構(gòu)函數(shù),釋放一個(gè)由基類指針指向的派生類對(duì)象時(shí),不會(huì)觸發(fā)動(dòng)態(tài)綁定,則只會(huì)調(diào)用基類的析構(gòu)函數(shù),不會(huì)調(diào)用派生類的。派生類中申請(qǐng)的空間則得不到釋放導(dǎo)致內(nèi)存泄漏。

          構(gòu)造/析構(gòu)函數(shù)調(diào)用虛函數(shù)

          派生類對(duì)象構(gòu)造期間進(jìn)入基類的構(gòu)造函數(shù)時(shí),對(duì)象類型變成了基類類型,而不是派生類類型。

          同樣,進(jìn)入基類析構(gòu)函數(shù)時(shí),對(duì)象也是基類類型。

          所以,虛函數(shù)始終僅僅調(diào)用基類的虛函數(shù)(如果是基類調(diào)用虛函數(shù)),不能達(dá)到多態(tài)的效果。

          虛函數(shù)表

          • 產(chǎn)生時(shí)間:編譯期
          • 存儲(chǔ)位置:只讀數(shù)據(jù)段 .rodata
          • 虛指針:類的每一個(gè)對(duì)象都包含一個(gè)虛指針(指向虛表),存在對(duì)象實(shí)例的最前面四個(gè)字節(jié)
          • 虛指針創(chuàng)建時(shí)間:構(gòu)造函數(shù)

          注:虛表中的指針會(huì)指向其繼承的最近的一個(gè)類的虛函數(shù)

          const 相關(guān)

          如何初始化 const 和 static 數(shù)據(jù)成員?

          通常在類外申明 static 成員,但是 static const 的整型( bool,char,int,long )可以在類中聲明且初始化,static const 的其他類型必須在類外初始化(包括整型數(shù)組)。

          static 和 const 分別怎么用,類里面 static 和 const 可以同時(shí)修飾成員函數(shù)嗎?

          static 的作用:對(duì) static 的三條作用做一句話總結(jié)。首先 static 的最主要功能是隱藏,其次因?yàn)?static 變量存放在靜態(tài)存儲(chǔ)區(qū),所以它具備持久性和默認(rèn)值 0。

          對(duì)變量

          局部變量

          在局部變量之前加上關(guān)鍵字 static,局部變量就被定義成為一個(gè)局部靜態(tài)變量。

          • 內(nèi)存中的位置:靜態(tài)存儲(chǔ)區(qū)
          • 初始化:未經(jīng)初始化的全局靜態(tài)變量會(huì)被程序自動(dòng)初始化為0(自動(dòng)對(duì)象的值是任意的,除非他被顯示初始化)
          • 作用域:作用域仍為局部作用域,當(dāng)定義它的函數(shù)或者語句塊結(jié)束的時(shí)候,作用域隨之結(jié)束。

          注:當(dāng) static 用來修飾局部變量的時(shí)候,它就改變了局部變量的存儲(chǔ)位置(從原來的棧中存放改為靜態(tài)存儲(chǔ)區(qū))及其生命周期(局部靜態(tài)變量在離開作用域之后,并沒有被銷毀,而是仍然駐留在內(nèi)存當(dāng)中,直到程序結(jié)束,只不過我們不能再對(duì)他進(jìn)行訪問),但未改變其作用域。

          全局變量

          在全局變量之前加上關(guān)鍵字 static,全局變量就被定義成為一個(gè)全局靜態(tài)變量。

          • 內(nèi)存中的位置:靜態(tài)存儲(chǔ)區(qū)(靜態(tài)存儲(chǔ)區(qū)在整個(gè)程序運(yùn)行期間都存在)
          • 初始化:未經(jīng)初始化的全局靜態(tài)變量會(huì)被程序自動(dòng)初始化為 0(自動(dòng)對(duì)象的值是任意的,除非他被顯示初始化)
          • 作用域:全局靜態(tài)變量在聲明他的文件之外是不可見的。準(zhǔn)確地講從定義之處開始到文件結(jié)尾。

          注:static 修飾全局變量,并未改變其存儲(chǔ)位置及生命周期,而是改變了其作用域,使當(dāng)前文件外的源文件無法訪問該變量,好處如下:

          • 不會(huì)被其他文件所訪問,修改
          • 其他文件中可以使用相同名字的變量,不會(huì)發(fā)生沖突。對(duì)全局函數(shù)也是有隱藏作用。而普通全局變量只要定義了,任何地方都能使用,使用前需要聲明所有的 .c 文件,只能定義一次普通全局變量,但是可以聲明多次(外部鏈接)。

          注意:全局變量的作用域是全局范圍,但是在某個(gè)文件中使用時(shí),必須先聲明。

          對(duì)類

          成員變量

          用 static 修飾類的數(shù)據(jù)成員實(shí)際使其成為類的全局變量,會(huì)被類的所有對(duì)象共享,包括派生類的對(duì)象。因此,static 成員必須在類外進(jìn)行初始化(初始化格式:int base::var=10;),而不能在構(gòu)造函數(shù)內(nèi)進(jìn)行初始化,不過也可以用 const 修飾 static 數(shù)據(jù)成員在類內(nèi)初始化 。因?yàn)殪o態(tài)成員屬于整個(gè)類,而不屬于某個(gè)對(duì)象,如果在類內(nèi)初始化,會(huì)導(dǎo)致每個(gè)對(duì)象都包含該靜態(tài)成員,這是矛盾的。

          特點(diǎn):

          • 不要試圖在頭文件中定義(初始化)靜態(tài)數(shù)據(jù)成員。在大多數(shù)的情況下,這樣做會(huì)引起重復(fù)定義這樣的錯(cuò)誤。即使加上#ifndef #define #endif或者#pragma once也不行。
          • 靜態(tài)數(shù)據(jù)成員可以成為成員函數(shù)的可選參數(shù),而普通數(shù)據(jù)成員則不可以。
          • 靜態(tài)數(shù)據(jù)成員的類型可以是所屬類的類型,而普通數(shù)據(jù)成員則不可以。普通數(shù)據(jù)成員的只能聲明為 所屬類類型的指針或引用。

          成員函數(shù)

          • 用 static 修飾成員函數(shù),使這個(gè)類只存在這一份函數(shù),所有對(duì)象共享該函數(shù),不含 this 指針。
          • 靜態(tài)成員是可以獨(dú)立訪問的,也就是說,無須創(chuàng)建任何對(duì)象實(shí)例就可以訪問。base::func(5,3);當(dāng) static 成員函數(shù)在類外定義時(shí)不需要加 static 修飾符。
          • 在靜態(tài)成員函數(shù)的實(shí)現(xiàn)中不能直接引用類中說明的非靜態(tài)成員,可以引用類中說明的靜態(tài)成員。因?yàn)殪o態(tài)成員函數(shù)不含this指針。

          不可以同時(shí)用 const 和 static 修飾成員函數(shù)。

          C++ 編譯器在實(shí)現(xiàn) const 的成員函數(shù)的時(shí)候?yàn)榱舜_保該函數(shù)不能修改類的實(shí)例的狀態(tài),會(huì)在函數(shù)中添加一個(gè)隱式的參數(shù) const this*。但當(dāng)一個(gè)成員為 static 的時(shí)候,該函數(shù)是沒有 this 指針的。也就是說此時(shí) const 的用法和 static 是沖突的。

          我們也可以這樣理解:兩者的語意是矛盾的。static 的作用是表示該函數(shù)只作用在類型的靜態(tài)變量上,與類的實(shí)例沒有關(guān)系;而 const 的作用是確保函數(shù)不能修改類的實(shí)例的狀態(tài),與類型的靜態(tài)變量沒有關(guān)系。因此不能同時(shí)用它們。

          const的作用:

          • 限定變量為不可修改。
          • 限定成員函數(shù)不可以修改任何數(shù)據(jù)成員。
          • const 與指針:

          const char *p 常量指針,可以換方向,不可以改內(nèi)容

          char * const p,指針常量,不可以換方向,可以改內(nèi)容

          構(gòu)造函數(shù)

          構(gòu)造函數(shù)調(diào)用順序

          • 虛基類構(gòu)造函數(shù)(被繼承的順序)
          • 非虛基類構(gòu)造函數(shù)(被繼承的順序)
          • 成員對(duì)象構(gòu)造函數(shù)(聲明順序)
          • 自己的構(gòu)造函數(shù)

          自身構(gòu)造函數(shù)順序

          • 虛表指針(防止初始化列表里面調(diào)用虛函數(shù),否則調(diào)用的是父類的虛函數(shù))
          • 初始化列表(const、引用、沒有定義默認(rèn)構(gòu)造函數(shù)的類型)
          • 花括號(hào)里的 (初始化列表直接初始化,這個(gè)先初始化后賦值)

          this 指針

          創(chuàng)建時(shí)間:成員函數(shù)調(diào)用前生成,調(diào)用后清除

          如何傳遞給成員函數(shù):通過函數(shù)參數(shù)的首參數(shù)來傳遞

          extern 關(guān)鍵字

          • 置于變量或者函數(shù)前,以標(biāo)示變量或者函數(shù)的定義在別的文件中,提示編譯器遇到此變量和函數(shù)時(shí)在其他模塊中尋找其定義
          • extern “C” void fun(); 告訴編譯器按C的規(guī)則去翻譯

          以下關(guān)鍵字的作用?使用場景?

          • inline:在 c/c++ 中,為了解決一些頻繁調(diào)用的小函數(shù)大量消耗棧空間(棧內(nèi)存)的問題,特別的引入了 inline 修飾符,表示為內(nèi)聯(lián)函數(shù)。
          • decltype:從表達(dá)式中推斷出要定義變量的類型,但卻不想用表達(dá)式的值去初始化變量。還有可能是函數(shù)的返回類型為某表達(dá)式的的值類型。
          • volatile:volatile 關(guān)鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統(tǒng)、硬件或者其它線程等。遇到這個(gè)關(guān)鍵字聲明的變量,編譯器對(duì)訪問該變量的代碼就不再進(jìn)行優(yōu)化,從而可以提供對(duì)特殊地址的穩(wěn)定訪問。

          淺拷貝與深拷貝

          什么時(shí)候用到拷貝函數(shù)?

          • 一個(gè)對(duì)象以值傳遞的方式傳入函數(shù)體(參數(shù));
          • 一個(gè)對(duì)象以值傳遞的方式從函數(shù)返回(返回值);
          • 一個(gè)對(duì)象需要通過另外一個(gè)對(duì)象進(jìn)行初始化(初始化)。

          如果在類中沒有顯式地聲明一個(gè)拷貝構(gòu)造函數(shù),那么,編譯器將會(huì)自動(dòng)生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù),該構(gòu)造函數(shù)完成對(duì)象之間的位拷貝。位拷貝又稱淺拷貝

          默認(rèn)拷貝構(gòu)造函數(shù)是淺拷貝。如果一個(gè)類擁有資源,當(dāng)這個(gè)類的對(duì)象發(fā)生復(fù)制過程的時(shí)候,資源重新分配,這個(gè)過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。

          如果實(shí)行位拷貝,也就是把對(duì)象里的值完全復(fù)制給另一個(gè)對(duì)象,如 A=B。這時(shí),如果 B 中有一個(gè)成員變量指針已經(jīng)申請(qǐng)了內(nèi)存,那 A 中的那個(gè)成員變量也指向同一塊內(nèi)存。這就出現(xiàn)了問題:當(dāng) B 把內(nèi)存釋放了(如:析構(gòu)),這時(shí)A內(nèi)的指針就是野指針了,出現(xiàn)運(yùn)行錯(cuò)誤。

          C++類中成員初始化順序

          成員變量在使用初始化列表初始化時(shí),與構(gòu)造函數(shù)中初始化成員列表的順序無關(guān),只與定義成員變量的順序有關(guān)。

          類中 const 成員常量必須在構(gòu)造函數(shù)初始化列表中初始化。類中 static 成員變量,只能在類外初始化(同一類的所有實(shí)例共享靜態(tài)成員變量)。

          構(gòu)造過程

          • 分配內(nèi)存
          • 進(jìn)行父類的構(gòu)造,按照父類的聲明順序(遞歸過程)
          • 構(gòu)造虛表指針,對(duì)虛表指針賦值
          • 根據(jù)初始化列表中的值初始化變量
          • 執(zhí)行構(gòu)造函數(shù){}內(nèi)的

          構(gòu)造函數(shù)初始化列表

          const 或引用類型的成員。因?yàn)?const 對(duì)象或引用類型只能初始化,不能對(duì)他們賦值。

          與對(duì)數(shù)據(jù)成員賦值的區(qū)別:

          • 內(nèi)置數(shù)據(jù)類型,復(fù)合類型(指針,引用):結(jié)果和性能上相同。
          • 用戶定義類型(類類型):結(jié)果上相同,但是性能上存在很大的差別。

          vector 中 size() 和 capacity() 的區(qū)別

          size() 指容器當(dāng)前擁有的元素個(gè)數(shù)(對(duì)應(yīng)的resize(size_type)會(huì)在容器尾添加或刪除一些元素,來調(diào)整容器中實(shí)際的內(nèi)容,使容器達(dá)到指定的大小。);capacity()指容器在必須分配存儲(chǔ)空間之前可以存儲(chǔ)的元素總數(shù)。

          size 表示的這個(gè) vector 里容納了多少個(gè)元素,capacity 表示 vector 能夠容納多少元素,它們的不同是在于 vector 的 size 是 2 倍增長的。如果 vector 的大小不夠了,比如現(xiàn)在的 capacity 是 4,插入到第五個(gè)元素的時(shí)候,發(fā)現(xiàn)不夠了,此時(shí)會(huì)給他重新分配 8 個(gè)空間,把原來的數(shù)據(jù)及新的數(shù)據(jù)復(fù)制到這個(gè)新分配的空間里。(會(huì)有迭代器失效的問題)

          定義一個(gè)空類編譯器做了哪些操作

          如果你只是聲明一個(gè)空類,不做任何事情的話,編譯器會(huì)自動(dòng)為你生成一個(gè)默認(rèn)構(gòu)造函數(shù)、一個(gè)拷貝默認(rèn)構(gòu)造函數(shù)、一個(gè)默認(rèn)拷貝賦值操作符和一個(gè)默認(rèn)析構(gòu)函數(shù)。這些函數(shù)只有在第一次被調(diào)用時(shí),才會(huì)被編譯器創(chuàng)建。所有這些函數(shù)都是 inline 和 public 的。

          強(qiáng)制類型轉(zhuǎn)換

          static_cast

          用法:static_cast < type-id > ( expression )

          q1. 為什么需要 static_cast 強(qiáng)制轉(zhuǎn)換?

          • void指針->其他類型指針 (不安全)
          • 改變通常的標(biāo)準(zhǔn)轉(zhuǎn)換
          • 用于類層次結(jié)構(gòu)中基類和子類之間指針或引用的轉(zhuǎn)換。進(jìn)行上行轉(zhuǎn)換(把子類的指針或引用轉(zhuǎn)換成基類表示)是安全的;進(jìn)行下行轉(zhuǎn)換(把基類指針或引用轉(zhuǎn)換成子類指針或引用)時(shí),由于沒有動(dòng)態(tài)類型檢查,所以是不安全的。

          dynamic_cast

          用法:dynamic_cast < type-id > ( expression )

          dynamic_cast 主要用于類層次間的上行轉(zhuǎn)換和下行轉(zhuǎn)換,還可以用于類之間的交叉轉(zhuǎn)換(同一基類的兩個(gè)同級(jí)派生類)。

          在類層次間進(jìn)行上行轉(zhuǎn)換時(shí),dynamic_caststatic_cast的效果是一樣的;在進(jìn)行下行轉(zhuǎn)換時(shí),dynamic_cast具有類型檢查的功能,比static_cast更安全。

          reinpreter_cast

          它可以把一個(gè)指針轉(zhuǎn)換成一個(gè)整數(shù),也可以把一個(gè)整數(shù)轉(zhuǎn)換成一個(gè)指針(先把一個(gè)指針轉(zhuǎn)換成一個(gè)整數(shù),在把該整數(shù)轉(zhuǎn)換成原類型的指針,還可以得到原先的指針值)。

          const_cast該運(yùn)算符用來修改類型的 const 或 volatile 屬性。除了 const  或 volatile 修飾之外, type_id 和 expression 的類型是一樣的。

          常量指針被轉(zhuǎn)化成非常量指針,并且仍然指向原來的對(duì)象;常量引用被轉(zhuǎn)換成非常量引用,并且仍然指向原來的對(duì)象;常量對(duì)象被轉(zhuǎn)換成非常量對(duì)象。

          volatile 關(guān)鍵字

          • 使用方法:int volatile x;
          • 作用:編譯器不再優(yōu)化。讓編譯器每次操作該變量時(shí)一定要從內(nèi)存中真正取出,而不是使用已經(jīng)存在寄存器中的值 。

          內(nèi)存管理

          C 內(nèi)存分配

          • malloc:在內(nèi)存的動(dòng)態(tài)分配區(qū)域中分配一個(gè)長度為 size 的連續(xù)空間,如果分配成功,則返回所分配內(nèi)存空間的首地址,否則返回 NULL,申請(qǐng)的內(nèi)存不會(huì)初始化。
          • calloc:分配一個(gè) num * size 連續(xù)的空間,會(huì)自動(dòng)初始化為0。
          • realloc:動(dòng)態(tài)分配一個(gè)長度為 size 的內(nèi)存空間,并把內(nèi)存空間的首地址賦值給 ptr,把 ptr 內(nèi)存空間調(diào)整為 size。

          C++ 內(nèi)存分配:

          -棧區(qū)(stack):主要存放函數(shù)參數(shù)以及局部變量,由系統(tǒng)自動(dòng)分配釋放。

          • 堆區(qū)(heap):由用戶通過 malloc/new 手動(dòng)申請(qǐng),手動(dòng)釋放。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表。
          • 全局/靜態(tài)區(qū):存放全局變量、靜態(tài)變量;程序結(jié)束后由系統(tǒng)釋放。- - 字符串常量區(qū):字符串常量就放在這里,程序結(jié)束后由系統(tǒng)釋放。
          • 代碼區(qū):存放程序的二進(jìn)制代碼。

          結(jié)構(gòu)體字節(jié)對(duì)齊問題?結(jié)構(gòu)體/類大小的計(jì)算?

          默認(rèn)字節(jié)對(duì)齊

          各成員變量存放的起始地址相對(duì)于結(jié)構(gòu)的起始地址的偏移量必須是該變量的類型所占用的字節(jié)數(shù)的倍數(shù),結(jié)構(gòu)的大小為結(jié)構(gòu)的字節(jié)邊界數(shù)(即該結(jié)構(gòu)中占用最大空間的類型所占用的字節(jié)數(shù))的倍數(shù) n 字節(jié)對(duì)齊。

          pragma pack(n)

          • 如果 n 大于等于該變量所占用的字節(jié)數(shù),那么偏移量必須滿足默認(rèn)的對(duì)齊方式;
          • 如果 n 小于該變量的類型所占用的字節(jié)數(shù),那么偏移量為 n 的倍數(shù),不用滿足默認(rèn)的對(duì)齊方式;
          • 如果 n 大于所有成員變量類型所占用的字節(jié)數(shù),那么結(jié)構(gòu)體的總大小必須為占用空間最大的變量占用的空間數(shù)的倍數(shù);否則必須為n的倍數(shù)(兩者相比,取小);

          虛函數(shù)的大小計(jì)算

          假設(shè)經(jīng)過成員對(duì)齊后的類的大小為 size 個(gè)字節(jié)。那么類的 sizeof 大小可以這么計(jì)算:size + 4*(虛函數(shù)指針的個(gè)數(shù) n)。

          聯(lián)合體的大小計(jì)算

          聯(lián)合體所占的空間不僅取決于最寬成員,還跟所有成員有關(guān)系,即其大小必須滿足兩個(gè)條件:

          • 大小足夠容納最寬的成員;
          • 大小能被其包含的所有基本數(shù)據(jù)類型的大小所整除。

          常見例子:

          class A {};: sizeof(A) = 1;
          class A { virtual Fun(){} };: sizeof(A) = 4(32位機(jī)器)/8(64位機(jī)器);
          class A { static int a; };: sizeof(A) = 1;
          class A { int a; };: sizeof(A) = 4;
          class A { static int a; int b; };: sizeof(A) = 4;

          指針和引用

          區(qū)別

          • 定義:指針是一個(gè)對(duì)象,引用本身不是對(duì)象,只是另一個(gè)對(duì)象的別名;
          • 指針是“指向”另外一種類型的復(fù)合類型;
          • 引用本身不是一個(gè)對(duì)象,所以不能定義引用的引用;
          • 引用只能綁定到對(duì)象上,它只是一個(gè)對(duì)象的別名,因此引用必須初始化,且不能更換引用對(duì)象。

          指針

          可以有 const 指針,但是沒有 const 引用(const 引用可讀不可改,與綁定對(duì)象是否為 const 無關(guān))

          注:引用可以指向常量,也可以指向變量。例如int &a=b,使引用 a 指向變量 b。而為了讓引用指向常量,必須使用常量引用,如const int &a=1; 它代表的是引用 a 指向一個(gè)const int型,這個(gè) int 型的值不能被改變,而不是引用 a 的指向不能被改變,因?yàn)橐玫闹赶虮緛砭褪遣豢勺兊模瑹o需加 const 聲明。即指針存在常量指針int const *p和指針常量int *const p,而引用只存在常量引用int const &a,不存在引用常量int& const a

          • 指針可以有多級(jí),但是引用只能是一級(jí)(int **p;合法 而 int &&a 是不合法的)
          • 指針的值可以為空,但是引用的值不能為 NULL,并且引用在定義的時(shí)候必須初始化;
          • 指針的值在初始化后可以改變,即指向其它的存儲(chǔ)單元,而引用在進(jìn)行初始化后就不會(huì)再改變了。
          • "sizeof 引用"得到的是所指向的變量(對(duì)象)的大小,而" sizeof 指針"得到的是指針本身的大小;
          • 指針和引用的自增(++)運(yùn)算意義不一樣;
          • 指針使用時(shí)需要解引用(*),引用則不需要;

          指針的注意點(diǎn)

          1、指針指向常量存儲(chǔ)區(qū)對(duì)象

          char *p="abc";此時(shí) p 指向的是一個(gè)字符串常量,不能對(duì) *p 的內(nèi)容進(jìn)行寫操作,如 srtcpy(p,s) 是錯(cuò)誤的,因?yàn)?p 的內(nèi)容為 “abc” 字符串常量,該數(shù)據(jù)存儲(chǔ)在常量存儲(chǔ)區(qū),但可以對(duì)指針 p 進(jìn)行操作,讓其指向其他的內(nèi)存空間。

          2、資源泄漏

          char *p=new char[3]; //分配三個(gè)字符空間,p指向該內(nèi)存空間
          p="ab"; //此時(shí)p指向常量“ab”,而不再是new char分配的內(nèi)存空間了,從而造成了資源泄漏
          delete []p; //釋放時(shí)報(bào)錯(cuò)

          3、內(nèi)存越界

          char *p=new char[3]; //分配三個(gè)字符空間,p指向該內(nèi)存空間
          strcpy(p,"abcd"); //將abcd存處在分配的內(nèi)存空間中,由于strlen("abcd")=4>3,越界
          delete []p; //釋放時(shí)出錯(cuò)

          new和malloc的區(qū)別

          • new 是運(yùn)算符,malloc() 是一個(gè)庫函數(shù);
          • new 會(huì)調(diào)用構(gòu)造函數(shù),malloc 不會(huì);
          • new 返回指定類型指針,malloc 返回 void* 指針,需要強(qiáng)制類型轉(zhuǎn)換;
          • new 會(huì)自動(dòng)計(jì)算需分配的空間,malloc 不行;
          • new 可以被重載,malloc 不能。

          懸空指針與野指針

          • 懸空指針:當(dāng)所指向的對(duì)象被釋放或者收回,但是沒有讓指針指向NULL;
          • 野指針:那些未初始化的指針;

          空指針能調(diào)用類成員函數(shù)嗎

          可以調(diào)用成員函數(shù)。當(dāng)調(diào)用p->func1(); 這句話時(shí),其實(shí)就是調(diào)用 A::func1(this) ,而成員函數(shù)的地址在編譯時(shí)就已經(jīng)確定, 所以空指針也是可以調(diào)用普通成員函數(shù),只不過此時(shí)的 this 指針指向空而已,但函數(shù) fun1 函數(shù)體內(nèi)并沒有用到 this 指針,所以不會(huì)出現(xiàn)問題。

          不可以調(diào)用虛函數(shù)。如果一個(gè)類中包含虛函數(shù),那么它所實(shí)例化處的對(duì)象的前四個(gè)字節(jié)是一個(gè)虛表指針,這個(gè)虛表指針指向的是虛函數(shù)表。當(dāng)然,虛函數(shù)的地址也是在編譯時(shí)就已經(jīng)確定了,這些虛函數(shù)地址存放在虛函數(shù)表里面,而虛函數(shù)表就在程序地址空間的數(shù)據(jù)段(靜態(tài)區(qū)),也就是說虛表的建立是在編譯階段就完成的;當(dāng)調(diào)用構(gòu)造函數(shù)的時(shí)候才會(huì)初始化虛函數(shù)表指針,即把虛表指針存放在對(duì)象前四個(gè)字節(jié)(32 位下)。試想一下,假如用空指針調(diào)用虛函數(shù),這個(gè)指針根本就找不到對(duì)應(yīng)的對(duì)象的地址,因此他也不知道虛表的地址,沒有虛表的地址,怎么能調(diào)用虛函數(shù)呢

          智能指針

          unique_ptr

          摒棄 auto_ptr 的原因:避免潛在的內(nèi)存崩潰問題。如下代碼用 auto_ptr 的話不會(huì)出現(xiàn)問題,但 p3 是無法訪問的。

          unique_ptr<string> p3 (new string ("auto");   
          unique_ptr<string> p4;                       
          p4 = p3;                                      // 編譯器認(rèn)為非法

          只允許基礎(chǔ)指針的一個(gè)所有者。unique_ptr小巧高效;大小等同于一個(gè)指針且支持右值引用,從而可實(shí)現(xiàn)快速插入和對(duì)STL集合的檢索。

          注意:當(dāng)程序試圖將一個(gè) unique_ptr 賦值給另一個(gè)時(shí),如果源 unique_ptr 是個(gè)臨時(shí)右值,編譯器允許這么做;如果源 unique_ptr 將存在一段時(shí)間,編譯器將禁止這么做。

          shared_ptr

          采用引用計(jì)數(shù)的智能指針,主要用于要將一個(gè)原始指針分配給多個(gè)所有者(例如,從容器返回了指針副本又想保留原始指針時(shí))的情況。當(dāng)所有的 shared_ptr 所有者超出了范圍或放棄所有權(quán),才會(huì)刪除原始指針。大小為兩個(gè)指針;一個(gè)用于對(duì)象,另一個(gè)用于包含引用計(jì)數(shù)的共享控制塊。

          最安全的分配和使用動(dòng)態(tài)內(nèi)存的方法是調(diào)用 make_shared 標(biāo)準(zhǔn)庫函數(shù),此函數(shù)在動(dòng)態(tài)分配內(nèi)存中分配一個(gè)對(duì)象并初始化它,返回對(duì)象的 shared_ptr。

          堆和棧

          • 棧 :只要棧的剩余空間大于所申請(qǐng)的空間,系統(tǒng)將為程序提供內(nèi)存,否則將報(bào)異常提示棧溢出。
          • 堆:首先應(yīng)該知道操作系統(tǒng)有一個(gè)記錄空閑內(nèi)存地址的鏈表,當(dāng)系統(tǒng)受到程序的申請(qǐng)時(shí),會(huì)遍歷該鏈表,尋找第一個(gè)空間大于所申請(qǐng)空間的堆。

          編譯與優(yōu)化

          靜態(tài)鏈接與動(dòng)態(tài)鏈接

          靜態(tài)鏈接:

          • 定義:在生成可執(zhí)行文件的時(shí)候(鏈接階段),把所有需要的函數(shù)的二進(jìn)制代碼都包含到可執(zhí)行文件中去。
          • 特點(diǎn):鏈接器需要知道參與鏈接的目標(biāo)文件需要哪些函數(shù),同時(shí)也要知道每個(gè)目標(biāo)文件都能提供什么函數(shù),這樣鏈接器才能知道是不是每個(gè)目標(biāo)文件所需要的函數(shù)都能正確地鏈接。如果某個(gè)目標(biāo)文件需要的函數(shù)在參與鏈接的目標(biāo)文件中找不到的話,鏈接器就報(bào)錯(cuò)了。目標(biāo)文件中有兩個(gè)重要的接口來提供這些信息:一個(gè)是符號(hào)表,另外一個(gè)是重定位表。
          • 缺點(diǎn):1. 程序體積會(huì)變大;2. 靜態(tài)庫有更新的話,所有可執(zhí)行文件都需要重新鏈接

          動(dòng)態(tài)鏈接:

          • 定義:在編譯的時(shí)候不直接拷貝可執(zhí)行代碼,而是通過記錄一系列符號(hào)和參數(shù),在程序運(yùn)行或加載時(shí)將這些信息傳遞給操作系統(tǒng),操作系統(tǒng)負(fù)責(zé)將需要的動(dòng)態(tài)庫加載到內(nèi)存中,然后程序在運(yùn)行到指定的代碼時(shí),去共享執(zhí)行內(nèi)存中已經(jīng)加載的動(dòng)態(tài)庫可執(zhí)行代碼,最終達(dá)到運(yùn)行時(shí)連接的目的。

          缺點(diǎn):1. 運(yùn)行時(shí)加載,影響性能

          靜態(tài)鏈接過程

          • 操作系統(tǒng)會(huì)讀取可執(zhí)行文件的頭部,檢查文件的合法性,然后從頭部中的 “Program Header” 中讀取每個(gè) “Segment” 的虛擬地址、文件地址和屬性,并將它們映射到進(jìn)程虛擬空間的相應(yīng)位置;
          • 操作系統(tǒng)就會(huì)把控制權(quán)交給可執(zhí)行文件的入口地址,然后程序開始執(zhí)行。

          動(dòng)態(tài)鏈接過程

          • 操作系統(tǒng)會(huì)讀取可執(zhí)行文件的頭部,檢查文件的合法性,然后從頭部中的 “Program Header” 中讀取每個(gè) “Segment” 的虛擬地址、文件地址和屬性,并將它們映射到進(jìn)程虛擬空間的相應(yīng)位置;
          • 操作系統(tǒng)啟動(dòng)一個(gè)動(dòng)態(tài)鏈接器——ld.so,它其實(shí)是個(gè)共享對(duì)象,操作系統(tǒng)同樣通過映射的方式將它加在到進(jìn)程的地址空間中,加載完動(dòng)態(tài)鏈接器之后,將控制權(quán)交給動(dòng)態(tài)鏈接器的入口地址;
          • 動(dòng)態(tài)鏈接器開始執(zhí)行一系列自身的初始化操作,然后根據(jù)當(dāng)前的環(huán)境參數(shù),開始對(duì)可執(zhí)行文件進(jìn)行動(dòng)態(tài)鏈接工作;
          • 所有動(dòng)態(tài)鏈接工作完成后,動(dòng)態(tài)鏈接器就會(huì)將控制權(quán)交給可執(zhí)行文件的入口地址,程序開始正式執(zhí)行。

          程序加載的內(nèi)存分布

          在多任務(wù)操作系統(tǒng)中,每個(gè)進(jìn)程都運(yùn)行在一個(gè)屬于自己的虛擬內(nèi)存中,而虛擬內(nèi)存被分為許多頁,并映射到物理內(nèi)存中,被加載到物理內(nèi)存中的文件才能夠被執(zhí)行。

          • 代碼段(.text):用來存放可執(zhí)行文件的機(jī)器指令。存放在只讀區(qū)域,以防止被修改。
          • 只讀數(shù)據(jù)段(.rodata):用來存放常量存放在只讀區(qū)域,如字符串常量、全局const變量等。
          • 可讀寫數(shù)據(jù)段(.data):用來存放可執(zhí)行文件中已初始化的全局變量和局部靜態(tài)變量。
          • BSS 段(.bss):未初始化的全局變量和局部靜態(tài)變量以及初始化為 0 的全局變量一般放在 .bss 的段里,以節(jié)省內(nèi)存空間。static int a=0;(初始化為 0 的全局變量(靜態(tài)變量)放在 .bss)。
          • 堆:用來容納應(yīng)用程序動(dòng)態(tài)分配的內(nèi)存區(qū)域。當(dāng)程序使用 malloc 或 new 分配內(nèi)存時(shí),得到的內(nèi)存來自堆。堆通常位于棧的下方。向上生長
          • 棧:用于維護(hù)函數(shù)調(diào)用的上下文。棧通常分配在用戶空間的最高地址處分配。向下生長
          • 動(dòng)態(tài)鏈接庫映射區(qū):如果程序調(diào)用了動(dòng)態(tài)鏈接庫,則會(huì)有這一部分。該區(qū)域是用于映射裝載的動(dòng)態(tài)鏈接庫。
          • 保留區(qū):內(nèi)存中受到保護(hù)而禁止訪問的內(nèi)存區(qū)域。

          溢出,越界,泄漏

          溢出

          1、棧溢出:棧的大小通常是 1M-2M,所以棧溢出包含兩種情況,一是分配的的大小超過棧的最大值,二是分配的大小沒有超過最大值,但是接收的 buff 比新 buff 小 ,具體情況如下。

          char a[10] = {0};
          strcpy(a, "abjjijjlljiojohihiihiiiiiiiiiiiiiiiiiiiiiiiiii");

          注意:調(diào)試時(shí)棧溢出的異常要在函數(shù)調(diào)用結(jié)束后才會(huì)檢測到,因?yàn)闂J窃诤瘮?shù)結(jié)束時(shí)才會(huì)開始進(jìn)行出棧操作。

          2、內(nèi)存溢出:使用 malloc 和 new 分配的內(nèi)存,在拷貝時(shí)接收 buff 小于新 buff 時(shí)造成的現(xiàn)象。

          越界

          通常指數(shù)組越界

          泄露

          指堆內(nèi)存泄漏,是指使用 malloc 和 new 分配的內(nèi)存沒有釋放造成的

          往期推薦




          專輯 | 趣味設(shè)計(jì)模式
          專輯 | 音視頻開發(fā)
          專輯 | C++ 進(jìn)階
          專輯 | 超硬核 Qt
          專輯 | 玩轉(zhuǎn) Linux
          專輯 | GitHub 開源推薦
          專輯 | 程序人生


          關(guān)注公眾號(hào)「高效程序員」??一起優(yōu)秀!

          回復(fù) “入群” 進(jìn)技術(shù)交流群,回復(fù) “1024” 獲取海量學(xué)習(xí)資源。
          瀏覽 42
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  少妇一级婬片50分钟 | 国产靠比| 一本久道无码 | 国产三级成人综合视频 | 又大又粗免费视频 |