1. 程式人生 > >2018秋招面試問題(五、C++基礎問題)

2018秋招面試問題(五、C++基礎問題)

注:面試過程中整理的學習資料,如有侵權聯絡我即刻刪除。

目錄

怎麼得到陣列的長度?

sizeof(array)/sizeof(array[0]);但是這種計算不適用於陣列作為函式引數的時候,當陣列名作為函式引數,會退化成指標。解決方法就是將陣列引用的陣列大小也宣告為一個函式引數。動態陣列用.size()就可以得到長度。

怎麼禁用類中的拷貝建構函式?

將拷貝建構函式和過載賦值運算子設為private來禁止拷貝。

介紹一下標準庫vector是怎麼進行記憶體管理的?resize和reserve分別有什麼作用?

(1)vector是一種序列式容器(其中的元素可以排序,但是並未排序)。它和array一樣,儲存空間是一段連續的記憶體,因此支援隨機訪問,但是,和array相比,vector支援動態增加刪除資料。

(2)特徵量有:size是vector當前所包含的元素個數;capacity是當前可以使用的容量,也就是預分配儲存空間的大小,capacity大於等於size;

reserve和resize的區別是:reserve是容器預留空間,但並不真正建立元素物件,在建立物件之前,不能引用容器內的元素;resize的作用是改變vector中元素的數目且建立物件,呼叫完resize可以直接引用vector的元素了

。如果n比當前的vector元素數目要小,vector的容量要縮減到n,並移除那些超出n的元素同時銷燬他們(此情況resize一個引數n)。如果n比當前vector元素數目要大,在vector的末尾擴充套件需要的元素數目,如果第二個引數val指定了,擴充套件的新元素初始化為val的副本,否則按型別預設初始化。resize完了之後size就等於n了。注意: 如果n大於當前的vector的容量(capacity),將會引起自動記憶體分配。

指標操作有什麼好處?

指標可以動態分配記憶體,很靈活。比如估計不到我們需要多少個變數,這個時候就需要指標。

資料結構離不開指標,連結串列、樹、圖都是離不開指標的。

交換兩個數的值有幾種寫法

指標傳遞,直接交換ab所在地址中的值的,但並不改變指標的指向。

二級指標傳遞,通過交換指標指向的地址來交換兩個值。

反正平級傳遞,是交換不了ab的值的。

說一下引用和指標的區別。常引用有什麼作用呢?將引用作為函式引數有什麼好處?拷貝建構函式的引數為什麼要用引用呢?

(1)指標是實體,需要分配記憶體空間,引用只是變數的別名,不需要分配記憶體空間;引用在定義的時候必須進行初始化,並且不能夠改變,指標定義的時候不一定初始化,指向的空間可變;指標和引用的自增運算結果不同,指標++是指向下一個空間,引用自增是指引用的變數值加一;sizeof引用得到的是對應變數的大小,sizeof指標就是指標本身的大小。

(2)常引用是 const int &y = x; 常引用是讓變數引用具有只讀欄位,不能通過y去修改x了。常引用的初始化有兩種,一種是用變數來初始化常引用,一種是用字面值來初始化常引用。

int x1 = 30;

const int &y1 = x1;//變數初始化常引用

const int &m = 43;//字面值初始化常引用

如果用int &m = 43;會報錯,引用是給記憶體取別名,字面值沒有記憶體,沒有記憶體無法取別名。而字面值初始化常引用,編譯器會給字面值分配記憶體空間。

(3)使用引用傳遞函式的引數,在記憶體中並沒有產生實參的副本,它是直接對實參操作;而使用一般變數傳遞函式的引數,當發生函式呼叫時,需要給形參分配儲存單元,形參變數是實參變數的副本;如果傳遞的是物件,還將呼叫拷貝建構函式。因此,當引數傳遞的資料較大時,用引用比用一般變數傳遞引數的效率和所佔空間都好。

使用指標作為函式的引數雖然也能達到與使用引用的效果,但是,指標傳遞本質上也是值傳遞,傳遞的是一個地址。在被調函式中同樣要給形參分配儲存單元,且需要重複使用\"*指標變數名\"的形式進行運算,這很容易產生錯誤且程式的閱讀性較差;另一方面,在主調函式的呼叫點處,必須用變數的地址作為實參。而引用更容易使用,更清晰。

(4)因為,如果假設拷貝建構函式中的引數不是引用型別的話,我們將物件傳進去,又會繼續觸發拷貝建構函式,陷入無限迴圈,所以只能用引用型別。而用const引用是為了防止傳入的物件在拷貝建構函式中被修改,是為了告訴程式設計師這個變數是隻讀的。

哪些地方會呼叫拷貝建構函式?

一個物件去初始化另一個物件;函式以物件作為引數傳遞;函式(型別)返回某個物件;

·      CExample aaa(2);    //呼叫帶參建構函式

·      CExample bbb(3);  

·      bbb = aaa;    //呼叫賦值運算子過載函式而不是拷貝建構函式

·      CExample ccc = aaa;  //呼叫拷貝建構函式

·      bbb.myTestFunc(aaa);  //呼叫拷貝建構函式

·      vector<myTestFunc> v(3); // 在分配的大小中,都會初始化容器的物件。這裡是呼叫一次預設建構函式,再呼叫2次拷貝建構函式。

什麼時候會過載運算子

一般是在,需要對非基本型別(比如自定義類)物件,做加減乘除等運算的時候,需要針對這個類做運算子過載。

虛擬函式是如何實現的?

在類的成員函式前面加個virtual關鍵字。當類中存在虛擬函式時,則編譯器會在編譯期自動的給該類生成一個虛擬函式表,並在所有該類的物件中生成一個指標變數,指向虛擬函式表。呼叫的時候就是通過這個指標變數在虛擬函式表中找到對應虛擬函式地址,來完成呼叫。

C++中為什麼虛擬函式比非虛擬函式耗費的時間更多?

虛擬函式在呼叫的時候需要去查詢虛表,根據指向物件的不同來確定呼叫的是哪一個函式,而普通函式直接通過函式名就得到了入口地址。所以虛擬函式耗時更多。

什麼是靜態繫結?什麼是動態繫結?

繫結:把一個方法與其所在的類/物件關聯起來。

靜態繫結:在程式編譯的時候就已經知道了方法是屬於哪一個類的,編譯時就能定位到這個方法。執行速度快。

動態繫結:是在程式執行過程中根據具體的例項物件,才能知道呼叫的具體是哪一個函式,主要是通過虛擬函式來實現的。通過指標或者引用才能實現。

動態繫結的缺點:a.動態繫結在函式呼叫時需要在虛擬函式表中查詢,所以效能比靜態繫結低一些。

b.通過基類型別的指標訪問派生類自己的虛擬函式,將發生錯誤。

任何妄圖使用父類指標想呼叫子類中的未覆蓋父類的成員函式的行為都會被編譯器視為非法

為什麼我們有些類會定義虛解構函式?如果不定義成虛解構函式會怎麼樣呢?

虛解構函式使得在刪除指向子類物件的基類指標時可以呼叫子類的解構函式達到釋放子類資源的目的,而防止記憶體洩露。

如果基類解構函式是虛擬函式的話,基類指標指向派生類的物件(多型性),如果刪除該指標delete []p;就會呼叫該指標指向的派生類解構函式,而派生類的解構函式又自動呼叫基類的解構函式,這樣整個派生類的物件完全被釋放。如果解構函式不被宣告成虛擬函式,則編譯器實施靜態繫結,在刪除基類指標時,只會呼叫基類的解構函式而不呼叫派生類解構函式,派生類物件沒有釋放,這樣會造成記憶體洩露。所以,將解構函式宣告為虛擬函式是十分必要的。

為什麼建構函式不可以是虛擬函式?

因為虛擬函式是存放在虛表中的,虛表指標又是物件例項化之後才建立的,虛表指標存放在物件的前四個位元組中,而建構函式中還沒有例項化物件,就沒有辦法通過虛表指標找到虛擬函式。所以建構函式不能是虛擬函式。

而且,構造物件的時候必須知道物件的實際型別,而虛擬函式是執行的時候才確定實際型別的。

虛擬函式表是在編譯期compile time就建立了,各個虛擬函式這時被組織成了一個虛擬函式的入口地址的陣列.而物件的隱藏成員--虛擬函式表指標是在執行期--也就是建構函式被呼叫時進行初始化的,這是實現多型的關鍵

建構函式中是否可以呼叫虛擬函式

可以,但是呼叫的是自己類中的虛擬函式,而不能呼叫子類中的虛擬函式。因為在基類建構函式中,子類物件還未被完全建立,也就不能通過子類的虛表指標來呼叫子類的虛擬函式。

C++哪些函式不能宣告為虛擬函式

兩類函式不能被宣告為虛擬函式:

一是不能被繼承的函式。

二是不能被重寫的函式。

有:普通函式(不屬於成員函式)、友元函式(不屬於成員函式)、建構函式、行內函數(編譯時展開的,虛擬函式是執行時實現多型的,兩個特性相違背)、靜態成員函式(編譯時確定的,不支援多型)。

為什麼有了虛解構函式,delete父類的指標(指向子類的物件)就能先呼叫子類的解構函式?

delete父類的指標p,程式會去找父類的指標p指向的地址,這個時候該地址就是子類頭部虛擬函式表指標的地址,由指標p找到子類的虛擬函式表,從而找到子類的虛解構函式。由於子類是從父類繼承來的,所以呼叫完子類的解構函式還會自動呼叫父類的解構函式。

派生類本身就包含兩部分,一部分繼承的基類,一部分自己的成員,所以析構派生類的時候編譯器也會自動呼叫父類的解構函式。(不管是不是虛析構,都會自動呼叫)

為什麼析構的時候是先呼叫派生類析構,再呼叫基類析構?

程式的執行是將每句指令調入記憶體中,以壓棧的方式存入棧中,對於先宣告的成員先進棧必定後析構,即先入後出,比如定義了兩個變數human man; student stu; 那麼系統先析構stu後析構man。同理對於派生類的物件,先呼叫了基類的構造後呼叫了繼承類的構造,所以在析構的時候先“彈出”繼承類的析構,後“彈出”基類的析構。

一個類包含一個普通成員函式、一個虛擬函式、一個純虛擬函式,那麼這個類的sizeof()有多大?為什麼是4位元組?虛表?

一個類中有虛擬函式,就會有虛擬函式指標,這個指標指向一個虛擬函式表,虛擬函式表中儲存的是虛擬函式的地址。多個虛擬函式也是隻有一個虛表,所以也只有四個位元組的大小。程式執行的時候就是在虛擬函式表中進行查詢真正要執行的虛擬函式的地址。每個類都會維護一張虛表,編譯時,編譯器根據類的宣告創建出虛表,當物件被構造時,虛表的地址就會被寫入這個物件記憶體。

注意:在基類有虛擬函式的前提下,虛表可以繼承,如果子類沒有重寫虛擬函式,那麼子類虛表中仍然會有該函式的地址,只不過這個地址指向的是基類的虛擬函式實現。如果基類有3個虛擬函式,那麼基類的虛表中就有三項(虛擬函式地址),派生類也會有虛表,至少有三項,如果重寫了相應的虛擬函式,那麼虛表中的地址就會改變,指向自身的虛擬函式實現。如果派生類有自己的虛擬函式,那麼虛表中就會新增該項。

虛擬函式指標的作用?

每一個類的物件都有一個指向虛表的虛指標。虛表是和類對應的,虛表指標是和物件對應的。這個指標指向了物件所屬類的虛表。每個物件呼叫的虛擬函式都是由虛指標來索引的,在程式執行時,根據物件的型別去初始化虛指標,從而讓虛指標正確的指向所屬類的虛表。在呼叫虛擬函式時,才能夠找到正確的函式。

虛表指標存在物件的什麼地方?

物件的前四個位元組設定為虛表指標的地址。

虛擬函式表存放在哪裡?

虛擬函式表是全域性共有的,因此是在全域性區。

上行轉換和下行轉換

上行轉換:子類指標和引用轉換為基類指標和引用,可隱式轉換。

下行轉換:基類指標轉成子類指標,只能強制轉換。

static_cast 和 dynamic_cast 的區別(RTTI)

這兩個都是做型別轉換的,發生的時間不同,static_cast是編譯時,dynamic_cast是執行時。

  1. dynamic_cast操作符會進行安全檢查,而static_cast操作符不會進行安全檢查
  2. dynamic_cast基本上是用來轉換指標和引用的,上行轉換static_cast可施加與任何型別;
  3. 上行轉換dynamic_cast和static_cast都是可行的,下行轉換這兩個都不會成功,但dynamic_cast要進行安全檢查,更安全。

基類指標指向子類物件,訪問子類中的虛方法,稱為多型。那麼子類指標指向基類物件呢?

必須強制轉換,這個指標如果呼叫的是子類繼承來的虛擬函式,那就是呼叫基類中的函式。如果呼叫的是子類的普通函式,那就是呼叫子類的函式。

C++物件的記憶體佈局

影響物件大小的因素有:成員變數、虛擬函式、單一繼承、多重繼承、虛擬繼承、還有位元組對齊。

單繼承物件的記憶體佈局:

虛表在最前面(被重寫的虛擬函式要在虛表中得到更新)。

成員變數根據繼承和宣告順序依次放在後面。

多重繼承物件的記憶體佈局:

每個父類都有自己的虛表(子類的虛表放在第一個父類虛表後面)。

父類佈局再按照宣告順序來排列。

虛繼承物件的記憶體佈局:

多了一個虛基類表來記錄虛繼承關係,有一個虛基類表指標