天天看點

c/c++面試題2

1.已知strcpy函數的原型是:

        char * strcpy(char * strDest,const char * strSrc);

    1.不調用庫函數,實作strcpy函數。

    2.解釋為什麼要傳回char *。

解說:

    1.strcpy的實作代碼

        char * strcpy(char * strDest,const char * strSrc)

        {

                if ((strDest==NULL)||(strSrc==NULL))    //[1]

                        throw "Invalid argument(s)";             //[2]

                char * strDestCopy=strDest;                    //[3]

                while ((*strDest++=*strSrc++)!='/0');       //[4]

                return strDestCopy;

        }

    錯誤的做法:

    [1]

    (A)不檢查指針的有效性,說明答題者不注重代碼的健壯性。

    (B)檢查指針的有效性時使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),說明答題者 對C語言中類型的隐式轉換沒有深刻認識。在本例中char *轉換為bool即是類型隐式轉換,這種功能雖然靈活,但更多的是導緻出錯機率增大和維護成本升高。是以C++專門增加了bool、true、false 三個關鍵字以提供更安全的條件表達式。

    (C)檢查指針的有效性時使用((strDest==0)||(strSrc==0)),說明答題者不知道使用常量的好處。直接使用字面常量(如本例中的 0)會減少程式的可維護性。0雖然簡單,但程式中可能出現很多處對指針的檢查,萬一出現筆誤,編譯器不能發現,生成的程式内含邏輯錯誤,很難排除。而使用 NULL代替0,如果出現拼寫錯誤,編譯器就會檢查出來。

    [2]

    (A)return new string("Invalid argument(s)");,說明答題者根本不知道傳回值的用途,并且他對記憶體洩漏也沒有警惕心。從函數中傳回函數體内配置設定的記憶體是十分危險的做法,他 把釋放記憶體的義務抛給不知情的調用者,絕大多數情況下,調用者不會釋放記憶體,這導緻記憶體洩漏。

    (B)return 0;,說明答題者沒有掌握異常機制。調用者有可能忘記檢查傳回值,調用者還可能無法檢查傳回值(見後面的鍊式表達式)。妄想讓傳回值肩負傳回正确值和異常 值的雙重功能,其結果往往是兩種功能都失效。應該以抛出異常來代替傳回值,這樣可以減輕調用者的負擔、使錯誤不會被忽略、增強程式的可維護性。

    [3]

    (A)忘記儲存原始的strDest值,說明答題者邏輯思維不嚴密。

    [4]

    (A)循環寫成while (*strDest++=*strSrc++);,同[1](B)。

    (B)循環寫成while (*strSrc!='/0') *strDest++=*strSrc++;,說明答題者對邊界條件的檢查不力。循環體結束後,strDest字元串的末尾沒有正确地加上'/0'。

    2.傳回strDest的原始值使函數能夠支援鍊式表達式,增加了函數的“附加值”。同樣功能的函數,如果能合理地提高的可用性,自然就更加理想。

    鍊式表達式的形式如:

        int iLength=strlen(strcpy(strA,strB));

    又如:

        char * strA=strcpy(new char[10],strB);

    傳回strSrc的原始值是錯誤的。其一,源字元串肯定是已知的,傳回它沒有意義。其二,不能支援形如第二例的表達式。其三,為了保護源字元串,形參用const限定strSrc所指的内容,把const char *作為char *傳回,類型不符,編譯報錯。

    參考文獻:林銳,《高品質程式設計指南--C/C++語言》,北京,電子工業出版社,2002.6。

2.已知類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(const char *str)

{

if ( str == NULL ) //strlen在參數為NULL時會抛異常才會有這步判斷

{

m_data = new char[1] ;

m_data[0] = ' ' ;

}

else

{

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

strcpy(m_data,str);

}

}

String::String(const String &other)

{

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

strcpy(m_data,other.m_data);

}

String & String::operator =(const String &other)

{

if ( this == &other)

return *this ;

delete []m_data;

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

strcpy(m_data,other.m_data);

return *this ;

}

String::~ String(void)

{

delete []m_data ;

}

3.簡答

3.1 頭檔案中的ifndef/define/endif 幹什麼用?

答:防止該頭檔案被重複引用。

3.2#i nclude <filename.h> 和#i nclude “filename.h” 有什麼差別?

答:對于#i nclude <filename.h> ,編譯器從标準庫路徑開始搜尋filename.h

對于#i nclude “filename.h”,編譯器從使用者的工作路徑開始搜尋filename.h

3.3 在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.4 一個類有基類、内部有一個其他類的成員對象,構造函數的執行順序是怎樣的。(Autodesk)

答:先執行基類的(如果基類當中有虛基類,要先執行虛基類的,其他基類則按照聲明派生類時的順序依次執行),再執行成員對象的,最後執行自己的。

3.5 請描述一個你熟悉的設計模式(Autodesk)

3.6 在UML 中,聚合(aggregation)群組合(composition)有什麼差別 Autodesk)

答案:聚合關系更強,類似于pages 和book 的關系;組合關系要弱,類似于books和bookshelf 的關系。

3.7C#和C++除了文法上的差别以外,有什麼不同的地方?(Autodesk,Microsoft)

答案:(C#我隻是了解,不是很精通)

(1) c#有垃圾自動回收機制,程式員不用擔心對象的回收。(2)c#嚴禁使用指針,隻能處理對象。如果希望使用指針,則僅可在unsafe 程式塊中能使用指針。(3)c#隻能單繼承。(4)必須通過類名通路靜态成員。不能像C++中那樣,通過對象通路靜态成員。(5)在子類中覆寫父類的虛函 數時必須用關鍵字override,覆寫父類的方法要用關鍵字new

3.8ADO.net 和ADO 的差別?

答案:實際上除了“能夠讓 應用程式處理存儲于DBMS 中的資料“這一基本相似點外,兩者沒有太多共同之處。但是ADO 使用OLE DB 接口并基于微軟的COM 技術,而ADO.NET 擁有自己的ADO.NET 接口并且基于微軟的.NET 體系架構。衆所周知.NET 體系不同于COM 體系,ADO.NET 接口也就完全不同于ADO和OLE DB 接口,這也就是說ADO.NET 和ADO是兩種資料通路方式。ADO.net 提供對XML 的支援。

3.9 New delete 與malloc free 的差別 ( Autodesk)

答案:用malloc 函數不能初始化對象,new 會調用對象的構造函數。Delete 會調用對象的destructor,而free 不會調用對象的destructor.

3.10 #define DOUBLE(x) x+x (Autodesk)

i = 5*DOUBLE(10); i 是多少?正确的聲明是什麼?

答案:i 為60。正确的聲明是#define DOUBLE(x) (x+x)

3.11 有哪幾種情況隻能用intialization list 而不能用assignment? (Autodesk)

答案:當類中含有const、reference 成員變量;基類的構造函數都需要參數;類中含有其他類的成員對象,而該類的構造函數都需要參數。

3.11 C++是不是類型安全的? (Autodesk)

答案:不是。兩個不同類型的指針之間可以強制轉換。C#是類型安全的。

3.12 main 函數執行以前,還會執行什麼代碼? (Autodesk)

答案:全局對象的構造函數會在main 函數之前執行。

3.13 描述記憶體配置設定方式以及它們的差別。 (Autodesk , Microsoft)

答案:1) 從靜态存儲區域配置設定。記憶體在程式編譯的時候就已經配置設定好,這塊記憶體在程式的整個運作期間都存在。例如全局變量,static 變量。

(2) 在棧上建立。在執行函數時,函數内局部變量的存儲單元都可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧記憶體配置設定運算内置于處理器的指令集。

(3) 從堆上配置設定,亦稱動态記憶體配置設定。程式在運作的時候用malloc 或new 申請任意多少的記憶體,程式員自己負責在何時用free 或delete 釋放記憶體。動态記憶體的生存期由我們決定,使用非常靈活,但問題也最多。

3.14 什麼是虛拟存儲器?virtual memory 怎樣映射到physical memory?頁面替換算法有哪些? (Microsoft)

見作業系統 p238 頁。掌握的頁面替換算法NRU,FIFO,第二次機會頁面替換算法,LRU

3.15 有四個同樣的容器,裡面裝滿了粒數相同的藥丸,正常藥丸的品質為m,變質藥丸的品質為m+1,現在已知這四個容器中,有一個裝的全是變質藥丸,用電子秤隻稱一次,找出哪個容器裝的是變質藥丸 (Microsoft)

答案:把四個容器依次編号為1、2、3、4,然後從中分别取出1、2、3、4 粒藥丸,稱這10 粒藥丸的品質,如果品質為10m+1,則說明第一個容器裝的是變質藥丸,如果為10m+2 則說明第二個裝的變質藥丸,依次類推。

3.16 比較一下C++中static_cast 和 dynamic_cast 的差別。 (Autodesk)

dynamic_casts在幫助你浏覽繼承層次上是有限制的。它不能被用于缺乏虛函數的類型上,它被用于安全地沿着類的繼承關系向下進行類型轉換。如你想在沒有繼承關系的類型中進行轉換,你可能想到static_cast

3.17 Struct 和class 的差別 (Autodesk)

答案:struct 中成員變量和成員函數預設通路權限是public,class 是private

3.18 當一個類A 中沒有生命任何成員變量與成員函數,這時sizeof(A)的值是多少,如果不是零,請解釋一下編譯器為什麼沒有讓它為零。(Autodesk)

答案:肯定不是零。我舉個反例,如果是零的話,聲明一個class A[10]對象數組,而每一個對象占用的空間是零,這時就沒辦法區分A[0],A[1]…了

3.19 在8086 彙編下,邏輯位址和實體位址是怎樣轉換的?(Intel)

答案:通用寄存器給出的位址,是段内偏移位址,相應段寄存器位址*10H+通用寄存器内位址,就得到了真正要通路的位址。

3.20 描述一下C++的多态 (microsoft)

答案:C++的多态表現在兩個部分,一個是靜态連編下的函數重載,運算符重載;動态連編下的虛函數、純虛函數(抽象類)

4.寫出BOOL,int,float,指針類型的變量a 與零的比較語句。

答案:

BOOL : if ( !a )

int : if ( a == 0)

float : const EXPRESSION EXP = 0.000001

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

pointer : if ( a != NULL)

5.請說出const 與#define 相比優點

答案:

(1) const 常量有資料類型,而宏常量沒有資料類型。編譯器可以對前者進行類型安全檢查。而對後者隻進行字元替換,沒有類型安全檢查,并且在字元替換可能會産生意料不到的錯誤。

(2) 有些內建化的調試工具可以對const 常量進行調試,但是不能對宏常量進行調試。

6.簡述數組與指針的差別

數組要麼在靜态存儲區被建立(如全局數組),要麼在棧上被建立。指針可以随時指向任意類型的記憶體塊。

(1)修改内容上的差别

char a[] = “hello”;

a[0] = ‘X’;

char *p = “world”; // 注意p 指向常量字元串

p[0] = ‘X’; // 編譯器不能發現該錯誤,運作時錯誤

(2) 用運算符sizeof 可以計算出數組的容量(位元組數)。sizeof(p),p 為指針得到的是一個指針變量的位元組數,而不是p 所指的記憶體容量。C++/C 語言沒有辦法知道指針所指的記憶體容量,除非在申請記憶體時記住它。注意當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指針。

char a[] = "hello world";

char *p = a;

cout<< sizeof(a) << endl; // 12 位元組

cout<< sizeof(p) << endl; // 4 位元組

計算數組和指針的記憶體容量

void Func(char a[100])

{

cout<< sizeof(a) << endl; // 4 位元組而不是100 位元組

}

7.類成員函數的重載、覆寫和隐藏差別

答案:

成員函數被重載的特征:

(1)相同的範圍(在同一個類中);

(2)函數名字相同;

(3)參數不同;

(4)virtual 關鍵字可有可無。

覆寫是指派生類函數覆寫基類函數,特征是:

(1)不同的範圍(分别位于派生類與基類);

(2)函數名字相同;

(3)參數相同;

(4)基類函數必須有virtual 關鍵字。

“隐藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下:

(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數将被隐藏(注意别與重載混淆)。

(2)如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隐藏(注意别與覆寫混淆)

8.There are two int variables: a and b, don’t use “if”, “? :”, “switch”

or other judgement statements, find out the biggest one of the two

numbers.

答案:( ( a + b ) + abs( a – b ) ) / 2

9.如何列印出目前源檔案的檔案名以及源檔案的目前行号?

答案:

cout << __FILE__ ;

cout<<__LINE__ ;

__FILE__和__LINE__是系統預定義宏,這種宏并不是在某個檔案中定義的,而是由編譯器定義的。

10.main 主函數執行完畢後,是否可能會再執行一段代碼,給出說明?

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

void main( void )

{

String str("zhanglin");

_onexit( fn1 );

_onexit( fn2 );

_onexit( fn3 );

_onexit( fn4 );

printf( "This is executed first./n" );

}

int fn1()

{

printf( "next./n" );

return 0;

}

int fn2()

{

printf( "executed " );

return 0;

}

int fn3()

{

printf( "is " );

return 0;

}

int fn4()

{

printf( "This " );

return 0;

}

The _onexit function is passed the address of a function (func) to be called when the program terminates normally. Successive calls to _onexit create a register of functions that are executed in LIFO (last-in-first-out) order. The functions passed to _onexit cannot take parameters.

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

答案:

#ifdef __cplusplus

cout<<"c++";

#else

cout<<"c";

#endif

12.檔案中有一組整數,要求排序後輸出到另一個檔案中

答案:

void Order(vector<int> &data) //起泡排序

{

int count = data.size() ;

int tag = false ;

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

{

for ( int j = 0 ; j < count - i - 1 ; j++)

{

if ( data[j] > data[j+1])

{

tag = true ;

int temp = data[j] ;

data[j] = data[j+1] ;

data[j+1] = temp ;

}

}

if ( !tag )

break ;

}

}

void main( void )

{

vector<int>data;

ifstream in("c://data.txt");

if ( !in)

{

cout<<"file error!";

exit(1);

}

int temp;

while (!in.eof())

{

in>>temp;

data.push_back(temp);

}

in.close();

Order(data);

ofstream out("c://result.txt");

if ( !out)

{

cout<<"file error!";

exit(1);

}

for ( i = 0 ; i < data.size() ; i++)

out<<data[i]<<" ";

out.close();

}

13.排序方法比較 (intel)

排序方法 平均時間 最壞時間 輔助存儲

直接插入排序

起泡排序

快速排序

簡單選擇排序

堆排序

歸并排序

基數排序

14.一個連結清單的結點結構

struct Node

{

int data ;

Node *next ;

};

typedef struct Node Node ;

(1)已知連結清單的頭結點head,寫一個函數把這個連結清單逆序 ( Intel)

Node * ReverseList(Node *head) //連結清單逆序

{

if ( head == NULL || head->next == NULL )

return head;

Node *p1 = head ;

Node *p2 = p1->next ;

Node *p3 = p2->next ;

p1->next = NULL ;

while ( p3 != NULL )

{

p2->next = p1 ;

p1 = p2 ;

p2 = p3 ;

p3 = p3->next ;

}

p2->next = p1 ;

head = p2 ;

return head ;

}

(2)已知兩個連結清單head1 和head2 各自有序,請把它們合并成一個連結清單依然有序。

Node * Merge(Node *head1 , Node *head2)

{

if ( head1 == NULL)

return head2 ;

if ( head2 == NULL)

return head1 ;

Node *head = NULL ;

Node *p1 = NULL;

Node *p2 = NULL;

if ( head1->data < head2->data )

{

head = head1 ;

p1 = head1->next;

p2 = head2 ;

}

else

{

head = head2 ;

p2 = head2->next ;

p1 = head1 ;

}

Node *pcurrent = head ;

while ( p1 != NULL && p2 != NULL)

{

if ( p1->data <= p2->data )

{

pcurrent->next = p1 ;

pcurrent = p1 ;

p1 = p1->next ;

}

else

{

pcurrent->next = p2 ;

pcurrent = p2 ;

p2 = p2->next ;

}

}

if ( p1 != NULL )

pcurrent->next = p1 ;

if ( p2 != NULL )

pcurrent->next = p2 ;

return head ;

}

(2)已知兩個連結清單head1 和head2 各自有序,請把它們合并成一個連結清單依然有序,這次要求用遞歸方法進行。 ( Autodesk)

答案:

Node * MergeRecursive(Node *head1 , Node *head2)

{

if ( head1 == NULL )

return head2 ;

if ( head2 == NULL)

return head1 ;

Node *head = NULL ;

if ( head1->data < head2->data )

{

head = head1 ;

head->next = MergeRecursive(head1->next,head2);

}

else

{

head = head2 ;

head->next = MergeRecursive(head1,head2->next);

}

return head ;

}

15.分析一下這段程式的輸出 (Autodesk)

class B

{

public:

B()

{

cout<<"default constructor"<<endl;

}

~B()

{

cout<<"destructed"<<endl;

}

B(int i):data(i)

{

cout<<"constructed by parameter" << data <<endl;

}

private:

int data;

};

B Play( B b)

{

return b ;

}

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

{

B temp = Play(5);

return 0;

}

請自己執行一下看看。

16.寫一個函數找出一個整數數組中,第二大的數 (microsoft)

答案:

const int MINNUMBER = -32767 ;

int find_sec_max( int data[] , int count) //類似于1 4 4 4這樣的序列将認為1是第二大數

{

int maxnumber = data[0] ;

int sec_max = MINNUMBER ;

for ( int i = 1 ; i < count ; i++)

{

if ( data[i] > maxnumber )

{

sec_max = maxnumber ;

maxnumber = data[i] ;

}

else

{

if ( data[i] > sec_max )

sec_max = data[i] ;

}

}

return sec_max ;

}

17 寫一個在一個字元串中尋找一個子串第一個位置的函數

這個題目的一般算法比較簡單我就不給出了,如果要求高效率的話請參見資料結構中的KMP 算法,不過在筆試時間有限情況下,寫出那個算法還是挺難的。

英文題目

1. Introduce yourself in English

2. What is your great advantage you think of yourself?

3. What is your drawback you think of yourself?

Maybe I will feel very tense if I make a speech in front of a lot of people.

4. How do you feel shanghai?

一. 華為一道面試題-1-n排序

有N個大小不等的自然數(1--N),請将它們由小到大排序。

要求程式算法:時間複雜度為O(n),空間複雜度為O(1)。

網上轉的,一開始也沒有注意到最開始的半句。

算法:N個不等的自然數1~N,排序完成後必然為1~N。是以可以一次周遊,遇到a[i]!=i的就把a[i]和a[a[i]]交換。

void sort(int a[], int n)

{

 int i;

 int t;

 for (i=1; i<n+1; i++)

 {

 while(a[i]!=i)

  {

 t = a[a[i]];

 a[a[i]] = a[i];//排好一個元素

 a[i] = t;

  }

 }

}

二. 一次周遊 找 連結清單倒數第n個節點

 一道面試題目,阿明和晨晨看到并且告訴我答案的。要求通過一次周遊找到連結清單中倒數第n個節點,連結清單可能相當大,可使用輔助空間,但是輔助空間的數目必須固定,不能和n有關。

算法思想:兩根指針,第一根先出發,相距n步後第二根出發。然後同時步進,直到第一根指針達到末尾。

struct iNode {

int value;

iNode * next;

};

iNode * getresult(iNode * head,int n)

{

iNode *pfirst;

iNode *psecond;

pfirst=head;

int counter;

for(counter=0;counter<n;counter++) {

 pfirst=pfirst->next;

}

psecond=head;

while(pfirst!=NULL) {

 pfirst=pfirst->next;

 psecond=psecond->next;

}

return psecond;

}

三. VC++學習筆記

1.       日期轉成字元串:

   COleDateTime    ww;

ww=COleDateTime::GetCurrentTime();

AfxMessageBox(ww.Format("%Y-%m-%d %H:%M:%S"));

2.       字元串轉成日期:

COleDateTime dt;

       dt.ParseDateTime(“2006-08-08 08:08:08”);

3.       資源檔案

資源檔案名:xxx.rc,其中要包含的主要檔案:resource.h和afxres.h

4.       vc開發環境沒有自動提示時:

   删除 目錄下的ncb檔案 ,再打開一般就ok了

5.       利用_variant_t 取資料庫資料的方法:

   _variant_t ibb;

       ibb=(_variant_t)rs->GetCollect("inta");

       if(ibb.vt!=VT_NULL)

       {

              m_b=ibb.lVal;

       }

6.       平時取記錄集字段值的方法:

   (LPCTSTR)(_bstr_t)rs->GetCollect("datea")

7.       DoModal()可以傳回兩個結果 IDOK,IDCANCEL,他們都是int型,分别是:1,2。通過EndDialog(IDOK)的方式傳回。

8.       一般将資料庫連接配接方面的資訊放到app中。則AfxGetApp()非常重要,如;

   CAdo2App* mapp=(CAdo2App*)AfxGetApp();

   Map->conn->Execute(sql,NULL,adCmdText);

9.       DECLARE_DYNCREATE(類名),IMPLEMENT_DYNCREATE(類名,基類名)  使得由CObject繼承來的類在程式運作的時候能夠動态的建立。

10.   DECLARE_DYNAMIC(類名),IMPLEMENT_DYNAMIC(類名,基類名)  可以在運作時獲得該類的資訊

11.   DECLARE_SERIAL(類名),IMPLEMENT_SERIAL(類名,基類名,0)為一個可以串行化的CObject派生類産生必要的C++标題代碼

12.   獲得文檔的方法: CMainFrame * pFrame=(CMainFrame *) AfxGetMainWnd();

CPClientDoc * pDoc =(CPClientDoc *) pFrame->GetActiveDocument();

13.   獲得視圖的方法:CMainFrame * pFrame=(CMainFrame *) AfxGetMainWnd();

myView =(CPClientView*) pFrame->GetActiveView();

14.   如果要引用全局變量或者全局方法,須在目前類中引入:extern 名字; 

16. 關聯、聚合(Aggregation)以及組合(Composition)的差別? 涉及到UML中的一些概念: 關聯是表示兩個類的一般性聯系,比如“學生”和“老師”就是一種關聯關系; 聚合表示has-a的關系,是一種相對松散的關系,聚合類不需要對被聚合類負責,如下圖所示,用空的菱形表示聚合關系:                            

c/c++面試題2

500){this.resized=true;this.style.width=500;}" border=0> 從實作的角度講,聚合可以表示為: class A {...}  class B { A* a; .....} 而 組合表示contains-a的關系,關聯性強于聚合:組合類與被組合類有相同的生命周期,組合類要對被組合類負責,采用實心的菱形表示組合關系:                            

c/c++面試題2

500){this.resized=true;this.style.width=500;}" border=0> 實作的形式是: class A{...} class B{ A a; ...} 參考文章: http://www.cnitblog.com/Lily/archive/2006/02/23/6860.html           http://www.vckbase.com/document/viewdoc/?id=422 17.面向對象的三個基本特征,并簡單叙述之? 1. 封裝:将客觀事物抽象成類,每個類對自身的資料和方法實行protection(private, protected,public) 2. 繼承:廣義的繼承有三種實作形式:實作繼承(指使用基類的屬性和方法而無需額外編碼的能力)、可視繼承(子窗體使用父窗體的外觀和實作代碼)、接口繼承 (僅使用屬性和方法,實作滞後到子類實作)。前兩種(類繼承)和後一種(對象組合=>接口繼承以及純虛函數)構成了功能複用的兩種方式。 3. 多态:是将父對象設定成為和一個或更多的他的子對象相等的技術,指派之後,父對象就可以根據目前指派給它的子對象的特性以不同的方式運作。簡單的說,就是一句話: 允許将子類類型的指針指派給父類類型的指針。 18. 重載(overload)和 重寫(overried,有的書也叫做“覆寫”)的差別? 常考的題目。從定義上來說: 重載:是指允許存在多個 同名函數,而這些函數的 參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。 重寫:是指子類重新定義複類虛函數的方法。 從實作原理上來說: 重載: 編譯器根據函數不同的參數表,對同名函數的名稱做修飾,然後這些同名函數就成了不同的函數(至少對于編譯器來說是這樣的)。如,有兩個同名函數: function func(p:integer):integer;和function func(p:string):integer;。 那麼編譯器做過修飾後的函數名稱可能是這樣的:int_func、str_func。對于這兩個函數的調用,在編譯器間就已經确定了,是靜态的。也就是說,它們的位址在編譯期就綁定了(早綁定),是以,重載和多态無關! 重寫:和多态真正相關 。當子類重新定義了父類的虛函數後,父類指針根據賦給它的不同的子類指針,動态的調用屬于子類的該函數,這樣的函數調用在編譯期間是無法确定的(調用的子類的虛函數的位址無法給出)。是以,這樣的函數位址是在運作期綁定的(晚綁定)。 19. 多态的作用? 主要是兩個:1. 隐藏實作細節,使得代碼能夠子產品化;擴充代碼子產品,實作代碼重用;2. 接口重用:為了類在繼承和派生的時候,保證使用家族中任一類的執行個體的某一屬性時的正确調用。 20. Ado與Ado.net的相同與不同? 除 了“能夠讓應用程式處理存儲于DBMS 中的資料“這一基本相似點外,兩者沒有太多共同之處。但是Ado使用OLE DB 接口并基于微軟的COM 技術,而ADO.NET 擁有自己的ADO.NET 接口并且基于微軟的.NET 體系架構。衆所周知.NET 體系不同于COM 體系,ADO.NET 接口也就完全不同于ADO和OLE DB 接口,這也就是說ADO.NET 和ADO是兩種資料通路方式。ADO.net 提供對XML 的支援。  

21. New delete 與malloc free 的聯系與差別?

答案:都是在堆(heap)上進行動态的記憶體操作。用malloc函數需要指定記憶體配置設定的位元組數并且不能初始化對象,new 會自動調用對象的構造函數。delete 會調用對象的destructor,而free 不會調用對象的destructor.

22. #define DOUBLE(x) x+x ,i = 5*DOUBLE(5); i 是多少?

答案:i 為30。

23. 有哪幾種情況隻能用intialization list 而不能用assignment?

答案:當類中含有const、reference 成員變量;基類的構造函數都需要初始化表。

24. C++是不是類型安全的?

答案:不是。兩個不同類型的指針之間可以強制轉換(用reinterpret cast)。C#是類型安全的。

25. main 函數執行以前,還會執行什麼代碼?

答案:全局對象的構造函數會在main 函數之前執行。

26. 描述記憶體配置設定方式以及它們的差別?

1) 從靜态存儲區域配置設定。記憶體在程式編譯的時候就已經配置設定好,這塊記憶體在程式的整個運作期間都存在。例如全局變量,static 變量。

2) 在棧上建立。在執行函數時,函數内局部變量的存儲單元都可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧記憶體配置設定運算内置于處理器的指令集。

3) 從堆上配置設定,亦稱動态記憶體配置設定。程式在運作的時候用malloc 或new 申請任意多少的記憶體,程式員自己負責在何時用free 或delete 釋放記憶體。動态記憶體的生存期由程式員決定,使用非常靈活,但問題也最多。

27.struct 和 class 的差別

答案:struct 的成員預設是公有的,而類的成員預設是私有的。struct 和 class 在其他方面是功能相當的。

從 感情上講,大多數的開發者感到類和結構有很大的差别。感覺上結構僅僅象一堆缺乏封裝和功能的開放的記憶體位,而類就象活的并且可靠的社會成員,它有智能服 務,有牢固的封裝屏障和一個良好定義的接口。既然大多數人都這麼認為,那麼隻有在你的類有很少的方法并且有公有資料(這種事情在良好設計的系統中是存在 的!)時,你也許應該使用 struct 關鍵字,否則,你應該使用 class 關鍵字。 

28.當一個類A 中沒有生命任何成員變量與成員函數,這時sizeof(A)的值是多少,如果不是零,請解釋一下編譯器為什麼沒有讓它為零。(Autodesk)

答案:肯定不是零。舉個反例,如果是零的話,聲明一個class A[10]對象數組,而每一個對象占用的空間是零,這時就沒辦法區分A[0],A[1]…了。

29. 在8086 彙編下,邏輯位址和實體位址是怎樣轉換的?(Intel)

答案:通用寄存器給出的位址,是段内偏移位址,相應段寄存器位址*10H+通用寄存器内位址,就得到了真正要通路的位址。

30. 比較C++中的4種類型轉換方式?

請參考:http://blog.bioon.com/user1/8688/archives/2006/45399.shtml,重點是static_cast, dynamic_cast和reinterpret_cast的差別和應用。

31.分别寫出BOOL,int,float,指針類型的變量a 與“零”的比較語句。

答案:

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

int :     if ( a == 0)

float :   const EXPRESSION EXP = 0.000001

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

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

32.請說出const與#define 相比,有何優點?

答案:1) const 常量有資料類型,而宏常量沒有資料類型。編譯器可以對前者進行類型安全檢查。而對後者隻進行字元替換,沒有類型安全檢查,并且在字元替換可能會産生意料不到的錯誤。

      2) 有些內建化的調試工具可以對const 常量進行調試,但是不能對宏常量進行調試。

33.簡述數組與指針的差別?

數組要麼在靜态存儲區被建立(如全局數組),要麼在棧上被建立。指針可以随時指向任意類型的記憶體塊。

(1)修改内容上的差别

char a[] = “hello”;

a[0] = ‘X’;

char *p = “world”; // 注意p 指向常量字元串

p[0] = ‘X’; // 編譯器不能發現該錯誤,運作時錯誤

(2) 用運算符sizeof 可以計算出數組的容量(位元組數)。sizeof(p),p 為指針得到的是一個指針變量的位元組數,而不是p 所指的記憶體容量。C++/C 語言沒有辦法知道指針所指的記憶體容量,除非在申請記憶體時記住它。注意當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指針。

char a[] = "hello world";

char *p = a;

cout<< sizeof(a) << endl; // 12 位元組

cout<< sizeof(p) << endl; // 4 位元組

計算數組和指針的記憶體容量

void Func(char a[100])

{

cout<< sizeof(a) << endl; // 4 位元組而不是100 位元組

}

34.類成員函數的重載、覆寫和隐藏差別?

答案:

a.成員函數被重載的特征:

(1)相同的範圍(在同一個類中);

(2)函數名字相同;

(3)參數不同;

(4)virtual 關鍵字可有可無。

b.覆寫是指派生類函數覆寫基類函數,特征是:

(1)不同的範圍(分别位于派生類與基類);

(2)函數名字相同;

(3)參數相同;

(4)基類函數必須有virtual 關鍵字。

c.“隐藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下:

(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數将被隐藏(注意别與重載混淆)。

(2)如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隐藏(注意别與覆寫混淆)

35. There are two int variables: a and b, don’t use “if”, “? :”, “switch”or other judgement statements, find out the biggest one of the two numbers.

答案:( ( a + b ) + abs( a - b ) ) / 2

36. 如何列印出目前源檔案的檔案名以及源檔案的目前行号?

答案:

cout << __FILE__ ;

cout<<__LINE__ ;

__FILE__和__LINE__是系統預定義宏,這種宏并不是在某個檔案中定義的,而是由編譯器定義的。

37. main 主函數執行完畢後,是否可能會再執行一段代碼,給出說明?

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

void main( void )

{

String str("zhanglin");

_onexit( fn1 );

_onexit( fn2 );

_onexit( fn3 );

_onexit( fn4 );

printf( "This is executed first./n" );

}

int fn1()

{

printf( "next./n" );

return 0;

}

int fn2()

{

printf( "executed " );

return 0;

}

int fn3()

{

printf( "is " );

return 0;

}

int fn4()

{

printf( "This " );

return 0;

}

The _onexit function is passed the address of a function (func) to be called when the program terminates normally. Successive calls to _onexit create a register of functions that are executed in LIFO (last-in-first-out) order. The functions passed to _onexit cannot take parameters.

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

答案:

#ifdef __cplusplus

cout<<"c++";

#else

cout<<"c";

#endif

39.檔案中有一組整數,要求排序後輸出到另一個檔案中

答案:

#i nclude<iostream>

#i nclude<fstream>

using namespace std;

void Order(vector<int>& data) //bubble sort

{

int count = data.size() ;

int tag = false ; // 設定是否需要繼續冒泡的标志位

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

{

for ( int j = 0 ; j < count - i - 1 ; j++)

{

if ( data[j] > data[j+1])

{

tag = true ;

int temp = data[j] ;

data[j] = data[j+1] ;

data[j+1] = temp ;

}

}

if ( !tag )

break ;

}

}

void main( void )

{

vector<int>data;

ifstream in("c://data.txt");

if ( !in)

{

cout<<"file error!";

exit(1);

}

int temp;

while (!in.eof())

{

in>>temp;

data.push_back(temp);

}

in.close(); //關閉輸入檔案流

Order(data);

ofstream out("c://result.txt");

if ( !out)

{

cout<<"file error!";

exit(1);

}

for ( i = 0 ; i < data.size() ; i++)

out<<data[i]<<" ";

out.close(); //關閉輸出檔案流

}

40. 連結清單題:一個連結清單的結點結構

struct Node

{

int data ;

Node *next ;

};

typedef struct Node Node ;

(1)已知連結清單的頭結點head,寫一個函數把這個連結清單逆序 ( Intel)

Node * ReverseList(Node *head) //連結清單逆序

{

if ( head == NULL || head->next == NULL )

return head;

Node *p1 = head ;

Node *p2 = p1->next ;

Node *p3 = p2->next ;

p1->next = NULL ;

while ( p3 != NULL )

{

p2->next = p1 ;

p1 = p2 ;

p2 = p3 ;

p3 = p3->next ;

}

p2->next = p1 ;

head = p2 ;

return head ;

}

(2)已知兩個連結清單head1 和head2 各自有序,請把它們合并成一個連結清單依然有序。(保留所有結點,即便大小相同)

Node * Merge(Node *head1 , Node *head2)

{

if ( head1 == NULL)

return head2 ;

if ( head2 == NULL)

return head1 ;

Node *head = NULL ;

Node *p1 = NULL;

Node *p2 = NULL;

if ( head1->data < head2->data )

{

head = head1 ;

p1 = head1->next;

p2 = head2 ;

}

else

{

head = head2 ;

p2 = head2->next ;

p1 = head1 ;

}

Node *pcurrent = head ;

while ( p1 != NULL && p2 != NULL)

{

if ( p1->data <= p2->data )

{

pcurrent->next = p1 ;

pcurrent = p1 ;

p1 = p1->next ;

}

else

{

pcurrent->next = p2 ;

pcurrent = p2 ;

p2 = p2->next ;

}

}

if ( p1 != NULL )

pcurrent->next = p1 ;

if ( p2 != NULL )

pcurrent->next = p2 ;

return head ;

}

(3)已知兩個連結清單head1 和head2 各自有序,請把它們合并成一個連結清單依然有序,這次要求用遞歸方法進行。 (Autodesk)

答案:

Node * MergeRecursive(Node *head1 , Node *head2)

{

if ( head1 == NULL )

return head2 ;

if ( head2 == NULL)

return head1 ;

Node *head = NULL ;

if ( head1->data < head2->data )

{

head = head1 ;

head->next = MergeRecursive(head1->next,head2);

}

else

{

head = head2 ;

head->next = MergeRecursive(head1,head2->next);

}

return head ;

}