close

原文鏈接: https://blog.csdn.net/kuweicai/article/details/82779648

以下內容部分整理自網絡,部分為自己面試的真題。

第一部分:計算機基礎1. C/C++內存有哪幾種類型?

C中,內存分為5個區:堆(malloc)、棧(如局部變量、函數參數)、程序代碼區(存放二進制代碼)、全局/靜態存儲區(全局變量、static變量)和常量存儲區(常量)。此外,C++中有自由存儲區(new)一說。

全局變量、static變量會初始化為缺省值,而堆和棧上的變量是隨機的,不確定的。

2. 堆和棧的區別?

1).堆存放動態分配的對象——即那些在程序運行時動態分配的對象,比如 new 出來的對象,其生存期由程序控制;

2).棧用來保存定義在函數內的非static對象,如局部變量,僅在其定義的程序塊運行時才存在;

3).靜態內存用來保存static對象,類static數據成員以及定義在任何函數外部的變量,static對象在使用之前分配,程序結束時銷毀;

4).棧和靜態內存的對象由編譯器自動創建和銷毀。

3. 堆和自由存儲區的區別?

總的來說,堆是C語言和操作系統的術語,是操作系統維護的一塊動態分配內存;自由存儲是C++中通過new與delete動態分配和釋放對象的抽象概念。他們並不是完全一樣。

從技術上來說,堆(heap)是C語言和操作系統的術語。堆是操作系統所維護的一塊特殊內存,它提供了動態分配的功能,當運行程序調用malloc()時就會從中分配,稍後調用free可把內存交還。

而自由存儲是C++中通過new和delete動態分配和釋放對象的抽象概念,通過new來申請的內存區域可稱為自由存儲區。

基本上,所有的C++編譯器默認使用堆來實現自由存儲,也即是缺省的全局運算符new和delete也許會按照malloc和free的方式來被實現,這時藉由new運算符分配的對象,說它在堆上也對,說它在自由存儲區上也正確。

4. 程序編譯的過程?

程序編譯的過程中就是將用戶的文本形式的源代碼(c/c++)轉化成計算機可以直接執行的機器代碼的過程。主要經過四個過程:預處理、編譯、匯編和鏈接。具體示例如下。

一個hello.c的c語言程序如下。

#include<stdio.h>intmain(){printf("happynewyear!\n");return0;}

其編譯過程如下:

在這裡插入圖片描述5. 計算機內部如何存儲負數和浮點數?

負數比較容易,就是通過一個標誌位和補碼來表示。拓展問題:

什麼是補碼?負數補碼為反碼加1 正數補碼為原碼

負數為什麼用補碼?統一加減法,正負零問題

對於浮點類型的數據採用單精度類型(float)和雙精度類型(double)來存儲,float數據占用32bit,double數據占用64bit,我們在聲明一個變量float f= 2.25f的時候,是如何分配內存的呢?

如果胡亂分配,那世界豈不是亂套了麼,其實不論是float還是double在存儲方式上都是遵從IEEE的規範的,float遵從的是IEEE R32.24 ,而double 遵從的是R64.53。更多可以參考浮點數表示。

無論是單精度還是雙精度在存儲中都分為三個部分:

6. 函數調用的過程?

如下結構的代碼,

intmain(void){...d=fun(a,b,c);cout<<d<<endl;...return0;}

調用fun()的過程大致如下:

main()========

1).參數拷貝(壓棧),注意順序是從右到左,即c-b-a;

2).保存d = fun(a, b, c)的下一條指令,即cout<<d<<endl(實際上是這條語句對應的匯編指令的起始位置);

3).跳轉到fun()函數,注意,到目前為止,這些都是在main()中進行的;

fun()=====

4).移動ebp、esp形成新的棧幀結構;

5).壓棧(push)形成臨時變量並執行相關操作;

6).return一個值;

7).出棧(pop);

8).恢復main函數的棧幀結構;

9).返回main函數;

main()========

。。。

7. 左值和右值

不是很嚴謹的來說,左值指的是既能夠出現在等號左邊也能出現在等號右邊的變量(或表達式),右值指的則是只能出現在等號右邊的變量(或表達式)。舉例來說我們定義的變量 a 就是一個左值,而malloc返回的就是一個右值。

或者左值就是在程序中能夠尋址的東西,右值就是一個具體的真實的值或者對象,沒法取到它的地址的東西(不完全準確),因此沒法對右值進行賦值,但是右值並非是不可修改的,比如自己定義的class, 可以通過它的成員函數來修改右值。

歸納一下就是:

可以取地址的,有名字的,非臨時的就是左值

不能取地址的,沒有名字的,臨時的,通常生命周期就在某個表達式之內的就是右值

8. 什麼是內存泄漏?面對內存泄漏和指針越界,你有哪些方法?你通常採用哪些方法來避免和減少這類錯誤?

用動態存儲分配函數動態開闢的空間,在使用完畢後未釋放,結果導致一直占據該內存單元即為內存泄露。

1). 使用的時候要記得指針的長度.

2). malloc的時候得確定在那裡free.

3). 對指針賦值的時候應該注意被賦值指針需要不需要釋放.

4). 動態分配內存的指針最好不要再次賦值.

5). 在C++中應該優先考慮使用智能指針.

第二部分:C v.s. C++1. C和C++的區別?

1). C++是C的超集;

2). C是一個結構化語言,它的重點在於算法和數據結構。C程序的設計首要考慮的是如何通過一個過程,對輸入(或環境條件)進行運算處理得到輸出(或實現過程(事務)控制),而對於C++,首要考慮的是如何構造一個對象模型,讓這個模型能夠契合與之對應的問題域,這樣就可以通過獲取對象的狀態信息得到輸出或實現過程(事務)控制。

2. int fun() 和 int fun(void)的區別?

這裡考察的是c 中的默認類型機制。

在c中,int fun() 會解讀為返回值為int(即使前面沒有int,也是如此,但是在c++中如果沒有返回類型將報錯),輸入類型和個數沒有限制, 而int fun(void)則限制輸入類型為一個void。

在c++下,這兩種情況都會解讀為返回int類型,輸入void類型。

3. const 有什麼用途

主要有三點:

1).定義只讀變量,或者常量(只讀變量和常量的區別參考下面一條);

2).修飾函數的參數和函數的返回值;

3).修飾函數的定義體,這裡的函數為類的成員函數,被const修飾的成員函數代表不能修改成員變量的值,因此const成員函數只能調用const成員函數, 可以訪問非const成員,但是不能修改;

4).只讀對象。只讀對象只能調用const成員函數。

classScreen{public:constcharcha;//const成員變量charget()const;//const成員函數};constScreen screen;//只讀對象4. 在C中用const 能定義真正意義上的常量嗎?C++中的const呢?

不能。c中的const僅僅是從編譯層來限定,不允許對const 變量進行賦值操作,在運行期是無效的,所以並非是真正的常量(比如通過指針對const變量是可以修改值的)。

但是c++中是有區別的,c++在編譯時會把const常量加入符號表,以後(仍然在編譯期)遇到這個變量會從符號表中查找,所以在C++中是不可能修改到const變量的。補充:

1). c中的局部const常量存儲在棧空間,全局const常量存在只讀存儲區,所以全局const常量也是無法修改的,它是一個只讀變量。

2). 這裡需要說明的是,常量並非僅僅是不可修改,而是相對於變量,它的值在編譯期已經決定,而不是在運行時決定。

3).c++中的const 和宏定義是有區別的,宏是在預編譯期直接進行文本替換,而const發生在編譯期,是可以進行類型檢查和作用域檢查的。

4).c語言中只有enum可以實現真正的常量。

5 ). c++中只有用字面量初始化的const常量會被加入符號表,而變量初始化的const常量依然只是只讀變量。

6). c++中const成員為只讀變量,可以通過指針修改const成員的值,另外const成員變量只能在初始化列表中進行初始化。

下面我們通過代碼來看看區別。同樣一段代碼,在c編譯器下,打印結果為*pa = 4,a = 4 在c++編譯下打印的結果為 *pa = 4, a = 8

intmain(void){constinta=8;int*pa=(int*)&a;*pa=4;printf("*pa=%d,a=%d",*pa,a);return0;}

另外值得一說的是,由於c++中const常量的值在編譯期就已經決定,下面的做法是OK的,但是c中是編譯通不過的。

intmain(void){constinta=8;constintb=2;intarray[a+b]={0};return0;}5. 宏和內聯(inline)函數的比較?

1). 首先宏是C中引入的一種預處理功能;

2). 內聯(inline)函數是C++中引入的一個新的關鍵字;C++中推薦使用內聯函數來替代宏代碼片段;

3). 內聯函數將函數體直接擴展到調用內聯函數的地方,這樣減少了參數壓棧,跳轉,返回等過程;

4). 由於內聯發生在編譯階段,所以內聯相較宏,是有參數檢查和返回值檢查的,因此使用起來更為安全;

5). 需要注意的是, inline會向編譯期提出內聯請求,但是是否內聯由編譯器決定(當然可以通過設置編譯器,強制使用內聯);

6). 由於內聯是一種優化方式,在某些情況下,即使沒有顯示的聲明內聯,比如定義在class內部的方法,編譯器也可能將其作為內聯函數。

7). 內聯函數不能過於複雜,最初C++限定不能有任何形式的循環,不能有過多的條件判斷,不能對函數進行取地址操作等,但是現在的編譯器幾乎沒有什麼限制,基本都可以實現內聯。更多請參考inline關鍵字

6. C++中有了malloc / free , 為什麼還需要 new / delete?

1). malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算符。它們都可用於申請動態內存和釋放內存。

2). 對於非內部數據類型(自定義類型)的對象而言,光用maloc/free無法滿足動態對象的要求。對象在創建的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數。由於malloc/free是庫函數而不是運算符,不在編譯器控制權限之內,不能夠把執行構造函數和析構函數的任務強加於malloc/free。因此C++語言需要一個能完成動態內存分配和初始化工作的運算符new,以一個能完成清理與釋放內存工作的運算符delete。注意new/delete不是庫函數。最後補充一點題外話,new 在申請內存的時候就可以初始化(如下代碼), 而malloc是不允許的。另外,由於malloc是庫函數,需要相應的庫支持,因此某些簡易的平台可能不支持,但是new就沒有這個問題了,因為new是C++語言所自帶的運算符。

int*p=newint(1);

特別的,在C++中,如下的代碼,用new創建一個對象(new 會觸發構造函數, delete會觸發析構函數),但是malloc僅僅申請了一個空間,所以在C++中引入new和delete來支持面向對象。

#include<cstdlib>classTest{...}Test*pn=newTest;Test*pm=(Test*)malloc(sizeof(Test));7. C和C++中的強制類型轉換?

C中是直接在變量或者表達式前面加上(小括號括起來的)目標類型來進行轉換,一招走天下,操作簡單,但是由於太過直接,缺少檢查,因此容易發生編譯檢查不到錯誤,而人工檢查又及其難以發現的情況;而C++中引入了下面四種轉換:

1). static_cast a. 用於基本類型間的轉換 b. 不能用於基本類型指針間的轉換 c. 用於有繼承關係類對象間的轉換和類指針間的轉換

2). dynamic_cast a. 用於有繼承關係的類指針間的轉換 b. 用於有交叉關係的類指針間的轉換 c. 具有類型檢查的功能 d. 需要虛函數的支持

3). reinterpret_cast a. 用於指針間的類型轉換 b. 用於整數和指針間的類型轉換

4). const_cast a. 用於去掉變量的const屬性 b. 轉換的目標類型必須是指針或者引用

拓展 在C++中,普通類型可以通過類型轉換構造函數轉換為類類型,那麼類可以轉換為普通類型嗎?答案是肯定的。

但是在工程應用中一般不用類型轉換函數,因為無法抑制隱式的調用類型轉換函數(類型轉換構造函數可以通過explicit來抑制其被隱式的調用),而隱式調用經常是bug的來源。

實際工程中替代的方式是定義一個普通函數,通過顯式的調用來達到類型轉換的目的。

classtest{intm_value;...public:operatorint()//類型轉換函數{returnm_value;}inttoInt()//顯示調用普通函數來實現類型轉換{returnm_value}};intmain(){...testa(5);inti=a;//相當於inti=test::operatorint(&a)...return0;}8. static 有什麼用途

1). 靜態(局部/全局)變量

2). 靜態函數

3). 類的靜態數據成員

4). 類的靜態成員函數

9. 類的靜態成員變量和靜態成員函數各有哪些特性?靜態成員變量

1). 靜態成員變量需要在類內聲明(加static),在類外初始化(不能加static),如下例所示;

2). 靜態成員變量在類外單獨分配存儲空間,位於全局數據區,因此靜態成員變量的生命周期不依賴於類的某個對象,而是所有類的對象共享靜態成員變量;

3). 可以通過對象名直接訪問公有靜態成員變量;

4). 可以通過類名直接調用公有靜態成員變量,即不需要通過對象,這一點是普通成員變量所不具備的。

classexample{public:staticintm_int;//static成員變量};intexample::m_int=0;//沒有staticcout<<example::m_int;//可以直接通過類名調用靜態成員變量靜態成員函數

1). 靜態成員函數是類所共享的;

2). 靜態成員函數可以訪問靜態成員變量,但是不能直接訪問普通成員變量(需要通過對象來訪問);需要注意的是普通成員函數既可以訪問普通成員變量,也可以訪問靜態成員變量;

3). 可以通過對象名直接訪問公有靜態成員函數;

4). 可以通過類名直接調用公有靜態成員函數,即不需要通過對象,這一點是普通成員函數所不具備的。

classexample{private:staticintm_int_s;//static成員變量intm_int;staticintgetI()//靜態成員函數在普通成員函數前加static即可{returnm_int_s;//如果返回m_int則報錯,但是可以returnd.m_int是合法的}};cout<<example::getI();//可以直接通過類名調用靜態成員變量10. 在C++程序中調用被C編譯器編譯後的函數,為什麼要加extern「C」?

C++語言支持函數重載,C語言不支持函數重載,函數被C++編譯器編譯後在庫中的名字與C語言的不同,假設某個函數原型為:

voidfoo(intx,inty);

該函數被C編譯器編譯後在庫中的名字為 _foo, 而C++編譯器則會產生像: _foo_int_int 之類的名字。為了解決此類名字匹配的問題,C++提供了C鏈接交換指定符號 extern 「C」。

11. 頭文件中的 ifndef/define/endif 是幹什麼用的? 該用法和 program once 的區別?

相同點: 它們的作用是防止頭文件被重複包含。不同點

1). ifndef 由語言本身提供支持,但是 program once 一般由編譯器提供支持,也就是說,有可能出現編譯器不支持的情況(主要是比較老的編譯器)。

2). 通常運行速度上 ifndef 一般慢於 program once,特別是在大型項目上, 區別會比較明顯,所以越來越多的編譯器開始支持 program once。

3). ifndef 作用於某一段被包含(define 和 endif 之間)的代碼, 而 program once 則是針對包含該語句的文件, 這也是為什麼 program once 速度更快的原因。

4). 如果用 ifndef 包含某一段宏定義,當這個宏名字出現「撞車」時,可能會出現這個宏在程序中提示宏未定義的情況(在編寫大型程序時特別需要注意,因為有很多程序員在同時寫代碼)。相反由於program once 針對整個文件, 因此它不存在宏名字「撞車」的情況, 但是如果某個頭文件被多次拷貝,program once 無法保證不被多次包含,因為program once 是從物理上判斷是不是同一個頭文件,而不是從內容上。

12. 當i是一個整數的時候++i和i++那個更快一點?i++和++i的區別是什麼?

答:理論上++i更快,實際與編譯器優化有關,通常幾乎無差別。

//i++實現代碼為:intoperator++(int){inttemp=*this;++*this;returntemp;}//返回一個int型的對象本身//++i實現代碼為:int&operator++(){*this+=1;return*this;}//返回一個int型的對象引用

i++和++i的考點比較多,簡單來說,就是i++返回的是i的值,而++i返回的是i+1的值。也就是++i是一個確定的值,是一個可修改的左值,如下使用:

cout<<++(++(++i))<<endl;cout<<++++i<<endl;

可以不停的嵌套++i。這裡有很多的經典筆試題,一起來觀摩下:

intmain(){inti=1;printf("%d,%d\n",++i,++i);//3,3printf("%d,%d\n",++i,i++);//5,3printf("%d,%d\n",i++,i++);//6,5printf("%d,%d\n",i++,++i);//8,9system("pause");return0;}

首先是函數的參數入棧順序從右向左入棧的,計算順序也是從右往左計算的,不過都是計算完以後再進行的壓棧操作:

對於第1個printf,首先執行++i,返回值是i,這時i的值是2,再次執行++i,返回值是i,得到i=3,將i壓入棧中,此時i為3,也就是壓入3,3;

對於第2個printf,首先執行i++,返回值是原來的i,也就是3,再執行++i,返回值是i,依次將3,5壓入棧中得到輸出結果

對於第3個printf,首先執行i++,返回值是5,再執行i++返回值是6,依次將5,6壓入棧中得到輸出結果

對於第4個printf,首先執行++i,返回i,此時i為8,再執行i++,返回值是8,此時i為9,依次將i,8也就是9,8壓入棧中,得到輸出結果。

上面的分析也是基於VS搞的,不過準確來說函數多個參數的計算順序是未定義的(the order of evaluation of function arguments are undefined)。筆試題目的運行結果隨不同的編譯器而異。

這裡還有一個 i++ 的典型應用案例。

map<char,int>b={{'a',1},{'b',2}};for(autoiter=b.begin();iter!=b.end();){if(iter->first=='a'){b.erase(iter++);//等價於autot=iter;iter=iter+1;b.erase(t);}else{iter++;}}第三部分:數組、指針 & 引用1. 指針和引用的區別?

相同點:

1). 都是地址的概念;

2). 都是「指向」一塊內存。指針指向一塊內存,它的內容是所指內存的地址;而引用則是某塊內存的別名;

3). 引用在內部實現其實是藉助指針來實現的,一些場合下引用可以替代指針,比如作為函數形參。

不同點:

1). 指針是一個實體,而引用(看起來,這點很重要)僅是個別名;

2). 引用只能在定義時被初始化一次,之後不可變;指針可變;引用「從一而終」,指針可以「見異思遷」;

3). 引用不能為空,指針可以為空;

4). 「sizeof 引用」得到的是所指向的變量(對象)的大小,而「sizeof 指針」得到的是指針本身的大小;

5). 指針和引用的自增(++)運算意義不一樣;

6). 引用是類型安全的,而指針不是 (引用比指針多了類型檢查)

7). 引用具有更好的可讀性和實用性。

2. 引用占用內存空間嗎?

如下代碼中對引用取地址,其實是取的引用所對應的內存空間的地址。這個現象讓人覺得引用好像並非一個實體。但是引用是占用內存空間的,而且其占用的內存和指針一樣,因為引用的內部實現就是通過指針來完成的。

比如 Type& name;<===> Type* const name。

intmain(void){inta=8;int&b=a;int*p=&b;//等價於int*p=&a;*p=0;cout<<a;//output0return0;}3. 三目運算符

在C中三目運算符(? :)的結果僅僅可以作為右值,比如如下的做法在C編譯器下是會報錯的,但是C++中卻是可以是通過的。這個進步就是通過引用來實現的,因為下面的三目運算符的返回結果是一個引用,然後對引用進行賦值是允許的。

intmain(void){inta=8;intb=6;(a>b?a:b)=88;cout<<a;//output88return0;}4. 指針數組和數組指針的區別

數組指針,是指向數組的指針,而指針數組則是指該數組的元素均為指針。

數組指針,是指向數組的指針,其本質為指針,形式如下。如 int (*p)[n],p即為指向數組的指針,()優先級高,首先說明p是一個指針,指向一個整型的一維數組,這個一維數組的長度是n,也可以說是p的步長。也就是說執行p+1時,p要跨過n個整型數據的長度。數組指針是指向數組首元素的地址的指針,其本質為指針,可以看成是二級指針。

類型名(*數組標識符)[數組長度]

指針數組,在C語言和C++中,數組元素全為指針的數組稱為指針數組,其中一維指針數組的定義形式如下。指針數組中每一個元素均為指針,其本質為數組。如 int *p[n], []優先級高,先與p結合成為一個數組,再由int*說明這是一個整型指針數組,它有n個指針類型的數組元素。這裡執行p+1時,則p指向下一個數組元素,這樣賦值是錯誤的:p=a;因為p是個不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它們分別是指針變量可以用來存放變量地址。但可以這樣 *p=a; 這裡*p表示指針數組第一個元素的值,a的首地址的值。

類型名*數組標識符[數組長度]5. 左值引用與右值引用

該部分主要摘自:c++ 學習筆記

左值引用就是我們通常所說的引用,如下所示。左值引用通常可以看作是變量的別名。

type-id&cast-expression//demointa=10int&b=aint&c=10//錯誤,無法對一個立即數做引用constint&d=10//正確,常引用引用常數量是ok的,其等價於constinttemp=10;constint&d=temp

右值引用是 C++11 新增的特性,其形式如下所示。右值引用用來綁定到右值,綁定到右值以後本來會被銷毀的右值的生存期會延長至與綁定到它的右值引用的生存期。

type-id&&cast-expression//demoint&&var=10;//okinta=10int&&b=a//錯誤,a為左值int&&c=var//錯誤,var為左值int&&d=move(a)//ok,通過move得到左值的右值引用

在匯編層面右值引用做的事情和常引用是相同的,即產生臨時量來存儲常量。但是,唯一 一點的區別是,右值引用可以進行讀寫操作,而常引用只能進行讀操作。

6. 右值引用的意義

右值引用支持移動語義的實現,可以減少拷貝,提升程序的執行效率。

下面的代碼是沒有採用右值引用時的實現。

classStack{public://構造Stack(intsize=1000):msize(size),mtop(0){cout<<"Stack(int)"<<endl;mpstack=newint[size];}//析構~Stack(){cout&lt;&lt;"~Stack()"&lt;&lt;endl;delete[]mpstack;mpstack=nullptr;}//拷貝構造Stack(constStack&amp;src):msize(src.msize),mtop(src.mtop){cout&lt;&lt;"Stack(constStack&amp;)"&lt;&lt;endl;mpstack=newint[src.msize];for(inti=0;i&lt;mtop;++i){mpstack[i]=src.mpstack[i];}}//賦值重載Stack&amp;operator=(constStack&amp;src){cout&lt;&lt;"operator="&lt;&lt;endl;if(this==&amp;src)return*this;delete[]mpstack;msize=src.msize;mtop=src.mtop;mpstack=newint[src.msize];for(inti=0;i&lt;mtop;++i){mpstack[i]=src.mpstack[i];}return*this;}intgetSize(){returnmsize;}private:int*mpstack;intmtop;intmsize;};StackGetStack(Stack&stack){Stacktmp(stack.getSize());returntmp;}intmain(){Stacks;s=GetStack(s);return0;}

運行結果如下。

Stack(int)//構造sStack(int)//構造tmpStack(constStack&)//tmp拷貝構造main函數棧幀上的臨時對象~Stack()//tmp析構operator=//臨時對象賦值給s~Stack()//臨時對象析構~Stack()//s析構

執行代碼的過程中調用拷貝構造,將內存中的內容逐個拷貝,在 C++ 11 中可以藉助右值引用實現移動拷貝構造和移動賦值來解決這個問題。

Stack(Stack&&src):msize(src.msize),mtop(src.mtop){cout<<"Stack(Stack&&)"<<endl;/*此處沒有重新開闢內存拷貝數據,把src的資源直接給當前對象,再把src置空*/mpstack=src.mpstack;src.mpstack=nullptr;}//帶右值引用參數的賦值運算符重載函數Stack&operator=(Stack&&src){cout<<"operator=(Stack&&)"<<endl;if(this==&amp;src)return*this;delete[]mpstack;msize=src.msize;mtop=src.mtop;/*此處沒有重新開闢內存拷貝數據,把src的資源直接給當前對象,再把src置空*/mpstack=src.mpstack;src.mpstack=nullptr;return*this;}

執行結果如下。可以看到,在有拷貝構造和移動拷貝構造函數的時候,優先調用了移動拷貝構造和移動賦值。在移動拷貝構造和移動賦值中直接把資源所有權進行了轉移,而非拷貝,這就大大提高了執行效率。

Stack(int)//構造sStack(int)//構造tmpStack(Stack&&)//調用帶右值引用的拷貝構造函數,直接將tmp的資源給臨時對象~Stack()//tmp析構operator=(Stack&&)//調用帶右值引用的賦值運算符重載函數,直接將臨時對象資源給s~Stack()//臨時對象析構~Stack()//s析構

右值引用可以使重載函數變得更加簡潔。右值引用可以適用 const T& 和 T& 形式的參數。

structW{W(int&,int&){}};structX{X(constint&,int&){}};structY{Y(int&,constint&){}};structZ{Z(constint&,constint&){}};template<typenameT,typenameA1,typenameA2>T*factory(A1&a1,A2&a2){returnnewT(a1,a2);}template<typenameT,typenameA1,typenameA2>T*factory_new(A1&&a1,A2&&a2){returnnewT(std::forward<A1>(a1),std::forward<A2>(a2));}//demointa=2;intb=2;W*c=factory<w>(a,b);//okZ*d=factory<Z>(2,2);//錯誤,2是右值W*pw=factory_new<W>(a,b);//okX*px=factory_new<X>(2,b);//okY*py=factory_new<Y>(a,2);//okZ*e=factory_new<Z>(2,2);//okW*f=factory_new<W>(2,2);//錯誤,

更多相關內容可以參考:c++——左值、右值、左值引用、右值引用

第四部分:C++特性1. 什麼是面向對象(OOP)?面向對象的意義?

Object Oriented Programming, 面向對象是一種對現實世界理解和抽象的方法、思想,通過將需求要素轉化為對象進行問題處理的一種思想。其核心思想是數據抽象、繼承和動態綁定(多態)。面向對象的意義在於:將日常生活中習慣的思維方式引入程序設計中;將需求中的概念直觀的映射到解決方案中;以模塊為中心構建可復用的軟件系統;提高軟件產品的可維護性和可擴展性。

2. 解釋下封裝、繼承和多態?

1). 封裝:封裝是實現面向對象程序設計的第一步,封裝就是將數據或函數等集合在一個個的單元中(我們稱之為類)。封裝的意義在於保護或者防止代碼(數據)被我們無意中破壞。從封裝的角度看,public, private 和 protected 屬性的特點如下。

和 public 一樣可以被子類繼承

和 private 一樣不能在類外被直接調用

特例:在衍生類中可以通過衍生類對象訪問,如下代碼所示

不管哪種屬性,內類都是可以訪問的

public 是一種暴露的手段,比如暴露接口,類的對象可以訪問

private 是一種隱藏的手段,類的對象不能訪問

protected 成員:

classBase{public:Base(){};virtual~Base(){};protected:intint_pro;};classA:publicBase{public:A(){};A(intda){int_pro=da;}//通過obj對象直接訪問protected成員voidSet(A&obj){obj.int_pro=24;}voidPrintPro(){cout<<"Theproteteddatais"<<int_pro<<endl;}};

2). 繼承: 繼承主要實現重用代碼,節省開發時間。 子類可以繼承父類的一些東西。

a.公有繼承(public) 公有繼承的特點是基類的公有成員和保護成員作為派生類的成員時,它們都保持原有的狀態(基類的私有成員仍然是私有的,不能被這個派生類的子類所訪問)。

b.私有繼承(private) 私有繼承的特點是基類的公有成員和保護成員都作為派生類的私有成員(並且不能被這個派生類的子類所訪問)。

c.保護繼承(protected) 保護繼承的特點是基類的所有公有成員和保護成員都成為派生類的保護成員(並且只能被它的派生類成員函數或友元訪問,基類的私有成員仍然是私有的)。

這裡特別提一下虛繼承。虛繼承是解決C++多重繼承問題(其一,浪費存儲空間;第二,存在二義性問題)的一種手段。比如菱形繼承,典型的應用就是 iostream, 其繼承於 istream 和 ostream,而 istream 和 ostream 又繼承於 ios。

3).多態:多態是指通過基類的指針或者引用,在運行時動態調用實際綁定對象函數的行為。與之相對應的編譯時綁定函數稱為靜態綁定。多態是設計模式的基礎,多態是框架的基礎。

3. 什麼時候生成默認構造函數(無參構造函數)?什麼時候生成默認拷貝構造函數?什麼是深拷貝?什麼是淺拷貝?默認拷貝構造函數是哪種拷貝?什麼時候用深拷貝?

1). 沒有任何構造函數時,編譯器會自動生成默認構造函數,也就是無參構造函數;當類沒有拷貝構造函數時,會生成默認拷貝構造函數。

2). 深拷貝是指拷貝後對象的邏輯狀態相同,而淺拷貝是指拷貝後對象的物理狀態相同;默認拷貝構造函數屬於淺拷貝。

3). 當系統中有成員指代了系統中的資源時,需要深拷貝。比如指向了動態內存空間,打開了外存中的文件或者使用了系統中的網絡接口等。如果不進行深拷貝,比如動態內存空間,可能會出現多次被釋放的問題。是否需要定義拷貝構造函數的原則是,類是否有成員調用了系統資源,如果定義拷貝構造函數,一定是定義深拷貝,否則沒有意義。

更多可以參考下面的代碼,比較容易混淆的是賦值操作符,其實區分很簡單,在出現等號的時候,如果有構造新的對象時調用的就是構造,不然就是調用賦值操作符。特別注意下面的 b 和 f,一個是拷貝構造,一個是構造。

classA{public:A(){m=newint[4]{1,2,3,4};std::cout<<"constructor"<<std::endl;}~A(){if(m!=nullptr){delete[]m;}}A(constA&a){this->m=newint[4];memcpy(a.m,this->m,this->len*sizeof(int));std::cout<<"copyconstructor"<<std::endl;}//移動構造A(A&&a):m(a.m){a.m=nullptr;std::cout<<"moveconstructor"<<std::endl;}//賦值操作符重載A&operator=(constA&a){memcpy(a.m,this->m,this->len*sizeof(int));std::cout<<"operator"<<std::endl;return*this;}private:intlen=4;int*m=nullptr;};AgetA(Aa){returna;}intmain(void){Aa;//constructAb=a;//copyconstructAc(a);//copyconstructAd;//constructd=a;//operateAe=getA(a);//construct,moveconstructAf=A();//constructreturn0;}4. 構造函數和析構函數的執行順序?構造函數

1). 首先調用父類的構造函數;

2). 調用成員變量的構造函數;

3). 調用類自身的構造函數。

析構函數

對於棧對象或者全局對象,調用順序與構造函數的調用順序剛好相反,也即後構造的先析構。對於堆對象,析構順序與delete的順序相關。

5. 虛析構函數的作用?

基類採用虛析構函數可以防止內存泄漏。比如下面的代碼中,如果基類 A 中不是虛析構函數,則 B 的析構函數不會被調用,因此會造成內存泄漏。

classA{public:A(){}//~A(){}virtual~A(){cout<<"Adisconstruct"<<endl;}//虛析構//~A(){cout<<"Adisconstruct"<<endl;}//析構};classB:publicA{public:B(){//newmemory//...cout<<"Bconstruct"<<endl;}~B(){//deletememory//...cout<<"Bdisconstruct"<<endl;}};intmain(intargc,char**argv){A*p=newB;//someoperations//...deletep;//由於基類中是虛析構,這裡會先調用B的析構函數,然後調用A的析構函數return0;}

但並不是要把所有類的析構函數都寫成虛函數。因為當類裡面有虛函數的時候,編譯器會給類添加一個虛函數表,裡面來存放虛函數指針,這樣就會增加類的存儲空間。所以,只有當一個類被用來作為基類的時候,才把析構函數寫成虛函數。

6. 細看拷貝構造函數

對於 class A,它的拷貝構造函數如下:

A::A(constA&a){}1) 為什麼必須是當前類的引用呢?

循環調用。如果拷貝構造函數的參數不是當前類的引用,而是當前類的對象,那麼在調用拷貝構造函數時,會將另外一個對象直接傳遞給形參,這本身就是一次拷貝,會再次調用拷貝構造函數,然後又將一個對象直接傳遞給了形參,將繼續調用拷貝構造函數……這個過程會一直持續下去,沒有盡頭,陷入死循環。

只有當參數是當前類的引用時,才不會導致再次調用拷貝構造函數,這不僅是邏輯上的要求,也是 C++ 語法的要求。

2) 為什麼是 const 引用呢?

拷貝構造函數的目的是用其它對象的數據來初始化當前對象,並沒有期望更改其它對象的數據,添加 const 限制後,這個含義更加明確了。

另外一個原因是,添加 const 限制後,可以將 const 對象和非 const 對象傳遞給形參了,因為非 const 類型可以轉換為 const 類型。如果沒有 const 限制,就不能將 const 對象傳遞給形參,因為 const 類型不能直接轉換為非 const 類型,這就意味着,不能使用 const 對象來初始化當前對象了。

7. C++的編譯環境

如下圖所示,C++的編譯環境由如下幾部分構成:C++標準庫、C語言兼容庫、編譯器擴展庫及編譯模塊。

在這裡插入圖片描述#include<iostream>//C++標準庫,不帶".h"#include<string.h>//C語言兼容庫,由編譯器廠商提供

值得注意的是,C語言兼容庫功能上跟C++標準庫中的C語言子庫相同,它的存中主要為了兼容C語言編譯器,也就是說如果一個文件只包含C語言兼容庫(不包含C++標準庫),那麼它在C語言編譯器中依然可以編譯通過。

8. Most vexing parse

直接上代碼吧。下面 f 和 g 是有問題的,這種情況就稱為 Most vexing parse。

classA{public:A(){cout<<"constwithoutparam"<<endl;}A(inta){cout<<"constwithparam"<<endl;}A(constA&b){cout<<"copyconstruct"<<endl;}};intmain(void){Aa;//const(construct)withoutparamAb(10);//constwithparamAc=A();//constwithoutparamAd=A(10);//constwithparamAe(d);//copyconstructAf();Ag(A());Ah{};//constwithoutparamAi{A{}};//constwithoutparamreturn0;}

問題在哪?

Af();//這個是不是可以看做聲明了一個返回值為A的函數,函數名為f,參數無Ag(A());//這個是不是可以看做聲明了一個返回值為A的函數,函數名為g,參數類型為函數指針,這個函數指針的返回值類型為A,參數無

解決辦法參考上面的 h, j。

————————————————

版權聲明:本文為CSDN博主「kuweicai」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。

原文鏈接:https://blog.csdn.net/kuweicai/article/details/82779648

-- END --

進技術交流群,掃碼添加我的微信:Byte-Flow

獲取視頻教程和源碼


推薦:

面試常問的 C/C++ 問題,你能答上來幾個?

C++ 面試必問:深入理解虛函數表

很多人搞不清 C++ 中的 delete 和 delete[ ] 的區別

看懂別人的代碼,總得懂點 C++ lambda 表達式吧

Java、C++ 內存模型都不知道,還敢說自己是高級工程師?

C++ std::thread 必須要熟悉的幾個知識點

現代 C++ 並發編程基礎

現代 C++ 智能指針使用入門

c++ thread join 和 detach 到底有什麼區別?

C++ 面試八股文:list、vector、deque 比較

C++經典面試題(最全,面中率最高)

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()