二十万字C/C++、嵌入式软开面试题全集宝典二
?
目錄
1、靜態綁定和動態綁定的介紹
2、C語言struct和C++struct區別
3、虛函數可以聲明為inline嗎?
4、介紹 C++ 所有的構造函數
5、 什么情況下會調用拷貝構造函數
6、 為什么拷貝構造函數必須是引用傳遞,不能是值傳遞?
7、 何時需要合成構造函數
8、 何時需要合成復制構造函數
9、 C++類的虛函數表和虛函數在內存中的位置
10、 同一個類,實例化多次,是否共享虛函數表?
11、 編譯器處理虛函數表應該如何處理
?12、 類成員初始化方式?構造函數的執行順序?為什么用成員初始化列表會快一些?
一、初始化方式
13、 成員列表初始化?
14、 何時需要成員初始化列表?過程是什么?
15、 構造函數為什么不能為虛函數?析構函數為什么要虛函數?
16、 析構函數的作用,如何起作用?
17、 構造函數和析構函數可以調用虛函數嗎,為什么
18、 構造函數析構函數可以調用虛函數嗎?
19、 構造函數的執行順序?析構函數的執行順序?構造函數內部干了啥?拷貝構造干了啥?
20、 虛析構函數的作用,父類的析構函數是否要設置為虛函數?
?
1、靜態綁定和動態綁定的介紹
只有在存在虛函數,并發生繼承時,基類指針或引用指向派生類對象時,才存在動態綁定。此時通過基類指針調用成員函數的話,如果調用的是虛函數,則執行派生類中的函數,如果不是虛函數,則調用基類的函數。
1.對象的靜態類型:對象在聲明時采用的類型。是在編譯期確定的。
2.對象的動態類型:目前所指對象的類型。是在運行期決定的。對象的動態類型可以更改,但是靜態類型無法更改。
3.靜態綁定:綁定的是對象的靜態類型,某特性(比如函數)依賴于對象的靜態類型,發生在編譯期。
4.動態綁定:綁定的是對象的動態類型,某特性(比如函數)依賴于對象的動態類型,發生在運行期。
2、C語言struct和C++struct區別
1、C語言中:struct是用戶自定義數據類型(UDT);C++中struct是抽象數據類型(ADT),支持成員函數的定義,(C++中的struct能繼承,能實現多態)。
2、C中struct是沒有權限的設置的,且struct中只能是一些變量的集合體,可以封裝數據卻不可以隱藏數據,而且成員不可以是函數。
3、C++中,struct的成員默認訪問說明符為public(為了與C兼容),class中的默認訪問限定符為private,struct增加了訪問權限,且可以和類一樣有成員函數。
4、struct作為類的一種特例是用來自定義數據結構的。一個結構標記聲明后,在C中必須在結構標記前加上struct,才能做結構類型名
3、虛函數可以聲明為inline嗎?
不能。
1、虛函數用于實現運行時的多態,或者稱為靜態綁定或動態綁定。而內聯函數用于提高效率。內聯函數的原理是,在編譯期間,對調用內聯函數的地方的代碼替換成函數代碼。內聯函數對于程序中需要頻繁使用和調用的小函數非常有用。
2.虛函數要求在運行時進行類型確定,而內聯函數要求在編譯期完成相關的函數替換;
4、介紹 C++ 所有的構造函數
類的對象被創建時,編譯系統為對象分配內存空間,并?動調?構造函數,由構造函數完成成員的初始化?作。
即構造函數的作?:初始化對象的數據成員。
1、?參數構造函數:即默認構造函數,如果沒有明確寫出?參數構造函數,編譯器會?動?成默認的?參數構造函數,函數為空,什么也不做,如果不想使??動?成的?參構造函數,必需要??顯示寫出?個?參構造函數。
2、?般構造函數:也稱重載構造函數,?般構造函數可以有各種參數形式,?個類可以有多個?般構造函數,前提是參數的個數或者類型不同,創建對象時根據傳?參數不同調?不同的構造函數。
3、拷?構造函數:拷?構造函數的函數參數為對象本身的引?,?于根據?個已存在的對象復制出?個新的該類的對象,?般在函數中會將已存在的對象的數據成員的值??復制到新創建的對象中。如果沒有顯示的寫拷?構造函數,則系統會默認創建?個拷?構造函數,但當類中有指針成員時,最好不要使?編譯器提供的默認的拷?構造函數,最好??定義并且在函數中執?深拷?。
4、類型轉換構造函數:根據?個指定類型的對象創建?個本類的對象,也可以算是?般構造函數的?種,這?提出來,是想說有的時候不允許默認轉換的話,要記得將其聲明為?explict?的,來阻??些隱式轉換的發?。
5、賦值運算符的重載:注意,這個類似拷?構造函數,將=右邊的本類對象的值復制給=左邊的對象,它不屬于構造函數,=左右兩邊的對象必需已經被創建。如果沒有顯示的寫賦值運算符的重載,系統也會?成默認的賦值運算符,做?些基本的拷??作。
這?區分
A a1, A a2; a1 = a2;//調?賦值運算符
A a3 = a1;//調?拷?構造函數,因為進?的是初始化?作, a3 并未存在
5、 什么情況下會調用拷貝構造函數
類的對象需要拷?時,拷?構造函數將會被調?,以下的情況都會調?拷?構造函數:
1、?個對象以值傳遞的?式傳?函數體,需要拷?構造函數創建?個臨時對象壓?到棧空間中。
2、?個對象以值傳遞的?式從函數返回,需要執?拷?構造函數創建?個臨時對象作為返回值。
3、?個對象需要通過另外?個對象進?初始化。
6、 為什么拷貝構造函數必須是引用傳遞,不能是值傳遞?
為了防止遞歸調用。
當?個對象需要以值?式進?傳遞時,編譯器會?成代碼調?它的拷?構造函數?成?個副本,如果類A的拷?構造函數的參數不是引?傳遞,?是采?值傳遞,那么就?需要為了創建傳遞給拷?構造函數的參數的臨時對象,???次調?類 A 的拷?構造函數,這就是?個?限遞歸。
1) 拷貝構造函數的作用就是用來復制對象的,再使用這個對象的實例來初始化這個對象的一個新的實例。
2) 參數傳遞過程到底發生了什么?將地址傳遞和值傳遞統一起來,歸根結底還是傳遞的是"值"(地址也是值,只不過通過它可以找到另一個值)!
i)值傳遞:
對于內置數據類型的傳遞時,直接賦值拷貝給形參(注意形參是函數內局部變量);
對于類類型的傳遞時,需要首先調用該類的拷貝構造函數來初始化形參(局部對象);如void foo(class_type obj_local){},?如果調用foo(obj); 首先class_type obj_local(obj)?,這樣就定義了局部變量obj_local供函數內部使用
ii)引用傳遞:
無論對內置類型還是類類型,傳遞引用或指針最終都是傳遞的地址值!而地址總是指針類型(屬于簡單類型), 顯然參數傳遞時,按簡單類型的賦值拷貝,而不會有拷貝構造函數的調用(對于類類型)。
上述1) 2)回答了為什么拷貝構造函數使用值傳遞會產生無限遞歸調用,內存溢出。
拷貝構造函數用來初始化一個非引用類類型對象,如果用傳值的方式進行傳參數,那么構造實參需要調用拷貝構造函數,而拷貝構造函數需要傳遞實參,所以會一直遞歸。
7、 何時需要合成構造函數
1.如果一個類沒有任何構造函數,但他含有一個成員對象,該成員對象含有默認構造函數,那么編譯器就為該類合成一個默認構造函數,因為不合成一個默認構造函數那么該成員對象的構造函數不能調用;
2.沒有任何構造函數的類派生自一個帶有默認構造函數的基類,那么需要為該派生類合成一個構造函數,只有這樣基類的構造函數才能被調用;
3.帶有虛函數的類,虛函數的引入需要進入虛表,指向虛表的指針,該指針是在構造函數中初始化的,所以沒有構造函數的話該指針無法被初始化;
4.帶有一個虛基類的類
5.并不是任何沒有構造函數的類都會合成一個構造函數
6.編譯器合成出來的構造函數并不會顯示設定類內的每一個成員變量
8、 何時需要合成復制構造函數
有三種情況會以一個對象的內容作為另一個對象的初值:
1.對一個對象做顯示的初始化操作,X xx = x;
2.當對象被當做參數交給某個函數時;
3.當函數傳回一個類對象時;
9、 C++類的虛函數表和虛函數在內存中的位置
C++類對象中虛函數表指針、虛函數表、虛函數之間的關系以及在內存中的布局_子木呀的博客-CSDN博客
關系:虛函數表指針(保存在堆或棧)->虛函數表(常量區?.rodata)->虛函數(代碼段 .text)
虛函數表指針是虛函數表所在位置的地址。虛函數表指針屬于對象實例。因而通過new 出來的對象的虛函數表指針位于堆,聲名對象的虛函數表指針位于棧。
總結:
1、虛函數表指針位置取決于對象在哪。如果是new的對象,則存在堆上,如果是直接聲明,則存在棧上。
2、虛函數表位于只讀數據段(.rodata),即:C++內存模型中的常量區;
3、虛函數代碼則位于代碼段(.text),也就是C++內存模型中的代碼區。
10、 同一個類,實例化多次,是否共享虛函數表?
所有實例是共用一個虛函數表。
1.為什么要共用同一個虛函數表?可能是為了節省內存吧,首先同一個類的對象虛函數都是一樣的,沒必要重新伴隨構造生成一份一模一樣的表,所以拷貝虛函數表的表地址就行。
2.C++的編譯器應該是保證虛函數表的指針存在于對象實例中最前面的位置(這是為了保證取到虛函數表的有最高的性能——假設有多層繼承或是多重繼承的情況下)。
3.這意味著我們通過對象實例的地址得到這張虛函數表。然后就能夠遍歷當中函數指針,并調用對應的函數。
11、 編譯器處理虛函數表應該如何處理
對于派?類來說,編譯器建?虛函數表的過程其實?共是三個步驟:
1、拷?基類的虛函數表,如果是多繼承,就拷?每個有虛函數基類的虛函數表
2、當?個基類的虛函數表和派?類?身的虛函數表是共用的?個虛函數表,也稱這個基類為派?類的主基類
3、查看派?類中是否有重寫基類中的虛函數,如果有,就替換成已經重寫的虛函數地址;
4、查看派?類是否有?身的虛函數,如果有,就追加?身的虛函數到?身的虛函數表中。
Derived *pd = new D(); B *pb = pd; C *pc = pd;?其中 pb, pd, pc 的指針位置是不同的。
要注意的是派?類的?身的內容要追加在主基類的內存塊后,虛函數表指針始終在內存最前面。
?12、 類成員初始化方式?構造函數的執行順序?為什么用成員初始化列表會快一些?
一、初始化方式
1.賦值初始化,通過在函數體內進行賦值初始化;
2.列表初始化,在冒號后使用初始化列表進行初始化。
這兩種方式的主要區別在于:
1.對于賦值初始化,是在所有的數據成員被分配內存空間后才進行的。
2.列表初始化是給數據成員分配內存空間時就進行初始化,就是說分配一個數據成員只要冒號后有此數據成員的賦值表達式(此表達式必須是括號賦值表達式),那么分配了內存空間后在進入函數體之前給數據成員賦值,就是說初始化這個數據成員此時函數體還未執行。
二、列表初始化比賦值初始化更快的原因
賦值初始化是在構造函數當中做賦值的操作,而列表初始化是做純粹的初始化操作。我們都知道,C++的賦值操作是會產生臨時對象的。臨時對象的出現會降低程序的效率。
三、一個派生類構造函數的執行順序如下:
1.虛擬基類的構造函數(多個虛擬基類則按照繼承的順序執行構造函數)。
2.基類的構造函數(多個普通基類也按照繼承的順序執行構造函數)。
3.類類型的成員對象的構造函數(按照初始化順序)
4.派生類自己的構造函數。
13、 成員列表初始化?
1.必須使用成員初始化的四種情況
①??當初始化一個引用成員時;
②??當初始化一個常量成員時;
③??當調用一個基類的構造函數,而它擁有一組參數時;
④??當調用一個成員類的構造函數,而它擁有一組參數時;
2.成員初始化列表做了什么
①??編譯器會一一操作初始化列表,以適當的順序在構造函數之內安插初始化操作,并且在任何顯示用戶代碼之前;
②??list中的項目順序是由類中的成員聲明順序決定的,不是由初始化列表的順序決定的;
14、 何時需要成員初始化列表?過程是什么?
1.當初始化一個引用成員變量時;
2.初始化一個const成員變量時;
3.當調用一個基類的構造函數,而構造函數擁有一組參數時;
4.當調用一個成員類的構造函數,而他擁有一組參數;
5.編譯器會一一操作初始化列表,以適當順序在構造函數之內安插初始化操作,并且在任何顯示用戶代碼前。list中的項目順序是由類中的成員聲明順序決定的,不是初始化列表中的排列順序決定的。
15、 構造函數為什么不能為虛函數?析構函數為什么要虛函數?
1.從存儲空間角度,虛函數相應一個指向vtable虛函數表的指針,這大家都知道,但是這個指向vtable的指針事實上是存儲在對象的內存空間的。問題出來了,假設構造函數是虛的,就須要通過 vtable來調用,但是對象還沒有實例化,也就是內存空間還沒有,怎么找vtable呢?所以構造函數不能是虛函數。
2.從使用角度,虛函數主要用于在信息不全的情況下,能使重載的函數得到相應的調用。構造函數本身就是要初始化實例,那使用虛函數也沒有實際意義呀。所以構造函數沒有必要是虛函數。虛函數的作用在于通過父類的指針或者引用來調用它的時候可以變成調用子類的那個成員函數。而構造函數是在創建對象時自己主動調用的,不可能通過父類的指針或者引用去調用,因此也就規定構造函數不能是虛函數。
3.構造函數不須要是虛函數,也不同意是虛函數,由于創建一個對象時我們總是要明白指定對象的類型,雖然我們可能通過實驗室的基類的指針或引用去訪問它但析構卻不一定,我們往往通過基類的指針來銷毀對象。這時候假設析構函數不是虛函數,就不能正確識別對象類型從而不能正確調用析構函數。
4.從實現上看,vbtl在構造函數調用后才建立,因而構造函數不可能成為虛函數從實際含義上看,在調用構造函數時還不能確定對象的真實類型(由于子類會調父類的構造函數);并且構造函數的作用是提供初始化,在對象生命期僅僅運行一次,不是對象的動態行為,也沒有必要成為虛函數。
5.當一個構造函數被調用時,它做的首要的事情之中的一個是初始化它的VPTR。因此,它僅僅能知道它是“當前”類的,而全然忽視這個對象后面是否還有繼承者。當編譯器為這個構造函數產生代碼時,它是為這個類的構造函數產生代碼——既不是為基類,也不是為它的派生類(由于類不知道誰繼承它)。所以它使用的VPTR必須是對于這個類的VTABLE。并且,僅僅要它是最后的構造函數調用,那么在這個對象的生命期內,VPTR將保持被初始化為指向這個VTABLE, 但假設接著另一個更晚派生的構造函數被調用,這個構造函數又將設置VPTR指向它的 VTABLE,等.直到最后的構造函數結束。VPTR的狀態是由被最后調用的構造函數確定的。這就是為什么構造函數調用是從基類到更加派生類順序的還有一個理由。可是,當這一系列構造函數調用正發生時,每一個構造函數都已經設置VPTR指向它自己的VTABLE。假設函數調用使用虛機制,它將僅僅產生通過它自己的VTABLE的調用,而不是最后的VTABLE(全部構造函數被調用后才會有最后的VTABLE)。
6.直接的講,C++中基類采用virtual虛析構函數是為了防止內存泄漏。具體地說,如果派生類中申請了內存空間,并在其析構函數中對這些內存空間進行釋放。假設基類中采用的是非虛析構函數,當刪除基類指針指向的派生類對象時就不會觸發動態綁定,因而只會調用基類的析構函數,而不會調用派生類的析構函數。那么在這種情況下,派生類中申請的空間就得不到釋放從而產生內存泄漏。所以,為了防止這種情況的發生,C++中基類的析構函數應采用virtual虛析構函數。
16、 析構函數的作用,如何起作用?
1.構造函數只是起初始化值的作用,但實例化一個對象的時候,可以通過實例去傳遞參數,從主函數傳遞到其他的函數里面,這樣就使其他的函數里面有值了。規則,只要你實例化一個對象,系統自動會調用一個構造函數,就是你不寫,編譯器也自動調用一次。
2.析構函數與構造函數的作用相反,用于撤銷對象的一些特殊任務處理,可以是釋放對象分配的內存空間;特點:析構函數與構造函數同名,但該函數前面加~。
3.析構函數沒有參數,也沒有返回值,而且不能重載,在一個類中只能有一個析構函數。當撤銷對象時,編譯器也會自動調用析構函數。每一個類必須有一個析構函數,用戶可以自定義析構函數,也可以是編譯器自動生成默認的析構函數。一般析構函數定義為類的公有成員。
17、 構造函數和析構函數可以調用虛函數嗎,為什么
1.在C++中,提倡不在構造函數和析構函數中調用虛函數;
2.構造函數和析構函數調用虛函數時都不使用動態聯編,如果在構造函數或析構函數中調用虛函數,則運行的是為構造函數或析構函數自身類型定義的版本;
3.因為父類對象會在子類之前進行構造,此時子類部分的數據成員還未初始化,因此調用子類的虛函數時不安全的,故而C++不會進行動態聯編;
4.析構函數是用來銷毀一個對象的,在銷毀一個對象時,先調用子類的析構函數,然后再調用基類的析構函數。所以在調用基類的析構函數時,派生類對象的數據成員已經銷毀,這個時候再調用子類的虛函數沒有任何意義。
5.繼承類在構造的時候總是首先調用其基類的構造函數來對屬于其基類的部分進行構造,在這個時候,整個類被當作基類來處理,繼承類的部分對整個類來說好像不存在一樣,直到基類的構造函數退出并進入繼承類的構造函數,該類才被當作繼承類來出來處理。對析構也一樣,只是析構的順序正好相反。
18、 構造函數析構函數可以調用虛函數嗎?
1.在構造函數最好不要調用虛函數;
2.構造函數或者析構函數調用虛函數并不會發揮虛函數動態綁定的特性,跟普通函數沒區別;
3.即使構造函數或者析構函數如果能成功調用虛函數,程序的運行結果也是不可控的。
lsy注:當有基類指針指向派生類時,需要將析構函數定義為虛函數。
19、 構造函數的執行順序?析構函數的執行順序?構造函數內部干了啥?拷貝構造干了啥?
1、構造函數順序
1.基類構造函數。如果有多個基類,則構造函數的調用順序是某類在類派生表中出現的順序,而不是它們在成員初始化表中的順序。
2.成員類對象構造函數。如果有多個成員類對象則構造函數的調用順序是對象在類中被聲明的順序,而不是它們出現在成員初始化表中的順序。
3.派生類構造函數。
2、析構函數順序
1.調用派生類的析構函數;
2.調用成員類對象的析構函數;
3.調用基類的析構函數。
20、 虛析構函數的作用,父類的析構函數是否要設置為虛函數?
1.C++中基類采用virtual虛析構函數是為了防止內存泄漏。具體地說,如果派生類中申請了內存空間,并在其析構函數中對這些內存空間進行釋放。假設基類中采用的是非虛析構函數,當刪除基類指針指向的派生類對象時就不會觸發動態綁定,因而只會調用基類的析構函數,而不會調用派生類的析構函數。那么在這種情況下,派生類中申請的空間就得不到釋放從而產生內存泄漏。所以,為了防止這種情況的發生,C++中基類的析構函數應采用virtual虛析構函數。
2.純虛析構函數一定得定義,因為每一個派生類析構函數會被編譯器加以擴張,以靜態調用的方式調用其每一個虛基類以及上一層基類的析構函數。因此,缺乏任何一個基類析構函數的定義,就會導致鏈接失敗。因此,最好不要把虛析構函數定義為純虛析構函數。
總結
以上是生活随笔為你收集整理的二十万字C/C++、嵌入式软开面试题全集宝典二的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二十万字C/C++、嵌入式软开面试题全集
- 下一篇: 二十万字C/C++、嵌入式软开面试题全集