c++面試基礎題匯總


1、棧上的分配內存快還是堆上快?

答:棧,原因如下:

①棧的分配有計算機底層驅動,算法簡單,堆的分配需要C++庫支持,算法比較復雜;

②棧的分配不會遇到清理內存碎片的情況,但堆的分配中可能會遇到未釋放的內存碎片垃圾的清理問題;

2、Top K問題

答:Top k問題即:在大量數據(n>>100000)中查找前k個最大的數據。

思路:排序是不可取的,因為大量數據排序耗時太大,且空間復雜度也很大,一般利用數據結構的最小堆(最小堆即父節點的值小於等於孩子節點的數值)來處理;

具體做法:建立一個含有K個節點的最小堆,遍歷海量數據分別與根節點比較,若小於根節點則舍棄,否則用新數值替換根節點數值,並進行最小堆的調整,那么最終得到的堆節點就是最大的k個數據。

時間復雜度=nlogK(堆調整時間復雜度為logK);

此題若用於熱門搜索推薦,即所有搜索項若都在日志文件中,查找搜索次數最多的K項。那么在top K計算之前還去要去統計每一搜索項的個數,此時需要用到數據結構——hashtable;

3、 C++中的什么是多態性? 是如何實現的?

答:多態性是面向對象程序設計語言繼數據抽象和繼承之后的第三個基本特征。它是在運行時出現的多態性通過派生類和虛函數實現。基類和派生類中使用同樣的函數名, 完成不同的操作具體實現相隔離的另一類接口,即把" w h a t"從"h o w"分離開來。多態性提高了代碼的組織性和可讀性,虛函數則根據類型的不同來進行不同的隔離。

4、i++是原子操作嗎?

答:不是,i++分為三個階段:①從內存讀取到寄存器;②寄存器數值自增;③寄存器寫回內存。

其每個階段之間都可以被打斷,故不是原子操作。

5、字典樹如何優化?

答:字典樹也是空間換時間的數據結構(哈希表是典型的空間換時間),一般優化方向就是:空間優化。

常用優化方法是:使用哈希表替換每個節點中指向孩子的指針數組,在建立字典樹時根據需要向哈希表中添加指針,從而避免有些指針數組方式中的多余指針元素浪費空間。

6、 多態的作用?

答:主要是兩個:

1)隱藏實現細節,使得代碼能夠模塊化;擴展代碼模塊,實現代碼重用;
2)接口重用,為了類在繼承和派生的時候,保證使用家族中任一類的實例的某一屬性時的正確調用。

7、智能指針的作用及實現

智能指針是一個類,用來存儲指向動態分配對象的指針,負責自動釋放動態分配的對象,防止堆內存泄漏。動態分配的資源,交給一個類對象去管理,當類對象聲明周期結束時,自動調用析構函數釋放資源。

智能指針的種類

        shared_ptr、unique_ptr、weak_ptr、auto_ptr 

(1) shared_ptr 

        實現原理:采用引用計數器的方法,允許多個智能指針指向同一個對象,每當多一個指針指向該對象時,指向該對象的所有智能指針內部的引用計數加1,每當減少一個智能指針指向對象時,引用計數會減1,當計數為0的時候會自動的釋放動態分配的資源。 

        1) 智能指針將一個計數器與類指向的對象相關聯,引用計數器跟蹤共有多少個類對象共享同一指針;

         2) 每次創建類的新對象時,初始化指針並將引用計數置為1;

         3) 當對象作為另一對象的副本而創建時,拷貝構造函數拷貝指針並增加與之相應的引用計數;

         4) 對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數為減至0,則刪除對象),並增加右操作數所指對象的引用計數;

         5) 調用析構函數時,構造函數減少引用計數(如果引用計數減至0,則刪除基礎對象)。

(2) unique_ptr 

       unique_ptr采用的是獨享所有權語義,一個非空的unique_ptr總是擁有它所指向的資源。轉移一個unique_ptr將會把所有權全部從源指針轉移給目標指針,源指針被置空;所以unique_ptr不支持普通的拷貝和賦值操作,不能用在STL標准容器中;局部變量的返回值除外(因為編譯器知道要返回的對象將要被銷毀);如果你拷貝一個unique_ptr,那么拷貝結束后,這兩個unique_ptr都會指向相同的資源,造成在結束時對同一內存指針多次釋放而導致程序崩潰。

(3) weak_ptr 

      weak_ptr:弱引用。 引用計數有一個問題就是互相引用形成環(環形引用),這樣兩個指針指向的內存都無法釋放。需要使用weak_ptr打破環形引用。weak_ptr是一個弱引用,它是為了配合shared_ptr而引入的一種智能指針,它指向一個由shared_ptr管理的對象而不影響所指對象的生命周期,也就是說,它只引用,不計數。如果一塊內存被shared_ptr和weak_ptr同時引用,當所有shared_ptr析構了之后,不管還有沒有weak_ptr引用該內存,內存也會被釋放。所以weak_ptr不保證它指向的內存一定是有效的,在使用之前使用函數lock()檢查weak_ptr是否為空指針。

(4) auto_ptr 

       auto_ptr不支持拷貝和賦值操作,不能用在STL標准容器中。STL容器中的元素經常要支持拷貝、賦值操作,在這過程中auto_ptr會傳遞所有權,auto_ptr采用的是獨享所有權語義,一個非空的unique_ptr總是擁有它所指向的資源。轉移一個auto_ptr將會把所有權全部從源指針轉移給目標指針,源指針被置空。

智能指針代碼實現: 用兩個類來實現智能指針的功能,一個是引用計數類,另一個則是指針類。

8、常用數據類型對應字節數

      可用如sizeof(char),sizeof(char*)等得出

      32位編譯器:

      char :1個字節
      char*(即指針變量): 4個字節(32位的尋址空間是2^32, 即32個bit,也就是4個字節。同理64位編譯器)
      short int : 2個字節
      int:  4個字節
      unsigned int : 4個字節
      float:  4個字節
      double:   8個字節
      long:   4個字節
      long long:  8個字節
      unsigned long:  4個字節

      64位編譯器:

      char :1個字節
      char*(即指針變量): 8個字節
      short int : 2個字節
      int:  4個字節
      unsigned int : 4個字節
      float:  4個字節
      double:   8個字節
      long:   8個字節
      long long:  8個字節
      unsigned long:  8個字節

9、三次握手,四次揮手,中間的等待

第一次握手:建立連接時,客戶端發送syn包(syn=j)到服務器,並進入SYN_SENT狀態,等待服務器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。

第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;

第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手。

1)客戶端進程發出連接釋放報文,並且停止發送數據。釋放數據報文首部,FIN=1,其序列號為seq=u(等於前面已經傳送過來的數據的最后一個字節的序號加1),此時,客戶端進入FIN-WAIT-1(終止等待1)狀態。 TCP規定,FIN報文段即使不攜帶數據,也要消耗一個序號。
2)服務器收到連接釋放報文,發出確認報文,ACK=1,ack=u+1,並且帶上自己的序列號seq=v,此時,服務端就進入了CLOSE-WAIT(關閉等待)狀態。TCP服務器通知高層的應用進程,客戶端向服務器的方向就釋放了,這時候處於半關閉狀態,即客戶端已經沒有數據要發送了,但是服務器若發送數據,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。
3)客戶端收到服務器的確認請求后,此時,客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送連接釋放報文(在這之前還需要接受服務器發送的最后的數據)。
4)服務器將最后的數據發送完畢后,就向客戶端發送連接釋放報文,FIN=1,ack=u+1,由於在半關閉狀態,服務器很可能又發送了一些數據,假定此時的序列號為seq=w,此時,服務器就進入了LAST-ACK(最后確認)狀態,等待客戶端的確認。
5)客戶端收到服務器的連接釋放報文后,必須發出確認,ACK=1,ack=w+1,而自己的序列號是seq=u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。注意此時TCP連接還沒有釋放,必須經過2∗∗MSL(最長報文段壽命)的時間后,當客戶端撤銷相應的TCB后,才進入CLOSED狀態。
6)服務器只要收到了客戶端發出的確認,立即進入CLOSED狀態。同樣,撤銷TCB后,就結束了這次的TCP連接。可以看到,服務器結束TCP連接的時間要比客戶端早一些。

10、多態,虛函數與虛函數表

C++ 支持靜態聯編的編譯時多態,和動態聯編的運行時多態。函數聯編是指對一個函數的調用,是確定“函數引用的目標函數體”的過程,C++ 的動態聯編通過虛函數(virtual method / virtual function)實現。虛函數存在於繼承關系中,在基類聲明虛函數,子類覆寫虛函數,使我們能夠通過表面使用基類的該函數,實際訪問到子類覆寫后的函數。

C++ 中虛函數是通過虛函數表(virtual table,v-table)來實現的。每個有虛函數的類有一個虛函數表,包括純虛函數和派生類中隱式聲明的虛函數。虛函數表的入口指針在對象最開始的位置。虛函數表只存儲虛函數“函數指針”的地址,不存放普通函數或是構造函數指針的地址。

11、 線程安全(單例模式, 懶漢,餓漢)

單例大約有兩種實現方法:懶漢與餓漢。
懶漢:故名思義,不到萬不得已就不會去實例化類,也就是說在第一次用到類實例的時候才會去實例化,
餓漢:餓了肯定要飢不擇食。所以在單例類定義的時候就進行實例化。

(1)餓漢

餓漢單例,即在最開始的時候,靜態對象就已經創建完成;
設計方法是類中包含一個靜態成員指針,該指針指向該類的一個對象,提供一個公有的靜態成員方法,返回該對象指針;為了使得對象唯一,還需要將構造函數設為私有,

(2)懶漢

所謂懶漢模式,就是盡可能晚的創建這個對象的實例,即在單例類第一次被引用時將自己初始化;其實C++里很多地方都是類似這樣的思想,比如晚綁定,寫時拷貝技術等,就是盡量使資源的利用最大化,不要讓空閑的人還占着有限的資源。

(3)懶漢的線程安全問題

如果此時多線程進行操作,簡單點以兩個線程為例,假設pthread_1剛判斷完 intance 為NULL 為真,准備創建實例的時候,切換到了pthread_2, 此時pthread_2也判斷intance為NULL為真,創建了一個實例,再切回pthread_1的時候繼續創建一個實例返回,那么此時就不再滿足單例模式的要求了, 既然這樣,是因為多線程訪問出的問題,那我們就來加把鎖,使得線程同步;

單例模式的適用場景
(1)系統只需要一個實例對象,或者考慮到資源消耗的太大而只允許創建一個對象。
(2)客戶調用類的單個實例只允許使用一個公共訪問點,除了該訪問點之外不允許通過其它方式訪問該實例 (就是共有的靜態方法)。

12、拷貝構造函數的參數為什么必須用引用?

答: 如果拷貝構造函數中的參數不是一個引用,即形如CClass(const CClass c_class),那么就相當於采用了傳值的方式(pass-by-value),而傳值的方式會調用該類的拷貝構造函數,從而造成無窮遞歸地調用拷貝構造函數。因此拷貝構造函數的參數必須是一個引用。

 

13、vector的動態增長?

    當添加元素時,如果vector空間大小不足,則會以原大小的兩倍另外配置一塊較大的新空間,然后將原空間內容拷貝過來,在新空間的內容末尾添加元素,並釋放原空間。vector的空間動態增加大小,並不是在原空間之后的相鄰地址增加新空間,因為vector的空間是線性連續分配的,不能保證原空間之后有可供配置的空間。因此,對vector的任何操作,一旦引起空間的重新配置,指向原vector的所有迭代器就會失效。

14、memcpy內存重疊的解決及其實現?

內存重疊:拷貝的目的地址在源地址范圍內。所謂內存重疊就是拷貝的目的地址和源地址有重疊。

在函數strcpy和函數memcpy都沒有對內存重疊做處理的,使用這兩個函數的時候只有程序員自己保證源地址和目標地址不重疊,或者使用memmove函數進行內存拷貝。

當源字節串和目標字節串重疊是,bcopy能夠正確處理,但是memcpy的操作結果不得而知,這種情況必須改用ANSI C的memmove函數[網絡編程]。故該函數實現過程中要考慮src 和dst是否有重疊的情況。
內存重疊:拷貝的目的地址在源地址范圍內。所謂內存重疊就是拷貝的目的地址和源地址有重疊。
重疊從兩方面考慮:
(1).dst部分數據是src的
(2).src部分數據是dst的

15、空的類是否占用內存?

答:空的類是會占用內存空間的,而且大小是1,原因是C++要求每個實例在內存中都有獨一無二的地址。 
(一)類內部的成員變量: 
普通的變量:是要占用內存的,但是要注意對齊原則(這點和struct類型很相似)。static修飾的靜態變量:不占用內容,原因是編譯器將其放在全局變量區。

(二)類內部的成員函數:普通函數:不占用內存。虛函數:要占用4個字節,用來指定虛函數的虛擬函數表的入口地址。所以一個類的虛函數所占用的地址是不變的,和虛函數的個數是沒有關系的。

16、堆排序

堆排序是指利用堆這種數據結構所設計的一種選擇排序算法。堆是一種近似完全二叉樹的結構(通常堆是通過一維數組來實現的),並滿足性質:以最大堆(也叫大根堆、大頂堆)為例,其中父結點的值總是大於它的孩子節點。

  我們可以很容易的定義堆排序的過程:

  1. 由輸入的無序數組構造一個最大堆,作為初始的無序區
  2. 把堆頂元素(最大值)和堆尾元素互換
  3. 把堆(無序區)的尺寸縮小1,並調用heapify(A, 0)從新的堆頂元素開始進行堆調整
  4. 重復步驟2,直到堆的尺寸為1

17、內聯函數INline和宏定義一起使用的區別。

解析:內聯函數是在編譯的時候已經做好將對應的函數代碼替換嵌入到對應的位置,適用於代碼較少的函數。 宏定義是簡單的替換變量,如果定義的是有參數的函數形式,參數不做類型校驗。

18、 C中static有什么作用

正確答案:
(1)隱藏。 當我們同時編譯多個文件時,所有未加static前綴的全局變量和函數都具有全局可見性,故使用static在不同的文件中定義同名函數和同名變量,而不必擔心命名沖突。
(2)static的第二個作用是保持變量內容的持久。存儲在靜態數據區的變量會在程序剛開始運行時就完成初始化,也是唯一的一次初始化。共有兩種變量存儲在靜態存儲區:全局變量和static變量。
(3)static的第三個作用是默認初始化為0.其實全局變量也具備這一屬性,因為全局變量也存儲在靜態數據區。在靜態數據區,內存中所有的字節默認值都是0×00,某些時候這一特點可以減少程序員的工作量。

19、引用和指針的區別與聯系

區別

指針是一個實體他在棧中有自己使用的空間,但是引用沒有;
引用必須初始化,指針不用但是最好初始化
指針使用時必須加*,引用不用;
引用只能初始化一次是個專一的人,指針不是;
引用不用const去修飾,但是指針可以
指針和地址運用自增(++)不同,引用是值進行自增,而指針是地址進行自增;
聯系

引用的內部使用指針實現的
引用是受了限制的指針

20、STL容器各自的優缺點

如果需要高效的隨機存取,不在乎插入和刪除的效率,使用vector;
如果需要大量的插入和刪除元素,不關心隨機存取的效率,使用list;
如果需要隨機存取,並且關心兩端數據的插入和刪除效率,使用deque;
如果打算存儲數據字典,並且要求方便地根據key找到value,一對一的情況使用map,一對多的情況使用multimap;
如果打算查找一個元素是否存在於某集合中,唯一存在的情況使用set,不唯一存在的情況使用multiset。

21、udp怎么保證能收到數據?

udp與tcp的區別

         TCP(TransmissionControl Protocol 傳輸控制協議)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議。

         UDP是User Datagram Protocol,一種無連接的傳輸層協議,提供面向事務的簡單不可靠信息傳送服務。可靠性由上層應用實現,所以要實現udp可靠性傳輸,必須通過應用層來實現和控制。

TCP如何實現可靠性傳輸?

         確認機制、重傳機制、滑動窗口。

22、重載和重寫區別?

方法重寫(overriding):

  1、也叫子類的方法覆蓋父類的方法,要求返回值、方法名和參數都相同。

  2、子類拋出的異常不能超過父類相應方法拋出的異常。(子類異常不能超出父類異常)

  3、子類方法的的訪問級別不能低於父類相應方法的訪問級別(子類訪問級別不能低於父類訪問級別)

方法重載(overloading):重載是在同一個類中的兩個或兩個以上的方法,擁有相同的方法名,但是參數卻不相同,方法體也不相同,最常見的重載的例子就是類的構造函數,可以參考API幫助文檔看看類的構造方法

23、stl的包括哪些模板?

向量(vector) 連續存儲的元素
列表(list) 由節點組成的雙向鏈表,每個結點包含着一個元素
雙端隊列(deque) 連續存儲的指向不同元素的指針所組成的數組
適配器容器
棧(stack) 后進先出的值的排列 
隊列(queue) 先進先出的值的排列 
優先隊列(priority_queue) 元素的次序是由作用於所存儲的值對上的某種謂詞決定的的一種隊列 
關聯式容器
集合(set) 由節點組成的紅黑樹,每個節點都包含着一個元素,節點之間以某種作用於元素對的謂詞排列,沒有兩個不同的元素能夠擁有相同的次序 
多重集合(multiset) 允許存在兩個次序相等的元素的集合 
映射(map) 由{鍵,值}對組成的集合,以某種作用於鍵對上的謂詞排列 
多重映射(multimap) 允許鍵對有相等的次序的映射

 24、淺拷貝和深拷貝

  在某些狀況下,類內成員變量需要動態開辟堆內存,如果實行位拷貝,也就是把對象里的值完全復制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。

  深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的對象發生復制過程的時候,資源重新分配,這個過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。

25、虛繼承

在多重繼承中,如果發生了如:類B繼承類A,類C繼承類A,類D同時繼承了類B和類C。最終在類D中就有了兩份類A的成員,這在程序中是不能容忍的。當然解決這個問題的方法就是利用虛繼承。

虛繼承是解決C++多重繼承問題的一種手段,從不同途徑繼承來的同一基類,會在子類中存在多份拷貝。這將存在兩個問題:其一,浪費存儲空間;第二,存在二義性問題,通常可以將派生類對象的地址賦值給基類對象,實現的具體方式是,將基類指針指向繼承類(繼承類有基類的拷貝)中的基類對象的地址,但是多重繼承可能存在一個基類的多份拷貝,這就出現了二義性。

虛繼承可以解決多種繼承前面提到的兩個問題:

虛繼承底層實現原理與編譯器相關,一般通過虛基類指針和虛基類表實現,每個虛繼承的子類都有一個虛基類指針(占用一個指針的存儲空間,4字節)和虛基類表(不占用類對象的存儲空間)(需要強調的是,虛基類依舊會在子類里面存在拷貝,只是僅僅最多存在一份而已,並不是不在子類里面了);當虛繼承的子類被當做父類繼承時,虛基類指針也會被繼承。

實際上,vbptr指的是虛基類表指針(virtual base table pointer),該指針指向了一個虛基類表(virtual table),虛表中記錄了虛基類與本類的偏移地址;通過偏移地址,這樣就找到了虛基類成員,而虛繼承也不用像普通多繼承那樣維持着公共基類(虛基類)的兩份同樣的拷貝,節省了存儲空間。 

26、為什么要把析構函數定義為虛函數?

new出來的是子類son的對象,采用一個父類father的指針來接收,故在析構的時候,編譯器因為只知道這個指針是父類的,所以只將父類部分的內存析構了,而不會去析構子類的內存,就造成了內存泄露。基類析構函數定義為虛擬函數的時候,在子類的對象的首地址開始會有一塊基類的虛函數表拷貝,在析構子類對象的時候會刪除此虛函數表,此時會調用基類的析構函數,所以此時內存是安全的。

27、堆棧溢出的原因?

沒有回收垃圾資源

棧溢出:

一般都是由越界訪問導致的。例如局部變量數組越界訪問或者函數內局部變量使用過多,超出了操作系統為該進程分配的棧的大小。

堆溢出:

由於堆是用戶申請的,所以溢出的原因可能是程序員申請了資源但是忘記釋放了。

28、為什么構造函數不能是虛函數?

構造函數不可以是虛函數的,這個很顯然,畢竟虛函數都對應一個虛函數表,虛函數表是存在對象內存空間的,如果構造函數是虛的,就需要一個虛函數表來調用,但是類還沒實例化沒有內存空間就沒有虛函數表,這根本就是個死循環。

29、介紹一下STL,詳細說明STL如何實現vector。 

STL是標准模版庫,由容器算法迭代器組成。

STL有以下的一些優點:

(1)可以很方便的對一堆數據進行排序(調用sort());

(2)調試程序時更加安全和方便;

(3)stl是跨平台的,在linux下也能使用。

vector實質上就是一個動態數組,會根據數據的增加,動態的增加數組空間

30、簡述#define #endif 和#ifndef的作用?

這三個命令一般是為了避免頭文件被重復引用。

#ifndef CH_H //意思是如果沒有引用ch.h

#define CH_H //引用ch.h

#endif  //否則不需要引用

31、正確區分重載、重寫和隱藏。

注意三個概念的適用范圍:處在同一個類中的函數才會出現重載。處在父類和子類中的函數才會出現重寫和隱藏。
重載:同一類中,函數名相同,但參數列表不同。
重寫:父子類中,函數名相同,參數列表相同,且有virtual修飾。 隱藏:父子類中,函數名相同,參數列表相同,但沒有virtual修飾;函數名相同,參數列表不同,無論有無virtual修飾都是隱藏
 基類中:
      (1) virtual void show(); //是虛函數 (2) void show(int); //不是虛函數
子類中:(3) void show(); //是虛函數 (4) void show(int); //不是虛函數

1,2構成重載,3,4構成重載,1,3構成重寫,2,4構成隱藏。另外2,3也會構成隱藏,子類對象無法訪問基類的void show(int)成員方法,但是由於子類中4的存在導致了子類對象也可以直接調用void show(int)函數,不過此時調用的函數不在是基類中定義的void show(int)函數2,而是子類中的與3重載的4號函數。

32、C++繼承機制?

n類成員的訪問控制方式

public:類本身、派生類和其它類均可訪問;

protected:類本身和派生類均可訪問,其它類不能訪問;

private(默認):類本身可訪問,派生類和其它類不能訪問。

繼承成員的訪問控制規則

——由父類成員的訪問控制方式和繼承訪問控制方式共同決定

private+public(protectd,private)=>不可訪問

pubic(protected)+public=>public(protected)

public(protected)+protected=>protected

public(protected)+private(默認)=>private

33、類和對象的兩個基本概念?

類的作用或概念:用來描述一組具有相似屬性的東西的對象的一種數據結構。類中有數據成員的聲明和定義,有成員函數的實現代碼。對象就是類的實例化。計算機中想要使用類,只能進行實例化。

34、什么是內存泄露?

C++內存泄漏檢測內存泄露是指程序中動態分配了內存,但是在程序結束時沒有釋放這部分內存,從而造成那一部分內存不可用的情況

有一些內存泄漏的檢測工具,比如BoundsChecker。

靜態內存泄漏通過工具或者仔細檢查代碼找到泄漏點。

動態的內存泄漏很難查,一般通過在代碼中加斷點跟蹤和Run-Time內存檢測工具來查找。

內存泄漏的檢測可以分以下幾個步驟:

(1)看代碼new之后是否delete,就是申請了靜態內存用完是否釋放。看析構函數是否真的執行,如果沒有真正執行,就需要動態釋放對象;

(2)讓程序長時間運行,看任務管理器對應程序內存是不是一直向上增加;

(3)使用常用內存泄漏檢測工具來檢測內存泄漏點。

35、頭文件的作用是什么?

(1)頭文件用於保存程序的聲明。
(2)通過頭文件可以來調用庫函數。因為有些代碼不能向用戶公布,只要向用戶提供頭文件和二進制的庫即可。用戶只需要按照頭文件中的接口聲明來調用庫功能,編譯器會從庫中提取相應的代碼。
(3)如果某個接口被實現或被使用時,其方式與頭文件中的聲明不一致,編譯器就會指出錯誤,這一簡單的規則能大大減輕程序員調試、改錯的負擔。

36、函數模板與類模板有什么區別?

答:函數模板的實例化是由編譯程序在處理函數調用時自動完成的,而類模板的實例化必須由程序員在程序中顯式地指定。

函數模板是模板的一種,可以生成各種類型的函數實例:

template

Type min( Type a, Type b ) 

{

return a < b ? a : b;

}

參數一般分為類型參數和非類型參數:

類型參數代表了一種具體的類型

非類型參數代表了一個常量表達式

37、析構函數和虛函數的用法和作用?

析構函數是類成員函數,在類對象生命期結束的時候,由系統自動調用,釋放在構造函數中分配的資源。

虛函數是為了實現多態。含有純虛函數的類稱為抽象類,不能實例化對象,主要用作接口類

Test(int j):pb(j),pa(pb+5)  
{  
}  

~Test()    
{    
cout<<"釋放堆區director內存空間1次";    
}    

析構函數的特點:

1. 函數名稱固定:~類名( )
2. 沒有返回類型,沒有參數
3. 不可以重載,一般由系統自動的調用

38、new、delete;malloc、free關系

new和delete是一組,new用調用構造函數來實例化對象和調用析構函數釋放對象申請的資源。

malloc和free是一對,malloc用來申請內存和釋放內存,但是申請和釋放的對象只能是內部數據類型。

區別:

malloc與free是C++/C語言的標准庫函數,new/delete是C++的運算符。

maloc/free只能操作內部數據類型

39、delete與 delete []區別

都是用來調用析構函數的:

(1)delete只會調用一次析構函數,delete[]會調用每一個成員的析構函數。

(2)delete與new配套,delete []與new []配套,用new分配的內存用delete刪除用new[]分配的內存用delete[]刪除

40、繼承優缺點

優點:

繼承可以方便地改變父類的實現,可以實現多態,子類可以繼承父類的方法和屬性。

缺點:

破壞封裝,子類和父類可能存在耦合。

子類不能改變父類的接口。

41、C和C++有什么不同?

(1)c是面向過程的,也就是說更偏向邏輯設計;c++是面向對象的,提供了類,偏向類的設計。

(2)c適合要求代碼體積小的,效率高的場合,如比如嵌入式。

42、析構函數的調用次序,子類析構時要調用父類的析構函數嗎?

析構函數調用的次序是:先派生類的析構后基類的析構,也就是說在基類的的析構調用的時候,派生類的信息已經全部銷毀了定義一個對象時先調用基類的構造函數、然后調用派生類的構造函數;

43、什么是“野指針”?

野指針指向一個已刪除的對象或無意義地址的指針。與空指針不同,野指針無法通過簡單地判斷是否為 NULL避免,而只能通過養成良好的編程習慣來盡力避免。造成的主要原因是:指針變量沒有被初始化,或者指針p被free或者delete之后,沒有置為NULL。

44、常量指針和指針常量的區別?

常量指針:是一個指向常量的指針。可以防止對指針誤操作而修改該常量。
指針常量:是一個常量,且是一個指針。指針常量不能修改指針所指向的地址,一旦初始化,地址就固定了,不能對它進行移動操作。但是指針常量的內容是可以改變。

45、如果NULL定義成#define NULL ((char *)0) 難道不就可以向函數傳入不加轉換的NULL了嗎?

不行。因為有的機器不同類型數據的指針有不同的內部表達。如果是字符指針的函數沒有問題, 但對於其它類型的指針參數仍然有問題, 而合法的構造如FILE *fp = NULL;則會失敗。
如果定義#define NULL ((void *)0)除了潛在地幫助錯誤程序運行以外, 這樣的定義還可以發現錯誤使用NULL 的程序。無論如何, ANSI 函數原型確保大多數指針參數在傳入函數時正確轉換。

46、空指針到底是什么?

空指針表示“未分配” 或者“尚未指向任何地方” 的指針。
空指針在概念上不同於未初始化的指針。空指針可以確保不指向任何對象或函數; 而未初始化指針則可能指向任何地方。

47、C++文件編譯與執行的四個階段

第一階段:預處理階段。根據文件中的預處理指令來修改源文件的內容。如#include指令,作用是把頭文件的內容添加到.cpp文件中。

第二階段:編譯階段,將其翻譯成等價的中間代碼或匯編代碼。

第三階段:匯編階段,把匯編語言翻譯成目標機器指令。

第四階段:是鏈接,例如,某個源文件中的函數可能引用了另一個源文件中定義的某個函數;在程序中可能調用了某個庫文件中的函數。

48、什么是預編譯?何時需要預編譯?

預編譯又稱為預處理 , 是做些代碼文本的替換工作。處理 # 開頭的指令 , 比如拷貝 #include 包含的文件代碼, #define 宏定義的替換 , 條件編譯等。
c 編譯系統在對程序進行通常的編譯之前,先進行預處理。 c 提供的預處理功能主要有以下三 種: 1 )宏定義  2 )文件包含  3 )條件編譯

49、內聯函數與宏有什么區別

內聯函數在編譯時展開,宏在預編譯時展開
在編譯的時候內聯函數可以直接被嵌入到目標代碼中,而宏只是一個簡單的文本替換
內聯函數可以完成諸如類型檢測、語句是否正確等編譯功能,宏就不具備這樣的功能
inline函數是函數,宏不是函數。

50、iostream與iostream.h的區別

#include <iostream.h>非標准輸入輸出流
#include <iostream>標准輸入輸出流
有“.h”的就是非標准的,C的標准庫函數,無“.h”的,就要用到命令空間,是C++的。

51、堆與棧的區別

(1)一個是靜態的,一個是動態的,堆是靜態的,由用戶申請和釋放,棧是動態的,保存程序的局部變量

(2)申請后系統的響應不同
棧:只要棧的剩余空間大於申請空間,系統就為程序提供內存,否則將拋出棧溢出異常
堆:當系統收到程序申請時,先遍歷操作系統中記錄空閑內存地址的鏈表,尋找第一個大於所申請空間的堆結點,然后將該結點從空間結點鏈表中刪除,並將該結點的空間分配給程序。
(3)申請大小限制的不同
棧:在windows下,棧的大小一般是2M,如果申請的空間超過棧的剩余空間時,將提示overflow。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由於系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。

52、虛函數與構造函數,析構函數,成員函數的關系

為什么基類析構函數是虛函數?

編譯器總是根據類型來調用類成員函數。但是一個派生類的指針可以安全地轉化為一個基類的指針。這樣刪除一個基類的指針的時候,C++不管這個指針指向一個基類對象還是一個派生類的對象,調用的都是基類的析構函數而不是派生類的。如果你依賴於派生類的析構函數的代碼來釋放資源,而沒有重載析構函數,那么會有資源泄漏。

為什么構造函數不能為虛函數

虛函數采用一種虛調用的方法。需調用是一種可以在只有部分信息的情況下工作的機制。如果創建一個對象,則需要知道對象的准確類型,因此構造函數不能為虛函數。

如果虛函數是有效的,那為什么不把所有函數設為虛函數?

不行。因為每個虛函數的對象都要維護一個虛函數表,因此在使用虛函數的時候都會產生一定的系統開銷,這是沒有必要的。

53、面向對象的三個基本特征,並簡單敘述之?

1. 封裝:將客觀事物抽象成類,每個類對自身的數據和方法。封裝可以使得代碼模塊化,目的是為了代碼重用
2. 繼承:子類繼承父類的方法和屬性,繼承可以擴展已存在的代碼,目的是為了代碼重用
3. 多態:允許將子類類型的指針賦值給父類類型的指針。

54、什么是“&(引用)”?申明和使用“引用”要注意哪些問題?

引用就是某個目標變量的“別名”。注意事項:(1)申明一個引用的時候,必須要對其進行初始化。(2)初始化后,該引用名不能再作為其他變量名的別名。

(3)引用本身不占存儲單元,系統不給引用分配存儲單元。(4)返回引用時,在內存中不產生被返回值的副本

(5)不能返回局部變量的引用。主要原因是局部變量會在函數返回后被銷毀.

55、引用與多態的關系?

引用就是對象的別名。引用主要用作函數的形參。引用必須用與該引用同類型的對象初始化: 引用是除指針外另一個可以產生多態效果的手段。這意味着,一個基類的引用可以指向它的派生類實例。
int ival = 1024;int &refVal = ival;
const 對象的引用只能是const類型的:const int ival = 1024;const int &refVal = ival;
多態是通過虛函數實現的。

56、指針和引用有什么區別;為什么傳引用比傳指針安全?

如果我使用常量指針難道不行嗎?
(1) 引用在創建的同時必須初始化,保證引用的對象是有效的,所以不存在NULL引用;而指針在定義的時候不必初始化,所以,指針則可以是NULL,可以在定義后面的任何地方重新賦值。
(2) 引用一旦被初始化為指向一個對象,它就不能被改變為另一個對象的引用;而指針在任何時候都可以改變為指向另一個對象.
(3) 引用的創建和銷毀並不會調用類的拷貝構造函數
因為不存在空引用,並且引用一旦被初始化為指向一個對象,它就不能被改變為另一個對象的引用,所以比指針安全。
由於const 指針仍然存在空指針,並且有可能產生野指針,所以還是不安全

57、拷貝構造函數相關問題,深拷貝,淺拷貝,臨時對象等。

深拷貝意味着拷貝了資源和指針,而淺拷貝只是拷貝了指針,沒有拷貝資源
這樣使得兩個指針指向同一份資源,可能造成對同一份析構兩次,程序崩潰。而且浪費時間,並且不安全。
臨時對象的開銷比局部對象小些。

58、面向對象如何實現數據隱藏

定義類來實現數據隱藏:

成員函數和屬性的類型:

私有成員private

保護成員protected
公共成員public

59、C++是不是類型安全的?

不是。兩個不同類型的指針之間可以強制轉換.

60、const char*, char const*, char *const的區別是什么?

把一個聲明從右向左讀,* 讀成指向
char  * const cp;//cp是常指針,指向char類型的數據
const char * cp;//cp是char類型的指針,指向const char
char const * p;//C++里面沒有const*的運算符,所以const屬於前面的類型。

61、什么是模板和宏?模板怎么實現?模板有什么缺點和優點?模版特化的概念,為什么特化?

標准庫大量采用了模板技術。比如容器。

模板是一個藍圖,它本身不是類或函數。編譯器用模板產生指定的類或函數的特定類型版本。模版的形參分為類型形參和非類型形參類型形參就是表示類型的形參,跟在關鍵字typename后非類型形參用來表示常量表達式

62、空指針和懸垂指針的區別?

空指針是指被賦值為NULL的指針;delete指向動態分配對象的指針將會產生懸垂指針。

空指針可以被多次delete,而懸垂指針再次刪除時程序會變得非常不穩定;

使用空指針和懸垂指針都是非法的,而且有可能造成程序崩潰,如果指針是空指針,盡管同樣是崩潰,但和懸垂指針相比是一種可預料的崩潰。

(a)指針數組和數組指針,函數指針和指針函數相關概念

指針數組:用於存儲指針的數組

int* a[4]          

元素表示:*a[i]

數組指針:指向數組的指針

int (*a)[4]

元素表示:(*a)[i]  

指針函數:函數返回類型是某一類型的指針,int *f(x,y);

指針函數與函數指針表示方法的不同。最簡單的辨別方式就是看函數名前面的指針*號有沒有被括號()包含,如果被包含就是函數指針,反之則是指針函數。

函數指針:是指向函數的指針變量,即本質是一個指針變量。

int (*f) (int x); /* 聲明一個函數指針 */ 類型說明符 (*指針的變量名)(參數)

f=func; /* 將func函數的首地址賦給指針f */

指向函數的指針包含了函數的地址

指針的指針:
例如:char ** cp;
如果有三個星號,那就是指針的指針的指針,依次類推。
指針的指針需要用到指針的地址。
        char c='A';
        char *p=&c;
        char **cp=&p;
通過指針的指針,不僅可以訪問它指向的指針,還可以訪問它指向的指針所指向的數據:
        char *p1=*cp;
        char c1=**cp;
指向指針數組的指針:
char *Names[]={ Bill,Sam,0};
char **nm=Names;
while(*nm!=0) printf(%s\n,*nm++);
先用字符型指針數組Names的地址來初始化指針nm。每次printf()的調用都首先傳遞指針nm指向的字符型指針,然后對nm進行自增運算使其指向數組的下一個元素(還是指針)。

63、什么是智能指針?

當類中有指針成員時,一般有兩種方式來管理指針成員:

(1)每個類對象都保留一份指針指向的對象的拷貝;

(2)使用智能指針,從而實現指針指向的對象的共享。實質是使用計數器與對象相關聯,這樣做可以保證對象正確的刪除,避免垂懸指針。

每次創建類的新對象時,初始化指針並將引用計數置為1;當對象作為另一對象的副本而創建時,拷貝構造函數拷貝指針並增加與之相應的引用計數;

對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數,並增加右操作數所指對象的引用計數;調用析構函數時,構造函數減少引用計數。

64、C++空類默認有哪些成員函數?

默認構造函數、析構函數、復制構造函數、賦值函數

65、哪一種成員變量可以在一個類的實例之間共享?

答:static靜態成員變量

66、什么是多態?多態有什么作用?如何實現的?多態的缺點?

多態就是一個接口,多種方法。所以說,多態的目的則是為了實現接口重用。也就是說,不論傳遞過來的究竟是那個類的對象,函數都能夠通過同一個接口調用到適應各自對象的實現方法。

C++的多態性是通過虛函數來實現的,虛函數允許子類重新定義成員函數,而子類重新定義父類的做法稱為覆蓋(override),或重寫。而重載則是允許有多個同名的函數,而這些函數的參數列表不同,允許參數個數不同,參數類型不同。編譯器會根據函數列表的不同,而生成一些不同名稱的預處理函數,來實現同名函數的重載。但這並沒有體現多態性。多態與非多態的實質區別就是函數的地址是運行時確定還是編譯時確定。如果函數的調用在編譯器編譯期間就可以確定函數的調用地址,並生產代碼,是靜態的。而如果函數調用的地址在運行時才確定,就是動態的。最常見的用法就是聲明基類的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,可以根據指向的子類的不同而實現不同的方法。如果沒有使用虛函數的話,即沒有利用C++多態性,則利用基類指針調用相應的函數的時候,將總被限制在基類函數本身,而無法調用到子類中被重寫過的函數

67、虛函數表解析和內存布局

虛函數表

虛函數是通過一張虛函數表來實現的。就像一個地圖一樣,指明了實際所應該調用的函數的地址。

這里我們着重看一下這張虛函數表。C++的編譯器保證了虛函數表的指針存在於對象實例中最前面的位置(為了性能)。因此我們可以通過對象實例的地址得到這張虛函數表,然后通過遍歷其中函數指針,並調用相應的函數。

為什么可以由父類的指針調用子類的對象的虛函數:

Derive d;//Derive 是Base的子類

Base *b1 = &d;//這必須使用父類的指針???

b1->f(); //Derive::f() 

68、公有繼承、受保護繼承、私有繼承

1)公有繼承時,派生類對象可以訪問基類中的公有成員,派生類的成員函數可以訪問基類中的公有和受保護成員;公有繼承時基類受保護的成員,可以通過派生類對象訪問但不能修改。

2)私有繼承時,基類的成員只能被直接派生類的成員訪問,無法再往下繼承;

3)受保護繼承時,基類的成員也只被直接派生類的成員訪問,無法再往下繼承。

69、有哪幾種情況只能用構造函數初始化列表而不能用賦值初始化?

答:const成員,引用成員

70、C++如何阻止一個類被實例化?一般在什么時候將構造函數聲明為private?

1)將類定義為抽象基類或者將構造函數聲明為private;

2)不允許類外部創建類對象,只能在類內部創建對象

71、類使用static成員的優點,如何訪問?

(1)static 成員的名字是在類的作用域中,因此可以避免與其他類的成員或全局對象名字沖突;

(2)可以實施封裝。static 成員可以是私有成員,而全局對象不可以;

(3) static 成員是與特定類關聯的,可清晰地顯示程序員的意圖。

72、static數據成員和static成員函數

(1)static數據成員:
static數據成員獨立於該類的任意對象而存在;static數據成員(const static數據成員除外)在類定義體內聲明,必須在類外進行初始化。不像普通數據成員,static成員不能在類的定義體中初始化,只能在定義時才初始化。 static數據成員定義放在cpp文件中,不能放在初始化列表中。Const static成員可就地初始化。

變量定義:用於為變量分配存儲空間,還可為變量指定初始值。程序中,變量有且僅有一個定義。

變量聲明:用於向程序表明變量的類型和名字。

(2)static成員函數:
在類的外部定義,Static成員函數沒有this形參,它可以直接訪問所屬類的static成員,不能直接使用非static成員。因為static成員不是任何對象的組成部分,所以static成員函數不能被聲明為const。同時,static成員函數也不能被聲明為虛函數。 

73、C++的內部連接和外部連接

編譯單元:當編譯cpp文件時,預處理器首先遞歸包含頭文件,形成一個編譯單元。這個編譯單元會被編譯成為一個與cpp文件名同名的目標文件(.o或是.obj)。連接程序把不同編譯單元中產生的符號聯系起來,構成一個可執行程序。

內部連接:如果一個名稱對於它的編譯單元來說是局部的,並且在連接時不會與其它編譯單元中的同樣的名稱相沖突,那么這個名稱有內部連接:

a)所有的聲明

b)名字空間(包括全局名字空間)中的靜態自由函數、靜態友元函數、靜態變量的定義

c)enum定義

d)inline函數定義(包括自由函數和非自由函數)

e)類的定義

f)名字空間中const常量定義

g)union的定義

外部連接:在一個多文件程序中,如果一個名稱在連接時可以和其它編譯單元交互,那么這個名稱就有外部連接。

以下情況有外部連接:

a)類非inline函數總有外部連接。包括類成員函數和類靜態成員函數

b)類靜態成員變量總有外部連接。

c)名字空間(包括全局名字空間)中非靜態自由函數、非靜態友元函數及非靜態變量

74、變量的分類,全局變量和局部變量有什么區別?實怎么實現的?操作系統和編譯器是怎么知道的?static全局變量與普通的全局變量有什么區別?static局部變量和普通局部變量有什么區別?static函數與普通函數有什么區別?

1)變量可以分為:全局變量、局部變量、靜態全局變量、靜態局部變量
全局變量在整個工程文件內都有效;靜態全局變量只在定義它的文件內有效局部變量在定義它的函數內有效,這個函數返回會后失效。
靜態局部變量只在定義它的函數內有效,只是程序僅分配一次內存,函數返回后,該變量不會消失,直到程序運行結束后才釋放;全局變量和靜態變量如果沒有手工初始化,則由編譯器初始化為0。局部變量的值不可知。
靜態全局變量是定義存儲類型為靜態型的外部變量,其作用域是從定義點到程序結束,所不同的是存儲類型決定了存儲地點,靜態型變量是存放在內存的數據區中的,它們在程序開始運行前就分配了固定的字節,在程序運行過程中被分配的字節大小是不改變的.只有程序運行結束后,才釋放所占用的內存.
變量的作用域:
形參變量只在被調用期間才分配內存單元,調用結束立即釋放。 這一點表明形參變量只有在函數內才是有效的, 離開該函數就不能再使用了。局部變量也稱為內部變量。其作用域僅限於函數內, 離開該函數后再使用這種變量是非法的。
全局變量也稱為外部變量,它不屬於哪一個函數,它屬於一個源程序文件。其作用域是整個源程序。在函數中使用全局變量,一般應作全局變量說明。 只有在函數內經過說明的全局變量才能使用。全局變量的說明符為extern。 但在一個函數之前定義的全局變量,在該函數內使用可不再加以說明。
對於全局變量還有以下幾點說明:
外部變量可加強函數模塊之間的數據聯系, 但是又使函數要依賴這些變量,因而使得函數的獨立性降低。從模塊化程序設計的觀點來看這是不利的, 因此在不必要時盡量不要使用全局變量。
在同一源文件中,允許全局變量和局部變量同名。在局部變量的作用域內,全局變量不起作用。
變量的存儲方式可分為“靜態存儲”和“動態存儲”兩種。
靜態存儲變量通常是在變量定義時就分定存儲單元並一直保持不變, 直至整個程序結束。動態存儲變量是在程序執行過程中,使用它時才分配存儲單元, 使用完畢立即釋放。 如果一個函數被多次調用,則反復地分配、 釋放形參變量的存儲單元。從以上分析可知, 靜態存儲變量是一直存在的, 而動態存儲變量則時而存在時而消失。我們又把這種由於變量存儲方式不同而產生的特性稱變量的生存期。 生存期表示了變量存在的時間。 生存期和作用域是從時間和空間這兩個不同的角度來描述變量的特性,這兩者既有聯系,又有區別。 一個變量究竟屬於哪一種存儲方式, 並不能僅從其作用域來判斷,還應有明確的存儲類型說明。
從作用域看:
全局變量具有全局作用域。全局變量只需在一個源文件中定義,就可以作用於所有的源文件。當然,其他不包含全局變量定義的源文件需要用extern 關鍵字再次聲明這個全局變量。
靜態局部變量具有局部作用域,它只被初始化一次,自從第一次被初始化直到程序運行結束都一直存在,它和全局變量的區別在於全局變量對所有的函數都是可見的,而靜態局部變量只對定義自己的函數體始終可見。
局部變量也只有局部作用域,它是自動對象(auto),它在程序運行期間不是一直存在,而是只在函數執行期間存在,函數的一次調用執行結束后,變量被撤銷,其所占用的內存也被收回。
靜態全局變量也具有全局作用域,它與全局變量的區別在於如果程序包含多個文件的話,它作用於定義它的文件里,不能作用到其它文件里,即被static關鍵字修飾過的變量具有文件作用域。這樣即使兩個不同的源文件都定義了相同名字的靜態全局變量,它們也是不同的變量。
從分配內存空間看:
全局變量,靜態局部變量,靜態全局變量都在靜態存儲區分配空間,而局部變量在棧里分配空間。
全局變量本身就是靜態存儲方式,靜態全局變量當然也是靜態存儲方式。這兩者在存儲方式上並無不同。這兩者的區別雖在於非靜態全局變量的作用域是整個源程序,當一個源程序由多個源文件組成時,非靜態的全局變量在各個源文件中都是有效的。而靜態全局變量則限制了其作用域,即只在定義該變量的源文件內有效,在同一源程序的其它源文件中不能使用它。由於靜態全局變量的作用域局限於一個源文件內,只能為該源文件內的函數公用,因此可以避免在其它源文件中引起錯誤。
1)、靜態變量會被放在程序的靜態數據存儲區(全局可見)中,這樣可以在下一次調用的時候還可以保持原來的賦值。這一點是它與堆棧變量和堆變量的區別。
2)、變量用static告知編譯器,自己僅僅在變量的作用范圍內可見。這一點是它與全局變量的區別。

程序的局部變量存在於(堆棧)中,全局變量存在於(靜態區 )中,動態申請數據存在於( 堆)中。

75、應用程序在運行時的內存包括代碼區和數據區,其中數據區又包括哪些部分?

對於一個進程的內存空間而言,可以在邏輯上分成 3個部份:代碼區,靜態數據區和動態數據區。

動態數據區一般就是“堆棧”。 棧是一種線性結構,堆是一種鏈式結構。進程的每個線程都有私有的“棧”。

全局變量和靜態變量分配在靜態數據區,本地變量分配在動態數據區,即堆棧中。程序通過堆棧的基地址和偏移量來訪問本地變量。

76、C++里面是不是所有的動作都是main()引起的?如果不是,請舉例。

比如全局變量的初始化,就不是由main函數引起的:

class A{};

A a; //a的構造函數限執行

int main() {}

77、異常框架

異常存在於程序的正常功能之外,並要求程序立即處理。 C++ 的異常處理包括: 1. throw 表達式,錯誤檢測部分使用這種表達式來說明遇到了不可處理的錯誤。2. try 塊,錯誤處理部分使用它來處理異常。try 語句塊以 try 關鍵字開 始,並以一個或多個 catch 子句結束。在 try 塊中執行的代碼所拋出 (throw)的異常,通常會被其中一個 catch 子句處理。3. 由標准庫定義的一組異常類,用來在 throw 和相應的 catch 之間傳遞有關的錯誤信息。 throw 表達式:if (!item1.same_isbn(item2))throw runtime_error("Data must refer to same ISBN");
try 塊:try {program-statements} catch (exception-specifier) {handler-statements} catch (exception-specifier) {handler-statements} 
函數在尋找處理代碼的過程中退出在復雜的系統中,程序的執行路徑也許在遇到拋出異常的代碼之前,就已經經過了多個 try 塊。拋出一個異常時,首先要搜索 的是拋出異常的函數。如果沒有找到匹配的 catch,則終止這個函數的執行,並在調用這個函數的函數中尋找相配的 catch。如果仍然沒有找到相應的處理代碼,該函數同樣要終止,搜索調用它的函數。直到找到適當類型的 catch 為止。 

78、死鎖及其預防和處理方法

  • 死鎖的規范定義如下:如果一個進程在等待只能由該進程停止才能引發的事件,那么該進程就是死鎖的。

    (1)產生死鎖的原因

  • 因為系統資源不足。
  • 進程運行推進的順序不合適。
  • 資源分配不當等。

    (2)產生死鎖的四個必要條件

  1. 互斥條件:每個資源要么已經分配給了一個進程,要么就是可用的。
  2. 占有和等待條件:已經得到了某個資源的進程可以再請求新的資源。
  3. 不可搶占條件:已經分配給一個進程的資源不能強制性地被搶占,只能被占有它的進程顯式地釋放;
  4. 環路等待條件:死鎖發生時,系統中一定有兩個或者兩個以上的進程組成的一條環路,該環路中的每個進程都在等待着下一個進程所占有的資源。

這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。

(3)處理死鎖的四種策略:

  1. 鴕鳥策略(忽略死鎖);
  2. 檢測死鎖並恢復;
  3. 仔細對資源進行分配,動態地避免死鎖;
  4. 通過破壞引起死鎖的四個必要條件之一,防止死鎖的產生。

(4)死鎖避免

死鎖避免的主要算法是基於一個安全狀態 的概念。在任何時刻,如果沒有死鎖發生,並且即使所有進程忽然請求對資源的最大請求,也仍然存在某種調度次序能夠使得每一個進程運行完畢,則稱該狀態是安全的。從安全狀態出發,系統能夠保證所有進程都能完成,而從不安全狀態出發,就沒有這樣的保證。銀行家算法 :判斷對請求的滿足是否會進入不安全狀態,如果是,就拒絕請求,如果滿足請求后系統仍然是安全的,就予以分配。不安全狀態不一定引起死鎖,因為客戶不一定需要其最大貸款額度。

79、ceph bluestore和filestore的區別,filestore為什么使用日志,以及分布式存儲中,日志的作用

ceph后端支持多種存儲引擎,以插件化的形式來進行管理使用,目前支持filestore,kvstore,memstore以及bluestore,目前默認使用的是filestore,但是目前bluestore也可以上生產。

1)Firestore存在的問題是:

  • 在寫數據前需要先寫journal,會有一倍的寫放大;
  • 若是另外配備SSD盤給journal使用又增加額外的成本;
  • filestore一開始只是對於SATA/SAS這一類機械盤進行設計的,沒有專門針對SSD這一類的Flash介質盤做考慮。

2)而Bluestore的優勢在於:

  • 減少寫放大;
  • 針對FLASH介質盤做優化;
  • 直接管理裸盤,進一步減少文件系統部分的開銷。

但是在機械盤場景Bluestore與firestore在性能上並沒有太大的優勢,bluestore的優勢在於flash介質盤。

80、進程和線程的區別?進程和程序的區別?

進程是資源(CPU、內存等)分配的基本單位,它是程序執行時的一個實例。程序運行時系統就會創建一個進程,並為它分配資源,然后把該進程放入進程就緒隊列,進程調度器選中它的時候就會為它分配CPU時間,程序開始真正運行。線程是程序執行時的最小單位,它是進程的一個執行流,是CPU調度和分派的基本單位,一個進程可以由很多個線程組成,線程間共享進程的所有資源,每個線程有自己的堆棧和局部變量。線程由CPU獨立調度執行,在多CPU環境下就允許多個線程同時運行。同樣多線程也可以實現並發操作,每個請求分配一個線程來處理。

進程和程序的區別

進程是動態的,而程序是靜態的。

進程有一定的生命期,而程序是指令的集合,本身無“運動”的含義。沒有建立進程的程序不能作為1個獨立單位得到操作系統的認可。1個程序可以對應多個進程,但1個進程只能對應1個程序。進程和程序的關系猶如演出和劇本的關系。

81、靜態連接與動態鏈接的區別

靜態鏈接
所謂靜態鏈接就是在編譯鏈接時直接將需要的執行代碼拷貝到調用處,優點就是在程序發布的時候就不需要依賴庫,也就是不再需要帶着庫一塊發布,程序可以獨立執行,但是體積可能會相對大一些。

動態鏈接

所謂動態鏈接就是在編譯的時候不直接拷貝可執行代碼,而是通過記錄一系列符號和參數,在程序運行或加載時將這些信息傳遞給操作系統,操作系統負責將需要的動態庫加載到內存中,然后程序在運行到指定的代碼時,去共享執行內存中已經加載的動態庫可執行代碼,最終達到運行時連接的目的。優點是多個程序可以共享同一段代碼,而不需要在磁盤上存儲多個拷貝,缺點是由於是運行時加載,可能會影響程序的前期執行性能。

 82、虛函數

簡單地說,那些被virtual關鍵字修飾的成員函數,就是虛函數。虛函數的作用,用專業術語來解釋就是實現多態性(Polymorphism),多態性是將接口與實現進行分離;用形象的語言來解釋就是實現以共同的方法,但因個體差異,而采用不同的策略

 83、構造函數中調用虛函數能發生多態嗎?

Vptr指針初始化的過程:
1.對象在創建的時,由編譯器對VPTR指針進行初始化
2.只有當對象的構造完全結束后VPTR的指向才最終確定
3.父類對象的VPTR指向父類虛函數表
4.子類對象的VPTR指向子類虛函數表

當定義一個子類對象的時候比較麻煩,因為構造子類對象的時候會首先調用父類的構造函數然后再調用子類的構造函數。當調用父類的構造函數的時候,此時會創建Vptr指針(也可以認為Vptr指針是屬於父類的成員,所以在子類中重寫虛函數的時候virtual關鍵字可以省略,因為編譯器會識別父類有虛函數,然后就會生成Vptr指針變量),該指針會指向父類的虛函數表;然后再調用子類的構造函數,此時Vptr又被賦值指向子類的虛函數表。
(執行父類的構造函數的時候Vptr指針指向的是父類的虛函數表,所以只能執行父類的虛函數)
上面的過程是Vptr指針初始化的過程。
這是因為這個原因,在構造函數中調用虛函數不能實現多態。

84、是否可將類的每個成員函數聲明為虛函數?

1.靜態成員函數不能定義為虛函數
1.因為靜態成員函數沒有this指針,並且靜態成員函數可以通過類名來訪問。
2.又因為虛函數是放在對象的虛表里面的,同一個類中的所有對象雖然共用同一張虛表,但是類名無法找到虛表。

2.內聯函數不能定義為虛函數
因為內聯函數沒有地址,而虛表里面存放的就是虛函數的地址。

3.構造函數不能定義為虛函數
1.因為虛函數是存放在對象的虛表里面,如果將構造函數定義為虛函數,則構造函數也必須存放在虛表里面,但是此時對象都還沒有創建也就沒有所謂的虛表。

85、父類指針和子類指針步長的問題?

C++中父類指針可以指向子類對象,很多時候這的確提供了方便之門。擔當遇到對象數組時,就要慎重考慮了。指針運算是按指針所指向數據類型的長度進行計算的,對於子類對象數組,當使用父類指針指向子類對象時,指針的步長依然是父類對象所占的長度,指針移動后,所指的位置不一定就是下一個子類對象數組元素的起始地址。只要是指針,就要符合指針的運算方式,不管你是不是類指針;如果子類在繼承了父類之后,沒有增加屬性,那么此時他們的步長一致,如果子類增加了屬性那么子類的步長將大於父類。

父類指針和子類指針的步長

1) 鐵律1:指針也只一種數據類型,C++類對象的指針p++/--,仍然可用。

2) 指針運算是按照指針所指的類型進行的。

p++《=》p=p+1 //p = (unsigned int)basep + sizeof(*p) 步長。

3) 結論:父類p++與子類p++步長不同;不要混搭,不要用父類指針++方式操作數組。

 86、多繼承二義性主要出現在:

 (1)不同繼承路徑有同名成員, 
(2)不同繼承路徑繼承共同的基類,存在多個基類子對象。 
消除二義性, 
對於(1)可以采用作用域限定; 
對於(2)可以采用作用域限定或虛基類。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM