C++面試常見問題整理匯總。本站提示廣大學習愛好者:(C++面試常見問題整理匯總)文章只能為提供參考,不一定能成為您想要的結果。以下是C++面試常見問題整理匯總正文
作者:jihite
這篇文章主要介紹了C++面試常見問題整理,匯總了C++基本語法、面向對象各種概念與易錯點,需要的朋友可以參考下本文總結講述了C++面試常見問題。分享給大家供大家參考,具體如下:
1. 繼承方式
public 父類的訪問級別不變
protected 父類的public成員在派生類編程protected,其余的不變
private 父類的所有成員變成private
#include <iostream> using namespace std; class base { public: void printa() { cout <<"base"<< endl; } protected: void printhello() { cout <<"helo"<< endl; } private: void printnohello() { cout <<"no hello"<< endl; } }; class derived : public base { public: void printb() { printhello(); } // void printc() { printnohello(); } //printnohello是父類的私有函數,不可訪問 }; int main() { base a; a.printa(); //a.printhello(); //printhello是類derived的protected函數,不可訪問。 }
2. sizeof 和 strlen 的區別
① sizeof 是一個操作符,strlen 是庫函數。
② sizeof 的參數可以是數據的類型,也可以是變量,而 strlen 只能以結尾為‘\ 0‘的字符串作參數。
③ 編譯器在編譯時就計算出了sizeof 的結果。而strlen 函數必須在運行時才能計算出來。並且 sizeof計算的是數據類型占內存的大小,而 strlen 計算的是字符串實際的長度。
④ 數組做 sizeof 的參數不退化,傳遞給 strlen 就退化為指針了。
#include <iostream> #include <cstdlib> #include <cstring> using namespace std; int main() { int a[] = {1, 2, 3, 4, 5}; cout << sizeof(a) << endl; //20 // cout << strlen(a) << endl; char b[] = {'a', 'b'}; cout << strlen(b) << endl; //6 cout << sizeof(b) << endl; //2 }
3. C中的 malloc 和C++中的 new 有什麼區別
new、delete 是操作符,可以重載,只能在 C++中使用。
malloc、free 是函數,可以覆蓋,C、C++中都可以使用。
new 可以調用對象的構造函數,對應的 delete 調用相應的析構函數。
malloc 僅僅分配內存,free 僅僅回收內存,並不執行構造和析構函數
new、delete 返回的是某種數據類型指針,malloc、free 返回的是 void 指針。
注意:malloc 申請的內存空間要用 free 釋放,而 new 申請的內存空間要用 delete 釋放,不要混用。
因為兩者實現的機理不同。
4.1. c/c++中static
要理解static,就必須要先理解另一個與之相對的關鍵字auto,其實我們通常聲明的不用static修飾的變量,都是auto的,因為它是默認的。auto的含義是由程序自動控制變量的生存周期,通常指的就是變量在進入其作用域的時候被分配,離開其作用域的時候被釋放;而static就是不auto,變量在程序初始化時被分配,直到程序退出前才被釋放;也就是static是按照程序的生命周期來分配釋放變量的,而不是變量自己的生命周期;所以,像這樣的例子:
void func() { int a; static int b; }
每一次調用該函數,變量a都是新的,因為它是在進入函數體的時候被分配,退出函數體的時候被釋放,所以多個線程調用該函數,都會擁有各自獨立的變量a,因為它總是要被重新分配的;而變量b不管你是否使用該函數,在程序初始化時就被分配的了,或者在第一次執行到它的聲明的時候分配(不同的編譯器可能不同),所以多個線程調用該函數的時候,總是訪問同一個變量b,這也是在多線程編程中必須注意的!
static的全部用法:
1.類的靜態成員:
class A { private: static int s_value; };
在cpp中必須對它進行初始化:
int A::s_value = 0; // 注意,這裡沒有static的修飾!
類的靜態成員是該類所有實例的共用成員,也就是在該類的范疇內是個全局變量,也可以理解為是一個名為A::s_value的全局變量,只不過它是帶有類安全屬性的;道理很簡單,因為它是在程序初始化的時候分配的,所以只分配一次,所以就是共用的;
類的靜態成員必須初始化,道理也是一樣的,因為它是在程序初始化的時候分配的,所以必須有初始化,類中只是聲明,在cpp中才是初始化,可以在初始化的代碼上放個斷點,在程序執行main的第一條語句之前就會先走到那;如果你的靜態成員是個類,那麼就會調用到它的構造函數;
2.類的靜態函數:
class A { private: static void func(int value); };
實現的時候也不需要static的修飾,因為static是聲明性關鍵字;類的靜態函數是在該類的范疇內的全局函數,不能訪問類的私有成員,只能訪問類的靜態成員,不需要類的實例即可調用;實際上,它就是增加了類的訪問權限的全局函數:void A::fun(int);
靜態成員函數可以繼承和覆蓋,但無法是虛函數;
3.只在cpp內有效的全局變量:
在cpp文件的全局范圍內聲明:
static int g_value = 0;
這個變量的含義是在該cpp內有效,但是其他的cpp文件不能訪問這個變量;如果有兩個cpp文件聲明了同名的全局靜態變量,那麼他們實際上是獨立的兩個變量;
如果不使用static聲明全局變量:
int g_value = 0;
那麼將無法保證這個變量不被別的cpp共享,也無法保證一定能被別的cpp共享,因為要讓多個cpp共享一個全局變量,應將它聲明為extern(外部)的;也有可能編譯會報告變量被重復定義;總之不建議這樣的寫法,不明確這個全局變量的用法;
如果在一個頭文件中聲明:
static int g_vaule = 0;
那麼會為每個包含該頭文件的cpp都創建一個全局變量,但他們都是獨立的;所以也不建議這樣的寫法,一樣不明確需要怎樣使用這個變量,因為只是創建了一組同名而不同作用域的變量;
這裡順便說一下如何聲明所有cpp可共享的全局變量,在頭文件裡聲明為extern的:
extern int g_value; // 注意,不要初始化值!
然後在其中任何一個包含該頭文件的cpp中初始化(一次)就好:
int g_value = 0; // 初始化一樣不要extern修飾,因為extern也是聲明性關鍵字;
然後所有包含該頭文件的cpp文件都可以用g_value這個名字訪問相同的一個變量;
4.2 C中static有什麼作用
① 隱藏。當我們同時編譯多個文件時,所有未加static前綴的全局變量和函數都具有全局可見性,故使用static在不同的文件中定義同名函數和同名變量,而不必擔心命名沖突。
② 保持變量內容的持久。存儲在靜態數據區的變量會在程序剛開始運行時就完成初始化,也是唯一的一次初始化。共有兩種變量存儲在靜態存儲區:全局變量和static變量。
③ 默認初始化為0.其實全局變量也具備這一屬性,因為全局變量也存儲在靜態數據區。在靜態數據區,內存中所有的字節默認值都是0×00,某些時候這一特點可以減少程序員的工作量。
5. 簡述C\C++程序編譯的內存情況分配
C、C++中內存分配方式可以分為三種:
(1)從靜態存儲區域分配:內存在程序編譯時就已經分配好,這塊內存在程序的整個運行期間都存在。速度快、不容易出錯,因為有系統會善後。例如全局變量,static變量等。
(2)在棧上分配:在執行函數時,函數內局部變量的存儲單元都在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
(3)從堆上分配:即動態內存分配。程序在運行的時候用 malloc 或 new 申請任意大小的內存,程序員自己負責在何時用 free 或 delete 釋放內存。動態內存的生存期由程序員決定,使用非常靈活。如果在堆上分配了空間,就有責任回收它,否則運行的程序會出現內存洩漏,另外頻繁地分配和釋放不同大小的堆空間將會產生堆內碎塊。
一個 C、C++程序編譯時內存分為 5 大存儲區:堆區、棧區、全局區、文字常量區、程序代碼區。
6. 面向對象的三大特征
① 封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。
② 繼承,是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展。通過繼承創建的新類稱為“子類”或“派生類”。繼承的過程,就是從一般到特殊的過程。要實現繼承,可以通過“繼承”和“組合”來實現。
③ 多態,簡單的說,就是一句話:允許將指向子類類型的指針賦值給父類類型的指針。實現多態,有二種方式,覆蓋,重載。
覆蓋,是指子類重新定義父類的虛函數的做法。
重載,是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。
總結:作用
① 封裝可以隱藏實現細節,使得代碼模塊化
② 繼承可以擴展已存在的代碼模塊(類);它們的目的都是為了——代碼重用
③ 多態則是為了實現另一個目的——接口重用!多態的作用,就是為了類在繼承和派生的時候,保證使用“家譜”中任一類的實例的某一屬性時的正確調用。
7. 簡述多態的實現原理
編譯器發現一個類中有虛函數,便會立即為此類生成虛函數表vtable。虛函數表的各表項為指向對應虛函數的指針。編譯器還會在此類中隱含插入一個指針 vptr指向虛函數表。調用此類的構造函數時,在類的構造函數中,編譯器會隱含執行 vptr 與 vtable 的關聯代碼,將 vptr 指向對應的 vtable,將類與此類的 vtable 聯系了起來。另外在調用類的構造函數時,指向基礎類的指針此時已經變成指向具體的類的 this 指針,這樣依靠此 this 指針即可得到正確的 vtable。
如此才能真正與函數體進行連接,這就是動態聯編,實現多態的基本原理。
注意:一定要區分虛函數,純虛函數、虛擬繼承的關系和區別。牢記虛函數實現原理,因為多態C++面試的重要考點之一,而虛函數是實現多態的基礎。
8. c++空類的成員函數
缺省的構造函數
缺省的拷貝構造函數
缺省的賦值運算符
缺省的析構函數
缺省的取址運算符
缺省的取址運算符const
注意:只有當實際使用這些函數的時候,編譯器才會去定義它們。
9. 談談你對拷貝構造函數和賦值運算符的認識
兩個不同之處:
① 拷貝構造函數生成新的類對象,而賦值運算符不能。
② 由於拷貝構造函數是直接構造一個新的類對象,所以在初始化這個對象之前不用檢驗源對象是否和新建對象相同。而賦值運算符則需要這個操作,另外賦值運算中如果原來的對象中有內存分配要先把內存釋放掉。
注意:當有類中有指針類型的成員變量時,一定要重寫拷貝構造函數和賦值運算符,不要使用默認的。
10. 用 C++設計一個不能被繼承的類
class Base { private: Base() {} ~Base() {} }; class Derived : public Base { public: Derived() : Base() {} ~Derived() {} };
C++ 中的流對象就是采用這樣的原理。防止被賦值、復制。
11. 類成員的重寫、重載和隱藏的區別
重寫和重載主要有以下幾點不同
① 范圍的區別:被重寫的和重寫的函數在兩個類中,而重載和被重載的函數在同一個類中。
② 參數的區別:被重寫函數和重寫函數的參數列表一定相同,而被重載函數和重載函數的參數列表一定不同。
③ virtual 的區別:重寫的基類中被重寫的函數必須要有 virtual 修飾,而重載函數和被重載函數可以被virtual 修飾,也可以沒有。
隱藏和重寫、重載有以下幾點不同
① 與重載的范圍不同:和重寫一樣,隱藏函數和被隱藏函數不在同一個類中
② 參數的區別:隱藏函數和被隱藏的函數的參數列表可以相同,也可不同,但是函數名肯定要相同。當參數不相同時,無論基類中的參數是否被 virtual 修飾,基類的函數都是被隱藏,而不是被重寫
說明:雖然重載和覆蓋都是實現多態的基礎,但是兩者實現的技術完全不相同,達到的目的也是完全不同的,覆蓋是動態綁定的多態,而重載是靜態綁定的多態。
12. extern 有什麼作用
extern 標識的變量或者函數聲明其定義在別的文件中,提示編譯器遇到此變量和函數時在其它模塊中尋找其定義。
13. 引用和指針區別
① 引用必須被初始化,但是不分配存儲空間。指針不必在聲明時初始化,在初始化的時候需要分配存儲空間
② 引用初始化以後不能被改變,指針可以改變所指的對象
③ 不存在指向空值的引用,但是存在指向空值的指針
14. 數組指針
#include <iostream> using namespace std; int main() { int a[5] = {1, 2, 3, 4, 5}; int *ptr = (int*)(&a+1); cout << *(ptr-1) << "\t" << *(ptr-2) << endl; // 5 4 cout << "----------------" << endl; int *p = (int *)(a+1); //2 cout << *p << endl; }
15. const int *a 和 int * const a 區別
int main() { int b = 3; int c = 4; const int *p = &b; //等價於 int const *p = &b; p = &c; //修飾值,指針可變 //*p = 5;//error 修飾值,值不可變 cout << *p << endl; int a = 5; int * const q = &a; //修飾指針 //p = &c;//error修飾指針,指針不可變 *p = 5; //修飾指針,值可變 }
希望本文所述對大家C++程序設計有所幫助。