秋招總結------C++面試題總結五

1.​this指針調用成員變量時,堆棧會發生什麼變化?

  1. 當在類的非靜態成員函數訪問類的非靜態成員時,編譯器會自動將對象的地址傳給作爲隱含參數傳遞給函數,這個隱含參數就是this指針。即使你並沒有寫this指針,編譯器在鏈接時也會加上this的,對各成員的訪問都是通過this的。例如你建立了類的多個對象時,在調用類的成員函數時,你並不知道具體是哪個對象在調用,此時你可以通過查看this指針來查看具體是哪個對象在調用。This指針首先入棧,然後成員函數的參數從右向左進行入棧,最後函數返回地址入棧。

2. 靜態綁定和動態綁定的介紹

  1. 對象的靜態類型:對象在聲明時採用的類型。是在編譯期確定的。
  2. 對象的動態類型:目前所指對象的類型。是在運行期決定的。對象的動態類型可以更改,但是靜態類型無法更改。
  3. 靜態綁定:綁定的是對象的靜態類型,某特性(比如函數)依賴於對象的靜態類型,發生在編譯期。
  4. 動態綁定:綁定的是對象的動態類型,某特性(比如函數)依賴於對象的動態類型,發生在運行期。

3. 虛函數的代價?

  1. 帶有虛函數的類,每一個類會產生一個虛函數表,用來存儲指向虛成員函數的指針,增大類;
  2. 帶有虛函數的類的每一個對象,都會有有一個指向虛表的指針,會增加對象的空間大小;
  3. 不能再是內斂的函數,因爲內斂函數在編譯階段進行替代,而虛函數表示等待,在運行階段才能確定到低是採用哪種函數,虛函數不能是內斂函數。

4. 類對象的大小

  1. 類的非靜態成員變量大小,靜態成員不佔據類的空間,成員函數也不佔據類的空間大小;
  2. 內存對齊另外分配的空間大小,類內的數據也是需要進行內存對齊操作的;
  3. 虛函數的話,會在類對象插入vptr指針,加上指針大小;
  4. 當該該類是某類的派生類,那麼派生類繼承的基類部分的數據成員也會存在在派生類中的空間中,也會對派生類進行擴展。

5. ​​​​​​​移動構造函數

  1. 有時候我們會遇到這樣一種情況,我們用對象a初始化對象b後對象a我們就不在使用了,但是對象a的空間還在呀(在析構之前),既然拷貝構造函數,實際上就是把a對象的內容複製一份到b中,那麼爲什麼我們不能直接使用a的空間呢?這樣就避免了新的空間的分配,大大降低了構造的成本。這就是移動構造函數設計的初衷
  2. 拷貝構造函數中,對於指針,我們一定要採用深層複製,而移動構造函數中,對於指針,我們採用淺層複製
  3. C++引入了移動構造函數,專門處理這種,用a初始化b後,就將a析構的情況
  4. 與拷貝類似,移動也使用一個對象的值設置另一個對象的值。但是,又與拷貝不同的是,移動實現的是對象值真實的轉移(源對象到目的對象):源對象將丟失其內容,其內容將被目的對象佔有。移動操作的發生的時候,是當移動值的對象是未命名的對象的時候。這裏未命名的對象就是那些臨時變量,甚至都不會有名稱。典型的未命名對象就是函數的返回值或者類型轉換的對象。使用臨時對象的值初始化另一個對象值,不會要求對對象的複製:因爲臨時對象不會有其它使用,因而,它的值可以被移動到目的對象。做到這些,就要使用移動構造函數和移動賦值:當使用一個臨時變量對象進行構造初始化的時候,調用移動構造函數。類似的,使用未命名的變量的值賦給一個對象時,調用移動賦值操作
  5. Example6 (Example6&& x) : ptr(x.ptr)

        {

            x.ptr = nullptr;

        }

        // move assignment

        Example6& operator= (Example6&& x)

        {

            delete ptr;

            ptr = x.ptr;

            x.ptr=nullptr;

            return *this;

    }

6. ​​​​​​​何時需要合成構造函數

  1. 如果一個類沒有任何構造函數,但他含有一個成員對象,該成員對象含有默認構造函數,那麼編譯器就爲該類合成一個默認構造函數,因爲不合成一個默認構造函數那麼該成員對象的構造函數不能調用;
  2. 沒有任何構造函數的類派生自一個帶有默認構造函數的基類,那麼需要爲該派生類合成一個構造函數,只有這樣基類的構造函數才能被調用
  3. 帶有虛函數的類,虛函數的引入需要進入虛表,指向虛表的指針,該指針是在構造函數中初始化的,所以沒有構造函數的話該指針無法被初始化;
  4. 帶有一個虛基類的類
  • 並不是任何沒有構造函數的類都會合成一個構造函數
  • 編譯器合成出來的構造函數並不會顯示設定類內的每一個成員變量

7. ​​​​​​​ 何時需要合成複製構造函數

  • 有三種情況會以一個對象的內容作爲另一個對象的初值:
  1. 對一個對象做顯示的初始化操作,X xx = x;
  2. 對象被當做參數交給某個函數時;
  3. 當函數傳回一個類對象時;
  • ​​​​​​​
  1. 如果一個類沒有拷貝構造函數,但是含有一個類類型的成員變量,該類型含有拷貝構造函數,此時編譯器會爲該類合成一個拷貝構造函數;
  2. 如果一個類沒有拷貝構造函數,但是該類繼承自含有拷貝構造函數的基類,此時編譯器會爲該類合成一個拷貝構造函數;
  3. 如果一個類沒有拷貝構造函數,但是該類聲明或繼承了虛函數,此時編譯器會爲該類合成一個拷貝構造函數;
  4. 如果一個類沒有拷貝構造函數,但是該類含有虛基類,此時編譯器會爲該類合成一個拷貝構造函數;

8. ​​​​​​​何時需要成員初始化列表?過程是什麼?

  1. 當初始化一個引用成員變量時;
  2. 初始化一個const成員變量時;
  3. 當調用一個基類的構造函數,而構造函數擁有一組參數時;
  4. 當調用一個成員類的構造函數,而他擁有一組參數;
  5. 編譯器會一一操作初始化列表,以適當順序在構造函數之內安插初始化操作,並且在任何顯示用戶代碼前。list中的項目順序是由類中的成員聲明順序決定的,不是初始化列表中的排列順序決定的。

9. ​​​​​​​程序員定義的析構函數被擴展的過程?

  1. 析構函數函數體被執行
  2. 如果class擁有成員類對象,而後者擁有析構函數,那麼它們會以其聲明順序的相反順序被調用;
  3. 如果對象有一個vptr,現在被重新定義
  4. 如果有任何直接的上一層非虛基類擁有析構函數,則它們會以聲明順序被調用;
  5. 如果任何虛基類擁有析構函數

10. ​​​​​​​哪些函數不能是虛函數

  1. 構造函數,構造函數初始化對象,派生類必須知道基類函數幹了什麼,才能進行構造;當有虛函數時,每一個類有一個虛表,每一個對象有一個虛表指針,虛表指針在構造函數中初始化;
  2. 內聯函數,內聯函數表示在編譯階段進行函數體的替換操作,而虛函數意味着在運行期間進行類型確定,所以內聯函數不能是虛函數;
  3. 靜態函數,靜態函數不屬於對象屬於類,靜態成員函數沒有this指針,因此靜態函數設置爲虛函數沒有任何意義。
  4. 友元函數友元函數不屬於類的成員函數,不能被繼承。對於沒有繼承特性的函數沒有虛函數的說法。
  5. 普通函數,普通函數不屬於類的成員函數,不具有繼承特性,因此普通函數沒有虛函數。

11. ​​​​​​​sizeof 和strlen 的區別

  1. strlen計算字符串的具體長度(只能是字符串),不包括字符串結束符。返回的是字符個數。
  2. sizeof計算聲明後所佔的內存數(字節大小),不是實際長度。
  3. sizeof是一個取字節運算符,而strlen是個函數
  4. sizeof的返回值=字符個數*字符所佔的字節數,字符實際長度小於定義的長度,此時字符個數就等於定義的長度。若未給出定義的大小,分類討論,對於字符串數組,字符大 小等於實際的字符個數+1;對於整型數組,字符個數爲實際的字符個數。字符串每個字符佔1個字節,整型數據每個字符佔的字節數需根據系統的位數類確定,32位佔4個字節。
  5. sizeof可以用類型做參數strlen只能用char*做參數,且必須以‘\0’結尾,sizeof還可以用函數做參數;
  6. 數組做sizeof的參數不退化,傳遞給strlen就退化爲指針;

12. ​​​​​​​將“引用”作爲函數參數有哪些特點?

  1. 傳遞引用給函數與傳遞指針的效果是一樣的。這時,被調函數的形參就成爲原來主調函數中的實參變量或對象的一個別名來使用,所以在被調函數中對形參變量的操作就是對其相應的目標對象(在主調函數中)的操作。
  2. 使用引用傳遞函數的參數,在內存中並沒有產生實參的副本,它是直接對實參操作;而使用一般變量傳遞函數的參數,當發生函數調用時,需要給形參分配存儲單元,形參變量是實參變量的副本;如果傳遞的是對象,還將調用拷貝構造函數。因此,當參數傳遞的數據較大時,用引用比用一般變量傳遞參數的效率和所佔空間都好。
  3. 使用指針作爲函數的參數雖然也能達到與使用引用的效果,但是,在被調函數中同樣要給形參分配存儲單元,且需要重複使用"*指針變量名"的形式進行運算,這很容易產生錯誤且程序的閱讀性較差;另一方面,在主調函數的調用點處,必須用變量的地址作爲實參。而引用更容易使用,更清晰。

13. ​​​​​​​局部變量全局變量的問題?

  1. 局部會屏蔽全局。要用全局變量,需要使用"::"局部變量可以與全局變量同名,在函數內引用這個變量時,會用到同名的局部變量,而不會用到全局變量。對於有些編譯器而言,在同一個函數內可以定義多個同名的局部變量,比如在兩個循環體內都定義一個同名的局部變量,而那個局部變量的作用域就在那個循環體內。
  2. 如何引用一個已經定義過的全局變量,可以用引用頭文件的方式,也可以用extern關鍵字,如果用引用頭文件方式來引用某個在頭文件中聲明的全局變理,假定你將那個變寫錯了,那麼在編譯期間會報錯,如果你用extern方式引用時,假定你犯了同樣的錯誤,那麼在編譯期間不會報錯,而在連接期間報錯。
  3. 全局變量可不可以定義在可被多個.C文件包含的頭文件中,在不同的C文件中以static形式來聲明同名全局變量。可以在不同的C文件中聲明同名的全局變量,前提是其中只能有一個C文件中對此變量賦初值,此時連接不會出錯。

​​​​​​​14. ​​​​​​​數組和指針的區別?

  1. 數組在內存中是連續存放的,開闢一塊連續的內存空間;數組所佔存儲空間:sizeof(數組名);數組大小:sizeof(數組名)/sizeof(數組元素數據類型);
  2. 運算符sizeof 可以計算出數組的容量(字節數)。sizeof(p),p 爲指針得到的是一個指針變量的字節數,而不是p 所指的內存容量。
  3. 編譯器爲了簡化對數組的支持,實際上是利用指針實現了對數組的支持。具體來說,就是將表達式中的數組元素引用轉換爲指針加偏移量的引用。
  4. 在向函數傳遞參數的時候,如果實參是一個數組,那用於接受的形參爲對應的指針。也就是傳遞過去是數組的首地址而不是整個數組,能夠提高效率
  5. 在使用下標的時候,兩者的用法相同,都是原地址加上下標值不過數組的原地址就是數組首元素的地址是固定的,指針的原地址就不是固定的。

15. ​​​​​​​如何禁止自動生成拷貝構造函數?

  1. 爲了阻止編譯器默認生成拷貝構造函數和拷貝賦值函數,我們需要手動去重寫這兩個函數,某些情況下,爲了避免調用拷貝構造函數和拷貝賦值函數,我們需要將他們設置成private,防止被調用
  2. 類的成員函數和friend函數還是可以調用private函數,如果這個private函數只聲明不定義,則會產生一個連接錯誤。
  3. 針對上述兩種情況,我們可以定一個base類,在base類中將拷貝構造函數和拷貝賦值函數設置成private,那麼派生類中編譯器將不會自動生成這兩個函數,且由於base類中該函數是私有的,因此,派生類將阻止編譯器執行相關的操作。

16. ​​​​​​​虛函數與純虛函數的區別在於

  1. 純虛函數只有定義沒有實現,虛函數既有定義又有實現;
  2. 含有純虛函數的類不能定義對象,含有虛函數的類能定義對象;

17. ​​​​​​​智能指針怎麼用?智能指針出現循環引用怎麼解決?

  1. shared_ptr
  • 調用一個名爲make_shared的標準庫函數,shared_ptr<int> p = make_shared<int>(42);通常用auto更方便,auto p = …;shared_ptr<int> p2(new int(2));

    每個shared_ptr都有一個關聯的計數器,通常稱爲引用計數,一旦一個shared_ptr的計數器變爲0,它就會自動釋放自己所管理的對象;shared_ptr的析構函數就會遞減它所指的對象的引用計數。如果引用計數變爲0,shared_ptr的析構函數就會銷燬對象,並釋放它佔用的內存。

  1. unique_ptr
  • 一個unique_ptr擁有它所指向的對象。某個時刻只能有一個unique_ptr指向一個給定對象。當unique_ptr被銷燬時,它所指向的對象也被銷燬。

  1. weak_ptr
  • weak_ptr是一種不控制所指向對象生存期的智能指針,它指向由一個shared_ptr管理的對象,將一個weak_ptr綁定到一個shared_ptr不會改變引用計數,一旦最後一個指向對象的shared_ptr被銷燬,對象就會被釋放,即使有weak_ptr指向對象,對象還是會被釋放。

  1. 弱指針用於專門解決shared_ptr循環引用的問題,weak_ptr不會修改引用計數,即其存在與否並不影響對象的引用計數器。循環引用就是:兩個對象互相使用一個shared_ptr成員變量指向對方。弱引用並不對對象的內存進行管理,在功能上類似於普通指針,然而一個比較大的區別是,弱引用能檢測到所管理的對象是否已經被釋放,從而避免訪問非法內存。

18.​​​​​​​ 回調函數的作用

  1. 當發生某種事件時,系統或其他函數將會自動調用你定義的一段函數;
  2. 回調函數就相當於一箇中斷處理函數,由系統在符合你設定的條件時自動調用。爲此,你需要做三件事:1,聲明;2,定義;3,設置觸發條件,就是在你的函數中把你的回調函數名稱轉化爲地址作爲一個參數,以便於系統調用;
  3. 回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作爲參數傳遞給另一個函數,當這個指針被用爲調用它所指向的函數時,我們就說這是回調函數;
  4. 因爲可以把調用者與被調用者分開。調用者不關心誰是被調用者,所有它需知道的,只是存在一個具有某種特定原型、某些限制條件(如返回值爲int)的被調用函數。​​​​​​​

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章