1. 程式人生 > >C++ 經典面試題

C++ 經典面試題

sel 備份 函數地址 開發 對象 const對象 代碼 結構 函數類型

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元素的數組

int *(*ptr)[4]; //指針的類型是 int *(*)[4] 他是一個指針,指向一個數組,數組裏放著4個指

【】的優先級大於指針*

針類型的元素

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

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

指針數組:就是一個由指針組成的數組,那個數組的各個元素都是指針,指向某個內存地址。 char *p[10];//p是一個指針數組
數組指針:數組名本身就是一個指針,指向數組的首地址。註意這是一個常數。 char (*p)[10] //p是一個數組指針
函數指針:本身是一個指針,指向一個函數入口地址,通過該指針可調用其指向的函數,使用函數指針可實現回調函數。

int func(int x); /* 聲明一個函數 */

int (*f) (int x); /* 聲明一個函數指針 */

f=func; /* 將func函數的首地址賦給指針f */

或者使用下面的方法將函數地址賦給函數指針:

f = &func;

賦值時函數func不帶括號,也不帶參數,由於func代表函數的首地址,因此經過賦值以後,指針f就指向函數func(x)的代碼的首地址。

指針函數:本身是一個函數,其返回值是一個指針。 void * fun(void);// fun是一個指針函數

常量指針;他是一個指針,指向一個常量。 const int *p; 或 int const *p;
註意 指針指向的對象不能通過這個指針來修改,可是仍然可以通過原來的聲明修改,也就是說常量指針可以被賦值為變量的地 址, 之所 以叫做常量指針,是限制了通過這個指針修改變量的值。
int a = 5;

const int b = 8;

const int *c = &a; // 這是合法的,非法的是對c的使用

*c = 6; // 非法,但可以這樣修改c指向的對象的值:a = 6;

const int *d = &b; // b是常量,d可以指向b,d被賦值為b的地址是合法的

細心的朋友在使用字符串處理函數的時候,應該會註意到這些函數的聲明。它們的參數一般聲明為常量指針。例如,字符串比較函數 的聲明是這樣的: str1和str2的內容顯然是可以更改的,函數的參數聲明用了常量指針的形式,就保證了在函數內部,那 個常量不 被更改。也就是說,str1str2的內容更改的操作在函數內部是不被允許的。這就是作用所在 (就目前的應用來看,我覺得 設置常量指針就是為函數參數聲明準 備的,不然還真不知道用在什麽地方呢,呵呵!)

int strcmp(const char *str1, const char *str2);
雖然常量指針指向的對象不能變化,可是因為常量指針是一個變量,因此,常量指針可以不被賦初始值,且可以被重新賦值。
const int a = 12; const int b = 15; const int *c = &a; const int *d;
d = &a; // 這樣當然是可以的 c = &b; // 雖然c已經被賦予初始值,可是仍然可以指向另一個變量

指針常量 int a; int *const b = &a; //const放在指針聲明操作符的右側 指針是常量 b的值是一個常量 b的值是a的地址,但 是a地址裏面的人可以換

指針的賦值:

把一個變量的地址 賦予指向 相同數據類型的指針變量( 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]指向了此空間首地址處

指針數組初始化賦值:
void *malloc(int size);
說明:malloc 向系統申請分配指定size個字節的內存空間。返回類型是 void* 類型。void* 表示未確定類型的指針。C,C++規定,void* 類型可以強制轉換為任何其它類型的指針。

一維指針開辟空間: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);

結構體指針數組,例如typedef struct{ char x; 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));

}

指針數組賦值

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<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); 首先是一個指針,指向一個函數,這類函數具有int*類型的形參,返回值類型是 int

(*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是一個數組,裏面是一個個指針,指向函數。

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

(4)int (*(*func)[5])(int *p); func是一個指針,指向五個元素的數組,什麽元素呢?指針元素,指向函數的指針元素。

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

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

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

註意:

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

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

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

例如:int (*(*func)[5][6])[7][8]; func 是一個指針,指向5X6的數組,數組裏面裝的是指針,指向7X8int元素的數組。

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

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

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

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)指針pfree或者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<SA&>(ra); rb.k = 10; //可以修改

(2)static_cast

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

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

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

int a; 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: int m_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用來對一段內存空間全部設置為某個字符,一般用在對定義的字符串進行初始化為‘ ‘或‘‘;

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

char temp[30]; memset(temp,‘\0‘,sizeof(temp));

char temp[30]只是分配了一定的內存空間給該字符數組,但並未初始化該內存空間,即數組。所以,需要使用memset()來進行初始化。

memcpy用來做內存拷貝,你可以拿它拷貝任何數據類型的對象,可以指定拷貝的數據長度;

strcpy就只能拷貝字符串了,它遇到‘\0‘就結束拷貝;例:char a[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, int y);該函數被C編譯器編譯後在庫中的名字為_foo,

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

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)class MyStruct{ double ddal; char dda; int type;}

在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 = = (const String& 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 = = (const String& rhs)

{

if(this == &rhs)

return &this;

delete[] m_data;

m_data = new char(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-automatic variables)

3) 多線程應用中被幾個任務共享的變量

深究:一個參數既可以是const還可以是volatile,一個例子是只讀的狀態寄存器,

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

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

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

答:調用一個DLL中的函數有兩種方法:

1.載入時動態鏈接(load-time dynamic linking),模塊非常明確調用某個導出函數

,使得他們就像本地函數一樣。這需要鏈接時鏈接那些函數所在DLL的導入庫,導入庫向

系統提供了載入DLL時所需的信息及DLL函數定位。

2.運行時動態鏈接(run-time dynamic linking),運行時可以通過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<class T>定義模板標識

T Add(T a, T b) //函數模板

{

T result = a + b;

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

}

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

如何在程序中調用該函數

#include<iostream> //包含標準輸入輸出頭文件

#include<string> //C++中的字符串處理頭文件

using namespace std;

template<class T>

T Add(T a, T b) //函數模板

{

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, int num> //定義多個模板參數,且其中一個直接使用int類型

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

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

#include<iostream>

template<class T, class T2, int num>

class CSampleTemplate

{

private:

T t1;

T2 t2;

public:

CSampleTemplate(T arg1, T2 arg2) //構造函數中使用模板參數

{

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();

return 0;

}

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> //包含頭文件

using std::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>::iterator first = 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)

{

T result; // 使用參數化的類型定義變量

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++ 經典面試題