天天看點

c++知識彙總

指針和引用的差別

1.指針是一個新的變量,指向另一個變量的位址,我們可以通過通路這個位址來修改另一個變量;而引用是一個别名,對引用的操作就是對變量的本身進行操作

2.指針可以有多級,引用隻有一級

3.傳參的時候,使用指針的話需要解引用才能對參數進行修改,而使用引用可以直接對參數進行修改

指針的大小一般是4個位元組,引用的大小取決于被引用對象的大小(指的是使用sizeof運算符得到的結果,引用本質上還是使用指針,是以所占記憶體和指針是一樣的)

指針可以為空,引用不可以。

在函數參數傳遞的時候,什麼時候使用指針,什麼時候使用引用?

1.需要傳回函數内局部變量的記憶體的時候用指針。使用指針傳參需要開辟記憶體,用完要記得釋放指針,不然會記憶體洩漏。而傳回局部變量的引用是沒有意義的

2.對棧空間大小比較敏感(比如遞歸)的時候使用引用。使用引用傳遞不需要建立臨時變量,開銷要更小

3.類對象作為參數傳遞的時候使用引用,這是c++類對象傳遞的标準方式

堆和棧有什麼差別

1.從定義上:堆是由new和malloc開辟的一塊記憶體,由程式員手動管理,棧是編譯器自動管理的記憶體,存放函數的參數和局部變量。

2.從定義上:堆是由new和malloc開辟的一塊記憶體,由程式員手動管理,棧是編譯器自動管理的記憶體,存放函數的參數和局部變量。

3.堆的生長空間向上,位址越來越大,棧的生長空間向下,位址越來越小

堆快一點還是棧快一點?

棧快一點。因為作業系統會在底層對棧提供支援,會配置設定專門的寄存器存放棧的位址,棧的入棧出棧操作也十分簡單,并且有專門的指令執行,是以棧的效率比較高也比較快。而堆的操作是由c/c++函數庫提供的,在配置設定堆記憶體的時候需要一定的算法尋找合适大小的記憶體。并且擷取堆的内容需要兩次通路,第一次通路指針,第二次根據指針儲存的位址通路記憶體,是以堆比較慢。

new和delete是如何實作的,new 與 malloc的異同處

1.在new一個對象的時候,首先會調用malloc為對象配置設定記憶體空間,然後調用對象的構造函數。delete會調用對象的析構函數,然後調用free回收記憶體

2.new與malloc都會配置設定空間,但是new還會調用對象的構造函數進行初始化,malloc需要給定空間大小,而new隻需要對象名

既然有了malloc/free,c++中為什麼還需要new/delete呢

答:對于非内部資料對象(eg:類對象),隻用malloc/free無法滿足動态對象的要求。這是因為對象在建立的同時需要自動執行構造函數,對象在消亡之前要自動執行析構函數,而由于malloc/free是庫函數而不是運算符,不在編譯器的控制權限内,也就不能自動執行構造函數和析構函數。是以,不能将執行構造函數和析構函數的任務強加給malloc/free。是以,在c++中需要一個能完成動态記憶體配置設定和初始化工作的運算符new,以及一個能完成清理和釋放記憶體工作的運算符delete。

什麼是内部資料類型和非内部資料類型

内部資料類型是編譯器本身就認識的,不需要使用者自己定義。如:基本資料類型:int,char,double等都是内部資料類型;

2:非内部資料類型不是編譯器本身就認識的,需要使用者自己定義才能讓編譯器識别。如:由class,struct,union等關鍵字修飾 的變量都是非内部資料類型

為什麼庫函數不在編譯器控制權限内,而運算符在

庫函數是已經編譯的代碼,編譯器不會在編譯檢查,由連結器将庫同使用者寫的代碼合成exe檔案。而運算符是否正确,編譯器在編譯掃描分析時就可以判定。

c和c++的差別

1.c是面向過程的語言,c++是面向對象的語言,c++有“封裝,繼承和多态”的特性。封裝隐藏了實作細節,使得代碼子產品化。繼承通過子類繼承父類的方法和屬性,實作了代碼重用。多态則是“一個接口,多個實作”,通過子類重寫父類的虛函數,實作了接口重用。

2.c和c++記憶體管理的方法不一樣,c使用malloc/free,c++除此之外還用new/delete

3.c++中還有函數重載和引用等概念

delete和delete[]的差別

1.delete隻會調用一次析構函數,而delete[]會調用每個成員的析構函數

2.用new配置設定的記憶體用delete釋放,用new[]配置設定的記憶體用delete[]釋放

c++、java的聯系與差別,包括語言特性、垃圾回收、應用場景等(java的垃圾回收機制)

1.c++ 和java都是面向對象的語言,c++是編譯成可執行檔案直接運作的,java是編譯之後在java虛拟機上運作的,是以java有良好的跨平台特性,但是執行效率沒有c++ 高。

2.c++的記憶體管理由程式員手動管理,java的記憶體管理是由java虛拟機完成的,它的垃圾回收使用的是标記-回收算法

3.c++有指針,java沒有指針,隻有引用

4.java和c++都有構造函數,但是c++有析構函數但是java沒有

c++和python的差別

1.python是一種腳本語言,是解釋執行的,而c++是編譯語言,是需要編譯後在特定平台運作的。python可以很友善的跨平台,但是效率沒有c++高。

2.python使用縮進來區分不同的代碼塊,c++使用花括号來區分

3.c++中需要事先定義變量的類型,而python不需要,python的基本資料類型隻有數字,布爾值,字元串,清單,元組等等

4.python的庫函數比c++的多,調用起來很友善

struct和class的差別

1.使用struct時,它的成員的通路權限預設是public的,而class的成員預設是private的

2.struct的繼承預設是public繼承,而class的繼承預設是private繼承

3.class可以用作模闆,而struct不能

define 和const的聯系與差別(編譯階段、安全性、記憶體占用等

聯系:它們都是定義常量的一種方法

差別:

1.define定義的常量沒有類型,隻是進行了簡單的替換,可能會有多個拷貝,占用的記憶體空間大,const定義的常量是有類型的,存放在靜态存儲區,隻有一個拷貝,占用的記憶體空間小。

2.define定義的常量是在預處理階段進行替換,而const在編譯階段确定它的值。

3.define不會進行類型安全檢查,而const會進行類型安全檢查,安全性更高

4.const可以定義函數而define不可以

在c++中const的用法(定義,用途)

1.const修飾類的成員變量時,表示常量不能被修改

2.const修飾類的成員函數,表示該函數不會修改類中的資料成員,不會調用其他非const的成員函數(非const對象是可以調用const成員函數的,const對象是不可以調用類中的非const成員函數 與傳入的那個this指針有關系)

c++中的static用法和意義

總:static的意思是靜态的,可以用來修飾變量,函數和類成員

變量:被static修飾的變量就是靜态變量,它會在程式運作過程中一直存在,會被放在靜态存儲區。局部靜态變量的作用域在函數體中,全局靜态變量的作用域在這個檔案裡。

函數:被static修飾的函數就是靜态函數,靜态函數隻能在本檔案中使用,不能被其他檔案調用,也不會和其他檔案中的同名函數沖突。

類:而在類中,被static修飾的成員變量是類靜态成員,這個靜态成員會被類的多個對象共用。被static修飾的成員函數也屬于靜态成員,不是屬于某個對象的,通路這個靜态函數不需要引用對象名,而是通過引用類名來通路。

(靜态成員函數要通路非靜态成員時,要用過對象來引用。局部靜态變量在函數調用結束後也不會被回收,會一直在程式記憶體中,直到該函數再次被調用,它的值還是保持上一次調用結束後的值。) 注:空類的大小是1, 在c++中空類會占一個位元組,這是為了讓對象的執行個體能夠互相差別,當該空白類作為基類時,該類的大小就優化為0了,子類的大小就是子類本身的大小。這就是所謂的空白基類最優化。 靜态成員存放在靜态存儲區,不占用類的大小, 普通函數也不占用類大小

c++的stl介紹(這個系列也很重要,建議侯捷老師的這方面的書籍與視訊),其中包括記憶體管理allocator,函數,實作機理,多線程實作等

1.算法包括排序,複制等常用算法,以及不同容器特定的算法

2.容器就是資料的存放形式,包括序列式容器和關聯式容器,序列式容器就是list,vector等,關聯式容器就是set,map等。

3.疊代器就是在不暴露容器内部結構的情況下對容器的周遊

stl源碼中的hash表的實作

1.stl中的hash表就unordered_map。使用的是哈希進行實作(注意與map的差別)。它記錄的鍵是元素的哈希值,通過對比元素的哈希值來确定元素的值。

2.unordered_map的底層實作是hashtable,采用開鍊法(也就是用桶)來解決哈希沖突,預設的桶大小是

解決哈希沖突的方式?

1.線性探查。該元素的哈希值對應的桶不能存放元素時,循序往後一一查找,直到找到一個空桶為止,在查找時也一樣,當哈希值對應位置上的元素與所要尋找的元素不同時,就往後一一查找,直到找到吻合的元素,或者空桶。

2.二次探查。該元素的哈希值對應的桶不能存放元素時,就往後尋找1^2,2^2,3^2,4^2.....i^2個位置

3.雙散列函數法。當第一個散列函數發生沖突的時候,使用第二個散列函數進行哈希,作為步長

4.開鍊法。在每一個桶中維護一個連結清單,由元素哈希值尋找到這個桶,然後将元素插入到對應的連結清單中,stl的hashtable就是采用這種實作方式。

5.建立公共溢出區。當發生沖突時,将所有沖突的資料放在公共溢出區

stl中unordered_map和map的差別

1.unordered_map是使用哈希實作的,占用記憶體比較多,查詢速度比較快,是常數時間複雜度。它内部是無序的,需要實作==操作符。

map底層是采用紅黑樹實作的,插入删除查詢時間複雜度都是o(log(n)),它的内部是有序的,是以需要實作比較操作符(<)。

stl中vector的實作

三個指針 start ,finish, end of storage

1.stl中的vector是封裝了動态數組的順序容器。不過與動态數組不同的是,vector可以根據需要自動擴大容器的大小。具體政策是每次容量不夠用時重新申請一塊大小為原來容量兩倍的記憶體,将原容器的元素拷貝至新容器,并釋放原空間,傳回新空間的指針。

c++中vector和list的差別

1.list是由雙向連結清單實作的,是以記憶體空間是不連續的。隻能通過指針通路資料,是以list的随機存取非常沒有效率,時間複雜度為o(n); 但由于連結清單的特點,能高效地進行插入和删除。

2.vector擁有一段連續的記憶體空間,能很好的支援随機存取,是以vector::iterator支援“+”,“+=”,“<”等操作符。

3.list的記憶體空間可以是不連續,它不支援随機通路,是以list::iterator則不支援“+”、“+=”、“<”等

c++中的重載和重寫的差別

函數壓榨

1.重載(overload):在c++程式中,可以将語義、功能相似的幾個函數用同一個名字表示,但參數或傳回值不同(包括類型、順序不同),即函數重載。

(1)相同的範圍(在同一個類中);

(2)函數名字相同;

(3)參數不同;

(4)virtual 關鍵字可有可無。

2.重寫(overwide):是指派生類的函數屏蔽了與其同名的基類函數,規則如下:

(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數将被隐藏(注意别與重載混淆)。

(2)如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隐藏(注意别與覆寫混淆)。

3.覆寫:是指派生類函數覆寫基類函數,特征是:

(1)不同的範圍(分别位于派生類與基類);

(3)參數相同;

(4)基類函數必須有virtual 關鍵字

c++記憶體管理

c++的記憶體空間分為以下5個部分:

靜态存儲區:記憶體在程式編譯的時候就已經配置設定好,這塊記憶體在程式的整個運作期間都存在。它主要存放靜态資料(局部static變量,全局static變量)、全局變量和常量。

棧區:在執行函數時,函數(包括main函數)内局部變量的存儲單元都可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧記憶體配置設定運算内置于處理器的指令集中,效率很高,但是配置設定的記憶體容量有限。(任何變量都處于棧區,例如int a[] = {1, 2},變量a處于棧區。數組的内容也存在于棧區。)

堆區:亦稱動态記憶體配置設定。程式在運作的時候用malloc或new申請任意大小的記憶體,程式員自己負責在适當的時候用free或delete釋放記憶體。動态記憶體的生存期可以由我們決定,如果我們不釋放記憶體,程式将在最後才釋放掉動态記憶體。 但是,良好的程式設計習慣是:如果某動态記憶體不再使用,需要将其釋放掉,并立即将指針置位null,防止産生野指針。

文字常量區:常量字元串放在這裡,程式結束後由系統釋放。

程式代碼區:存放函數體的二進制代碼。

指針:函數指針指向code區,是程式運作的指令代碼,資料指針指向data,heap,stack區,是程式依賴以運作的各種資料

注:當static用來修飾全局變量的時候,它就改變了全局變量的作用域(在聲明它的檔案之外是不可見的),但是沒有改變它的存放位置,還是在靜态存儲區中。

堆與棧的差別

1、管理方式不同

2、空間大小不同

3、産生碎片不同

4、生長方向不同

5、配置設定方式不同

6、配置設定效率不同(棧是機器系統提供的資料結構,計算機會在底層對棧提供支援:配置設定專門的寄存器存放棧的位址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高)

堆和棧相比,由于大量new/delete的使用,容易造成大量的記憶體碎片;由于沒有專門的系統支援,效率很低;由于可能引發使用者态和核心态的切換,記憶體的 介紹面向對象的三大特性,并且舉例說明每一個。 1.封裝隐藏了類的實作細節和成員資料,實作了代碼子產品化,如類裡面的private和public; 2.繼承使得子類可以複用父類的成員和方法,實作了代碼重用; 3.多态則是“一個接口,多個實作”,通過父類調用子類的成員,實作了接口重用,如父類的指針指向子類的對象 多态的實作 c++ 多态包括編譯時多态和運作時多态,編譯時多态展現在函數重載和模闆上,運作時多态展現在虛函數上。 虛函數:在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運作時将會根據對象的實際類型來調用相應的函數。如果對象類型是派生類,就調用派生類的函數;如果對象類型是基類,就調用基類的函數.

c++虛函數相關(虛函數表,虛函數指針),虛函數的實作原理

c++的虛函數是實作多态的機制。它是通過虛函數表實作的,虛函數表是每個類中存放虛函數位址的指針數組,類的執行個體在調用函數時會在虛函數表中尋找函數位址進行調用,如果子類覆寫了父類的函數,則子類的虛函數表會指向子類實作的函數位址,否則指向父類的函數位址。一個類的所有執行個體都共享同一張虛函數表。

如果多重繼承和多繼承的話,子類的虛函數表長什麼樣子?

多重繼承的情況下越是祖先的父類的虛函數更靠前,多繼承的情況下越是靠近子類名稱的類的虛函數在虛函數表中更靠前

實作編譯器處理虛函數表應該如何處理

如果類中有虛函數,就将虛函數的位址記錄在類的虛函數表中。派生類在繼承基類的時候,如果有重寫基類的虛函數,就将虛函數表中相應的函數指針設定為派生類的函數位址,否則指向基類的函數位址。

基類的析構函數一般寫成虛函數的原因

首先析構函數可以為虛函數,當析構一個指向子類的父類指針時,編譯器可以根據虛函數表尋找到子類的析構函數進行調用,進而正确釋放子類對象的資源。

如果析構函數不被聲明成虛函數,則編譯器實施靜态綁定,在删除指向子類的父類指針時,隻會調用父類的析構函數而不調用子類析構函數,這樣就會造成子類對象析構不完全造成記憶體洩漏。

構造函數為什麼一般不定義為虛函數

1.由于對象還未建立成功,編譯器無法知道對象的實際類型

2.虛函數的調用需要虛函數表指針,而該指針存放在對象的記憶體空間中;若構造函數聲明為虛函數,那麼由于對象還未建立,還沒有記憶體空間,更沒有虛函數表位址用來調用虛函數即構造函數了

構造函數或者析構函數中調用虛函數會怎樣

在構造函數中調用虛函數,由于目前對象還沒有構造完成,此時調用的虛函數指向的是基類的函數實作方式。

在析構函數中調用虛函數,此時調用的是子類的函數實作方式。

純虛函數

純虛函數是隻有聲明沒有實作的虛函數,是對子類的限制,是接口繼承

包含純虛函數的類是抽象類,它不能被執行個體化,隻有實作了這個純虛函數的子類才能生成對象

使用場景:當這個類本身産生一個執行個體沒有意義的情況下,把這個類的函數實作為純虛函數,比如動物可以派生出老虎兔子,但是執行個體化一個動物對象就沒有意義。并且可以規定派生的子類必須重寫某些函數的情況下可以寫成純虛函數。

靜态綁定和動态綁定差別

靜态綁定發生在編譯期,動态綁定發生在運作期;

對象的動态類型可以更改,但是靜态類型無法更改;

要想實作動态,必須使用動态綁定;

在繼承體系中隻有虛函數使用的是動态綁定,其他的全部是靜态綁定;

注:1.絕對不要重新定義繼承而來的非虛(non-virtual)函數(《effective c++ 第三版》條款36),因為這樣導緻函數調用由對象聲明時的靜态類型确定了,而和對象本身脫離了關系,沒有多态,也這将給程式留下不可預知的隐患和莫名其妙的bug; 2.絕對不要重新定義一個繼承而來的virtual函數的預設參數值,因為預設參數值都是靜态綁定(為了執行效率),而virtual函數卻是動态綁 深拷貝和淺拷貝的差別

淺拷貝就是将對象的指針進行簡單的複制,原對象和副本指向的是相同的資源。

而深拷貝是新開辟一塊空間,将原對象的資源複制到新的空間中,并傳回該空間的位址。

深拷貝可以避免重複釋放和寫沖突。例如使用淺拷貝的對象進行釋放後,對原對象的釋放會導緻記憶體洩漏或程式崩潰。

對象複用的了解,零拷貝的了解

對象複用指得是設計模式,對象可以采用不同的設計模式達到複用的目的,最常見的就是繼承群組合模式了。

零拷貝指的是在進行操作時,避免cpu從一處存儲拷貝到另一處存儲。在linux中,我們可以減少資料在核心空間和使用者空間的來回拷貝實作,比如通過調用mmap()來代替read調用

用程式調用mmap(),磁盤上的資料會通過dma被拷貝的核心緩沖區,接着作業系統會把這段核心緩沖區與應用程式共享,這樣就不需要把核心緩沖區的内容往使用者空間拷貝。應用程式再調用write(),作業系統直接将核心緩沖區的内容拷貝到socket緩沖區中,這一切都發生在核心态,最後,socket緩沖區再把資料發到網卡去

c++中的構造函數主要有三種類型:預設構造函數、重載構造函數和拷貝構造函數

1.預設構造函數是當類沒有實作自己的構造函數時,編譯器預設提供的一個構造函數。

2.重載構造函數也稱為一般構造函數,一個類可以有多個重載構造函數,但是需要參數類型或個數不相同。可以在重載構造函數中自定義類的初始化方式。

3.拷貝構造函數是在發生對象複制的時候調用的。

什麼情況下會調用拷貝構造函數

1.對象以值傳遞的方式傳入函數參數

2.對象以值傳遞的方式從函數傳回

3.對象需要通過另外一個對象進行初始化

結構體記憶體對齊方式和為什麼要進行記憶體對齊

因為結構體的成員可以有不同的資料類型,所占的大小也不一樣。同時,由于cpu讀取資料是按塊讀取的,記憶體對齊可以使得cpu一次就可以将所需的資料讀進來

記憶體洩露的定義,如何檢測與避免

動态配置設定記憶體所開辟的空間,在使用完畢後未手動釋放,導緻一直占據該記憶體,即為記憶體洩漏。

造成記憶體洩漏的幾種原因:

1)類的構造函數和析構函數中new和delete沒有配套

2)在釋放對象數組時沒有使用delete[],使用了delete

3)沒有将基類的析構函數定義為虛函數,當基類指針指向子類對象時,如果基類的析構函數不是virtual,那麼子類的析構函數将不會被調用,子類的資源沒有正确釋放,是以造成記憶體洩露

4)沒有正确的清楚嵌套的對象指針

避免方法:

1.malloc/free要配套

2.使用智能指針;

3.将基類的析構函數設為虛函數;

c++的智能指針有哪些

auto_ptr,shared_ptr,weak_ptr和unique_ptr。

1.auto_ptr是較早版本的智能指針,在進行指針拷貝和指派的時候,新指針直接接管舊指針的資源并且将舊指針指向空,但是這種方式在需要通路舊指針的時候,就會出現問題。

2.unique_ptr是auto_ptr的一個改良版,不能指派也不能拷貝,保證一個對象同一時間隻有一個智能指針。

3.shared_ptr可以使得一個對象可以有多個智能指針,當這個對象所有的智能指針被銷毀時就會自動進行回收。(内部使用計數機制進行維護)

4.weak_ptr是為了協助shared_ptr而出現的。它不能通路對象,隻能觀測shared_ptr的引用計數,防止出現死鎖

如何判斷weak_ptr() 對象是否失效 1.expired() 檢查引用類型是否删除 2.lock()會傳回shared指針指針會判斷該指針是否為空 3.use_count()可以得到引用的個數 但是速度慢 gdb調試 gdb單獨調試子程序 $ps -ef | grep 程序名 //通過上述指令的到待調試程序的pid $gdb (gdb) attach "pid" //上面的“pid”即待調試程序的pid 調試器選項follow-fork-mode set follow-fork-mode mode(parent / child) 多線程程式 1.info threads:顯示目前可調試的所有線程 2.thread id:調試目标id指定的線程 3.set scheduler-locking[off|on|step]

1.通過設定斷點進行調試 b

2.列印log進行調試 info

3.列印中間結果進行調試 print

遇到coredump要怎麼調試

coredump是程式由于異常或者bug在運作時異常退出或者終止,在一定的條件下生成的一個叫做core的檔案,這個core檔案會記錄程式在運作時的記憶體,寄存器狀态,記憶體指針和函數堆棧資訊等等。對這個檔案進行分析可以定位到程式異常的時候對應的堆棧調用資訊

1.使用gdb指令對core檔案進行調試

gdb [可執行檔案名] [core檔案名]

inline關鍵字說一下 和宏定義有什麼差別

inline是内聯的意思,可以定義比較小的函數。因為函數頻繁調用會占用很多的棧空間,進行入棧出棧操作也耗費計算資源,是以可以用inline關鍵字修飾頻繁調用的小函數。編譯器會在編譯階段将代碼體嵌入内聯函數的調用語句塊中。

1、内聯函數在編譯時展開,而宏在預編譯時展開

2、在編譯的時候,内聯函數直接被嵌入到目标代碼中去,而宏隻是一個簡單的文本替換。

3、内聯函數可以進行諸如類型安全檢查、語句是否正确等編譯功能,宏不具有這樣的功能。

4、宏不是函數,而inline是函數

5、宏在定義時要小心處理宏參數,一般用括号括起來,否則容易出現二義性。而内聯函數不會出現二義性。

6、inline可以不展開,宏一定要展開。因為inline訓示對編譯器來說,隻是一個建議,編譯器可以選擇忽略該建議,不對該函數進行展開。

7、宏定義在形式上類似于一個函數,但在使用它時,僅僅隻是做預處理器符号表中的簡單替換,是以它不能進行參數有效性的檢測,也就不能享受c++編譯器嚴格類型檢查的好處,另外它的傳回值也不能被強制轉換為可轉換的合适的類型,這樣,它的使用就存在着一系列的隐患和局限性。

模闆的用法與适用場景 實作原理

用template <typename t>關鍵字進行聲明,接下來就可以進行模闆函數和模闆類的編寫了

編譯器會對函數模闆進行兩次編譯:第一次編譯在聲明的地方對模闆代碼本身進行編譯,這次編譯隻會進行一個文法檢查,并不會生成具體的代碼。第二次編譯時對代碼進行參數替換後再進行編譯,生成具體的函數代碼。

成員初始化清單的概念,為什麼用成員初始化清單會快一些(性能優勢)?

因為使用成員初始化清單進行初始化的話,會直接使用傳入參數的拷貝構造函數進行初始化,省去了一次執行傳入參數的預設構造函數的過程,否則會調用一次傳入參數的預設構造函數。是以使用成員初始化清單效率會高一些。

(使用初始化清單主要是基于性能問題,對于内置類型,如int, float等,使用初始化類表和在構造函數體内初始化差别不是很大,但是對于類類型來說,最好使用初始化清單,成員變量的初始化順序 成員是按照他們在類中出現的順序進行初始化的,而不是按照他們在初始化清單出現的順序初始化的) 有三種情況是必須使用成員初始化清單進行初始化的: 1.常量成員的初始化,因為常量成員隻能初始化不能指派 2.引用類型 3.沒有預設構造函數的對象必須使用成員初始化清單的方式進行初始化 用過c11嗎,知道c11新特性嗎?

1.自動類型推導auto:auto的自動類型推導用于從初始化表達式中推斷出變量的資料類型。通過auto的自動類型推導,可以大大簡化我們的程式設計工作

2.nullptr是為了解決原來c++中null的二義性問題而引進的一種新的類型,因為null實際上代表的是0,而nullptr是void類型的

3.lambda表達式:它類似javascript中的閉包,它可以用于建立并定義匿名的函數對象,以簡化程式設計工作。

4.thread類和mutex類

5.新的智能指針 unique ptr和shared ptr

c++的調用慣例(簡單一點c++函數調用的壓棧過程)

1.從棧空間配置設定存儲空間

2.從實參的存儲空間複制值到形參棧空間

3.進行運算

數組作為參數的函數調用方式是位址傳遞,形參和實參都指向相同的記憶體空間,調用完成後,形參指針被銷毀,但是所指向的記憶體空間依然存在,不能也不會被銷毀。 當函數有多個傳回值的時候,不能用普通的 return 的方式實作,需要通過傳回位址的形式進行,即位址/指針傳遞。

c++的四種強制轉換

四種強制類型轉換操作符分别為:static_cast、dynamic_cast、const_cast、reinterpret_cast

1.static_cast :用于各種隐式轉換。具體的說,就是使用者各種基本資料類型之間的轉換,比如把int換成char,float換成int等。以及派生類(子類)的指針轉換成基類(父類)指針的轉換。

特性與要點: 1.它沒有運作時類型檢查,是以是有安全隐患的。 2.在派生類指針轉換到基類指針時,是沒有任何問題的,在基類 指針轉換到派生類指針的時候,會有安全問題。 static_cast不能轉換const,volatile等屬性 dynamic_cast:用于動态類型轉換。具體的說,就是在基類指針到派生類指針,或者派生類到基類指針的轉換 1.dynamic_cast能夠提供運作時類型檢查,隻用于含有虛函數的類 2.dynamic_cast如果不能轉換傳回null。 const_cast:: 1.用于去除const常量屬性,使其可以修改 ,也就是說,原本定義為const的變量在定義後就不能進行修改的,但是使const_cast操作之後,可以通過這個指針或變量進行修改; 另外還有volatile屬性的轉換。 4.reinterpret_cast: 1.幾乎什麼都可以轉,用在任意的指針之間的轉換,引用之間的轉換,指針和足夠大的int型之間的轉換,整數到指針的轉換等。但是不夠安全。 string的底層實作 1.string繼承自basic_string,其實是對char進行了封裝,封裝的string包含了char數組,容量,長度等等屬性 string可以進行動态擴充,在每次擴充的時候另外申請一塊原空間大小兩倍的空間(2*n),然後将原字元串拷貝過去,并加上新增的内容 一個函數或者可執行檔案的生成過程或者編譯過程是怎樣的 預處理,編譯,彙編,連結 1.預處理: 對預處理指令進行替換等預處理操作 2.編譯:代碼優化和生成彙編代碼 3.彙編:将彙編代碼轉化為機器語言 4.連結:将目标檔案彼此連結起來 set,map和vector的插入複雜度 1.set,map的插入複雜度就是紅黑樹的插入複雜度,是log(n) 2.unordered_set,unordered_map的插入複雜度是常數,最壞是o(n). 3.vector的插入複雜度是o(n),最壞的情況下(從頭插入)就要對所有其他元素進行移動,或者擴容重新拷貝 定義和聲明的差別 1.聲明是告訴編譯器變量的類型和名字,不會為變量配置設定空間 2.定義就是對這個變量和函數進行記憶體配置設定和初始化。需要配置設定空間,同一個變量可以被聲明多次,但是隻能被定義一次

typdef和define差別

#define是預處理指令,在預處理是執行簡單的替換,不做正确性的檢查

typedef是在編譯時處理的,它是在自己的作用域内給已經存在的類型一個别名

被free回收的記憶體是立即返還給作業系統嗎?為什麼

如果每次free掉的記憶體都還給os的話,尤其是在小位元組的情況下,那麼造成的情況,就是一大塊的記憶體被你弄的千瘡百孔,也就是說一塊記憶體,裡面有很多gap

記憶體管理一般會有一個free block list,free掉的東西就放在這裡來。那麼你可能會釋放很散亂的記憶體過來,沒關系,我們在這裡會嘗試合并這些散亂的block,而malloc首先找的也是free block list,而非從os申請新的記憶體

引用作為函數參數以及傳回值的好處

1.在函數内部可以對此參數進行修改

2.提高函數調用和運作的效率(因為沒有了傳值和生成副本的時間和空間消耗)

限制

1.不能傳回局部變量的引用。因為函數傳回以後局部變量就會被銷毀

2.不能傳回函數内部new配置設定的記憶體的引用。雖然不存在局部變量的被動銷毀問題,可對于這種情況(傳回函數内部new配置設定記憶體的引用),又面臨其它尴尬局面。例如,被函數傳回的引用隻是作為一 個臨時變量出現,而沒有被賦予一個實際的變量,那麼這個引用所指向的空間(由new配置設定)就無法釋放,造成memory leak

3.可以傳回類成員的引用,但是最好是const。因為如果其他對象可以獲得該屬性的非常量的引用,那麼對該屬性的單純指派就會破壞業務規則的完整性。

友元函數和友元類

友元函數:友元函數是指某些雖然不是類成員函數卻能夠通路類的所有成員的函數

友元類:友元類的所有成員函數都是另一個類的友元函數,都可以通路另一個類中的隐藏資訊(包括私有成員和保護成員)。當希望一個類可以存取另一個類的私有成員時,可以将該類聲明為另一類的友元類。

注意事項

1.友元關系不能被繼承

2.友元關系是單向的,不具有交換性。若類b是類a的友元,類a不一定是類b的友元,要看在類中是否有相應的聲明

3.友元關系不具有傳遞性。若類b是類a的友元,類c是b的友元,類c不一定是類a的友元,同樣要看類中是否有相應的申明。

4.必須先定義包含成員函數的類,才能将成員函數設為友元。另一方面,不必預先聲明類和非成員函數來将它們設為友元。

說一下volatile關鍵字的作用

它修飾的變量的值十分容易被改變,是以編譯器就不會對這個變量進行優化(cpu的優化是讓該變量存放到cpu寄存器而不是記憶體),進而提供穩定的通路。每次讀取volatile的變量時,系統總是會從記憶體中讀取這個變量,并且将它的值立刻儲存。

stl中的sort()算法是用什麼實作的,stable_sort()呢

stl中的sort是用快速排序和插入排序結合的方式實作的,stable_sort()是歸并排序。

vector會疊代器失效嗎?什麼情況下會疊代器失效

1.當vector在插入的時候,如果原來的空間不夠,會将申請新的記憶體并将原來的元素移動到新的記憶體,此時指向原記憶體位址的疊代器就失效了,first和end疊代器都失效

2.當vector在插入的時候,end疊代器肯定會失效

3.當vector在删除的時候,被删除元素以及它後面的所有元素疊代器都失效

為什麼c++沒有實作垃圾回收

1.首先,實作一個垃圾回收器會帶來額外的空間和時間開銷。你需要開辟一定的空間儲存指針的引用計數和對他們進行标記mark。然後需要單獨開辟一個線程在空閑的時候進行free操作。

2.垃圾回收會使得c++不适合進行很多底層的操作