C++經典面試問題

  c++經典面試問題分享

1,關於動態申請記憶體

答:記憶體分配方式三種:

(1)從靜態存儲區域分配:記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個運行期間都存在。

全局變數,static變數。

(2)在棧上創建:在執行函式時,函式內局部變數的存儲單元都可以在棧上創建,

函式執行結束時這些存儲單元自動被釋放。

棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。

(3)用malloc或new申請記憶體之後,應該立即檢查指針值是否為null.防止使用指針值為null的記憶體,

不要忘記為數組和動態記憶體賦初值。防止將未被初始化的記憶體作為右值使用。避免數組或指針的下標越界,

特別要當心發生“多1”或者“少1”操作。動態記憶體的申請與釋放必須配對,防止記憶體泄漏。

用free或delete釋放了記憶體之後,立即將指針設定為null,防止產生“野指針”。從堆上分配,亦稱動態記憶體分配。

程式在運行的時候用malloc或new申請任意多少的記憶體,程式設計師自己負責在何時用free或delete釋放記憶體。

動態記憶體的生存期由程式設計師決定,使用非常靈活。(int *parray; int myarray[6]; parray = &myarray[0];)

如果在申請動態記憶體時找不到足夠大的記憶體塊,malloc和new將返回null指針,

判斷指針是否為null,如果是則馬上用return語句終止本函式,

或者馬上用exit(1)終止整個程式的運行,為new和malloc設定異常處理函式。

2,c++指針攻破

答案:指針是一個變數,專門存放記憶體地址,特點是能訪問所指向的記憶體

指針本身占據了4個位元組的長度

int **ptr; //指針的類型是 int **

int (*ptr)[3]; //指針的類型是 int(*)[3]

int *(*ptr)[4]; //指針的類型是 int *(*)[4]

ptr++:指針ptr的值加上了sizeof(int)

ptr+=5:將指針ptr的值加上5*sizeof(int)

指針的賦值:

把一個變數的地址賦予指向相同數據類型的指針變數( int a; int *ip; ip=&a; )

把一個指針變數的值賦予指向相同類型變數的另一個指針變數(int a; int *pa=&a; int *pb; pb=pa; )

把數組的首地址賦予指向數組的指針變數(int a[5],*pa; pa=a; 也可寫為:pa=&a[0];)

如果給指針加1或減1 ,實際上是加上或減去指針所指向的數據類型大小。

當給指針加上一個整數值或減去一個整數值時,表達式返回一個新地址。

相同類型的兩個指針可以相減,減後返回的整數代表兩個地址間該類型的實例個數。

int ** cc=new (int*)[10]; 聲明一個10個元素的數組,數組每個元素都是一個int *指針,

每個元素還可以單獨申請空間,因為cc的類型是int*型的指針,所以你要在堆里申請的話就要用int *來申請;

int ** a= new int * [2]; //申請兩個int * 型的空間

a[0] = new int[4];////為a的第一個元素申請了4個int 型空間,a[0] 指向了此空間的首地址處

a[1] = new int[3];//為a的第二個元素又申請了3個int 型空間,a[1]指向了此空間首地址處

指針數組初始化賦值:

一維指針開闢空間:char *str;int *arr; scanf("%d",&n);

str=(char*)malloc(sizeof(char)*n);

arr=(int*)malloc(sizeof(int)*n);

二維指針開闢空間:int **arr, i; scanf("%d%d",&row,&col);

arr=(int**)malloc(sizeof(int)*row);

for(i=0;i

arr[i]=(int*)malloc(sizeof(int)*col);

結構體指針數組,例如typedef struct{ char x; int y; }quan,*qquan;

定義一個結構體指針數組如:qquan a[max]

for(i=0;i

{

a[i]=(qquan)malloc(sizeof(quan));

memset(a[i],0,sizeof(quan));

}

指針數組賦值

float a[]={100,200,300,400,500};

float *p[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};

char *units[1000];

char get_unit[250];

for(int i=0;i

scanf("%s", get_unit); strcpy(units[i],get_unit);}

3,複雜指針解析:

(1)int (*func)(int *p);

(*func)()是一個函式,func是一個指向這類函式的指針,就是一個函式指針,這類函式具有int*類型的形參,返回值類型是 int。

(2)int (*func)(int *p, int (*f)(int*));

func是一個指向函式的指針,這類函式具有int *和int (*)(int*)這樣的形參。形參int (*f)(int*),f也是一個函式指針

(3)int (*func[5])(int *p);

func數組的元素是函式類型的指針,它所指向的函式具有int*類型的形參,返回值類型為int。

(4)int (*(*func)[5])(int *p);

func是一個指向數組的指針,這個數組的元素是函式指針,這些指針指向具有int*形參,返回值為int類型的函式。

(5)int (*(*func)(int *p))[5];

func是一個函式指針,這類函式具有int*類型的形參,返回值是指向數組的指針,所指向的數組的元素是具有5個int元素的數組。

注意:

需要聲明一個複雜指針時,如果把整個聲明寫成上面所示的形式,對程式可讀性是一大損害。

應該用typedef來對聲明逐層,分解,增強可讀性,例如對於聲明:int (*(*func)(int *p))[5];

這樣分解:typedef int (*para)[5]; typedef para (*func)(int *);

例如:int (*(*func)[5][6])[7][8];

func是一個指向數組的指針,這類數組的元素是一個具有5x6個int元素的二維數組,而這個二維數組的元素又是一個二維數組。

typedef int (*para)[7][8];

typedef para (*func)[5][6];

例如:int (*(*(*func)(int *))[5])(int *);

func是一個函式指針,這類函式的返回值是一個指向數組的指針,

所指向數組的元素也是函式指針,指向的函式具有int*形參,返回值為int。

typedef int (*para1)(int*);

typedef para1 (*para2)[5];

typedef para2 (*func)(int*);

4,函式指針詳解

答:函式指針是指向一個函式入口的指針

一個函式指針只能指向一種類型的函式,即具有相同的返回值和相同的參數的函式。

函式指針數組定義:void(*fun[3])(void*); 相應指向類a的成員函式的指針:void (a::*pmf)(char *, const char *);

指向外部函式的指針:void (*pf)(char *, const char *); void strcpy(char * dest, const char * source); pf=strcpy;

5,野指針

答:“野指針”是很危險的,if語句對它不起作用。“野指針”的成因主要有兩種:

(1)指針變數沒有被初始化。指針變數在創建的同時應當被初始化,要么將指針設定為null,要么讓它指向合法的記憶體。

char *p = null; char *str = (char *) malloc(100);

(2)指針p被free或者delete之後,沒有置為null

(3)指針操作超越了變數的作用範圍。所指向的記憶體值對象生命期已經被銷毀

6,引用和指針有什麼區別?

答:引用必須初始化,指針則不必;引用初始化以後不能改變,指針可以改變其指向的對象;

不存在指向空值的引用,但存在指向控制的指針;

引用是某個對象的別名,主要用來描述函式和參數和返回值。而指針與一般的變數是一樣的,會在記憶體中開闢一塊記憶體。

如果函式的參數或返回值是類的對象的話,採用引用可以提高程式的效率。

7,c++中的const用法

答:char * const p; // 指針不可改,也就說指針只能指向一個地址,不能更改為其他地址,修飾指針本身

char const * p; // 所指內容不可改,也就是說*p是常量字元串,修飾指針所指向的變數

const char * const p 和 char const * const p; // 內容和指針都不能改

const修飾函式參數是它最廣泛的一種用途,它表示函式體中不能修改參數的值,

傳遞過來的參數在函式內不可以改變,參數指針所指內容為常量不可變,參數指針本身為常量不可變

在引用或者指針參數的時候使用const限制是有意義的,而對於值傳遞的參數使用const則沒有意義

const修飾類對象表示該對象為常量對象,其中的任何成員都不能被修改。

const修飾的對象,該對象的任何非const成員函式都不能被調用,因為任何非const成員函式會有修改成員變數的企圖。

const修飾類的成員變數,表示成員常量,不能被修改,同時它只能在初始化列表中賦值。static const 的成員需在聲明的地方直接初始。

const修飾類的成員函式,則該成員函式不能修改類中任何非const成員。一般寫在函式的最後來修飾。

在函式實現部分也要帶const關鍵字.

對於const類對象/指針/引用,只能調用類的const成員函式,因此,const修飾成員函式的最重要作用就是限制對於const對象的使用

使用const的一些建議:在參數中使用const應該使用引用或指針,而不是一般的對象實例

const在成員函式中的三種用法(參數、返回值、函式)要很好的使用;

const在成員函式中的三種用法(參數、返回值、函式)要很好的使用;

不要輕易的將函式的返回值類型定為const;除了重載操作符外一般不要將返回值類型定為對某個對象的const引用;

8,const常量與define宏定義的區別

答:(1) 編譯器處理方式不同。define宏是在預處理階段展開,生命周期止於編譯期。

只是一個常數、一個命令中的參數,沒有實際的存在。

#define常量存在於程式的代碼段。const常量是編譯運行階段使用,const常量存在於程式的數據段.

(2)類型和安全檢查不同。define宏沒有類型,不做任何類型檢查,僅僅是展開。

const常量有具體的類型,在編譯階段會執行類型檢查。

(3) 存儲方式不同。define宏僅僅是展開,有多少地方使用,就展開多少次,不會分配記憶體。

const常量會在記憶體中分配(可以是堆中也可以是棧中)

9,解釋堆和棧的區別

答:1、棧區(stack)— 由編譯器自動分配釋放,存放函式的參數值,局部變數的值等。其操作方式類似於數據結構中的棧。

由系統自動分配。聲明在函式中一個局部變數 int b; 系統自動在棧中為b開闢空間 。

只要棧的剩餘空間大於所申請空間,系統將為程式提供記憶體,否則將報異常提示棧溢出。

在windows下,棧是向低地址擴展的數據結構,是一塊連續的記憶體的區域,棧的大小是2m。

如果申請的空間超過棧的剩餘空間時,將提示overflow。

棧由系統自動分配,速度較快。但程式設計師是無法控制的。

函式調用時,第一個進棧的是主函式中後的下一條指令,的地址,然後是函式的各個參數。

在大多數的c編譯器中,參數是由右往左入棧的,然後是函式中的局部變數。注意靜態變數是不入棧的。

堆區(heap) — 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由os回收 。

注意它與數據結構中的堆是兩回事,分配方式倒是類似於鍊表,需要程式設計師自己申請,並指明大小,在c中malloc函式

在c++中用new運算符。首先應該知道作業系統有一個記錄空閒記憶體地址的鍊表,當系統收到程式的申請時,

另外,由於找到的堆結點的大小不一定正好等於申請的大小,系統會自動的將多餘的那部分重新放入空閒鍊表中。

堆是向高地址擴展的數據結構,是不連續的記憶體區域。而鍊表的遍歷方向是由低地址向高地址。

堆的大小受限於計算機系統中有效的虛擬記憶體。

堆是由new分配的記憶體,一般速度比較慢,而且容易產生記憶體碎片,不過用起來最方便

一般是在堆的頭部用一個位元組存放堆的大小。

10,論述含參數的宏和函式的優缺點

(1)函式調用時,先求出實參表達式的值,然後代入形參。而使用帶參的宏只是進行簡單的字元替換

(2)函式調用是在程式運行時處理的,分配臨時的記憶體單元;而宏展開是在編譯時進行的,在展開時不進行

記憶體分配,不進行值得傳遞處理,沒有“返回值”概念

(3)對函式中的形參和實參都要定義類型,類型要求一致,如不一致則進行類型轉換。而宏不存在類型問題

(4)調用函式只可得到一個返回值,而用宏則可以設法得到幾個結果

(5)實用宏次數多時,宏展開後源程式變長,沒展開一次源程式增長,函式調用則不會

(6)宏替換不占用運行時間,只占編譯時間,而函式調用占用運行時間

11,c++的空類,默認產生哪些類成員函式?

答:class empty

{

public:

empty(); //預設構造函式

empty(const empty& ); //拷貝構造函式

~empty(); //虛構函式

empty& operator(const empty& ) //賦值運算符

empty& operator&(); //取址運算符

const empty* operator&() const; // 取址運算符 const

}

12,談談類和結構體的區別

答:結構體在默認情況下的成員都是public的,而類在默認情況下的成員是private的。結構體和類都必須使用new創建,

struct保證成員按照聲明順序在記憶體在存儲,而類不保證。

13,c++四種強制類型轉換

答:(1)const_cast

字面上理解就是去const屬性,去掉類型的const或volatile屬性。

struct sa{ int k}; const sa ra;

ra.k = 10; //直接修改const類型,編譯錯誤 sa& rb = const_cast(ra); rb.k = 10; //可以修改

(2)static_cast

主要用於基本類型之間和具有繼承關係的類型之間的轉換。用於指針類型的轉換沒有太大的意義

static_cast是無條件和靜態類型轉換,可用於基類和子類的轉換,基本類型轉換,把空指針轉換為目標類型的空指針,

把任何類型的表達式轉換成void類型,static_cast不能進行無關類型(如非基類和子類)指針之間的轉換。

int a; double d = static_cast(a); //基本類型轉換

int &pn = &a; void *p = static_cast(pn); //任意類型轉換為void

(3)dynamic_cast

你可以用它把一個指向基類的指針或引用對象轉換成繼承類的對象

動態類型轉換,運行時類型安全檢查(轉換失敗返回null)

基類必須有虛函式,保持多態特性才能用dynamic_cast

只能在繼承類對象的指針之間或引用之間進行類型轉換

class baseclass{public: int m_inum; virtual void foo(){};};

class derivedclass:baseclass{public: char* szname[100]; void bar(){};};

baseclass* pb = new derivedclass();

derivedclass *p2 = dynamic_cast(pb);

baseclass* pparent = dynamic_cast(p2);

//子類->父類,動態類型轉換,正確

(4)reinterpreter_cast

轉換的類型必須是一個指針、引用、算術類型、函式指針或者成員指針。

主要是將一個類型的指針,轉換為另一個類型的指針

不同類型的指針類型轉換用reinterpreter_cast

最普通的用途就是在函式指針類型之間進行轉換

int dosomething(){return 0;};

typedef void(*funcptr)(){};

funcptr funcptrarray[10];

funcptrarray[0] = reinterpreter_cast(&dosomething);

14,c++函式中值的傳遞方式有哪幾種?

答:函式的三種傳遞方式為:值傳遞、指針傳遞和引用傳遞。

15,將“引用”作為函式參數有哪些特點

答:(1)傳遞引用給函式與傳遞指針的效果是一樣的,這時,被調函式的形參就成為原來主調函式的實參變數或者

對象的一個別名來使用,所以在被調函式中形參的操作就是對相應的目標對象的操作

(2)使用引用傳遞函式的參數,在記憶體中並沒有產生實參的副本,它是直接對實參操作,當參數數據較大時,引用

傳遞參數的效率和所占空間都好

(3)如果使用指針要分配記憶體單元,需要重複使用“*指針變數名”形式進行計算,容易出錯且閱讀性較差。