C++經典面試題

lostinai發表於2014-05-27

1,關於動態申請記憶體

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

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

全域性變數,static變數。

(2)在棧上建立:在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,

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

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

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

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

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

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

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

動態記憶體的生存期由程式設計師決定,使用非常靈活。(int*pArray;   intMyArray[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)

 

指標的賦值:

把一個變數的地址賦予指向相同資料型別的指標變數( inta;   int*ip;   ip=&a;)

把一個指標變數的值賦予指向相同型別變數的另一個指標變數(inta;  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= newint * [2];     //申請兩個int * 型的空間

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

   a[1] = newint[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<row;i++)

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

 

結構體指標陣列,例如typedefstruct{   charx;   int  y;}Quan,*QQuan;

定義一個結構體指標陣列如:QQuan a[MAX] 

for(i=0;i<MAX;i++)

{

   a[i]=(QQuan)malloc(sizeof(Quan));

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

}

指標陣列賦值

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

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

 

 

char *units[1000];

charget_unit[250];                                                                           

for(int i=0;i<get_unit_num;i++){units[i]=(char*) malloc(60*sizeof(char*)); 

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 *); voidstrcpy(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 * constp;    //指標不可改,也就說指標只能指向一個地址,不能更改為其他地址,修飾指標本身

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

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

 

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

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

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

 

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

const修飾的物件,該物件的任何非const成員函式都不能被呼叫,因為任何非const成員函式會有修改成員變數的企圖。

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

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(constEmpty&)    //賦值運算子

Empty&operator&();              //取址運算子

const Empty* operator&()const;   // 取址運算子 const

}

 

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

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

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

 

13,C++四種強制型別轉換

答:(1)const_cast

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

struct SA{  intk};  const SAra;   

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

 

(2)static_cast

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

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

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

inta;    double d =static_cast<double>(a);  //基本型別轉換

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

 

(3)dynamic_cast

 

你可以用它把一個指向基類的指標或引用物件轉換成繼承類的物件

動態型別轉換,執行時型別安全檢查(轉換失敗返回NULL)

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

只能在繼承類物件的指標之間或引用之間進行型別轉換

class BaseClass{public:  intm_iNum;  virtual void foo(){};};

class DerivedClass:BaseClass{public: char*szName[100];  void bar(){};};

BaseClass* pb = new DerivedClass();

 

DerivedClass *p2 =dynamic_cast<DerivedClass *>(pb);

BaseClass* pParent =dynamic_cast<BaseClass*>(p2);

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

 

(4)reinterpreter_cast

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

主要是將一個型別的指標,轉換為另一個型別的指標

不同型別的指標型別轉換用reinterpreter_cast

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

int DoSomething(){return 0;};

typedef void(*FuncPtr)(){};

FuncPtr funcPtrArray[10];

funcPtrArray[0] =reinterpreter_cast<FuncPtr>(&DoSomething);

 

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

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

 

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

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

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

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

傳遞引數的效率和所佔空間都好

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

 

16,簡單敘述物件導向的三個基本特徵

答:封裝性

把客觀事物封裝成抽象的類,對自身的資料和方法進行(public,private,protected)

繼承性

繼承概念的實現方式有三類:實現繼承、介面繼承和可視繼承。

實現繼承是指使用基類的屬性和方法而無需額外編碼的能力;

介面繼承是指僅使用屬性和方法的名稱、但是子類必須提供實現的能力;

可視繼承是指子窗體(類)使用基窗體(類)的外觀和實現程式碼的能力。

抽象類僅定義將由子類建立的一般屬性和方法,建立抽象類時,請使用關鍵字 Interface 而不是Class

多型性

多型性(polymorphisn)是允許你將父物件設定成為和一個或更多的他的子物件相等的技術,賦值之後,

父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作。允許將子類型別的指標賦值給父類型別的指標。

實現多型,有二種方式,覆蓋(子類重新定義父類的虛擬函式),過載(允許存在多個同名函式,引數個數,型別不同)。

 

17,類成員函式的overload, override 和 隱藏的區別

答:
(1)成員函式被過載的特徵:相同的類範圍,函式名字相同,引數不同,virtual 關鍵字可有可無。

(2)覆蓋指派生類的函式覆蓋基類函式,特徵是分別位於基類和派生類,函式名字相同,引數相同,基類函式必須有virtual關鍵字

(3)隱藏是指派生類的函式遮蔽了與其同名的基類函式。1,派生類的函式與基類的函式同名,但是引數不同,

不論有無virtual關鍵字,基類的函式將被隱藏2,派生類的函式與基類的函式同名,並且引數也相同,

但是基類函式沒有virtual 關鍵字。此時,基類的函式被隱藏

3種情況怎麼執行:過載:看引數;隱藏:用什麼就呼叫什麼;覆蓋:呼叫派生類 。

 

18,什麼是預編譯,何時需要預編譯

答:就是指程式執行前的一些預處理工作,主要指#表示的.

需要預編譯的情況:總是使用不經常改動的大型程式碼體。所有模組都使用一組標準的包含檔案和相同的編譯選項。

 

19,memset ,memcpy 和strcpy 的根本區別?

答:memset用來對一段記憶體空間全部設定為某個字元,一般用在對定義的字串進行初始化為''或'';

它對較大的結構體或陣列進行清零操作的一種最快方法。

chartemp[30];    memset(temp,'\0',sizeof(temp));

chartemp[30]只是分配了一定的記憶體空間給該字元陣列,但並未初始化該記憶體空間,即陣列。所以,需要使用memset()來進行初始化。

memcpy用來做記憶體拷貝,你可以拿它拷貝任何資料型別的物件,可以指定拷貝的資料長度;

strcpy就只能拷貝字串了,它遇到'\0'就結束拷貝;例:chara[100],b[50];strcpy(a,b);

 

20,多型類中的虛擬函式表是Compile-Time,還是Run-Time時建立的?

答:虛擬函式表是在編譯期就建立了,各個虛擬函式這時被組織成了一個虛擬函式的入口地址的陣列.

而物件的隱藏成員--虛擬函式表指標是在執行期也就是建構函式被呼叫時進行初始化的,這是實現多型的關鍵.

 

21,Template有什麼特點?什麼時候用?

答: Template可以獨立於任何特定的型別編寫程式碼,是泛型程式設計的基礎.

當我們編寫的類和函式能夠多型的用於跨越編譯時不相關的型別時,用Template.

模板主要用於STL中的容器,演算法,迭代器等以及模板超程式設計.

C++的template是實現在庫設計和嵌入式設計中的關鍵,

能實現抽象和效率的結合;同時template還能有效地防止程式碼膨脹

C++中為什麼用模板類?

1)可用來建立動態增長和減小的資料結構

2)它是型別無關的,因此具有很高的可複用性

3)它在編譯時而不是執行時檢查資料型別,保證了型別安全

4)它是平臺無關的,可移植性

5)可用於基本資料型別

 

22,程式和執行緒的差別?

答:執行緒是指程式內的一個執行單元,也是程式內的可排程實體.區別:

(1)排程:執行緒作為排程和分配的基本單位,程式作為擁有資源的基本單位

(2)併發性:不僅程式之間可以併發執行,同一個程式的多個執行緒之間也可併發執行

(3)擁有資源:程式是擁有資源的一個獨立單位,執行緒不擁有系統資源,但可以訪問隸屬於程式的資源.

(4)系統開銷:建立撤消程式,系統都要為之分配和回收資源,系統的開銷明顯大於建立撤消執行緒

多程式與多執行緒,兩者都可以提高程式的併發度,提高程式執行效率和響應時間。

 

23,請說出static關鍵字儘可能多的作用

答:(1)函式體內作用範圍為該函式體,該變數記憶體只被分配一次,具有記憶能力

(2)在模組內的static全域性變數可以被模組內所有函式訪問,但不能被模組外其它函式訪問;

(3)在模組內的static函式只可被這一模組內的其它函式呼叫,這個函式的使用範圍被限制在宣告它的模組內;

(4)在類中的static成員變數屬於整個類所擁有,對類的所有物件只有一份拷貝;

(5)在類中的static成員函式屬於整個類所擁有,這個函式不接收this指標,因而只能訪問類的static成員變數。

 

24,標頭檔案的作用是什麼?

答:一,通過標頭檔案來呼叫庫功能。在很多場合,原始碼不便(或不準)向使用者公佈,只要向使用者提供標頭檔案

和二進位制的庫即可。使用者只需要按照標頭檔案中的介面宣告來呼叫庫功能,而不必關心介面怎麼實現的。

編譯器會從庫中提取相應的程式碼。

二,標頭檔案能加強型別安全檢查。如果某個介面被實現或被使用時,其方式與標頭檔案中的宣告不一致,

編譯器就會指出錯誤,這一簡單的規則能大大減輕程式設計師除錯、改錯的負擔。

 

25,在C++程式中呼叫C編譯後的函式,為什麼要加extern C的宣告?

答:因為C++支援函式過載,而C不支援函式過載,函式被C++編譯後在庫中的名字與C語言的不同。

假設某個函式的原型為:void foo(int x, inty);該函式被C編譯器編譯後在庫中的名字為_foo,

而C++編譯器則產生像_foo_int_int之類的名字。 C++提供externC來解決名字匹配問題

 

26,C++中哪些函式不能被宣告為虛擬函式?

答:普通函式(非成員函式),建構函式,內聯成員函式、靜態成員函式、友元函式。

(1)虛擬函式用於基類和派生類,普通函式所以不能

(2)建構函式不能是因為虛擬函式採用的是虛呼叫的方法,允許在只知道部分資訊的情況的工作機制,

特別允許呼叫只知道介面而不知道物件的準確型別的方法,但是呼叫建構函式即使要建立一個物件,

那勢必要知道物件的準確型別。

(3)內聯成員函式的實質是在呼叫的地方直接將程式碼擴充套件開

(4)繼承時,靜態成員函式是不能被繼承的,它只屬於一個類,因為也不存在動態聯編等

(5)友元函式不是類的成員函式,因此也不能被繼承

 

27, 陣列int c[3][3]; 為什麼c,*c的值相等,(c+1),(*c+1)的值不等,c,*c,**c,代表什麼意思?

答:c是第一個元素的地址,*c是第一行元素的首地址,其實第一行元素的地址就是第一個元素的地址,

**c是提領第一個元素。 為什麼c,*c的值相等?

c: 陣列名;是一個二維指標,它的值就是陣列的首地址,也即第一行元素的首地址(等於 *c),

也等於第一行第一個元素的地址( &c[0][0]);可以說成是二維陣列的行指標。

*c: 第一行元素的首地址;是一個一維指標,可以說成是二維陣列的列指標。

**c:二維陣列中的第一個元素的值;即:c[0][0]

所以:c 和 *c的值是相等的,但他們兩者不能相互賦值,(型別不同)

(c + 1) :c是行指標,(c + 1)是在c的基礎上加上二維陣列一行的地址長度,

即從&c[0][0]變到了&c[1][0];

(*c + 1):*c是列指標,(*c +1)是在*c的基礎上加上二陣列一個元素的所佔的長度,

&c[0][0]變到了&c[0][1],從而(c + 1)和(*c +1)的值就不相等了。

 

28,定義  int**pa[4][3],則變數pa佔有的記憶體空間是多少?

答:int **p,在32位機器上 sizeof(p) = 4;

總共佔有4*3*sizeof(p) = 48.

 

29,拷貝建構函式相關問題,深拷貝,淺拷貝,臨時物件等

答:在C++中,三種物件需要拷貝的情況:一個物件以值傳遞的方式傳入函式體,

 一個物件以值傳遞的方式從函式返回,一個物件需要通過另外一個物件進行初始化。

執行先父類後子類的構造,對類中每一個資料成員遞迴地執行成員拷的動作.

深拷貝:如果一個類擁有資源,深拷貝意味著拷貝了資源和指標

淺拷貝:如果物件存在資源,而淺拷貝只是拷貝了指標,沒有拷貝資源,

這樣使得兩個指標指向同一份資源,造成對同一份析構兩次,程式崩潰。

臨時物件的開銷比區域性物件小些。

 

臨時物件:輔助一個表示式的計算 a + b + c,或者間接構造的實參,函式返回非引用的時候,

都可能產生臨時物件,臨時物件生命週期,是單個語句,是右值。

臨時物件的開銷比區域性物件小些。

 

30,指標和引用有什麼分別;

答:引用必須初始化,即引用到一個有效的物件;而指標在定義的時候不必初始化,

可以在定義後面的任何地方重新賦值。

引用初始化後不能改變,指標可以改變所指的物件

不存在指向NULL的引用,但存在指向NULL的指標

引用的建立和銷燬並不會呼叫類的拷貝建構函式

語言層面,引用的用法和物件一樣;在二進位制層面,引用一般都是通過指標來實現的,

只不過編譯器幫我們完成了轉換.引用既具有指標的效率,又具有變數使用的方便性和直觀性.

 

31,寫一個"標準"巨集MIN,這個巨集輸入兩個引數並返回較小的一個

答:面試者注意謹慎將巨集定義中的“引數”和整個巨集用括號括起來

#define     MIN(A,B)    ((A) <= (B)? (A):(B))

 

32,用一個巨集定義FIND求一個結構體struc中某個變數相對struc的偏移量

答:  #define FIND(struc,e)  (size_t)&(((struc*)0)->e )

解析:其中(struc*)0表示將常量0轉化為struc*型別指標所指向的地址。

&( ((struc*)0)->e)表示取結構體指標(struc*)0的成員e的地址,因為該結構體的首地址為0,

所以其實就是得到了成員e距離結構體首地址的偏移量,(size_t)是一種資料型別,為了便於不同系統之間的移植,

最好定義為一種無符號型資料,一般為unsigned int

 

33,解析sizeof 以及 結構體的對齊問題

答:(1)sizeof(type),用於資料型別;

sizeof(var_name)或sizeof var_name用於變數 

sizeof操作符不能用於函式型別,不完全型別或位欄位。

不完全型別指具有未知儲存大小的資料型別,如未知儲存大小的陣列型別、未知內容的結構或聯合型別、void型別等。

如int max(), char char_v [MAX]且MAX未知  , void型別

那麼sizeof(max),sizeof(char_v),sizeof(void)都是錯誤的

 

當sizeof的引數為陣列或者指標時

int  a[50];    //sizeof(a)=4*50=200;  求陣列所佔的空間大小

int  *a=new  int[50];//  sizeof(a)=4;  a為一個指標,sizeof(a)是求指標  

當sizeof的引數為結構或類時候

結構或者類中的靜態成員不對結構或者類的大小產生影響,因為靜態變數的儲存位置 。

與結構或者類的例項地址無關。沒有成員變數的結構或類的大小為1,

因為必須保證結構或類的每一 例項在記憶體中都有唯一的地址

 

(2)classMyStruct{   doubleddal;    chardda;    inttype;}

在VC中測試上面結構的大小時,你會發現sizeof(MyStruct)為16。

其實,這是VC對變數儲存的一個特殊處理。為了提高CPU的儲存速度,VC對一些變數的起始

地址做了“對齊”處理。在預設情況下,VC規定各成員變數存放的起始地址相對於結構的

始地址偏移量必須為該變數的型別佔用位元組數的倍數,如Char偏移量為sizeof(char)即1的倍數

 

先為第一個成員dda1分配空間,其起始地址跟結構的起始地址相同,偏移量0剛好為sizeof(double)的倍數,

該成員變數佔用sizeof(double)=8個位元組;接下來為第二個成員dda分配空間,這時

下一個可以分配的地址對於結構的起始地址的偏移量為8,是sizeof(char)的倍數,佔sizeof(char)=1位元組

為第三個成員type分配空間,這時下一個可以分配的地址對於結構的起始地址的偏移量為9

,不是sizeof(int)=4的倍數,為了滿足對齊方式對偏移量的約束問題,VC自動填充3個位元組

這時下一個可以分配的地址對於結構的起始地址的偏移量是12,剛好是sizeof(int)=4的倍數,

所以把type存放在偏移量為12的地方,佔用sizeof(int)=4個位元組。總的佔用的空間大

小為:8+1+3+4=16,剛好為結構的位元組邊界數(即結構中佔用最大空間的型別所佔用的位元組

數sizeof(double)=8)的倍數,所以沒有空缺的位元組需要填充。

 

34,在main函式執行之前,還會執行什麼程式碼和工作

答:執行全域性構造器,全域性物件的建構函式會在main函式之前執行

設定棧指標,初始化static靜態和global全域性變數,即資料段的內容

將未初始化部分的賦初值:數值型short,int,long等為0,bool為FALSE,指標為NULL等

將main函式的引數,argc,argv等傳遞給main函式

 

35,如何判斷一段程式是由C 編譯程式還是由C++ 編譯程式編譯的?

答:C++ 編譯時定義了 __cplusplus

C 編譯時定義了 _STDC_

 

36,分別寫出BOOL,int, float, 指標型別的變數 a 與 “零值”的比較語句

答:

  BOOL:if(!a)  or   if(a)

    int : if( 0== a)

  float : const EXPRESSION EXP =0.000001;

  if(a < EXP&& a > -EXP)

 pointer:    if(a != NULL) or  if(a == NULL)

 

37,已知String類定義如下,嘗試寫出類的成員函式實現

class{

public:

String(const char*str =NULL);            //通用建構函式

String(const String&another);            //拷貝建構函式

~String();                                //解構函式

String& operator = = (constString& rhs);  //賦值函式

private:

char*m_data;                             //用於儲存字串

};

 

答:

String::String(const char*str)

{

    if(str == NULL)

    {

           m_data = new char[1];

           m_data[0] = '\0';

    }

    else

    {

           m_data = new char[strlen(str)+1];

           strcpy(m_data,str);        

    }

}   

String::String(const String&another)

{

    m_data = new char[strlen(another.m_data)+1];

    strcpy(m_data, another.m_data); 

}                         

String::String& operator = = (constString& rhs)

{

    if(this ==&rhs)

    return&this;

   delete[]  m_data;

    m_data = newchar(strlen(rhs.m_data)+1);  //刪除原來的資料,新開一塊記憶體

   strcpy(m_data,rhs.m_data); 

    return *this;

}

~String()

{

   delete[]  m_data;

}

 

38,論述C++類繼承的優缺點

答:一,優點:類繼承是在編譯時刻靜態定義的,可以直接使用,類繼承可以較方便的改變從父類繼承的實現

二,缺點:1,因為繼承在編譯時刻就定義了,所以無法在執行時刻改變從父類繼承的實現

2,父類通常至少定義了子類的部分行為,父類的任何改變都可能影響到子類的行為

3,如果繼承下來的實現不適合解決新的問題,則父類必須重寫或被其他更適合的類替換

這種依賴關係先限制了靈活性並最終限制了複用性

 

39,運算子過載的三種方式和不允許過載的5個運算子

答:運算子過載意義是為了對使用者自定義資料的操作和內定義的資料型別的操作形式一致

(1)普通函式,友元函式,類成員函式

(2).*(成員指標訪問運算子)   

     ::(域運算子) 

     sizeof長度運算子   

   ?:條件運算子   

    .(成員訪問運算子)

40,友元關係有什麼特性?

答:單向的,非傳遞的, 不能繼承的.

 

41,理解解構函式和虛擬函式的用法和作用?

答:解構函式也是特殊的類成員函式,它沒有返回型別,沒有引數,不能隨意呼叫,也沒有過載。

在類物件生命期結束的時候,由系統自動呼叫釋放在建構函式中分配的資源。

解構函式一般在物件撤消前做收尾工作,比如回收記憶體等工作。

 

虛擬函式的功能是使子類可以用同名的函式對父類函式進行過載,並且在呼叫時自動呼叫子類過載函

數,在基類中通過使用關鍵字virtual來宣告一個函式為虛擬函式,該函式的功能可能在將來的派生類

中定義或者在基類的基礎上擴充套件,系統只能在執行階段才能動態的決定呼叫哪一個函式,動態的多型性,

如果是純虛擬函式,則純粹是為了在子類過載時有個統一的命名而已。

 

42,關鍵字volatile有什麼含意?並給出三個不同的例子

答:一個定義為volatile的變數是說這變數可能會被意想不到地改變,編譯器就不會去假設這個變數的值了。

精確地說就是,優化器在用到這個變數時必須每次都小心地重新讀取這個變數的值

而不是使用儲存在暫存器裡的備份。下面是volatile變數的幾個例子:

1) 並行裝置的硬體暫存器(如:狀態暫存器)

2) 一箇中斷服務子程式中會訪問到的非自動變數(Non-automaticvariables)

3) 多執行緒應用中被幾個任務共享的變數

 

深究:一個引數既可以是const還可以是volatile,一個例子是隻讀的狀態暫存器,

它是volatile因為它可能被意想不到地改變,是const因為程式不應該試圖去修改它。

一個指標可以是volatile,一個例子是當一箇中服務子程式修該一個指向一個buffer的指標時。

 

43,動態連線庫的兩種方式?

答:呼叫一個DLL中的函式有兩種方法:

1.載入時動態連結(load-time dynamiclinking),模組非常明確呼叫某個匯出函式

,使得他們就像本地函式一樣。這需要連結時連結那些函式所在DLL的匯入庫,匯入庫向

系統提供了載入DLL時所需的資訊及DLL函式定位。

2.執行時動態連結(run-time dynamiclinking),執行時可以通過LoadLibrary或Loa

dLibraryEx函式載入DLL。DLL載入後,模組可以通過呼叫GetProcAddress獲取DLL函式的

出口地址,然後就可以通過返回的函式指標呼叫DLL函式了。如此即可避免匯入庫檔案了。

 

44,C和C++有什麼不同?

答:從機制上:c是程式導向的。c++是物件導向的,提供了類。c++編寫物件導向的程式比c容易。

從適用的方向:c適合要求程式碼體積小的,效率高的場合,如嵌入式;c++適合更上層的,複雜的; 

llinux核心大部分是c寫的,因為它是系統軟體,效率要求極高。

C語言是結構化程式語言,C++是物件導向程式語言

C++側重於物件而不是過程,側重於類的設計而不是邏輯的設計。

 

45,C++編譯器自動為類產生的四個確預設函式是什麼?

答:預設建構函式,拷貝建構函式,解構函式,賦值函式

 

46,簡單描述Windows記憶體管理的方法。

答:程式執行時需要從記憶體中讀出這段程式的程式碼,程式碼的位置必須在實體記憶體中才能被執行,

由於現在的作業系統中有非常多的程式執行著,記憶體中不能夠完全放下,所以引出了虛擬記憶體的概念。

把哪些不常用的程式片斷就放入虛擬記憶體,當需要用到它的時候在load入主存(實體記憶體)中。

記憶體管理也計算程式片段在主存中的物理位置,以便CPU排程。

 

記憶體管理有塊式管理,頁式管理,段式和段頁式管理。現在常用段頁式管理

塊式管理:把主存分為一大塊、一大塊的,當所需的程式片斷不在主存時就分配一塊主存空間,

把程 序片斷load入主存,就算所需的程式片度只有幾個位元組也只能把這一塊分配給它。

這樣會造成很大的浪費,平均浪費了50%的記憶體空間,但時易於管理。

頁式管理:把主存分為一頁一頁的,每一頁的空間要比一塊一塊的空間小很多,顯然這種方法

的空間利用率要比塊式管理高很多

段式管理:把主存分為一段一段的,每一段的空間又要比一頁一頁的空間小很多,

這種方法在空間利用率上又比頁式管理高很多,但是也有另外一個缺點。一個程式片斷可能會被分為幾十段,

這樣很多時間就會被浪費在計算每一段的實體地址上,計算機最耗時間的大家都知道是I/O吧

段頁式管理:結合了段式管理和頁式管理的優點。把主存分為若干頁,每一頁又分為若干段,好處就很明顯

 

47,Linux有核心級執行緒嗎?

答:執行緒通常被定義為一個程式中程式碼的不同執行路線。從實現方式上劃分,執行緒有兩種型別:

“使用者級執行緒”和“核心級執行緒”。使用者執行緒指不需要核心支援而在使用者程式中實現的執行緒,其不依賴於作業系統核心,

應用程式利用執行緒庫提供建立、同步、排程,和管理執行緒的函式來控制使用者執行緒。核心級執行緒需要核心的參與,

由核心完成執行緒的排程。其依賴於作業系統核心,由核心的內部需求進行建立和撤銷。

 

使用者執行緒不需要額外的核心開支,並且使用者態執行緒的實現方式可以被定製或修改以適應特殊應用的要求,

但是當一個執行緒因 I/O 而處於等待狀態時,整個程式就會被排程程式切換為等待狀態,其他執行緒得不

到執行的機會;而核心執行緒則沒有這個個限制,有利於發揮多處理器的併發優勢,但卻佔用了更多的系統開支。

 

48,main 主函式執行完畢後,是否可能會再執行一段程式碼,給出說明?

答:可以,可以用_onexit 註冊一個函式,它會在main 之後執行int fn1(void),fn2(void), fn3(void), fn4 (void)

 

49, i++ 相比 ++i 哪個更高效?為什麼?

答:

(1)++i 比 i++效率高。

(2)i++要多呼叫一次類的構造和析夠函式

 

50,windows平臺下網路程式設計有哪幾種網路程式設計模型?

答:有阻塞,select,基於窗體的事件模型,事件模型,重疊模型,完成埠模型。

除了阻塞模型外,其他都是非阻塞模型,其中效率最高的是完成埠模型,尤其在windows下伺服器最合適了。

做客戶端一般用事件模型了,select在window和類unix都可以使用。

 

51,什麼是函式模板

答:函式模板技術定義了引數化的非成員函式,使得程式能夠使用不同的引數型別呼叫相同的函式,而至於是何種型別,

則是由編譯器確定從模板中生成相應型別的程式碼。編譯器確定了模板函式的實際型別引數,稱之為模板的例項化。

template<classT>定義模板標識

T Add(T a, Tb)        //函式模板

{

   T result = a +b;

   return a +b;   //將兩個引數使用“+”運算子進行運算,這兩個引數並不知道是何種型別

}

該函式與一般函式的不同之處在於沒有明確指出使用何種資料型別和返回值又是哪一種型別

如何在程式中呼叫該函式

#include<iostream> //包含標準輸入輸出標頭檔案

#include<string>  //C++中的字串處理標頭檔案

using namespace std;

template<class T>

T Add(T a, Tb)        //函式模板

{

   T result = a +b;

   return a +b;   //將兩個引數使用“+”運算子進行運算,這兩個引數並不知道是何種型別

}

int main(int argc, char* argv[])

{

   cout<<"2+3="<<Add(2,3)<<endl; //輸出整形的+運算結果

  cout<<"sdf+123="<<Add(string("sdf"),string("123"))<<endl;

  return 0;

}

 

52,什麼是類别範本

答:描述了能夠管理其他資料型別的通用資料型別,通常用於建立包含其他型別的容器類

對於這些容器,無論是哪一種資料型別,其操作方式是一樣的,但是針對具體的型別又是專用的,

template<class T>

class TemplateSample

{

   private:

   T&emtity;           //使用引數型別成員

    public:

  void F(T&arg);       //使用引數型別定義成員函式

}

該示例定義了一個類别範本,類别範本中的模板形參T需要使用者在使用的時候進行定義

TemplateSample<int>demo;  //針對該模板使用int型別

demo.F(123);  //呼叫類别範本中的成員函式

template<class T1, class T2, intnum>     //定義多個模板引數,且其中一個直接使用int型別

該示例的前兩個引數可以是任何型別,但是第三個引數一定是int型別

TemplateSample<int , char,12>demo;     //使用非類型別的模板

#include<iostream>

template<class T, class T2, intnum>

class CSampleTemplate

{

   private:

   T t1;

   T2 t2;

  public:

  CSampleTemplate(T arg1, T2arg2)        //建構函式中使用模板引數

  {

     t1 = arg1 + num;

     t2 = arg2 + num;

  }

  void Write()

 {

 std::cout<<"t1:"<<t1<<"t2"<<t2<<endl;

 }

 

CSampleTemplate ()

{}

}

int main(int argc, char* argv[])

{

   CSampleTemplate<int, int,3>temp(1,2);

   temp.Write();

    return0;

}

53,什麼是容器

答:STL是一個標準的C++庫,容器只是其中一個重要的組成部分,有順序容器和關聯容器

1)順序容器,指的是一組具有相同型別T的物件,以嚴格的線性形式組織在一起

包括vector<T>,  deque<T>,   list<T>

2)關聯容器,提供一個key實現對物件的隨機訪問,其特點是key是有序的元素是按照預定義的鍵順序插入的i

set<Key>,集合,  支援唯一鍵值,提供對鍵本身的快速檢索,例如set<long>:{學號}

set<Key>,多重集合,支援可重複鍵值,提供對鍵本身的快速檢索,例如multiset<string>:{姓名}

map<Key,T>,支援唯一Key型別的鍵值,提供對另一個基於鍵的型別的快速檢索,例如map<long,string>:{學號,姓名}

multimap<Key, T>,多重對映,支援可重複Key值,提供對另外一個基於鍵型別T的快速檢索,例如map<string,string>:{姓名,地址}

 

54,介紹關聯容器

答:#include<vector>    //包含標頭檔案

usingstd::vector          //使用命名限定

vector<int>vInts;

建立一個Widget型別為空的vector物件

vector<Widget>vWidgets;                   //空的vector物件

vector<Widget>vWidgets(500);            //包含500個物件的vector

vector<Widget>vWidgets(500,Widget(0));      //包含500個物件的vector,並且初始化為0

vector<Widget>vWidgetFromAnother(vWeigets);       //利用現有的vector建立一個拷貝

向vector中新增一個資料,預設方式是push_back,表示將資料新增到vector的尾部,並且按照需要來分配記憶體,如

for(int i = 0 ; i < 10; i ++)

v.push_back(Widget(i));

 

如果想獲取vector v的大小,但不知道它是否為空,或者已經包含了資料,可用如下程式碼實現

int nSize = v.empty()?-1:static_cast<int>(v.size());

 

訪問vector中的資料有兩種方法vector::at()  和  vector::operator[],其中vector::at()進行了邊界檢查

 

vector<int>v;                 //定義了vector物件

v.reserve(10);                    //分配空間但是沒有初始化

for(int i = 0 ; i < 7; i++)

{  v. push_back(i);}

int iVal1 =v[7];                   //不進行邊界檢查

int iVal2 =v.at(7);          //進行邊界檢查

 

deque容器是一個雙端佇列,存放的元素不是以連續的方式存放的

list容器是一種連結串列的實現,儲存的元素是通過使用雙向連結串列實現的

 

55,什麼是迭代器的範圍

答:迭代器是STL提供的對一個容器中的物件的訪問方法,定義了容器中物件的範圍,迭代器就如同一個指標。

vector<int>v;    //宣告vector變數

v.push_back(2);   //插入資料

v.push_back(1);

vector<int>::iteratorfirst =v.begin();     //獲取vector<int>的一個元素的迭代器

while1(first !=v.end())          //使用迭代器遍歷vector,一直到最後一個元素

{

   int i =*first;    //獲取迭代器指向的元素的值

first++;

}

 

55,C++如何實現泛型程式設計

答:泛型程式設計實現了於特定型別的操作演算法,由編譯器根據泛型的呼叫所傳遞的類及模板生成該型別專用的程式碼。

#include<iostream>

#include<string>

using namespaces std;

template<class T>

T Add(T a, T b)

{

    Tresult;          // 使用引數化的型別定義變數

   result = a +b;

  return result;

}

int main(int argc, char* argv[])

{

 cout<<"2+3="<<Add(2,3)<<endl;  

 cout<<"sdf+123="<<Add(string("sdf"),string("123"));

   return 0;

}

 

56,引數傳遞的方式和多型引數傳遞的實現

答:引數傳遞有傳值,傳指標,或者是引用等三種,下面做詳細的介紹

1)傳值方式適合一般的數值傳遞,並且不改變原資料,但是要消耗記憶體空間

2)傳遞指標方式適合傳遞陣列和指標,由於傳遞的是地址,所以直接操作會改變原資料

3)引用方式和指標方式比較類似,是相對比較新的一種方式,一般情況下能用傳地址的就能用引用

而且使用引用更方便一些

實現多型主要是採用指標和引用,傳值方式是複製資料,其型別編譯器就已經決定,而多型是型別要等到執行器才能決定,

所以不適用傳值方式來實現多型引數傳遞

 

57,C++和C定義結構體區別是什麼?

答:C++中的結構和類其實具備幾乎一樣的功能,結構體內也是可以宣告函式,C++的結構體和類預設具有不一樣的訪問屬性

 

 

                          C++演算法題,挑戰程式設計的快樂!!!

相關文章