C++面試題 比較實用

C++面試題
參考:http://blog.csdn.net/Ghost90/archive/2009/04/22/4099672.aspx
整理:松鼠
時間:2009-5-8

1、const 有什麼用途?(請至少說明兩種)
答:
(1)可以定義 const 常量
(2)const可以修飾函數的參數、返回值,甚至函數的定義體。被const修飾的東西都受到強制保護,可以預防意外的變動,能提高程序的健壯性。

2、在C++ 程序中調用被 C編譯器編譯後的函數,爲什麼要加 extern “C”?
答:C++語言支持函數重載,C語言不支持函數重載。函數被C++編譯後在庫中的名字與C語言的不同。假設某個函數的原型爲: void foo(int x, int y);
該函數被C編譯器編譯後在庫中的名字爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字。
C++提供了C連接交換指定符號extern“C”來解決名字匹配問題。

3、請簡述以下兩個for循環的優缺點(5分)
for (i=0; i<N; i++)
{
if (condition)
DoSomething();
else
DoOtherthing();
} if (condition)
{
for (i=0; i<N; i++)
DoSomething();
}
else
{
for (i=0; i<N; i++)
DoOtherthing();
}
優點:程序簡潔
缺點:多執行了N-1次邏輯判斷,並且打斷了循環“流水線”作業,使得編譯器不能對循環進行優化處理,降低了效率。 優點:循環的效率高
缺點:程序不簡潔

4、有關內存的思考題
void GetMemory(char *p)
{
p = (char *)malloc(100);
}

void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}

請問運行Test函數會有什麼樣的結果?
答:程序崩潰。
因爲GetMemory並不能傳遞動態內存,
Test函數中的 str一直都是 NULL。
strcpy(str, "hello world");將使程序崩潰。 char *GetMemory(void)
{
char p[] = "hello world";
return p;
}

void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}

請問運行Test函數會有什麼樣的結果?
答:可能是亂碼。
因爲GetMemory返回的是指向“棧內存”的指針,該指針的地址不是 NULL,但其原現的內容已經被清除,新內容不可知。
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}

void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
請問運行Test函數會有什麼樣的結果?
答:
(1)能夠輸出hello
(2)內存泄漏 void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
請問運行Test函數會有什麼樣的結果?
答:篡改動態內存區的內容,後果難以預料,非常危險。
因爲free(str);之後,str成爲野指針,
if(str != NULL)語句不起作用。

5、編寫strcpy函數(10分)
已知strcpy函數的原型是
char *strcpy(char *strDest, const char *strSrc);
其中strDest是目的字符串,strSrc是源字符串。
(1)不調用C++/C的字符串庫函數,請編寫函數 strcpy
char *strcpy(char* strDest, const char* strSrc)
{
assert((strDest!=NULL) && (strSrc !=NULL));//2分
char *address = strDest;//2分
while( (*strDest++ = * strSrc++) != ‘/0’ )//2分
NULL;
return address;// 2分
}
5.1 strcpy能把strSrc的內容複製到strDest,爲什麼還要char * 類型的返回值?
答:爲了實現鏈式表達式。 // 2分
例如int length = strlen( strcpy( strDest, "hello world") );

6、編寫類String的構造函數、析構函數和賦值函數(25分)
已知類String的原型爲:
class String
{
public:
String(const char *str = NULL);//普通構造函數
String(const String &other);//拷貝構造函數
~ String(void);// 析構函數
String & operate =(const String &other);// 賦值函數
private:
char* m_data;// 用於保存字符串
};
請編寫String的上述4個函數。
標準答案:
// String的析構函數
String::~String(void) // 3分
{
delete [] m_data;
// 由於m_data是內部數據類型,也可以寫成delete m_data;
}

// String的普通構造函數
String::String(const char *str) // 6分
{
if(str==NULL)
{
m_data = new char[1]; // 若能加NULL 判斷則更好
*m_data = ‘/0’;
}
else
{
int length = strlen(str);
m_data = new char[length+1]; // 若能加NULL 判斷則更好
strcpy(m_data, str);
}
}
// 拷貝構造函數
String::String(const String &other) // 3分
{
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加NULL 判斷則更好
strcpy(m_data, other.m_data);
}

// 賦值函數
String & String::operate =(const String &other) // 13分
{
// (1) 檢查自賦值 // 4分
if(this == &other)
return *this;

// (2) 釋放原有的內存資源 // 3分
delete [] m_data;

// ()分配新的內存資源,並複製內容// 3分
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加NULL 判斷則更好
strcpy(m_data, other.m_data);

// ()返回本對象的引用 // 3分
return *this;
}

7、實現雙向鏈表刪除一個節點P,在節點P後插入一個節點,寫出這兩個函數。
void DeleteNode(DuNode *p)
{
p->prior->next=p->next;
p->next->prior=p->prior;
}
void InsertNode(DuNode *p, DuNode *s)//Node "s" is inserted after "p"
{
s->next=p->next;
p->next->prior=s;
p->next=s;
s->prior=p;
}

8、Windows程序的入口是哪裏?寫出Windows消息機制的流程。
WINDOWS入口是WinMain函數
消息機制的流程:
系統中發生了某個事件
Windows把這個事件翻譯爲消息,然後把它放到消息隊列中
1. 應用程序從消息隊列中接收到這個消息,把它存放在TMsg記錄中
2. 應用程序把消息傳遞給一個適當的窗口的窗口過程
3. 窗口過程響應這個消息並進行處理


9.寫一個函數,將其中的/t都轉換成4個空格。
#include<iostream>
using namespace std;

char* Convert_t(char *des,char *src)
{
char *temp;
des=new char[100];
temp=des;
while(*src!='/0')
{
if(*src=='/t')
{
src++;
*des++=' ';
*des++=' ';
*des++=' ';
*des++=' ';
continue;
}
*des++=*src++;
}
*des='/0';
des=temp;
return des;
}

int main()
{
char *t="asdf/tasd/tasasddas//tdfasdf",*d;
cout<<t<<endl;
cout<<Convert_t(d,t);
getchar();
}
10.如何定義和實現一個類的成員函數爲回調函數?
如果類的成員函數是一個callback函數, 必須宣告它爲"static",才能把C++ 編譯器加諸於函數的一個隱藏參數this去掉。

11.C++裏面是不是所有的動作都是main()引起的?如果不是,請舉例。
不是的,C++裏面有些動作不是引起的,比如,全局對象的實例化、全局變量的動態空間申請,等等
下面是一個例子:
#include<iostream>
using namespace std;
char *des=new char[100];
//全局變量的動態空間申請在程序運行之後,main運行之前完成。所以不是所有的動作都是main引起的。

int main()
{
char *des="abc";
cout<<des<<endl;
getchar();
}
12.C++裏面如何聲明const void f(void)函數爲C程序中的庫函數?
extern "C" const void f(void);
這樣聲明之後,相當於告訴C, 函數const void f(void)是在C++語言的文件中聲明或者實現的,c程序可以使用這個C++中的函數了,從而實現C++和c的混合編程。

13、編寫一個函數,作用是把一個char組成的字符串循環右移n個。比如原來是“abcdefghi”如果n=2,移位後應該是“hiabcdefgh”
正確解答1:
void LoopMove(char* pStr, int steps)
{
int n = strlen( pStr ) - steps;
char tmp[MAX_LEN];
strcpy ( tmp, pStr + n );
strcpy ( tmp + steps, pStr);
*( tmp + strlen ( pStr ) ) = '/0';
strcpy( pStr, tmp );
}
正確解答2:
void LoopMove(char* pStr,int steps )
{
int n = strlen( pStr ) - steps;
char tmp[MAX_LEN];
memcpy( tmp, pStr + n, steps );
memcpy(pStr + steps, pStr, n );
memcpy(pStr, tmp, steps );
}

14、寫出輸出結果
void fun(char s[10])
{
char a[10];
cout<<"a:"<<sizeof(a)<<endl;
cout<<"s:"<<sizeof(s)<<endl;
}
輸出: a:10
s:4

15、內存的分配方式的分配方式有幾種?
答:
1. 從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量。
2. 在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
3. 從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc或new申請任意多少的內存,程序員自己負責在何時用free或delete釋放內存。動態內存的生存期由我們決定,使用非常靈活,但問題也最多。

16.是不是一個父類寫了一個virtual 函數,如果子類覆蓋它的函數不加virtual ,也能實現多態?
答:virtual修飾符會被隱形繼承的。private 也被集成,只事派生類沒有訪問權限而已。virtual可加可不加。子類的空間裏有父類的所有變量(static除外)。同一個函數只存在一個實體(inline除外)。子類覆蓋它的函數不加virtual ,也能實現多態。在子類的空間裏,有父類的私有變量。私有變量不能直接訪問。

17.進程間通信的方式有?
進程間通信的方式有 共享內存, 管道 ,Socket ,消息隊列 , DDE等

18.C++中什麼數據分配在棧或堆中,New分配數據是在近堆還是遠堆中?
答:棧: 存放局部變量,函數調用參數,函數返回值,函數返回地址。由系統管理
堆: 程序運行時動態申請,new 和 malloc申請的內存就在堆上
(Google搜):DOS下程序是獨佔方式,堆分爲近堆和遠堆,近堆和棧是在數據段開闢的同一塊內存地址,棧從下往上增長,堆從上向下分配,中間沒有規定分界線,所以程序控制不當,如深層次的遞歸,大量的動態地址分配很容易造成堆棧衝突,即堆棧地址重疊,從而造成死機和程序運行異常。堆和棧連在一起說的原因就是如此。至於遠堆則是指在數據段和代碼段以外計算機所有沒有使用的剩餘基本內存。Windows採用的是虛擬地址,內存分配方式就不一樣了
補充:DOS下堆棧的分配是由程序而不是操作系統自己控制的,具體分配大小、方式隨編譯系統、程序模式不同而異。C語言的堆棧分配代碼在啓動代碼中.

19.全局變量和局部變量在內存中是否有區別?如果有,是什麼區別?
全局變量儲存在靜態數據庫,局部變量在堆棧。

20.TCP/IP 建立連接的過程?(3-way shake)
答:在TCP/IP協議中,TCP協議提供可靠的連接服務,採用三次握手建立一個連接。
第一次握手:建立連接時,客戶端發送syn包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認;
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。

21.winsock建立連接的主要實現步驟?
答:服務器端:socker()建立套接字,綁定(bind)並監聽(listen),用accept()等待客戶端連接。
客戶端:socker()建立套接字,連接(connect)服務器,連接上後使用send()和recv(),在套接字上寫讀數據,直至數據交換完畢,closesocket()關閉套接字。
服務器端:accept()發現有客戶端連接,建立一個新的套接字,自身重新開始等待連接。該新產生的套接字使用send()和recv()寫讀數據,直至數據交換完畢,closesocket()關閉套接字。

22.static有什麼用途?(請至少說明兩種)
1) 在函數體,一個被聲明爲靜態的變量在這一函數被調用過程中維持其值不變。
2) 在模塊內(但在函數體外),一個被聲明爲靜態的變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問。它是一個本地的全局變量。
3) 在模塊內,一個被聲明爲靜態的函數只可被這一模塊內的其它函數調用。那就是,這個函數被限制在聲明它的模塊的本地範圍內使用

23.引用與指針有什麼區別?
1) 引用必須被初始化,指針不必。
2) 引用初始化以後不能被改變,指針可以改變所指的對象。
3) 不存在指向空值的引用,但是存在指向空值的指針。

24、請寫出下列代碼的輸出內容
#include<stdio.h>

main()
{
int a,b,c,d;

a=10;
b=a++;
c=++a;
d=10*a++;
printf("b,c,d:%d,%d,%d",b,c,d);
return 0;
}
答:10,12,120

main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);

printf("%d,%d",*(a+1),*(ptr-1));
}
輸出:2,5
*(a+1)就是a[1],*(ptr-1)就是a[4],執行結果是2,5
&a+1不是首地址+1,系統會認爲加一個a數組的偏移,是偏移了一個數組的大小(本例是5個int)
int *ptr=(int *)(&a+1);
則ptr實際是&(a[5]),也就是a+5
原因如下:
&a是數組指針,其類型爲 int (*)[5];
而指針加1要根據指針類型加上一定的值,
不同類型的指針+1之後增加的大小不同
a是長度爲5的int數組指針,所以要加 5*sizeof(int)
所以ptr實際是a[5]
但是prt與(&a+1)類型是不一樣的(這點很重要)
所以prt-1只會減去sizeof(int*)
a,&a的地址是一樣的,但意思不一樣,a是數組首地址,也就是a[0]的地址,&a是對象(數組)首地址,a+1是數組下一元素的地址,即a[1],&a+1是下一個對象的地址,即a[5]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章