1. 程式人生 > >C++面試 2017總結

C++面試 2017總結

 C++常考面試題 : (1)(2)  

1、儘可能說出static關鍵字的作用?

  (1) static修飾函式區域性變數(包括main函式裡的),該變數不會隨著函式作用域的退出而銷燬,而是隻分配一次記憶體,下次呼叫時為上次呼叫值。

  (2) static修飾全域性變數,限定了該變數只能被本檔案訪問,不能被其他檔案訪問。

  (3) static修飾的函式只能被本檔案訪問,不能被其他檔案訪問。

  (4) static menber屬於類,所有該類的例項物件共享一份拷貝(此處是名詞)

  (5) static menber function不接受this指標,只能訪問靜態成員

       由此衍生出問題:靜態變數和靜態成員函式的用法?

2、儘可能說出const的用法?

const不能簡單的只認為是 “常數”,c語言中要使用常數,比如PI,會用define,<<effective c++>>推薦使用const,因為使用巨集如果編譯報錯,是不會顯示PI的,只提示3.1415數字。const的真正用意是為了提醒程式設計師,該物件是不可修改的

  (1) 用const修飾一個變數,需要初始化,忘記初始化會編譯報錯

  (2) const與指標作用時,<<c++ primer 5th>> 分為pointer to const(指向常量的指標,指標自以為指向的是常量,不能用指標來修改物件,而不要求該物件是 const的),const pointer(指標不能指向其他物件),或者const int * const p =&a; 

  (3) const 修飾形參,表明不能通過形參修改實參,可以接收const物件作為實參或non-const物件

  (4) const引用,不能用引用去修改所引物件,但物件本身可以是可修改的, 引用本身不能再指向其他物件

  (5) const修飾成員函式,則該函式不能修改成員變數,但是如果變數是指標,用指標可以修改所指的資源(見effctive c++ item 03);const men fun是可以 過載的,如const T &operator[]() const 、T&operator[]();前者可以被const T型別的物件呼叫,後者不能,因為const物件會把this指標轉換為const 指標

  (6) const修飾函式範圍型別,這樣就不會有a*b=c(假設過載了*)這樣詭異的合法程式碼。

1 const classA operator*(const classA& a1,const classA& a2);  
2 operator*的返回結果必須是一個const物件。如果不是,這樣的變態程式碼也不會編譯出錯:  
3 classA a, b, c;  
4 (a * b) = c; // 對a*b的結果賦值 

ps:const mem fun隱含有一個const *this引數,static mem fun隱含沒有this指標,所以兩者一起修飾成員函式是不能通過編譯的

3、引用與指標的區別?為什麼不用string *const p 代替string &p

因為加入引用是為了支援operator overloading。這裡有一個假設,如果沒有引用,那麼,用指標來operator overloading操作。A operator +(const A *a, const A *_a); 那麼使用的時候,&a + &b,這樣看起來是不是很難受。使用指標可讀性差,*p。

而引入引用的概念,既可以滿足overload operator,也不失過載value和pointer的靈活性。而且引用還帶來一個指標無法替代的特性: 引用臨時物件。因為引用必須在定義的時候就賦值,以後無法更改。string &s=string();

  (1) 引用必須被初始化,沒有void 引用,編譯時引用必須與某個物件bind,引用不是物件,只是別名,引用本身不分配記憶體。

  (2) 不能有string & const s,可以有const string&s ,後者為常量引用。

  (3) 解引用操作是對指標,獲取指標所指物件,不是對引用操作

  (4) sizeof(引用)得到bind物件的大小,sizeof(pointer)得到指標本身的大小

  (5) 作為函式形參,如果實參是指標,用實參初始化形參,是傳址(本質上也是傳值)——>可能需要指標型別轉換,或派生類指標傳給基類指標;如果是傳引用,傳遞的是物件本身,不需要要額外的複製 開銷。如果需要修改指標,可以用二級指標

4、將“引用”作為函式引數有哪些特點?

  (1) 傳遞引用給函式與傳遞指標的效果是一樣的。這時,被調函式的形參就成為原來主調函式中的實參變數或物件的一個別名來使用,所以在被調函式中對形參變數的操作就是對相應的目標物件(位於主調函式中)的操作。

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

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

5、什麼是多型?多型有什麼用途?

要理解多型,首先要理解靜態型別與動態型別,變數或表示式的static type是編譯期已知的,它是變數宣告的型別或表示式生成的型別;變數或表示式表示的記憶體中的物件是dynamic type,直到run time才知道。

 1 class Base{
 2 public:
 3     virtual void  foo();
 4 
 5 };
 6 class Derived :public Base {
 7 public:
 8     virtual void  foo();
 9 
10 };
11 int main(){
12     Base *b = new  Derived();
13     b->foo();
14     Derived der;
15     Derived *d = &der;
16     d->foo();
17 }

b的動態型別是派生類物件,基類的指標或引用可以bind到派生類物件,使用該指標或引用時並不清楚所綁物件的真實型別。總結為三句話:

  (1) 基類指標(引用)可以指向子類物件;對基類指標(引用)的函式呼叫根據所繫結的真實物件傳遞引數—— void bar(const Base&)  ,即一個介面,多種型別的物件;用基類指標 呼叫虛擬函式,在執行時決定呼叫的版本。

多型的用途就是編寫一個介面,可以使用於各種型別。面向物件程式設計的偉大之處

多型:是對於不同物件接收相同訊息時產生不同的動作。C++的多型性具體體現在執行和編譯兩個方面:在程式執行時的多型性通過繼承和虛擬函式來體現;在程式編譯時多型性體現在函式和運算子的過載上;

6、數?什麼函式不能是虛函數?虛擬函式必須要實現嗎?虛擬函式可以過載嗎? 分辨三個詞的區別:過載,覆蓋,隱藏

virtual Base(); 建構函式不能為虛擬函式,而解構函式可以且常常是虛擬函式 ,基類虛擬函式可以不實現,但遇到需要基類物件例項呢?所以不實現應該用純虛擬函式。
virtual static int b();  nonstatic 成員函式才能為虛擬函式。

  (1) 基類必須將兩種成員函式區分開:一種是希望派生類只繼承介面,而不繼承實現,實現與派生類型別有關,不是通用的,二是直接繼承而不修改。前者就是虛擬函式,基類與之類各有實現版本,所謂override

  (2) 虛解構函式的用處是用基類指標指向了子類物件,delete 該基類指標,需要執行子類的解構函式,否則將產生未定義行為。

  (3)  過載(overload)發生在同一個類內,宣告virtual的函式是可以過載的,而虛擬函式的覆蓋發生在類層次關係中。

7、派生類的作用域與基類的作用域關係?

派生類構成的類作用域是巢狀在基類中的,即基類的所有成員都宣告在派生類的外部,如果派生類聲明瞭基類的同名成員函式,會隱藏基類的函式。見effective c++ item:避免遮掩繼承而來的名字

 

  基類的mf1被隱藏了。

8、虛擬函式的實現機制?虛擬函式導致的開銷?

虛擬函式的實現是C++標準沒有規定,目前各編譯器主流的實現是vptr 與vtable(可以用函式指標的陣列實現)。

  (1) 基類的每個物件擁有一個虛指標(物件不含有vtable),指向一個虛表,表內是基類的虛擬函式函式指標(見effective c++item07),為了效率,vptr位於物件例項地址的最前面(虛擬函式表的圖解

  (2) 派生類物件的虛指標指向的vtable裡含有基類的虛擬函式和自己的虛擬函式,依宣告順序存放。

  (3) vptr在32位系統上佔用了四個位元組,這導致物件體積變大,每次呼叫都需要多一步查詢虛表的操作。 http://blog.csdn.net/hengyunabc/article/details/7461919 。無端的宣告為 virtual 是錯誤的,只有作為基類宣告為虛擬函式,表明派生類需要overide。

9、new 與malloc的區別?

new的過程分三步:一、呼叫 new檔案裡的函式operator new()或operator new[]() 分配記憶體;二、編譯器呼叫物件的建構函式,並傳入初始值,三、返回一個指標,這三步卻是隱藏在一條new expression後面,即完成了記憶體的分配與物件的初始化。malloc 是個庫函式,分配的是未初始化的記憶體,不會呼叫物件的建構函式,下面的2和並不會呼叫建構函式,會產生執行時錯誤。用malloc分配的p,可以用定位new來建立物件:new(p) string("aa"); 使用palcement new可以控制物件的建立位置
1 string *p = static_cast<string*> (malloc(sizeof(string)*2));
2 *p = "dwah";
3 p[0] = "dal";
4 cout << p[0];

9.1、SGI版本的stl實現了兩個空間配置器

一是符合標準的allocator,封裝了new和delete,效率不佳;二是alloc,alloc::allocate呼叫malloc分配記憶體(單分配記憶體大於128bytes時)或者從memory pool中取小記憶體。C++ 標準庫的allocator類由allocate、construct、destory、deallocate來完成記憶體分配(未初始化,不能使用)、物件構造、物件析構、記憶體釋放的操作。(見stl原始碼分析)

9.2、delete容易造成的問題?

  (1) 忘記寫delete,記憶體洩漏

  (2) delete同一塊記憶體兩次,未定義(重複釋放或釋放後仍使用)

  (3) 兩個指標指向同一塊new的記憶體,delete後,另一個指標不重新賦值就為野指標,指向垃圾記憶體(野指標)

  (4) delete語句的前面發生異常,程式終止,delete語句不執行,記憶體洩漏(異常不安全)

  (5)delete的指標必須是指向記憶體首地址,如果改變了指標,不能delete

9.2.1 delete和delete []的區別

delete只會呼叫一次解構函式,而delete[]會呼叫每一個成員函式的解構函式。

在More Effective C++中有更為詳細的解釋:“當delete操作符用於陣列時,它為每個陣列元素呼叫解構函式,然後呼叫operator delete來釋放記憶體。”delete與new配套,delete []與new []配套

9.3 c++記憶體佈局

  (1) 常量區,存字串常量,不可修改

  (2) 全域性/靜態儲存區,存全域性變數或靜態變數

  (3) 堆疊,存區域性變數,隨著函式的執行而建立變數,函式退出時變數會自動銷燬

  (4) 堆區或自由區,malloc或new出來的記憶體

9.4  自己過載的operator new 如何使用?

遇到new表示式,編譯器開始查詢operator new 函式,如果分配的是類型別,首先在類及其基類作用域中查詢,此時含有operator new成員函式會被呼叫;否則編譯器在全域性作用域查詢,此時如果找到了使用者自定義的版本,就使用該版本,沒找到就使用標準庫版本。

10、share_prt引用計數什麼時候+1?

  (1) 假設有一個share_ptr p ,p=q,q的引用計數+1,p的引用計數-1;p值傳遞給函式時,p作為值返回時,用p建立新物件時

11、程式編譯連結的過程和函式找不到在哪個階段報錯

  (1) 預處理—>編譯—>連結 ,預處理有標頭檔案替換和巨集替換、條件編譯,如include<iostream>這條巨集會被替換為iostream標頭檔案的內容;cpp檔案是C++的編譯單元,h檔案不編譯,編譯時各個cpp檔案是互 相看不見的,A檔案裡呼叫了B檔案裡的函式,編譯到此處,只形成一條呼叫指令;連結器有一個符號表,記錄了符號查詢的位置,A裡的函式呼叫要到B中去尋找。

  (2) 函式找不到會發生在連結階段。

11.1 模板編譯的全過程,模板編譯為什麼報錯十分複雜,為什麼模板的實現與宣告只能在一個頭檔案裡?

12、C/C++記憶體模型

13、動態繫結的介紹?引用是否能實現動態繫結,為什麼引用可以實現

14、介紹所有的建構函式?什麼情況下要給類寫拷貝建構函式

  (1) 如果自己不定義,編譯器會為類預設生成default ctr,拷貝建構函式,copy assignment 函式,解構函式。如果自定義某個函式,就不會產生預設的;如果定義了其他函式,但是想用編譯器的預設版本,可以寫成 Base ()=default;如果要阻止拷貝,賦值,可以Base(const Base&) =delete; 不能delete解構函式,否則物件不可析構了。

  (2) 這些預設生成的函式都是public 且inine的,直到呼叫時編譯器才生成。自定義版本可以把拷貝建構函式設為private,這樣外部就不能訪問了。

  (3) 會呼叫拷貝建構函式的情況:一、拷貝初始化,Base c=b,二,用其他例項初始化 Base d(c), 三、以值傳遞方式給函式傳遞引數,如foo(Base b),四、返回類型別的值,

  (4) 不能寫成Base (const Base b)形式,這樣實參初始化形參,會迴圈呼叫拷貝建構函式

  (5) 預設的copy ctr採用bitewise 拷貝的方式(淺拷貝),如果類含有指標,則只拷貝指標,不拷貝指標所指向的資源,這樣造成了被拷貝物件與拷貝物件間共享了資源;解決辦法是深拷貝。

  (6) =運算子的過載形式為:Base& operator =(const Base& b),返回引用可以寫出連續賦值的形式,另外要檢測自我賦值:b=b,檢測語句為 if(this==&b) 

  (7) 注意單參建構函式定義了一個隱式轉換,如string s=“jdka",就是由字元指標到string的轉換,要禁止這種轉換,用關鍵字explicit

15、struct與class的區別

   關鍵字struct也是聲明瞭一種自定義型別,這與class一樣,而不再只是c語言裡的資料集合體;struct也可以有成員變數和成員函式,預設的訪問許可權為public,struct不能代替class的用處是class 作為模板引數。(見inside  the c++ object model 1.2 節)。

16、繼承機制中物件之間是如何轉換的

基類指標(引用)可以指向子類物件,子類物件是一個父類物件,含有父類的所有成員;子類指標不能指向父類物件或指標。假設B是A的子類:
A *ap = new B();
B* bp = ap; // 錯誤
bp = new A; // 錯誤
存在基類物件與派生類物件之間的轉換嗎? 當使用一個派生類物件為一個基類物件初始化或賦值時,只有派生類物件中的基類部分會被拷貝,移動或賦值,其他部分會被忽略掉。基類物件不能用來初始化或賦值給派生類物件。

17、須在建構函式初始化式裡進行初始化的資料成員有哪些?

初始化從無到有,建立了新物件;如:string foo = "Hello World!" 賦值沒有建立新物件,而是對已有物件賦值。 如:string bar; bar = "Hello World!" 有時我們可以忽略資料成員初始化和賦值之間的差異,但並非總能這樣。 如果成員是const或者是引用的話,必須將其初始化。類似的,當成員屬於某種類型別且該類沒有定義預設建構函式時,也必須將這個成員初始化。(比如:類A中有一成員是B b,但類B中沒有定義預設建構函式時,就必須對A中b成員進行初始化) 隨著建構函式體一開始執行(即大括號裡面部分),初始化就完成了(建構函式體內只是賦值操作)。因此,上面三種情況,比如初始化const或者引用型別的資料成員的唯一機會就是通過建構函式初始值,形如:ConstRef::ConstRef(int n) : i(n), j(n) { }   (1) static屬於類並不屬於具體的物件,所以 static成員是不允許在類內初始化的,在類外初始化   (2) C++11標準中允許直接對類內成員進行初始化,不用再寫到建構函式的初始化列表中

18、記憶體溢位有那些因素?

  (1) 使用不安全的c語言函式,strcpy,gets

  (2) 遞迴呼叫層次太深,超過了棧空間

18.1 c語言緩衝區溢位的函式?

19 、C++返回{1,2},用vector作為返回型別

看了一些C++面經,有基礎問題如const的幾種作用、虛擬函式的實現機制、熟悉shared_ptr嗎、這些是C++的語言特性部分,如果要考察STL,我目前只直到vector、list,對各種演算法其他容器不熟悉,更不用說容器的時間效率與空間開銷;如果要考察演算法,連結串列和二叉樹、快速排序、堆排序,這又可以結合make_head() 等堆函式;如果是要考察程式碼實現,如memcpy、strstr、strcmp等。所以我需要牢固掌握C++的基礎知識,STL,再就是刷題,掌握各種常考的演算法,這些才是我能達到的優勢項,linux、專案等不是我短期能擅長的地方。