面試總結6--C++基礎問題Part2

1、C和C++中struct有什麼區別,c++中struct和class區別?

C的struct與C++的class的區別:struct只是作爲一種複雜數據類型定義,不能用於面向對象編程。在純粹的C語言中,struct不能定義成員函數,只能定義變量

C++中的struct擴充了C的struct功能struct還有構造函數和成員函數,其實它還擁有class的其他特性,例如繼承、虛函數等。

C++中的struct和class的區別:對於成員訪問權限以及繼承方式,class中默認的是private的,而struct中則是public的。class還可以用於表示模板類型,struct則不行。

 

2、指針和引用的區別?

1、本質:指針是一個變量,存儲內容是一個地址,指向內存的一個存儲單元。而引用是原變量的一個別名,實質上和原變量是一個東西,是某塊內存的別名。2、指針的值可以爲空,且非const指針可以被重新賦值以指向另一個不同的對象。而引用的值不能爲空,並且引用在定義的時候必須初始化,一旦初始化,就和原變量“綁定”,不能更改這個綁定關係。

3、對指針執行sizeof()操作得到的是指針本身的大小(32位系統爲4,64位系統爲8)。而對引用執行sizeof()操作得到的是所綁定的對象的所佔內存大小。4、指針的自增(++)運算表示對地址的自增,自增大小要看所指向單元的類型。而引用的自增(++)運算表示對值的自增。

5、在作爲函數參數進行傳遞時的區別:指針所以函數傳輸作爲傳遞時,函數內部的指針形參是指針實參的一個副本,改變指針形參並不能改變指針實參的值,通過解引用*運算符來更改指針所指向的內存單元裏的數據。而引用在作爲函數參數進行傳遞時,實質上傳遞的是實參本身,即傳遞進來的不是實參的一個拷貝,因此對形參的修改其實是對實參的修改,所以在用引用進行參數傳遞時,不僅節約時間,而且可以節約空間。

 

3、處理New分配內存失敗情況?

我們經常會使用new給一個對象分配內存空間,而當內存不夠會出現內存不足的情況。C++提供了兩種報告方式:

1、拋出bad_alloc異常來報告分配失敗;

2、返回空指針,而不會拋出異常

operator new 無法滿足內存需求時,它會不只一次地調用new_handler函數(如果new_handler沒有退出程序的話);它會不斷地調用,直到找到足夠的內存爲止。可以用set_new_hander 函數爲 new 設置用戶自己定義的異常處理函數,也可以讓 malloc 享用與 new 相同的異常處理函數。


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

不是。兩個不同類型的指針之間可以強制轉換(用reinterpret_cast)


5、當一個類中沒有聲命任何成員變量與成員函數這時sizeof(A)的值是多少,如果不是零,請解釋一下編譯器爲什麼沒有讓它爲零。

通常是1,用作佔位的。爲了確保不同對象有不同的地址


6爲什麼內聯函數,構造函數,靜態成員函數不能爲virtual函數?

1> 內聯函數

內聯函數是在編譯時期展開,而虛函數的特性是運行時才動態聯編,所以兩者矛盾,不能定義內聯函數爲虛函數。

2> 構造函數

構造函數用來創建一個新的對象,而虛函數的運行是建立在對象的基礎上,在構造函數執行時,對象尚未形成,所以不能將構造函數定義爲虛函數

3> 靜態成員函數

靜態成員函數屬於一個類而非某一對象,沒有this指針,它無法進行對象的判別。

這個可以從兩個角度去理解:

1。virtual意味着在執行時期進行綁定,所以在編譯時刻需確定信息的不能爲virtual

構造函數需在編譯時刻,因爲需構造出個對象,才能執行動作。
          靜態成員函數不屬於任何一個對象,編譯時刻確定不存在執行的時候選擇執行哪個的情形。
          內聯函數,由於屬於編譯器的建議機制,所以其實可以virtual。

2。virtual意味着派生類可以改寫其動作
        派生類的構造函數會先執行基類的構造函數而不是取代基類構造函數,也就是說基類的構造函數可以看作派生類構造函數的組成,所以並不能改寫這個函數。
        靜態成員函數不屬於任何一個對象,所以更不能改寫其動作了。

       inline和virtual不會同時起作用。帶virtual的函數在不需要動態綁定調用的時候,就可以inline。


7、C++函數中那些不可以被聲明爲虛函數的函數?爲什麼C++不支持友元函數爲虛函數?

常見的不不能聲明爲虛函數的有:普通函數(非成員函數);靜態成員函數;內聯成員函數;構造函數;友元函數。

因爲C++不支持友元函數的繼承,對於沒有繼承特性的函數沒有虛函數的說法。


8、構造函數和析構函數爲什麼沒有返回值?

構造函數和析構函數是兩個非常特殊的函數:它們沒有返回值。這與返回值爲void的函數顯然不同,後者雖然也不返回任何值,但還可以讓它做點別的事情,而構造函數和析構函數則不允許。在程序中創建和消除一個對象的行爲非常特殊,就像出生和死亡,而且總是由編譯器來調用這些函數以確保它們被執行。如果它們有返回值,要麼編譯器必須知道如何處理返回值,要麼就只能由客戶程序員自己來顯式的調用構造函數與析構函數,這樣一來,安全性就被人破壞了。另外,析構函數不帶任何參數,因爲析構不需任何選項。

 

9、函數模板與類模板的區別?

函數模板的實例化是由編譯程序在處理函數調用時自動完成的,而類模板的實例化必須由程序員在程序中顯式地指定。即函數模板允許隱式調用和顯式調用而類模板只能顯示調用。

 

10、基類的析構函數不是虛函數,會帶來什麼問題?

如果基類的爲虛的話,那麼在釋放派生類對象的時候,會首先調用派生類的析構函數,然後再調用基類的析構函數

如果基類析構函數不爲虛的話,在釋放派生類對象的時候就不會調用派生類的析構函數,有可能造成內存泄露

 

11、構造函數可以調用虛函數嗎?語法上通過嗎?語義上可以通過嗎?

 調用當然是沒有問題的但多態這個功能被屏蔽了。

在構造一個子類對象的時候,首先會構造它的基類,如果有多層繼承關係,實際上會從最頂層的基類逐層往下構造(虛繼承、多重繼承這裏不討論),如果是按照上面的情形進行輸出的話,那就是說在構造Base的時候,也就是在Base的構造函數中調用Fuction的時候,調用了子類A的Fuction,而實際上A還沒有開始構造,這樣函數的行爲就是完全不可預測的,因此顯然不是這樣。

在調用Base的構造函數時已經出現了虛函數表指針,這個指針指向Base的虛函數表,所以在Base的構造函數中調用的虛函數其實都是Base的虛函數;而在構造A時,虛函數表指針被指向了A的虛函數表,所以此時調用的虛函數其實是A的虛函數表中的項。


12、析構函數可以拋出異常嗎?爲什麼不能拋出異常?除了資源泄露,還有其他需考慮的因素嗎?

 

1C++中析構函數的執行不應該拋出異常;

2)如果析構函數拋出異常,則異常點之後的程序不會執行,如果析構函數在異常點之後執行了某些必要的動作比如釋放某些資源,則這些動作不會執行,會造成諸如資源泄漏的問題。

通常異常發生時,c++的機制會調用已經構造對象的析構函數來釋放資源,此時若析構函數本身也拋出異常,則前一個異常尚未處理,又有新的異常,會造成程序崩潰的問題。析構函數中拋出異常導致程序不明原因的崩潰是許多系統的致命內傷

解決辦法:那就是把異常完全封裝在析構函數內部,決不讓異常拋出函數之外。如使用

Try

 {  }

Catch

{/這裏可以什麼都不做,只是保證catch塊的程序拋出的異常不會被扔出析構函數之外}

 

13、c++中類型轉換機制?各適用什麼環境?dynamic_cast轉換失敗時,會出現什麼情況?

1.static_cast

最常用的類型轉換符,在正常狀況下的類型轉換,如把int轉換爲float,如:int ifloatf f=floati;或者f=static_cast<float>(i);

2.const_cast

用於取出const屬性,把const類型的指針變爲非const類型的指針,如:const int *fun(int x,int y){}int *ptr=const_cast<int*>(fun(2.3))

3.dynamic_cast(在繼承體系中使用)

該操作符用於運行時檢查該轉換是否類型安全,但只在多態類型時合法,即該類至少具有一個虛擬方法。dynamic_caststatic_cast具有相同的基本語法,dynamic_cast主要用於類層次間的上行轉換和下行轉換,還可以用於類之間的交叉轉換。在類層次間進行上行轉換時,dynamic_caststatic_cast的效果是一樣的;在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全。

4.reinterpret_cast(最常用途是轉換函數指針類型)

interpret是解釋的意思,reinterpret即爲重新解釋,此標識符的意思即爲數據的二進制形式重新解釋,但是不改變其值。如:int i; char *ptr="hello freind!";i=reinterpret_cast<int>(ptr);這個轉換方式很少使用。

dynamic_cast轉換失敗時對指針,返回NULL.對引用,拋出bad_cast異常


14、虛函數與純虛函數區別?

定義一個函數爲虛函數,不代表函數爲不被實現的函數。
定義他爲虛函數是爲了允許用基類的指針來調用子類的這個函數。
定義一個函數爲純虛函數,才代表函數沒有被實現。
定義純虛函數是爲了實現一個接口,起到一個規範的作用,規範繼承這個類的程序員必須實現這個函數。純虛函數最顯著的特徵是:它們必須在繼承類中重新聲明函數(不要後面的=0,否則該派生類也不能實例化),而且它們在抽象類中往往沒有定義。

1.  虛函數和純虛函數可以定義在同一個類(class)中,含有純虛函數的類被稱爲抽象類(abstract class),而只含有虛函數的類(class)不能被稱爲抽象類(abstract class)。

2.  虛函數可以被直接使用,也可以被子類(sub class)重載以後以多態的形式調用,而純虛函數必須在子類(sub class)中實現該函數纔可以使用,因爲純虛函數在基類(base class)只有聲明而沒有定義。

3.  虛函數和純虛函數都可以在子類(sub class)中被重載,以多態的形式被調用

 

15、C++ static、const和static const 以及它們的初始化?

static靜態成員變量不能在類的內部初始化。在類的內部只是聲明,定義必須在類定義體的外部,通常在類的實現文件中初始化,

const數據成員的初始化只能在類的構造函數的初始化列表中進行。要想建立在整個類中都恆定的常量,應該用類中的枚舉常量來實現,或者static cosnt。

但是static const 的整型(bool,char,int,long)可以在類聲明中初始化 

 

16、私有繼承作用?is-a,has-a區別?

私有繼承是一種實現繼承,有“with a”的語義。子類利用父類中某些代碼實現子類的某些功能。

   is-a繼承關係,has-a組合關係。

    Public繼承意味is-a,適用於base classes身上的每一件事情一定也適用於derive classes身上,因爲每一個derive class對象也都是一個base class對象。

    has-a我們想實現一個Set類,而已經有一個List類可提供給你使用,我們到底用is-a(public繼承)關係還是用has-a(組合)關係呢?

    答案是用has-a(組合)關係,因爲list可以含重複元素而,set不可以。


17、在構造函數和析構函數中拋出異常會發生什麼?什麼是棧展開?

 

構造函數中可以拋出異常,構造拋異常之前必須把已經申請的資源釋放掉這樣,就算你的對象是new出來的,也不會造成內存泄漏。
     因爲析構函數不會被調用,所以拋出異常後,你沒機會釋放資源。在構造函數中拋出異常導致析構這個對象。

構造函數中拋出異常時概括性總結
1 C++中通知對象構造失敗的唯一方法那就是在構造函數中拋出異常;

2構造函數中拋出異常將導致對象的析構函數不被執行;

3當對象發生部分構造時,已經構造完畢的子對象將會逆序地被析構;

棧展開:拋出異常時,將暫停當前函數的執行,開始查找匹配的catch子句。首先檢查throw本身是否在try塊內部,如果是,檢查與該try相關的catch子句,看是否可以處理該異常。如果不能處理,就退出當前函數,並且釋放當前函數的內存並銷燬局部對象,繼續到上層的調用函數中查找,直到找到一個可以處理該異常的catch。這個過程稱爲棧展開(stack unwinding

18、++iterator 和iterator++效率問題?

兩種方式iterator遍歷的次數是相同的,但在STL中效率不同,前++返回引用,後++返回一個臨時對象,因爲iterator是類模板,使用 it++這種形式要返回一個無用的臨時對象,而it++函數重載,所以編譯器無法對其進行優化,所以每遍歷一個元素,你就創建並銷燬了一個無用的臨時對象。
      
除了特殊需要和對內置類型外使用++it來進行元素遍歷的。

 

19、string的內存怎麼實現的,長度的動態變化怎麼處理,爲什麼兩倍擴展。stringa; string b = a;這時會不會有內存拷貝相關的東西?

 String對象可能是1~7char*大小,當插入string的長度大於其Capacity會重新分配一個2倍長度的內存,將現在的內容拷貝過去然後將新的內容加入到尾部,並將老的空間釋放掉。

不會有拷貝相關的東西,ba指向同一地址的字符串,引用計數加1.


20map是怎麼實現的?

 

21、動態鏈接和靜態鏈接比較?

靜態庫:代碼的裝載速度快,執行速度也比較快,因爲編譯時它只會把你需要的那部分鏈接進去,應用程序相對比較大。但是如果多個應用程序使用的話,會被裝載多次,浪費內存。動態鏈接可以動態的安裝和卸載,更加靈活。


22孤兒進程:父進程先退出,子進程被init進程收養;殭屍進程:子進程退出,父進程並沒有獲取子進程的狀態信息,子進程的狀態描述符仍然存在系統中;守護進程:Linux的後臺服務進程,比如crond等,不受終端控制,在系統啓動時啓動,在系統關閉時終止。


23髒讀:當一個事務讀取其它完成一半事務的記錄時,就會發生髒讀取。例如:用戶A,B看到的值都是6,用戶B把值改爲2,用戶A讀到的值仍爲6。

悲觀鎖:假定會發生併發衝突,屏蔽一切可能違反數據完整性的操作。[1]悲觀鎖假定其他用戶企圖訪問或者改變你正在訪問、更改的對象的概率是很高的,因此在悲觀鎖的環境中,在你開始改變此對象之前就將該對象鎖住,並且直到你提交了所作的更改之後才釋放鎖。悲觀的缺陷是不論是頁鎖還是行鎖,加鎖的時間可能會很長,這樣可能會長時間的限制其他用戶的訪問,也就是說悲觀鎖的併發訪問性不好。

樂觀鎖:假設不會發生併發衝突,只在提交操作時檢查是否違反數據完整性。[1] 樂觀鎖不能解決髒讀的問題。 樂觀鎖則認爲其他用戶企圖改變你正在更改的對象的概率是很小的,因此樂觀鎖直到你準備提交所作的更改時纔將對象鎖住,當你讀取以及改變該對象時並不加鎖。可見樂觀鎖加鎖的時間要比悲觀鎖短,樂觀鎖可以用較大的鎖粒度獲得較好的併發訪問性能。但是如果第二個用戶恰好在第一個用戶提交更改之前讀取了該對象,那麼當他完成了自己的更改進行提交時,數據庫就會發現該對象已經變化了,這樣,第二個用戶不得不重新讀取該對象並作出更改。這說明在樂觀鎖環境中,會增加併發用戶讀取對象的次數。


24、Windows內存管理的方法

內存管理的主要方式:塊式管理頁式管理,段式管理,段頁式管理。

25、宏定義和內聯函數有什麼區別嗎,分別在什麼樣的場景下應用啊

內聯函數在編譯時,將調用處進行函數替換,避免調來調去壓榨出棧的時間開銷,以空間換時間,還有內聯函數有類型檢測,宏替換沒有類型檢測,內聯函數只是向編譯器申請,若內聯函數體內有循環遞歸等,申請會失敗,系統會當初普通函數處理。

 

26、如何減少頻繁分配內存(malloc或者new)造成的內存碎片?

內存池(Memory Pool)是一種內存分配方式。 通常我們習慣直接使用new、malloc等API申請分配內存,這樣做的缺點在於:由於所申請內存塊的大小不定,當頻繁使用時會造成大量的內存碎片並進而降低性能。內存池則是在真正使用內存之前,先申請分配一定數量的、大小相等(一般情況下)的內存塊留作備用。當有新的內存需求時,就從內存池中分出一部分內存塊,若內存塊不夠再繼續申請新的內存。這樣做的一個顯著優點是儘量避免了內存碎片,使得內存分配效率得到提升。

 

27、Windows程序的入口是哪裏?寫出Windows消息機制的流程?

入口在main()/WinMain()

<1>操作系統接收應用程序的窗口消息,將消息投遞到該應用程序的消息隊列中

<2>應用程序在消息循環中調用GetMessage函數從消息隊列中取出一條一條的消息,取出消息後,應用程序可以對消息進行一些預處理

<3>應用程序調用DispatchMessage,將消息回傳給操作系統

<4>系統利用WNDCLASS結構體的ipfoWndProc成員保存的窗口過程函數的指針調用窗口過程,對消息進行處理


28、拋出exception、傳遞對象到函數、和調用虛函數之間的差異?

主要有3個差異:

1、 exception對象總是會被複制,如果以by value方式捕捉,它會被複制兩次。傳遞給函數的對象則不一定得複製。

2、 被拋出的exception只允許繼承架構中的類型轉換和有型指針與void* 轉化,所以一個對const void* 指針而設計的catch子句,可以捕捉任何指針類型的exception。

3、 exception處理機制採用最先吻合策略,第一個匹配成功者便執行,而虛函數則採用最佳吻合策略。

4、 函數調用控制權最終會回到調用端,而拋出一個exception控制權不會回再回到拋出端。

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