C++面試常問問題彙總

1. extern關鍵字的作用

     extern置於變量或函數前,用於標示變量或函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。它只要有兩個作用:

  • 當它與“C”一起連用的時候,如:extern "C" void fun(int a,int b);則告訴編譯器在編譯fun這個函數時候按着C的規矩去翻譯,而不是C++的(這與C++的重載有關,C++語言支持函數重載,C語言不支持函數重載,函數被C++編譯器編譯後在庫中的名字與C語言的不同
  • 當extern不與“C”在一起修飾變量或函數時,如:extern int g_Int;它的作用就是聲明函數或全局變量的作用範圍的關鍵字,其聲明的函數和變量可以在本模塊或其他模塊中使用。記住它是一個聲明不是定義!也就是說B模塊(編譯單元)要是引用模塊(編譯單元)A中定義的全局變量或函數時,它只要包含A模塊的頭文件即可,在編譯階段,模塊B雖然找不到該函數或變量,但它不會報錯,它會在連接時從模塊A生成的目標代碼中找到此函數。
2.static關鍵字的作用

  •   修飾局部變量

    static修飾局部變量時,使得被修飾的變量成爲靜態變量,存儲在靜態區。存儲在靜態區的數據生命週期與程序相同,在main函數之前初始化,在程序退出時銷燬。(無論是局部靜態還是全局靜態)

  •   修飾全局變量

    全局變量本來就存儲在靜態區,因此static並不能改變其存儲位置。但是,static限制了其鏈接屬性。被static修飾的全局變量只能被該包含該定義的文件訪問(即改變了作用域)。

  •   修飾函數

     static修飾函數使得函數只能在包含該函數定義的文件中被調用。對於靜態函數,聲明和定義需要放在同一個文件夾中。

  •   修飾成員變量
      用static修飾類的數據成員使其成爲類的全局變量,會被類的所有對象共享,包括派生類的對象,所有的對象都只維持同一個實例。 因此,static成員必須在類外進行初始化(初始化格式:int base::var=10;),而不能在構造函數內進行初始化,不過也可以用const修飾static數據成員在類內初始化。
  •   修飾成員函數

     用static修飾成員函數,使這個類只存在這一份函數,所有對象共享該函數,不含this指針,因而只能訪問類的static成員變量靜態成員是可以獨立訪問的,也就是說,無須創建任何對象實例就可以訪問。例如可以封裝某些算法,比如數學函數,如ln,sin,tan等等,這些函數本就沒必要屬於任何一個對象,所以從類上調用感覺更好,比如定義一個數學函數類Math,調用Math::sin(3.14);還可以實現某些特殊的設計模式:如Singleton;

最重要的特性:隱藏

    當同時編譯多個文件時,所有未加static前綴的全局變量和函數都具有全局可見性,其它的源文件也能訪問。利用這一特性可以在不同的文件中定義同名函數和同名變量,而不必擔心命名衝突。static可以用作函數和變量的前綴,對於函數來講,static的作用僅限於隱藏。

不可以同時用const和static修飾成員函數。

   C++編譯器在實現const的成員函數的時候爲了確保該函數不能修改類的實例的狀態,會在函數中添加一個隱式的參數const this*。但當一個成員爲static的時候,該函數是沒有this指針的。也就是說此時const的用法和static是衝突的。我們也可以這樣理解:兩者的語意是矛盾的。static的作用是表示該函數只作用在類型的靜態變量上,與類的實例沒有關係;而const的作用是確保函數不能修改類的實例的狀態,與類型的靜態變量沒有關係。因此不能同時用它們。

3.volatile的作用

     用來修飾變量的,表明某個變量的值可能會隨時被外部改變,因此這些變量的存取不能被緩存到寄存器,每次使用需要重新讀取。   

     假如有一個對象A裏面有一個boolean變量a,值爲true,現在有兩個線程T1,T2訪問變量a,T1把a改成了false後T2讀取a,T2這時讀到的值可能不是false,即T1修改a的這一操作,對T2是不可見的。發生的原因可能是,針對T2線程,爲了提升性能,虛擬機把a變量置入了寄存器(即C語言中的寄存器變量),這樣就會導致,無論T2讀取多少次a,a的值始終爲true,因爲T2讀取了寄存器而非內存中的值。聲明瞭volatile或synchronized 後,就可以保證可見性,確保T2始終從內存中讀取變量,T1始終在內存中修改變量。總結:防止髒讀,增加內存屏障

     也可參考該地址19問答案:http://www.sohu.com/a/166382914_538662

4.const的作用

  • 定義變量爲只讀變量,不可修改
  • 修飾函數的參數和返回值(後者應用比較少,一般爲值傳遞)
  • const成員函數(只需要在成員函數參數列表後加上關鍵字const,如char get() const;)可以訪問const成員變量和非const成員變量,但不能修改任何變量。在聲明一個成員函數時,若該成員函數並不對數據成員進行修改操作,應儘可能將該成員函數聲明爲const成員函數。
  • const對象只能訪問const成員函數,而非const對象可以訪問任意的成員函數,包括const成員函數.即對於class A,有const A a;那麼a只能訪問A的const成員函數。而對於:A b;b可以訪問任何成員函數。

    使用const關鍵字修飾的變量,一定要對變量進行初始化
5.指針與引用的區別

  • 指針只是一個變量,只不過這個變量存儲的是一個地址;而引用跟原來的變量實質上是同一個東西,只不過是原變量的一個別名而已,不佔用內存空間。
  • 引用必須在定義的時候初始化,而且初始化後就不能再改變;而指針不必在定義的時候初始化,初始化後可以改變。
  • 指針可以爲空,但引用不能爲空(這就意味着我們拿到一個引用的時候,是不需要判斷引用是否爲空的,而拿到一個指針的時候,我們則需要判斷它是否爲空。這點經常在判斷函數參數是否有效的時候使用。
  • “sizeof 引用" = 指向變量的大小 , "sizeof 指針"= 指針本身的大小
  • 指針可以有多級,而引用只能是一級

6.new與malloc的區別

  • malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算符。它們都可用於申請動態內存和釋放內存。
  • 對於非內部數據類型的對象而言,光用malloc/free無法滿足動態對象的要求。對象在創建的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數。
  • new可以認爲是malloc加構造函數的執行。new出來的指針是直接帶類型信息的。而malloc返回的都是void指針。

7.C++的多態性

        多態性可以簡單地概括爲“一個接口,多種方法”,程序在運行時才決定調用的函數 。C++多態性主要是通過虛函數實現的,虛函數允許子類重寫override(注意和overload的區別,overload是重載,是允許同名函數的表現,這些函數參數列表/類型不同)

    多態與非多態的實質區別就是函數地址是早綁定還是晚綁定。如果函數的調用,在編譯器編譯期間就可以確定函數的調用地址,並生產代碼,是靜態的,就是說地址是早綁定的。而如果函數調用的地址不能在編譯器期間確定,需要在運行時才確定,這就屬於晚綁定。

      在絕大多數情況下,程序的功能是在編譯的時候就確定下來的,我們稱之爲靜態特性。反之,如果程序的功能是在運行時刻才能確定下來的,則稱之爲動態特性。C++中,虛函數,抽象基類,動態綁定和多態構成了出色的動態特性。

    最常見的用法就是聲明基類的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,可以根據指向的子類的不同而實現不同的方法。

          a、編譯時多態性:通過重載函數實現 
       b、運行時多態性:通過虛函數實現

    有關重載,重寫,覆蓋的區別請移步:https://www.cnblogs.com/LUO77/p/5771237.html

8.虛函數表

     請移步:https://www.cnblogs.com/LUO77/p/5771237.html

9.動態綁定與靜態綁定

  • 靜態綁定發生在編譯期,動態綁定發生在運行期;
  • 對象的動態類型可以更改,但是靜態類型無法更改;
  • 要想實現動態,必須使用動態綁定;
  • 在繼承體系中只有虛函數使用的是動態綁定,其他的全部是靜態綁定;
  • 靜態多態是指通過模板技術或者函數重載技術實現的多態,其在編譯器確定行爲。動態多態是指通過虛函數技術實現在運行期動態綁定的技術

動態綁定:有一個基類,兩個派生類,基類有一個virtual函數,兩個派生類都覆蓋了這個虛函數。現在有一個基類的指針或者引用,當該基類指針或者引用指向不同的派生類對象時,調用該虛函數,那麼最終調用的是該被指向對象對應的派生類自己實現的虛函數。

10.虛函數表是針對類的還是針對對象的?同一個類的兩個對象的虛函數表是怎麼維護的?

編譯器爲每一個類維護一個虛函數表(本質是一個函數指針數組,數組裏面存放了一系列函數地址 ),每個對象的首地址保存着該虛函數表的指針,同一個類的不同對象實際上指向同一張虛函數表。調用形式:*(this指針+調整量)[虛函數在vftable內的偏移]()

在類內部添加一個虛擬函數表指針,該指針指向一個虛擬函數表,該虛擬函數表包含了所有的虛擬函數的入口地址,每個類的虛擬函數表都不一樣,在運行階段可以循此脈絡找到自己的函數入口純虛函數相當於佔位符, 先在虛函數表中佔一個位置由派生類實現後再把真正的函數指針填進去。除此之外和普通的虛函數沒什麼區別。

在單繼承形式下,子類的完全獲得父類的虛函數表和數據。子類如果重寫了父類的虛函數(如fun),就會把虛函數表原本fun對應的記錄(內容MyClass::fun)覆蓋爲新的函數地址(內容MyClassA::fun),否則繼續保持原本的函數地址記錄。

使用這種方式,就可以實現多態的特性。假設我們使用如下語句:

  1. MyClass*pc=new MyClassA;
  2. pc->fun();  

因爲虛函數表內的函數地址已經被子類重寫的fun函數地址覆蓋了,因此該處調用的函數正是MyClassA::fun,而不是基類的MyClass::fun。

如果使用MyClassA對象直接訪問fun,則不會出發多態機制,因爲這個函數調用在編譯時期是可以確定的,編譯器只需要直接調用MyClassA::fun即可。

注:對象不包含虛函數表,只有虛指針,類才包含虛函數表,派生類會生成一個兼容基類的虛函數表

詳情可以參考:http://www.cnblogs.com/fanzhidongyzby/archive/2013/01/14/2859064.html

11.智能指針怎麼實現?什麼時候改變引用計數?

  • 構造函數中計數初始化爲1;
  • 拷貝構造函數中計數值加1;
  • 賦值運算符中,左邊的對象引用計數減一,右邊的對象引用計數加一;
  • 析構函數中引用計數減一;
  • 在賦值運算符和析構函數中,如果減一後爲0,則調用delete釋放對象。

12.內聯函數,宏定義和普通函數的區別

  • 內聯函數要做參數類型檢查,這是內聯函數跟宏相比的優勢
  • 宏定義是在預編譯的時候把所有的宏名用宏體來替換,簡單的說就是字符串替換, 內聯函數則是在編譯的時候進行代碼插入,編譯器會在每處調用內聯函數的地方直接把內聯函數的內容展開,這樣可以省去函數的調用的壓棧出棧的開銷,提高效率。
  • 內聯函數是指嵌入代碼,就是在調用函數的地方不是跳轉,而是把代碼直接寫到那裏去。對於短小簡單的代碼來說,內聯函數可以帶來一定的效率提升,而且和C時代的宏函數相比,內聯函數 更安全可靠。可是這個是以增加空間消耗爲代價
  • const與#define的區別:宏在預處理階段替換,const在編譯階段替換;宏沒有類型,不做安全檢查,const有類型,在編譯階段進行安全檢查

13. C++內存管理

: 存放函數參數以及局部變量,在出作用域時,將自動被釋放.棧內存分配運算內置於處理器的指令集中,效率,但分配的內存容量有限.

:new分配的內存塊(包括數組,類實例),delete手動釋放.如果未釋放,在整個程序結束後,OS會幫你回收掉.

自由存儲區:malloc分配的內存塊,free手動釋放.它和堆有些相似.

全局/靜態區:保存自動全局變量和static變量(包括static全局和局部變量)。靜態區的內容在整個程序的生命週期內都存在,有編譯器在編譯的時候分配(數據段(存儲全局數據和靜態數據)和代碼段(可執行的代碼/只讀常量))。

常量存儲區:常量(const)存於此處,此存儲區不可修改.



棧與堆的區別:       

管理方式不同棧是編譯器自動管理的,堆需手動釋放
空間大小不同32OS,堆內存可達到4GB的的空間,而棧就小得可憐.(VC6,棧默認大小是1M,當然,你可以修改它)
能否產生碎片不同:對於棧來說,進棧/出棧都有着嚴格的順序(先進後出),不會產生碎片;而堆頻繁的new/delete,會造成內存空間的不連續,容易產生碎片.
生長方向不同:向下生長,降序分配內存地址;向上生長,升序分配內在地址.
分配方式不同:堆動態分配,靜態分配;棧分爲靜態分配和動態分配,比如局部變量的分配,就是動態分配(alloca函數),函數參數的分配就是動態分配(我想的).
分配效率不同:棧是系統提供的數據結構,計算機會在底層對棧提供支持,進棧/出棧都有專門的指令,這就決定了棧的效率比較高.堆則不然,它由C/C++函數庫提供,機制複雜,堆的效率要比棧低得多.
可以看出,棧的效率要比堆高很多,所以,推薦大家儘量用棧.不過,雖然棧有如此多的好處,但遠沒有堆使用靈活.

14 . 常用的設計模式

     單例模式:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點;

     工廠模式:定義一個用於創建對象的接口,讓子類決定實例化哪一個類。

     參考:https://www.cnblogs.com/Y1Focus/p/6707121.html

               https://www.zhihu.com/question/34574154?sort=created

15.手寫strcpy,memcpy,strcat,strcmp等函數

https://blog.csdn.net/gao1440156051/article/details/51496782

https://blog.csdn.net/wilsonboliu/article/details/7919773

16.i++是否爲原子操作?

不是。操作系統原子操作是不可分割的,在執行完畢不會被任何其它任務或事件中斷,分爲兩種情況(兩種都應該滿足)

 (1) 在單線程中, 能夠在單條指令中完成的操作都可以認爲是" 原子操作",因爲中斷只能發生於指令之間。

 (2) 在多線程中,不能被其它進程(線程)打斷的操作就叫原子操作。

i++分爲三個階段:

內存到寄存器
寄存器自增
寫回內存
這三個階段中間都可以被中斷分離開.

17.有關數組,指針,函數的三者結合問題

數組指針和指針數組的區別:https://blog.csdn.net/men_wen/article/details/52694069

右左法則的說明:http://www.cnblogs.com/zhangjing0502/archive/2012/06/08/2542059.html

指針常量和常量指針:https://www.zhihu.com/question/19829354

                                 https://blog.csdn.net/xingjiarong/article/details/47282563

注意:所謂指向常量的指針或引用(即常量引用、常量指針),不過是指針或引用“自以爲是”罷了,它們覺得自己指向了常量,所以自覺地不去改變所指對象的值,但這些對象卻可以通過其他途徑改變。

const int *a;  等價於int const *a;    const在前面所以內容不可以改變,但是指針指向可以改變。也就是常量指針

int *const a;  表示的是指針指向不可改變,但是指針所存放的內容可以改變,也即是指針常量

補充:

18.C++中類與結構體的區別?

  • 最本質的一個區別就是默認的訪問控制: struct作爲數據結構的實現體,它默認的數據訪問控制是public的,而class作爲對象的實現體,它默認的成員變量訪問控制是private的。
  • “class”這個關鍵字還用於定義模板參數,就像“typename”。但關鍵字“struct”不用於定義模板參數。

19.析構函數的作用?

析構函數是用來釋放所定義的對象中使用的指針,默認的析構函數不用顯示調用,自建的析構函數要在程序末尾調用。

如果你的類裏面只用到的基本類型,如int char double等,系統的默認析構函數其實什麼都沒有做

但如果你使用了其他的類如vector,string等,系統的默認析構函數就會調用這些類對象的析構函數

如果是自己寫析構函數的話,如果你的類裏面分配了系統資源,如new了內存空間,打開了文件等,那麼在你的析構函數中就必須釋放相應的內存空間和關閉相關的文件;這樣系統就會自動調用你的析構函數釋放資源,避免內存泄漏

例如:

  1. class A
  2. {
  3. private:
  4. char *data;
  5. public:
  6. A()
  7. {
  8. data = new char[10];
  9. }
  10. ~A()
  11. {
  12. delete[] data;
  13. }
  14. };
A  a;
a 中將 new 10個 char
當 a 這個變量消亡的時候,將自動執行 ~A(),釋放空間

對象消亡時,自動被調用,用來釋放對象佔用的空間,避免內存泄漏

20.虛函數的作用?

      虛函數可以讓成員函數操作一般化,用基類的指針指向不同的派生類的對象時,基類指針調用其虛成員函數,則會調用其真正指向對象的成員函數,而不是基類中定義的成員函數(只要派生類改寫了該成員函數)。若不是虛函數,則不管基類指針指向的哪個派生類對象,調用時都會調用基類中定義的那個函數。虛函數是C++多態的一種表現,可以進行靈活的動態綁定。

     重點可參考:https://www.cnblogs.com/wangxiaobao/p/5850949.html

                     http://www.cnblogs.com/fanzhidongyzby/archive/2013/01/14/2859064.html

21.操作系統和編譯器如何區分全局變量和局部變量?

       操作系統只管調度進程,編譯器通過內存分配的位置來知道的,全局變量分配在全局數據段並且在程序開始運行的時候被加載。局部變量則分配在棧裏面 。

22. Makefile文件的作用?

   makefile關係到了整個工程的編譯規則。一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件需要先編譯,哪些文件需要後編譯,哪些文件需要重新編譯,甚至於進行更復雜的功能操作,因爲makefile就像一個Shell腳本一樣,其中也可以執行操作系統的命令。

23.結構體和聯合體的區別?

      結構和聯合都是由多個不同的數據類型成員組成, 但在任何同一時刻, 聯合中只存放了一個被選中的成員(所有成員共用一塊地址空間), 而結構的所有成員都存在(不同成員的存放地址不同)。 

     對於聯合的不同成員賦值, 將會對其它成員重寫, 原來成員的值就不存在了, 而對於結構的不同成員賦值是互不影響的。

24.列表初始化問題?

      使用初始化列表主要是基於性能問題,對於內置類型,如int, float等,使用初始化列表和在構造函數體內初始化差別不是很大;但是對於類類型來說,最好使用初始化列表。這樣就可以直接調用拷貝構造函數初始化,省去了一次調用默認構造函數的過程。

  1. struct Test1
  2. {
  3. Test1() // 無參構造函數
  4. {
  5. cout << "Construct Test1" << endl ;
  6. }
  7. Test1(const Test1& t1) // 拷貝構造函數
  8. {
  9. cout << "Copy constructor for Test1" << endl ;
  10. this->a = t1.a ;
  11. }
  12. Test1& operator = (const Test1& t1) // 賦值運算符
  13. {
  14. cout << "assignment for Test1" << endl ;
  15. this->a = t1.a ;
  16. return *this;
  17. }
  18. int a ;
  19. };
  20. struct Test2 //普通初始化
  21. {
  22. Test1 test1 ;
  23. Test2(Test1 &t1)
  24. {
  25. test1 = t1 ;
  26. }
  27. };
  1. struct Test2 //2.列表初始化
  2. {
  3. Test1 test1 ;
  4. Test2(Test1 &t1):test1(t1){}
  5. }
  1. Test1 t1 ; //調用
  2. Test2 t2(t1) ;

普通初始化:

列表初始化:


下列情況一定要使用初始化成員列表

  • 常量成員,因爲常量只能初始化不能賦值,所以必須放在初始化列表裏面
  • 引用類型,引用必須在定義的時候初始化,並且不能重新賦值,所以也要寫在初始化列表裏面
  • 需要初始化的數據成員是對象的情況

  參考地址:https://www.cnblogs.com/weizhixiang/p/6374430.html 

25. 重載與重寫的區別?

    從定義上來說:重載:是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。重寫:是指子類重新定義父類虛函數的方法。

   從實現原理上來說:重載:編譯器根據函數不同的參數表,對同名函數的名稱做修飾,然後這些同名函數就成了不同的函數。重寫:當子類重新定義了父類的虛函數後,父類指針根據賦給它的不同的子類指針,動態的調用屬於子類的該函數,這樣的函數調用在編譯期間是無法確定的(調用的子類的虛函數的地址無法給出)。

   補充:“隱藏”是指派生類的函數屏蔽了與其同名的基類函數。規則如下: 
(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。 
(2)如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。

26.類型安全以及C++中的類型轉換?

     類型安全很大程度上可以等價於內存安全,類型安全的代碼不會試圖訪問自己沒被授權的內存區域。C只在局部上下文中表現出類型安全,比如試圖從一種結構體的指針轉換成另一種結構體的指針時,編譯器將會報告錯誤,除非使用顯式類型轉換。然而,C中相當多的操作是不安全的。

 詳情可以移步:https://blog.csdn.net/chengonghao/article/details/50974022

   四種類型轉換:

  • static_cast <T*> (content)  靜態轉換.在編譯期間處理,可以實現C++中內置基本數據類型之間的相互轉換。如果涉及到類的話,static_cast只能在有相互聯繫的類型中進行相互轉換,不一定包含虛函數
  • dynamic_cast<T*>(content) 動態類型轉換;也是向下安全轉型;是在運行的時候執行;基類中一定要有虛函數,否則編譯不通過。在類層次間進行上行轉換時(如派生類指針轉爲基類指針),dynamic_cast和static_cast的效果是一樣的。在進行下行轉換時(如基類指針轉爲派生類指針),dynamic_cast具有類型檢查的功能,比static_cast更安全。
  • const_cast<T*>(content) 去常轉換;編譯時執行;
  • reinterpret_cast<T*>(content) 重解釋類型轉換;

詳情可以移步:https://blog.csdn.net/u010025211/article/details/48626687

                        https://blog.csdn.net/xtzmm1215/article/details/46475565

                        https://blog.csdn.net/xingkongfenqi/article/details/49148885

27.內存對齊的原則以及作用?

  • 結構體內的成員按自身長度自對齊(32位機器上,如char=1,short=2,int=4,double=8),所謂自對齊是指該成員的起始地址必須是它自身長度的整數倍。如int只能以0,4,8這類地址開始。
  • 結構體的總大小爲結構體的有效對齊值的整數倍(默認以結構體中最長的成員長度爲有效值的整數倍,當用#pragrma pack(n)指定時,以n和結構體中最長的成員的長度中較小者爲其值)。即sizeof的值,必須是其內部最大成員的整數倍,不足的要補齊。

 例如:

  1. class A
  2. {
  3. char c;
  4. int a;
  5. char d;
  6. };
  7. cout << sizeof(A) << endl;
  8. class B
  9. {
  10. char c;
  11. char d;
  12. int a;
  13. };
  14. cout << sizeof(B) << endl;

sizeof(A)=12,sizeof(B)=8;

因爲左邊是1+(3)+4+1+(3)=12,而右邊是1+1+(2)+4=8。括號中爲補齊的字節。

內存對齊的作用:

1、平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。

2、性能原因:經過內存對齊後,CPU的內存訪問速度大大提升。

詳情可以移步:https://blog.csdn.net/chy19911123/article/details/48894579

28.關鍵字registr,typdef的作用?

register關鍵字的作用

請求CPU儘可能讓變量的值保存在CPU內部的寄存器中,減去CPU從內存中抓取數據的時間,提高程序運行效率。

使用register關鍵字應注意什麼

1.只有局部變量纔可以被聲明用register修飾

(register不能修飾全局變量和函數的原因:全局變量可能被多個進程訪問,而用register修飾的變量,只能被當前進程訪問)

2.不能用取地址獲取用register修飾的變量的地址(原因:變量保存在寄存器中,而取地址獲取的地址的是內存的地址)

3. 用register修飾的變量一定要是CPU所接受的數據類型

typedef關鍵字的作用

給數據類型定義一個新名字,

1.  提高了移植性

2.  簡化複雜的類型聲明,提高編碼效率

3.  解釋數據類型的作用

29.什麼情況下需要將析構函數定義爲虛函數?

    當基類指針指向派生類的對象(多態性)時。如果定義爲虛函數,則就會先調用該指針指向的派生類析構函數,然後派生類的析構函數再又自動調用基類的析構函數,這樣整個派生類的對象完全被釋放。如果析構函數不被聲明成虛函數,則編譯器實施靜態綁定,在刪除基類指針時,只會調用基類的析構函數而不調用派生類析構函數,這樣就會造成派生類對象析構不完全。所以,將析構函數聲明爲虛函數是十分必要的。

  詳情可以移步:https://blog.csdn.net/jiadebin890724/article/details/7951461

30.有關純虛函數的理解?

     純虛函數是爲你的程序制定一種標準,純虛函數只是一個接口,是個函數的聲明而已,它要留到子類裏去實現。

  1. class A{
  2. protected:
  3. void foo();//普通類函數
  4. virtual void foo1();//虛函數
  5. virtual void foo2() = 0;//純虛函數
  6. }
   帶純虛函數的類抽象類,這種類不能直接生成對象,而只有被繼承,並重寫其虛函數後,才能使用。
    虛函數是爲了繼承接口和默認行爲

    純虛函數只是繼承接口,行爲必須重新定義

   (在很多情況下,基類本身生成對象是不合情理的。例如,動物作爲一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常  理。所以引入了純虛函數的概念

    詳情可以參考:https://blog.csdn.net/ybhjx/article/details/51788396

30.基類指針指向派生類,派生類指針指向基類?

    基類指針可以指向派生類對象,從而實現多態,例如:

  1. #include <iostream>
  2. using namespace std;
  3. class Shape {
  4. public:
  5. virtual double area() const = 0; //純虛函數
  6. };
  7. class Square : public Shape {
  8. double size;
  9. public:
  10. Square(double s) {
  11. size = s;
  12. }
  13. virtual double area() const {
  14. return size * size;
  15. }
  16. };
  17. class Circle : public Shape {
  18. double radius;
  19. public:
  20. Circle(double r) {
  21. radius = r;
  22. }
  23. virtual double area() const {
  24. return 3.14159 * radius * radius;
  25. }
  26. };
  27. int main()
  28. {
  29. Shape* array[2]; //定義基類指針數組
  30. Square Sq(2.0);
  31. Circle Cir(1.0);
  32. array[0] = &Sq;
  33. array[1] =&Cir;
  34. for (int i = 0; i < 2; i++) /
  35. {
  36. cout << array[i]->area() << endl;
  37. }
  38. return 0;
  39. }

     上面的不同對象Sq,Cir(來自繼承同一基類的不同派生類)接受同一消息(求面積,來自基類的成員函數area()),但是卻根據自身情況調用不同的面積公式(執行了不同的行爲,它是通過虛函數實現的)。我們可以理解爲,繼承同一基類的不同派生對象,對來自基類的同一消息執行了不同的行爲,這就是多態,它是通過繼承和虛函數實現的。而接受同一消息的實現就是基於基類指針。 

     但是要注意的是,這個指針只能用來調用基類的成員函數。

     如果試圖通過基類指針調用派生類纔有的成員函數,則編譯器會報錯。

     爲了避免這種錯誤,必須將基類指針強制轉化爲派生類指針。然後派生類指針可以用來調用派生類的功能。這稱爲向下強制類型轉換,這是一種潛在的危險操作。

     派生類指針不可以指向基類對象,例如:

     有個people類是基類,成員有姓名和身份證號,有個派生類學生student,添加了成員學號,現在如果你說的這個情況成立student的指針----pt讓他指向people成員t,則t只有兩個成員變量,而*pt有3個,現在pt->學號這個變量在pt下是可以使用的,但它指向的實體卻沒有這個變量,所以出錯,於是C++直接就避免了這樣的隱式轉換。
     所以根據上述信息我們可以知道:
    進行上行轉換(把派生類的指針或引用轉換成基類表示)是安全的;
  進行下行轉換(把基類指針或引用轉換成派生類表示)是不安全的。

    參考鏈接:https://blog.csdn.net/flyingbird_sxf/article/details/41358737

                     https://www.cnblogs.com/rednodel/p/5800142.html

   31. 繼承機制中引用和指針之間如何轉換? 
    基類——>派生類:用dynamic_cast轉換(顯示轉換),首先檢查基類指針(引用)是否真正指向一個派生類對象,然後再做相應處理,對指針進行dynamic_cast,成功返回派生類對象,失敗返回空指針,對引用進行dynamic_cast,成功返回派生類對象,失敗拋出一個異常。 不允許隱式轉換。
    派生類——>基類:可以用dynamic_cast或者直接進行類型轉換(直接賦值)。

    32.c語言和c++有什麼區別? 
    C語言是結構化的編程語言,它是面向過程的,而C++是面向對象的。 
    封裝:將數據和函數等集合在一個單元中(即類)。被封裝的類通常稱爲抽象數據類型。封裝的意義在於保護或者防止代碼(數據)被我們無意中破壞。 
    繼承:繼承主要實現重用代碼,節省開發時間。它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展。 
    多態:同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果。在運行時,可以通過指向派生類的基類指針,來調用實現派生類中的方法。有編譯時多態和運行時多態。

   33.C++中的公有,私有,保護的問題? 

 

類對象

公有繼承派生類

私有繼承派生類

保護繼承派生類

公有繼承派生類對象

私有繼承派生類對象

保護繼承派生類對象

公有成員

X

X

私有成員

X

X

X

X

X

X

X

保護成員

X

X

X

X

   √:代表可以訪問,X代表不能訪問。     

 參考鏈接:https://zhidao.baidu.com/question/551075894.html

34.如何實現類對象只能靜態分配或動態分配?

 C++中建立類的對象有兩種方式:
(1)靜態建立,例如 A a;
     靜態建立一個類對象,就是由編譯器爲對象在棧空間中分配內存。使用這種方法,是直接調用類的構造函數。
(2)動態建立,例如 A* p = new A();
     動態建立一個類對象,就是使用new運算符爲對象在堆空間中分配內存。這個過程分爲兩步:第一步執行operator new( )函數,在堆空間中搜索一塊內存並進行分配;第二步調用類的構造函數構造對象。這種方法是間接調用類的構造函數。

只能動態分配:  

      其實,編譯器在爲類對象分配棧空間時,會先檢查類的析構函數的訪問性(其實不光是析構函數,只要是非靜態的函數,編譯器都會進行檢查)。如果類的析構函數在類外部無法訪問,則編譯器拒絕在棧空間上爲類對象分配內存。因此,可以將析構函數設爲private,這樣就無法在棧上建立類對象了。但是爲了子類可以繼承,最好設置成protected。
  1. class A  
  2. {  
  3. protected:  
  4.      A(){}  
  5.      ~A(){}  
  6. public:  
  7.      static A* create(){return new A();}  
  8.      void destory(){delete this;}  
  9. };  

只能靜態分配:

    只有使用new運算符,對象纔會被建立在堆上。因此只要限制new運算符就可以實現類對象只能建立在棧上。可以將new運算符設爲私有。

  1. class A  
  2. {  
  3. private:  
  4.      void* operator new(size_t t){}            //注意函數的第一個參數和返回值都是固定的  
  5.      void  operator delete(void* ptr)()        //重載了new就需要重載delete  
  6. public:  
  7.      A(){}  
  8.      ~A(){}  
  9. };  

35.explicit關鍵字的作用?

C++中, 一個參數的構造函數(或者除了第一個參數外其餘參數都有默認值的多參構造函數), 承擔了兩個角色。 1 是個構造器 ,2 是個默認且隱含的類型轉換操作符。
所以, 有時候在我們寫下如 AAA = XXX, 這樣的代碼, 且恰好XXX的類型正好是AAA單參數構造器的參數類型, 這時候編譯器就自動調用這個構造器, 創建一個AAA的對象。
這樣看起來好象很酷, 很方便。 但在某些情況下(見下面權威的例子), 卻違背了我們(程序員)的本意。 這時候就要在這個構造器前面加上explicit修飾, 指定這個構造器只能被明確的調用/使用, 不能作爲類型轉換操作符被隱含的使用。
  1. class Test1
  2. {
  3. public:
  4. Test1(int n)
  5. {
  6. num=n;
  7. }//普通構造函數
  8. private:
  9. int num;
  10. };
  11. class Test2
  12. {
  13. public:
  14. explicit Test2(int n)
  15. {
  16. num=n;
  17. }//explicit(顯式)構造函數
  18. private:
  19. int num;
  20. };
  21. int main()
  22. {
  23. Test1 t1=12;//隱式調用其構造函數,成功
  24. Test2 t2=12;//編譯錯誤,不能隱式調用其構造函數
  25. Test2 t2(12);//顯式調用成功
  26. return 0;
  27. }
Test1的構造函數帶一個int型的參數,代碼23行會隱式轉換成調用Test1的這個構造函數。而Test2的構造函數被聲明爲explicit(顯式),這表示不能通過隱式轉換來調用這個構造函數,因此代碼24行會出現編譯錯誤。
普通構造函數能夠被隱式調用。而explicit構造函數只能被顯式調用。

36.內存溢出,內存泄漏的原因?

    內存溢出是指程序在申請內存時,沒有足夠的內存空間供其使用。原因可能如下:

  •  內存中加載的數據量過於龐大,如一次從數據庫取出過多數據
  •  代碼中存在死循環或循環產生過多重複的對象實體
  •  遞歸調用太深,導致堆棧溢出等
  •  內存泄漏最終導致內存溢出

    內存泄漏是指向系統申請分配內存進行使用(new),但是用完後不歸還(delete),導致佔用有效內存。常見的幾種情況:

   (1) 在類的構造函數和析構函數中沒有匹配的調用newdelete函數

        兩種情況下會出現這種內存泄露:一是在堆裏創建了對象佔用了內存,但是沒有顯示地釋放對象佔用的內存;二是在類的構造函數中動態的分配了內存,但是在析構 函數中沒有釋放內存或者沒有正確的釋放內存

   (2) 在釋放對象數組時在delete中沒有使用方括號

      方括號是告訴編譯器這個指針指向的是一個對象數組,同時也告訴編譯器正確的對象地址值病調用對象的析構函數,如果沒有方括號,那麼這個指針就被默認爲只指向一個對象,對象數組中的其他對象的析構函數就不會被調用,結果造成了內存泄露。

    (3)沒有將基類的析構函數定義爲虛函數

       當基類指針指向子類對象時,如果基類的析構函數不是virtual,那麼子類的析構函數將不會被調用,子類的資源沒有正確是釋放,因此造成內存泄露

     參考鏈接: https://blog.csdn.net/hyqwmxsh/article/details/52813307    

     緩衝區溢出(棧溢出)

     程序爲了臨時存取數據的需要,一般會分配一些內存空間稱爲緩衝區。如果向緩衝區中寫入緩衝區無法容納的數據,機會造成緩衝區以外的存儲單元被改寫,稱爲緩衝區溢出。而棧溢出是緩衝區溢出的一種,原理也是相同的。分爲上溢出和下溢出。其中,上溢出是指棧滿而又向其增加新的數據,導致數據溢出;下溢出是指空棧而又進行刪除操作等,導致空間溢出。

37.auto_ptr類與shared_ptr類?

      從c++11開始, auto_ptr已經被標記爲棄用, 常見的替代品爲shared_ptr。shared_ptr的不同之處在於引用計數, 在複製(或賦值)時不會像auto_ptr那樣直接轉移所有權。 兩者都是模板類,卻可以像指針一樣去使用。只是在指針上面的一層封裝。

      auto_ptr實際也是一種類, 擁有自己的析構函數, 生命週期結束時能自動釋放資源,正因爲能自動釋放資源, 特別適合在單個函數內代替new/delete的調用, 不用自己調用delete,也不用擔心意外退出造成內存的泄漏。

   atuo_ptr的缺陷:

  •  auto_ptr不能共享所有權,即不要讓兩個auto_ptr指向同一個對象(因爲它採用的是轉移語義的拷貝,原指針會變爲NULL)。
  •  auto_ptr不能管理對象數組(因爲它內部的析構函數調用的是delete而不是delete[])。
  •  auto_ptr不能作爲容器對象,STL容器中的元素經常要支持拷貝,賦值等操作,在這過程中auto_ptr會傳遞所有權。

   詳情原因可以參考:https://blog.csdn.net/uestclr/article/details/51316001

                                    https://blog.csdn.net/kezunhai/article/details/38514823

     

     shared_ptr 使用引用計數的方式來實現對指針資源的管理。同一個指針資源,可以被多個 shared_ptr 對象所擁有,直到最後一個 shared_ptr 對象析構時才釋放所管理的對象資源。

      可以說,shared_ptr 是最智能的智能指針,因爲其特點最接近原始的指針。不僅能夠自由的賦值和拷貝,而且可以安全的用在標準容器中。

38.   有4種情況,編譯器必須爲未聲明的constructor的classes合成一個default constructor:

l  “帶有默認構造函數”的成員對象

l  “帶有默認構造函數”的基類

l  “帶有虛函數”的類

l  “帶有虛擬基類”的類

被合成的構造函數只能滿足編譯器(而非程序員)的需要。在合成默認的構造函數中,只有基類的子對象和成員對象會被初始化,其他非靜態的數據成員(如整數,指針等)都不會被初始化。

所以並不是任何的類如果沒有定義默認的構造函數,都會被合成一個出來。

39. 虛基類

在C++中,如果在多條繼承路徑上有一個公共的基類,那麼在這些路徑中的某幾條路徑的匯合處,這個公共的基類就會產生多個實例(從而造成二義性).如果想使這個公共的基類只產生一個實例,則可將這個基類說明爲虛基類. 這要求在從base類派生新類時,使用關鍵字virtual將base類說明爲虛基類.

用例子說明吧。

class base{protected:int b};
clase base1:public base{..};
clase base2:public base{..};
clase derived:public base1,public base2 {..};
derived d;
d.b //錯誤.
d.base::b //錯誤. 因爲不知是用d.base1::b還是d.base2::b
=================================================
class base{protected:int b..};
clase base1:virtual public base{..}; //說明base爲base1虛基類
clase base2:virtual public base{..}; //說明base爲base2虛基類
clase derived:public base1,public base2 {..};
derived d;
d.b //對.
d.base::b //對. 因爲d.base::b和d.base1::b還是d.base2::b都是引用同一虛基類成員b,具有相同的值.

40. 模板的特例化

引入原因:編寫單一的模板,它能適應大衆化,使每種類型都具有相同的功能,但對於某種特定類型,如果要實現其特有的功能,單一模板就無法做到,這時就需要模板特例化。 
定義:是對單一模板提供的一個特殊實例,它將一個或多個模板參數綁定到特定的類型或值上。

函數模板特例化:必須爲原函數模板的每個模板參數都提供實參,且使用關鍵字template後跟一個空尖括號對<>,表明將原模板的所有模板參數提供實參。

1.   template <typename T>  

2.   void fun(T a)  

3.   {  

4.       cout << "The main template fun(): " << a << endl;  

5.   }  

6.     

7.   template <>   // int型特例化  

8.   void fun(int a)  

9.   {  

10.     cout << "Specialized template for int type: " << a << endl;  

11. }  

12.   

13. int main()  

14. {  

15.     fun<char>('a');  

16.     fun<int>(10);  

17.     fun<float>(9.15);  

18.     return 0;  

19. }  

對於除int型外的其他數據類型,都會調用通用版本的函數模板fun(T a);對於int型,則會調用特例化版本的fun(int a)。注意,一個特例化版本的本質是一個實例,而非函數的重載。因此,特例化不影響函數匹配。

 

類模板的特例化

1.   template <typename T>  

2.   class Test{  

3.   public:  

4.       void print(){  

5.           cout << "General template object" << endl;  

6.       }  

7.   };  

8.     

9.   template<>   // int型特例化  

10. class Test<int>{  

11. public:  

12.     void print(){  

13.         cout << "Specialized template object" << endl;  

14.     }  

15. };  

另外,與函數模板不同,類模板的特例化不必爲所有模板參數提供實參。我們可以只指定一部分而非所有模板參數,這種叫做類模板的偏特化 或部分特例化(partial specialization)。例如,C++標準庫中的類vector的定義:

[cpp] view plain copy

1.   template <typename T, typename Allocator>  

2.   class vector  

3.   {  

4.       /*......*/  

5.   };  

6.     

7.   // 部分特例化  

8.   template <typename Allocator>  

9.   class vector<bool, Allocator>  

10. {  

11.     /*......*/  

12. };  

在vector這個例子中,一個參數被綁定到bool類型,而另一個參數仍未綁定需要由用戶指定。注意,一個類模板的部分特例化版本仍然是一個模板,因爲使用它時用戶還必須爲那些在特例化版本中未指定的模板參數提供實參。 

參考鏈接:https://blog.csdn.net/lisonglisonglisong/article/details/38057367

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