1. 程式人生 > >C++ 筆試題目

C++ 筆試題目

1.new 、 delete 、 malloc 、 free 關係

delete 會呼叫物件的解構函式 , 和 new 對應 free 只會釋放記憶體, new 呼叫建構函式。 malloc 與 free 是 C++/C 語言的標準庫函式, new/delete 是 C++ 的運算子。它們都可用於申請動態記憶體和釋放記憶體。對於非內部資料型別的物件而言,光用 maloc/free 無法滿足動態物件的要求。物件在建立的同時要自動執行建構函式,物件在消亡之前要自動執行解構函式。由於 malloc/free 是庫函式而不是運算子,不在編譯器控制權限之內,不能夠把執行建構函式和解構函式的任務強加於 malloc/free 。因此 C++ 語言需要一個能完成動態記憶體分配和初始化工作的運算子 new ,以及一個能完成清理與釋放記憶體工作的運算子 delete 。注意 new/delete 不是庫函式。

總結:new和delete會自動呼叫物件的構造與解構函式而malloc與free不會;

      new和delete式C++運算子,而malloc和free是C/C++標準庫函式。

delete 只會呼叫一次解構函式,而 delete[] 會呼叫每一個成員的解構函式。在 More Effective  C++ 中有更為詳細的解釋:“當 delete 操作符用於陣列時,它為每個陣列元素呼叫解構函式,然後呼叫 operatordelete 來釋放記憶體。” delete 與 New 配套, delete [] 與 new [] 配套

  MemTest*mTest1=newMemTest[10];

  MemTest*mTest2=newMemTest;

  int*pInt1=newint[10];

  int*pInt2=newint;

  delete[]pInt1;  //-1-

  delete[]pInt2;  //-2-

  delete[]mTest1;//-3-

  delete[]mTest2;//-4-

  在 -4- 處報錯。

這就說明:對於內建簡單資料型別, delete 和 delete[] 功能是相同的。對於自定義的複雜資料型別, delete 和 delete[] 不能互用。 delete[] 刪除一個數組, delete 刪除一個指標簡單來說,用 new 分配的記憶體用 delete 刪除用 new[] 分配的記憶體用 delete[] 刪除 delete[] 會呼叫陣列元素的解構函式。內部資料型別沒有解構函式,所以問題不大。如果你在用 delete 時沒用括號, delete 就會認為指向的是單個物件,否則,它就會認為指向的是一個數組。

總結: delete 只會呼叫一次解構函式,而 delete[] 會呼叫每一個成員的解構函式。

4. 繼承優缺點。

類繼承是在編譯時刻靜態定義的,且可直接使用,類繼承可以較方便地改變父類的實現。但是類繼承也有一些不足之處。首先,因為繼承在編譯時刻就定義了,所以無法在執行時刻改變從父類繼承的實現。更糟的是,父類通常至少定義了子類的部分行為,父類的任何改變都可能影響子類的行為。如果繼承下來的實現不適合解決新的問題,則父類必須重寫或被其他更適合的類替換。這種依賴關係限制了靈活性並最終限制了複用性。

(待補充)

5.C++ 有哪些性質(面向物件特點)

封裝,繼承和多型。

在面向物件程式設計語言中,封裝是利用可重用成分構造軟體系統的特性,它不僅支援系統的可重用性,而且還有利於提高系統的可擴充性;訊息傳遞可以實現傳送一個通用的訊息而呼叫不同的方法;封裝是實現資訊隱蔽的一種技術,其目的是使類的定義和實現分離。

6. 子類析構時要呼叫父類的解構函式嗎?

解構函式呼叫的次序是先派生類的析構後基類的析構,也就是說在基類的的析構呼叫的時候 , 派生類的資訊已經全部銷燬了定義一個物件時先呼叫基類的建構函式、然後呼叫派生類的建構函式;析構的時候恰好相反:先呼叫派生類的解構函式、然後呼叫基類的解構函式 Java 無解構函式深拷貝和淺拷貝

7. 多型,虛擬函式,純虛擬函式

8. 求下面函式的返回值(微軟)

int func(x) 

    int countx = 0; 
    while(x) 
    { 
          countx ++; 
          x = x&(x-1); 
     } 
    return countx; 

假定 x = 9999 。 答案: 8

思路:將 x 轉化為 2 進位制,看含有的 1 的個數。

9. 什麼是 “ 引用 ” ?申明和使用 “ 引用 ” 要注意哪些問題?

答:引用就是某個目標變數的 “ 別名 ”(alias) ,對應用的操作與對變數直接操作效果完全相同。申明一個引用的時候,切記要對其進行初始化。引用宣告完畢後,相當於目標變數名有兩個名稱,即該目標原名稱和引用名,不能再把該引用名作為其他變數名的別名。宣告一個引用,不是新定義了一個變數,它只表示該引用名是目標變數名的一個別名,它本身不是一種資料型別,因此引用本身不佔儲存單元,系統也不給引用分配儲存單元。不能建立陣列的引用。

10. 將 “ 引用 ” 作為函式引數有哪些特點?

( 1 )傳遞引用給函式與傳遞指標的效果是一樣的。這時,被調函式的形參就成為原來主調函式中的實參變數或物件的一個別名來使用,所以在被調函式中對形參變數的操作就是對其相應的目標物件(在主調函式中)的操作。

( 2 )使用引用傳遞函式的引數,在記憶體中並沒有產生實參的副本,它是直接對實參操作;而使用一般變數傳遞函式的引數,當發生函式呼叫時,需要給形參分配儲存單元,形參變數是實參變數的副本;如果傳遞的是物件,還將呼叫拷貝建構函式。因此,當引數傳遞的資料較大時,用引用比用一般變數傳遞引數的效率和所佔空間都好。

( 3 )使用指標作為函式的引數雖然也能達到與使用引用的效果,但是,在被調函式中同樣要給形參分配儲存單元,且需要重複使用 "* 指標變數名 " 的形式進行運算,這很容易產生錯誤且程式的閱讀性較差;另一方面,在主調函式的呼叫點處,必須用變數的地址作為實參。而引用更容易使用,更清晰。

11. 在什麼時候需要使用 “ 常引用 ” ?  

如果既要利用引用提高程式的效率,又要保護傳遞給函式的資料不在函式中被改變,就應使用常引用。常引用宣告方式: const 型別識別符號 & 引用名 = 目標變數名;

例 1

int a ;
const int &ra=a;
ra=1; // 錯誤 
a=1; // 正確

例 2

string foo( );
void bar(string & s);

那麼下面的表示式將是非法的:

bar(foo( ));
bar("hello world");

原因在於 foo( ) 和 "hello world" 串都會產生一個臨時物件,而在 C++ 中,這些臨時物件都是 const 型別的。因此上面的表示式就是試圖將一個 const 型別的物件轉換為非 const 型別,這是非法的。引用型引數應該在能被定義為 const 的情況下,儘量定義為 const 。

12. 將 “ 引用 ” 作為函式返回值型別的格式、好處和需要遵守的規則 ?

格式:型別識別符號 & 函式名(形參列表及型別說明) { // 函式體 }

好處:在記憶體中不產生被返回值的副本;(注意:正是因為這點原因,所以返回一個區域性變數的引用是不可取的。因為隨著該區域性變數生存期的結束,相應的引用也會失效,產生 runtime error! 注意事項:

( 1 )不能返回區域性變數的引用。這條可以參照 Effective C++[1] 的 Item 31 。主要原因是區域性變數會在函式返回後被銷燬,因此被返回的引用就成為了 " 無所指 " 的引用,程式會進入未知狀態。

( 2 )不能返回函式內部 new 分配的記憶體的引用。這條可以參照 Effective C++[1] 的 Item 31 。雖然不存在區域性變數的被動銷燬問題,可對於這種情況(返回函式內部 new 分配記憶體的引用),又面臨其它尷尬局面。例如,被函式返回的引用只是作為一個臨時變量出現,而沒有被賦予一個實際的變數,那麼這個引用所指向的空間(由 new 分配)就無法釋放,造成 memory leak 。

( 3 )可以返回類成員的引用,但最好是 const 。這條原則可以參照 Effective C++[1] 的 Item 30 。主要原因是當物件的屬性是與某種業務規則( business rule )相關聯的時候,其賦值常常與某些其它屬性或者物件的狀態有關,因此有必要將賦值操作封裝在一個業務規則當中。如果其它物件可以獲得該屬性的非常量引用(或指標),那麼對該屬性的單純賦值就會破壞業務規則的完整性。

( 4 )流操作符過載返回值申明為 “ 引用 ” 的作用:

流操作符 << 和 >> ,這兩個操作符常常希望被連續使用,例如: cout << "hello" << endl;  因此這兩個操作符的返回值應該是一個仍然支援這兩個操作符的流引用。可選的其它方案包括:返回一個流物件和返回一個流物件指標。但是對於返回一個流物件,程式必須重新(拷貝)構造一個新的流物件,也就是說,連續的兩個 << 操作符實際上是針對不同物件的!這無法讓人接受。對於返回一個流指標則不能連續使用 << 操作符。因此,返回一個流物件引用是惟一選擇。這個唯一選擇很關鍵,它說明了引用的重要性以及無可替代性,也許這就是 C++ 語言中引入引用這個概念的原因吧。 賦值操作符 = 。這個操作符象流操作符一樣,是可以連續使用的,例如: x = j = 10; 或者 (x=10)=100; 賦值操作符的返回值必須是一個左值,以便可以被繼續賦值。因此引用成了這個操作符的惟一返回值選擇。

例 3

# i nclude <iostream.h>
int &put(int n);
int vals[10];
int error=-1;
void main()
{
put(0)=10; // 以 put(0) 函式值作為左值,等價於 vals[0]=10; 
put(9)=20; // 以 put(9) 函式值作為左值,等價於 vals[9]=20; 
cout<<vals[0]; 
cout<<vals[9];

int &put(int n)
{
if (n>=0 && n<=9 ) return vals[n]; 
else { cout<<"subscript error"; return error; }
}

( 5 )在另外的一些操作符中,卻千萬不能返回引用: +-*/ 四則運算子。它們不能返回引用, Effective C++[1] 的 Item23 詳細的討論了這個問題。主要原因是這四個操作符沒有 side effect ,因此,它們必須構造一個物件作為返回值,可選的方案包括:返回一個物件、返回一個區域性變數的引用,返回一個 new 分配的物件的引用、返回一個靜態物件引用。根據前面提到的引用作為返回值的三個規則,第 2 、 3 兩個方案都被否決了。靜態物件的引用又因為 ((a+b) == (c+d)) 會永遠為 true 而導致錯誤。所以可選的只剩下返回一個物件了。

13.“ 引用 ” 與多型的關係?

引用是除指標外另一個可以產生多型效果的手段。這意味著,一個基類的引用可以指向它的派生類例項。例 4

Class A; Class B : Class A{...};  B b; A& ref = b;

14.“ 引用 ” 與指標的區別是什麼?

指標通過某個指標變數指向一個物件後,對它所指向的變數間接操作。程式中使用指標,程式的可讀性差;而引用本身就是目標變數的別名,對引用的操作就是對目標變數的操作。此外,就是上面提到的對函式傳 ref 和 pointer 的區別。

15. 什麼時候需要 “ 引用 ” ?

流操作符 << 和 >> 、賦值操作符 = 的返回值、拷貝建構函式的引數、賦值操作符 = 的引數、其它情況都推薦使用引用。以上 2-8 參考: http://develop.csai.cn/c/NO0000021.htm

16. 結構與聯合有和區別?


(1). 結構和聯合都是由多個不同的資料型別成員組成 , 但在任何同一時刻 , 聯合中只存放了一個被選中的成員(所有成員共用一塊地址空間) , 而結構的所有成員都存在(不同成員的存放地址不同)。  
 (2). 對於聯合的不同成員賦值 , 將會對其它成員重寫 ,  原來成員的值就不存在了 , 而對於結構的不同成員賦值是互不影響的。

17. 面關於 “ 聯合 ” 的題目的輸出?

a)

# i nclude <stdio.h>
union
{
int i;
char x[2];
}a;


void main()
{
a.x[0] = 10; 
a.x[1] = 1;
printf("%d",a.i);
}
答案: 266 ( 低位低地址,高位高地址,記憶體佔用情況是 Ox010A )

b)

main() 
     { 
          union{                   /* 定義一個聯合 */ 
               int i; 
               struct{             /* 在聯合中定義一個結構 */ 
                    char first; 
                    char second; 
               }half; 
          }number; 
          number.i=0x4241;         /* 聯合成員賦值 */ 
          printf("%c%cn", number.half.first, mumber.half.second); 
          number.half.first='a';   /* 聯合中結構成員賦值 */ 
          number.half.second='b'; 
          printf("%xn", number.i); 
          getch(); 
     } 
答案: AB   (0x41 對應 'A', 是低位; Ox42 對應 'B', 是高位)

       6261 (number.i 和 number.half 共用一塊地址空間)

18. 關聯、聚合 (Aggregation) 以及組合 (Composition) 的區別?

涉及到 UML 中的一些概念:關聯是表示兩個類的一般性聯絡,比如 “ 學生 ” 和 “ 老師 ” 就是一種關聯關係;聚合表示 has-a 的關係,是一種相對鬆散的關係,聚合類不需要對被聚合類負責,如下圖所示,用空的菱形表示聚合關係:從實現的角度講,聚合可以表示為 :

class A {...}  class B { A* a; .....}

而組合表示 contains-a 的關係,關聯性強於聚合:組合類與被組合類有相同的生命週期,組合類要對被組合類負責,採用實心的菱形表示組合關係:實現的形式是 :

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

19. 面向物件的三個基本特徵,並簡單敘述之?

1. 封裝:將客觀事物抽象成類,每個類對自身的資料和方法實行 protection(private, protected,public)

2. 繼承:廣義的繼承有三種實現形式:實現繼承(指使用基類的屬性和方法而無需額外編碼的能力)、可視繼承(子窗體使用父窗體的外觀和實現程式碼)、介面繼承(僅使用屬性和方法,實現滯後到子類實現)。前兩種(類繼承)和後一種(物件組合 => 介面繼承以及純虛擬函式)構成了功能複用的兩種方式。

3. 多型:是將父物件設定成為和一個或更多的他的子物件相等的技術,賦值之後,父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作。簡單的說,就是一句話:允許將子類型別的指標賦值給父類型別的指標。

20. 過載( overload) 和重寫 (overried ,有的書也叫做 “ 覆蓋 ” )的區別?

常考的題目。從定義上來說:

過載:是指允許存在多個同名函式,而這些函式的引數表不同(或許引數個數不同,或許引數型別不同,或許兩者都不同)。

重寫:是指子類重新定義父類虛擬函式的方法。

從實現原理上來說:

過載:編譯器根據函式不同的引數表,對同名函式的名稱做修飾,然後這些同名函式就成了不同的函式(至少對於編譯器來說是這樣的)。如,有兩個同名函式: function func(p:integer):integer; 和 function func(p:string):integer; 。 那麼編譯器做過修飾後的函式名稱可能是這樣的: int_func 、 str_func 。對於這兩個函式的呼叫,在編譯器間就已經確定了,是 靜態 的。也就是說, 它們的地址在編譯期就綁定了(早繫結), 因此, 過載和多型無關 !

重寫:和多型真正相關。當子類重新定義了父類的虛擬函式後,父類指標根據賦給它的不同的子類指標, 動態的呼叫 屬於子類的該函式,這樣的函 數呼叫在編譯期間是無法確定的 (呼叫的子類的虛擬函式的地址無法給出)。因此, 這樣的函式地址是在執行期繫結的(晚繫結)。

21. 多型的作用?

主要是兩個:

1. 隱藏實現細節,使得程式碼能夠模組化;擴充套件程式碼模組,實現程式碼重用;

2. 介面重用:為了類在繼承和派生的時候 ,保證使用家族中任一類的例項的某一屬性時的正確呼叫 。

22.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 的支援。

23.New delete 與 malloc free 的聯絡與區別 ?

答案:都是在堆 (heap) 上進行動態的記憶體操作。用 malloc 函式需要指定記憶體分配的位元組數並且不能初始化物件, new 會自動呼叫物件的建構函式。 delete 會呼叫物件的 destructor ,而 free 不會呼叫物件的 destructor.

答案: i 為 30 。

25. 有哪幾種情況只能用 intialization list 而不能用 assignment?

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

26. C++ 是不是型別安全的?

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

27. main 函式執行以前,還會執行什麼程式碼?

答案:全域性物件的建構函式會在 main 函式之前執行。

28.  描述記憶體分配方式以及它們的區別 ?

1 ) 從靜態儲存區域分配 。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如 全域性變數, static 變數 。 
2 ) 在棧上建立 。在執行函式時, 函式內區域性變數的儲存單元都可以在棧上建立 ,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集。 
3 ) 從堆上分配 , 亦稱動態記憶體分配 。程式在執行的時候用 malloc 或 new 申請任意多少的記憶體,程式設計師自己負責在何時用 free 或 delete 釋放記憶體。動態記憶體的生存期由程式設計師決定,使用非常靈活,但問題也最多。

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

30. 當一個類 A 中沒有任何成員變數與成員函式 , 這時 sizeof(A) 的值是多少?

答案:如果不是零,請解釋一下編譯器為什麼沒有讓它為零。( Autodesk )肯定不是零。舉個反例,如果是零的話,宣告一個 class A[10] 物件陣列,而每一個物件佔用的空間是零,這時就沒辦法區分 A[0],A[1] …了。

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

答案:通用暫存器給出的地址,是段內偏移地址,相應段暫存器地址 *10H+ 通用暫存器內地址,就得到了真正要訪問的地址。

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

請參考: http://blog.csdn.net/wfwd/archive/2006/05/30/763785.aspx ,重點是 static_cast, dynamic_cast 和 reinterpret_cast 的區別和應用。

dynamic_casts 在幫助你瀏覽繼承層次上是有限制的。它不能被用於缺乏虛擬函式的型別上,它被用於安全地沿著類的繼承關係向下進行型別轉換。如你想在沒有繼承關係的型別中進行轉換,你可能想到 static_cast

33. 分別寫出 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)

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

答案:

Const 作用:定義常量、修飾函式引數、修飾函式返回值三個作用。被 Const 修飾的東西都受到強制保護,可以預防意外的變動,能提高程式的健壯性。

1 ) const 常量有資料型別,而巨集常量沒有資料型別 。編譯器可以對前者進行型別 安全檢查 。而對後者只進行字元替換,沒有型別安全檢查,並且在字元替換可能會產生意料不到的錯誤。 
      2 ) 有些整合化的除錯工具可以對 const 常量進行除錯 ,但是不能對巨集常量進行除錯。

35. 簡述陣列與指標的區別?

陣列要麼在靜態儲存區被建立(如全域性陣列),要麼在棧上被建立。指標可以隨時指向任意型別的記憶體塊。 
(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 位元組 
}

36. 類成員函式的過載、覆蓋和隱藏區別?

答案: a. 成員函式被過載的特徵: 
( 1 )相同的範圍(在同一個類中); 
( 2 )函式名字相同; 
( 3 )引數不同; 
( 4 ) virtual 關鍵字可有可無。 
b. 覆蓋是指派生類函式覆蓋基類函式,特徵是: 
( 1 )不同的範圍(分別位於派生類與基類); 
( 2 )函式名字相同; 
( 3 )引數相同; 
( 4 )基類函式必須有 virtual 關鍵字。 
  c. “隱藏”是指派生類的函式遮蔽了與其同名的基類函式,規則如下: 
( 1 )如果派生類的函式與基類的函式同名,但是引數不同。此時,不論有無 virtual 關鍵字,基類的函式將被隱藏(注意別與過載混淆)。 
( 2 )如果派生類的函式與基類的函式同名,並且引數也相同,但是基類函式沒有 virtual 關鍵字。此時,基類的函式被隱藏(注意別與覆蓋混淆)

37. 求出兩個數中的較大這

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

38. 如何打印出當前原始檔的檔名以及原始檔的當前行號?

答案: 
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__ 和 __LINE__ 是系統預定義巨集,這種巨集並不是在某個檔案中定義的,而是由編譯器定義的。

39. 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.

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

答案: 
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif

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


答案:

# 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(); // 關閉輸出檔案流 
}

42. 連結串列題:一個連結串列的結點結構

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 ;

----------

41. 分析一下這段程式的輸出 (Autodesk)
class B
{
public:
B()
{
cout<<"default constructor"<<endl;
}
~B()
{
cout<<"destructed"<<endl;
}
B(int i):data(i)    //B(int) works as a converter ( int -> instance of  B)
{
cout<<"constructed by parameter " << data <<endl;
}
private:
int data;
};


B Play( B b) 
{
return b ;
}

(1)                                            results:
int main(int argc, char* argv[])      constructed by parameter 5
{                                     destructed  B(5) 形參析構 
B t1 = Play(5); B t2 = Play(t1);     destructed  t1 形參析構 
return 0;                destructed  t2  注意順序! 
}                                     destructed  t1

(2)                                   results:
int main(int argc, char* argv[])      constructed by parameter 5
{                                     destructed  B(5) 形參析構 
B t1 = Play(5); B t2 = Play(10);     constructed by parameter 10
return 0;                destructed  B(10) 形參析構 
}                                     destructed  t2  注意順序!

                                      destructed  t1

43. 寫一個函式找出一個整數陣列中,第二大的數 ( microsoft )

答案: 
const int MINNUMBER = -32767 ;
int find_sec_max( int data[] , int count)
{
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 ;
}

44. 寫一個在一個字串 (n) 中尋找一個子串 (m) 第一個位置的函式。

KMP 演算法效率最好,時間複雜度是O (n+m), 詳見: http://www.zhanglihai.com/blog/c_335_kmp.html

46. 多重繼承的記憶體分配問題:

比如有 class A : public class B, public class C {} 那麼 A 的記憶體結構大致是怎麼樣的? 
這個是 compiler-dependent 的 , 不同的實現其細節可能不同。如果不考慮有虛擬函式、虛繼承的話就相當簡單;否則的話,相當複雜。可以參考《深入探索 C++ 物件模型》,或者: 
http://blog.csdn.net/rainlight/archive/2006/03/03/614792.aspx
http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnarvc/html/jangrayhood.asp

47. 如何判斷一個單鏈表是有環的?(注意不能用標誌位,最多隻能用兩個額外指標)

struct node { char val; node* next;}
bool check(const node* head) {} //return false : 無環; true: 有環一種 O ( n )的辦法就是(搞兩個指標,一個每次遞增一步,一個每次遞增兩步,如果有環的話兩者必然重合,反之亦然): 
bool check(const node* head)
{
    if(head==NULL)  return false;
    node *low=head, *fast=head->next;
    while(fast!=NULL && fast->next!=NULL)
    {
        low=low->next;
        fast=fast->next->next;
        if(low==fast) return true;
    }
    return false;
}

48. 指標找錯題

分析這些面試題,本身包含很強的趣味性 ; 而作為一名研發人員,通過對這些面試題的深入剖析則可進一步增強自身的內功。 
   2. 找錯題 試題 1 : 
以下是引用片段: 
void test1()  // 陣列越界 
   {
   char string[10];
   char* str1 = "0123456789";
   strcpy( string, str1 );
   }
  試題 2 :  
以下是引用片段: 
  void test2()
   {
   char string[10], str1[10]; 
   int i;
   for(i=0; i<10; i++)
   {
   str1= 'a';
   }
   strcpy( string, str1 );
   }
  試題 3 :   
以下是引用片段: 
void test3(char* str1)
   {
   char string[10];
   if( strlen( str1 ) <= 10 )
   {
   strcpy( string, str1 );
   }
   }
  解答: 
  試題 1 字串 str1 需要 11 個位元組才能存放下 ( 包括末尾的 ’/0’) ,而 string 只有 10 個位元組的空間, strcpy 會導致陣列越界 ; 對試題 2 ,如果面試者指出字元陣列 str1 不能在陣列內結束可以給 3 分 ; 如果面試者指出 strcpy(string,str1) 呼叫使得從 str1 記憶體起復制到 string 記憶體起所複製的位元組數具有不確定性可以給 7 分,在此基礎上指出庫函式 strcpy 工作方式的給 10 分 ;
對試題 3 , if(strlen(str1) <= 10) 應改為 if(strlen(str1) <10) ,因為 strlen 的結果未統計 ’/0’ 所佔用的 1 個位元組。剖析:考查對基本功的掌握 
   (1) 字串以 ’/0’ 結尾 ;
   (2) 對陣列越界把握的敏感度 ;
   (3) 庫函式 strcpy 的工作方式,

49. 如果編寫一個標準 strcpy 函式

總分值為 10 ,下面給出幾個不同得分的答案: 2 分 以下是引用片段: 
void strcpy( char *strDest, char *strSrc )
   {
   while( (*strDest++ = * strSrc++) != ‘/0’ );
   }
   4 分 以下是引用片段: 
  void strcpy( char *strDest, const char *strSrc )
   // 將源字串加 const ,表明其為輸入引數,加 2 分 
   {
   while( (*strDest++ = * strSrc++) != ‘/0’ );
   }
   7 分 以下是引用片段: 
void strcpy(char *strDest, const char *strSrc)
   {
   // 對源地址和目的地址加非 0 斷言,加 3 分 
   assert( (strDest != NULL) &&(strSrc != NULL) );
   while( (*strDest++ = * strSrc++) != ‘/0’ );
   }
   10 分 以下是引用片段: 
// 為了實現鏈式操作,將目的地址返回,加 3 分 !
   char * strcpy( char *strDest, const char *strSrc )
   {
   assert( (strDest != NULL) &&(strSrc != NULL) ); 
   char *address = strDest;
   while( (*strDest++ = * strSrc++) != ‘/0’ );
   return address;
   }
  從 2 分到 10 分的幾個答案我們可以清楚的看到,小小的 strcpy 竟然暗藏著這麼多玄機,真不是蓋的 ! 需要多麼紮實的基本功才能寫一個完美的 strcpy 啊 !
   (4) 對 strlen 的掌握,它沒有包括字串末尾的 '/0' 。 
  讀者看了不同分值的 strcpy 版本,應該也可以寫出一個 10 分的 strlen 函數了,完美的版本為: int strlen( const char *str ) // 輸入引數 const  以下是引用片段: 
  {
   assert( strt != NULL ); // 斷言字串地址非 0
   int len=0; // 注,一定要初始化。 
   while( (*str++) != '/0' )
   {
   len++;
   }
   return len;
   }
  試題 4 :以下是引用片段: 
void GetMemory( char *p )
   {
   p = (char *) malloc( 100 );
   }
   void Test( void )
   {
   char *str = NULL;
   GetMemory( str );
   strcpy( str, "hello world" );
   printf( str );
   } 
  試題 5 :  
以下是引用片段: 
char *GetMemory( void )
   {
   char p[] = "hello world";
   return p;
   }
   void Test( void )
   {
   char *str = NULL;
   str = GetMemory();
   printf( str );
   }
  試題 6 :以下是引用片段: 
void GetMemory( char **p, int num )
   {
   *p = (char *) malloc( num );
   }
   void Test( void )
   {
   char *str = NULL;
   GetMemory( &str, 100 );
   strcpy( str, "hello" );
   printf( str );
   }
  試題 7 :以下是引用片段: 
  void Test( void )
   {
   char *str = (char *) malloc( 100 );
   strcpy( str, "hello" );
   free( str );
   ... // 省略的其它語句 
   }
  解答:試題 4 傳入中 GetMemory( char *p ) 函式的形參為字串指標,在函式內部修改形參並不能真正的改變傳入形參的值,執行完 
   char *str = NULL; 
   GetMemory( str );
  後的 str 仍然為 NULL; 試題 5 中 
   char p[] = "hello world";
   return p;
  的 p[] 陣列為函式內的區域性自動變數,在函式返回後,記憶體已經被釋放。這是許多程式設計師常犯的錯誤,其根源在於不理解變數的生存期。 
  試題 6 的 GetMemory 避免了試題 4 的問題,傳入 GetMemory 的引數為字串指標的指標,但是在 GetMemory 中執行申請記憶體及賦值語句 tiffanybracelets
   *p = (char *) malloc( num );
  後未判斷記憶體是否申請成功,應加上: 
   if ( *p == NULL )
   {
   ...// 進行申請記憶體失敗處理 

   }
  試題 7 存在與試題 6 同樣的問題,在執行 
   char *str = (char *) malloc(100);
  後未進行記憶體是否申請成功的判斷 ; 另外,在 free(str) 後未置 str 為空,導致可能變成一個 “ 野 ” 指標,應加上: 
   str = NULL;
  試題 6 的 Test 函式中也未對 malloc 的記憶體進行釋放。 
  剖析: 
  試題 4 ~ 7 考查面試者對記憶體操作的理解程度,基本功紮實的面試者一般都能正確的回答其中 50~60 的錯誤。但是要完全解答正確,卻也絕非易事。

軟體開發網 www.mscto.com
  對記憶體操作的考查主要集中在: 
   (1) 指標的理解 ;
   (2) 變數的生存期及作用範圍 ;
   (3) 良好的動態記憶體申請和釋放習慣。 
  再看看下面的一段程式有什麼錯誤:   
以下是引用片段: 
swap( int* p1,int* p2 )
   {
   int *p;
   *p = *p1;
   *p1 = *p2;
   *p2 = *p;
   }
  在 swap 函式中, p 是一個 “ 野 ” 指標,有可能指向系統區,導致程式執行的崩潰。在 VC++ 中 DEBUG 執行時提示錯誤 “Access Violation” 。該程式應該改為 
以下是引用片段: 
swap( int* p1,int* p2 )
   {
   int p;
   p = *p1;
   *p1 = *p2;
   *p2 = p;
   }

已知 String 類定義如下: 

class String
{
public:
String(const char *str = NULL); // 通用建構函式 
String(const String &another); // 拷貝建構函式 
~ String(); // 解構函式 
String & operater =(const String &rhs); // 賦值函式 
private:
char *m_data; // 用於儲存字串 
};

嘗試寫出類的成員函式實現。 

答案: 
String::String(const char *str)
{
if ( str == NULL ) //strlen 在引數為 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,other.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::~String()
{
delete []m_data ;
}

51.h 標頭檔案中的 ifndef/define/endif 的作用?

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

52. # i nclude<file.h> 與 # i nclude "file.h" 的區別?

答:前者是從 Standard Library 的路徑尋找和引用 file.h ,而後者是從當前工作路徑搜尋並引用 file.h 。 

53. 在 C++ 程式中呼叫被 C 編譯器編譯後的函式,為什麼要加 extern “C” ?

C++ 語言支援函式過載, C 語言不支援函式過載。 C++ 提供了 C 連線交換指定符號 extern “C”

解決名字匹配問題。


首先,作為 extern 是 C/C++ 語言中表明函式和全域性變數作用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其宣告的函式和變數可以在本模組或其它模組中使用。 

通常,在模組的標頭檔案中對本模組提供給其它模組引用的函式和全域性變數以關鍵字 extern 宣告。例如,如果模組 B 欲引用該模組 A 中定義的全域性變數和函式時只需包含模組 A 的標頭檔案即可。這樣,模組 B 中呼叫模組 A 中的函式時,在編譯階段,模組 B 雖然找不到該函式,但是並不會報錯;它會在連線階段中從模組 A 編譯生成的目的碼中找到此函式 

extern "C" 是連線申明 (linkage declaration), 被 extern "C" 修飾的變數和函式是按照 C 語言方式編譯和連線的 , 來看看 C++ 中對類似 C 的函式是怎樣編譯的: 


作為一種面向物件的語言, C++ 支援函式過載,而過程式語言 C 則不支援。函式被 C++ 編譯後在符號庫中的名字與 C 語言的不同。例如,假設某個函式的原型為: 

void foo( int x, int y );
   

該函式被 C 編譯器編譯後在符號庫中的名字為 _foo ,而 C++ 編譯器則會產生像 _foo_int_int 之類的名字(不同的編譯器可能生成的名字不同,但是都採用了相同的機制,生成的新名字稱為 “mangled name” )。 

_foo_int_int 這樣的名字包含了函式名、函式引數數量及型別資訊, C++ 就是靠這種機制來實現函式過載的。例如,在 C++ 中,函式 void foo( int x, int y ) 與 void foo( int x, float y ) 編譯生成的符號是不相同的,後者為 _foo_int_float 。 

同樣地, C++ 中的變數除支援區域性變數外,還支援類成員變數和全域性變數。使用者所編寫程式的類成員變數可能與全域性變數同名,我們以 "." 來區分。而本質上,編譯器在進行編譯時,與函式的處理相似,也為類中的變數取了一個獨一無二的名字,這個名字與使用者程式中同名的全域性變數名字不同。 

未加 extern "C" 宣告時的連線方式 

假設在 C++ 中,模組 A 的標頭檔案如下: 

// 模組 A 標頭檔案  moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif    

在模組 B 中引用該函式: 

// 模組 B 實現檔案  moduleB.cpp
# i nclude "moduleA.h"
foo(2,3);

加 extern "C" 聲明後的編譯和連線方式 

加 extern "C" 聲明後,模組 A 的標頭檔案變為: 

// 模組 A 標頭檔案  moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif    

在模組 B 的實現檔案中仍然呼叫 foo( 2,3 ) ,其結果是: 
( 1 )模組 A 編譯生成 foo 的目的碼時,沒有對其名字進行特殊處理,採用了 C 語言的方式; 

( 2 )聯結器在為模組 B 的目的碼尋找 foo(2,3) 呼叫時,尋找的是未經修改的符號名 _foo 。 

如果在模組 A 中函式聲明瞭 foo 為 extern "C" 型別,而模組 B 中包含的是 extern int foo( int x, int y ) ,則模組 B 找不到模組 A 中的函式;反之亦然。 

所以,可以用一句話概括 extern “C” 這個宣告的真實目的(任何語言中的任何語法特性的誕生都不是隨意而為的,來源於真實世界的需求驅動。我們在思考問題時,不能只停留在這個語言是怎麼做的,還要問一問它為什麼要這麼做,動機是什麼,這樣我們可以更深入地理解許多問題):實現 C++ 與 C 及其它語言的混合程式設計。   

明白了 C++ 中 extern "C" 的設立動機,我們下面來具體分析 extern "C" 通常的使用技巧: 

extern "C" 的慣用法 

( 1 )在 C++ 中引用 C 語言中的函式和變數,在包含 C 語言標頭檔案(假設為 cExample.h )時,需進行下列處理: 


extern "C"
{
# i nclude "cExample.h"
}

而在 C 語言的標頭檔案中,對其外部函式只能指定為 extern 型別, C 語言中不支援 extern "C" 宣告,在 .c 檔案中包含了 extern "C" 時會出現編譯語法錯誤。 

C++ 引用 C 函式例子工程中包含的三個檔案的原始碼如下: 

/* c 語言標頭檔案: cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif


/* c 語言實現檔案: cExample.c */
# i nclude "cExample.h"
int add( int x, int y )
{
return x + y;
}


// c++ 實現檔案,呼叫 add : cppFile.cpp
extern "C"
{
# i nclude "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}

如果 C++ 呼叫一個 C 語言編寫的 .DLL 時,當包括 .DLL 的標頭檔案或宣告介面函式時,應加 extern "C" {   } 。 

( 2 )在 C 中引用 C++ 語言中的函式和變數時, C++ 的標頭檔案需新增 extern "C" ,但是在 C 語言中不能直接引用聲明瞭 extern "C" 的該標