【原文鏈接】
http://www.cnblogs.com/hellogiser/p/100-interview-questions-of-cplusplus-basics-1-10.html
【題目1】
我們可以用static修飾一個類的成員函數,也可以用const修飾類的成員函數(寫在函數的最后表示不能修改成員變量,不是指寫在前面表示返回值為常量)。請問:能不能同時用static和const修飾類的成員函數?
【分析】
不可以。C++編譯器在實現const的成員函數的時候為了確保該函數不能修改類的實例的狀態,會在函數中添加一個隱式的參數const this*。但當一個成員為static的時候,該函數是沒有this指針的。我們也可以這樣理解:兩者的語意是矛盾的。static是針對類型,與類的實例沒有關系;而const是針對實例,確保函數不能修改實例的狀態。因此不能同時用它們。
【題目2】
運行下面的代碼,輸出是什么?
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class
A { }; class B { public : B() {} ~B() {} }; class C { public : C() {} virtual ~C() {} }; int _tmain( int argc, _TCHAR *argv[]) { printf( "%d, %d, %d\n" , sizeof (A), sizeof (B), sizeof (C)); return 0 ; } |
【分析】
答案是1, 1, 4。
class A是一個空類型,它的實例不包含任何信息,本來求sizeof應該是0。但當我們聲明該類型的實例的時候,它必須在內存中占有一定的空間,否則無法使用這些實例。至於占用多少內存,由編譯器決定。Visual Studio 2008中每個空類型的實例占用一個byte的空間。
class B在class A的基礎上添加了構造函數和析構函數。由於構造函數和析構函數的調用與類型的實例無關(調用它們只需要知道函數地址即可),在它的實例中不需要增加任何信息。所以sizeof(B)和sizeof(A)一樣,在Visual Studio 2008中都是1。
class C在class B的基礎上把析構函數標注為虛擬函數。C++的編譯器一旦發現一個類型中有虛擬函數,就會為該類型生成虛函數表,並在該類型的每一個實例中添加一個指向虛函數表的指針。在32位的機器上,一個指針占4個字節的空間,因此sizeof(C)是4。
【題目3】
運行下面中的代碼,得到的結果是什么?
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#include
"stdafx.h"
class A { private : int m_value; public : A( int value) { m_value = value; } void Print1() { printf( "hello world" ); } void Print2() { printf( "%d" , m_value); } }; int _tmain( int argc, _TCHAR *argv[]) { A *pA = NULL ; pA->Print1(); // "hello world" pA->Print2(); // ERROR return 0 ; } |
【分析】
答案是Print1調用正常,打印出hello world,但運行至Print2時,程序崩潰。調用Print1時,並不需要pA的地址,因為Print1的函數地址是固定的。編譯器會給Print1傳入一個this指針,該指針為NULL,但在Print1中該this指針並沒有用到。只要程序運行時沒有訪問不該訪問的內存就不會出錯,因此運行正常。在運行print2時,需要this指針才能得到m_value的值。由於此時this指針為NULL,因此程序崩潰了。
【題目4】
運行下面中的代碼,得到的結果是什么?
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class
A { private : int m_value; public : A( int value) { m_value = value; } void Print1() { printf( "hello world" ); } virtual void Print2() { printf( "hello world" ); } }; int _tmain( int argc, _TCHAR *argv[]) { A *pA = NULL ; pA->Print1(); pA->Print2(); return 0 ; } |
【分析】
答案是Print1調用正常,打印出hello world,但運行至Print2時,程序崩潰。Print1的調用情況和上面的題目一樣,不在贅述。由於Print2是虛函數。C++調用虛函數的時候,要根據實例(即this指針指向的實例)中虛函數表指針得到虛函數表,再從虛函數表中找到函數的地址。由於這一步需要訪問實例的地址(即this指針),而此時this指針為空指針,因此導致內存訪問出錯。
【題目5】
靜態成員函數能不能同時也是虛函數?
【分析】
答案是不能。調用靜態成員函數不要實例,但調用虛函數需要從一個實例中獲取指向虛函數表的指針以得到函數的地址,因此調用虛函數需要一個實例。兩者相互矛盾。
【題目6】
運行下列C++代碼,輸出什么?
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include
"stdafx.h"
struct Point3D { int x; int y; int z; }; int _tmain( int argc, _TCHAR *argv[]) { Point3D *pPoint = NULL ; int offset = ( int )(&pPoint->z); printf( "%d" , offset); // 8 return 0 ; } /* + pPoint 0x00000000 {x=??? y=??? z=??? } Point3D * + &pPoint->x 0x00000000 int * + &pPoint->y 0x00000004 int * + &pPoint->z 0x00000008 int * */ |
【分析】
輸出8。&(pPoint->z)的語意是求pPoint中變量z的地址(pPoint的地址0加z的偏移量8),並不需要訪問pPoint指向的內存。只要不訪問非法的內存,程序就不會出錯。
【題目7】
運行下列C++代碼,輸出什么?
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#include
"stdafx.h"
class A { public : A() { Print(); } ~A() { printf( "A is erased.\n" ); } virtual void Print() { printf( "A is constructed.\n" ); } }; class B: public A { public : B() { Print(); } ~B() { printf( "B is erased.\n" ); } virtual void Print() { printf( "B is constructed.\n" ); } }; int _tmain( int argc, _TCHAR *argv[]) { A *pA = new B(); delete pA; B *pB = new B(); delete pB; return 0 ; } /* A is constructed. B is constructed. A is erased. A is constructed. B is constructed. B is erased. A is erased. */ |
【分析】
輸出結果如上所示。
【題目8】
運行下列C#代碼,輸出是什么?
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
namespace
ChangesOnString { class Program { static void Main( string [] args) { String str = "hello" ; str.ToUpper(); str.Insert( 0 , " WORLD" ); Console.WriteLine(str); } } } |
【分析】
輸出是hello。由於在.NET中,String有一個非常特殊的性質:String的實例的狀態不能被改變。如果String的成員函數會修改實例的狀態,將會返回一個新的String實例。改動只會出現在返回值中,而不會修改原來的實例。所以本題中輸出仍然是原來的字符串值hello。如果試圖改變String的內容,改變之后的值可以通過返回值拿到。用StringBuilder是更好的選擇,特別是要連續多次修改的時候。如果用String連續多次修改,每一次修改都會產生一個臨時對象,開銷太大。
【題目9】
在C++和C#中,struct和class有什么不同?
【分析】
在C++中,如果沒有標明函數或者變量是的訪問權限級別,在struct中,是public的;而在class中,是private的。
在C#中,如果沒有標明函數或者變量的訪問權限級別,struct和class中都是private的。
struct和class的區別是:struct定義值類型,其實例在棧上分配內存;class定義引用類型,其實例在堆上分配內存。
【題目10】
運行下圖中的C#代碼,輸出是什么?
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
namespace
StaticConstructor { class A { public A( string text) { Console.WriteLine(text); } } class B { static A a1 = new A( "a1" ); A a2 = new A( "a2" ); static B() { a1 = new A( "a3" ); } public B() { a2 = new A( "a4" ); } } class Program { static void Main( string [] args) { B b = new B(); } } } |
【分析】
打印出四行,分別是a1、a3、a2、a4。
在調用類型B的代碼之前先執行B的靜態構造函數。靜態函數先初始化類型的靜態變量,再執行靜態函數內的語句。因此先打印a1再打印a3。接下來執行B b = new B(),即調用B的普通構造函數。構造函數先初始化成員變量,在執行函數體內的語句,因此先后打印出a2、a4。
【參考】
http://zhedahht.blog.163.com/blog/static/254111742011012111557832/
【原文鏈接】
http://www.cnblogs.com/hellogiser/p/100-interview-questions-of-cplusplus-basics-1-10.html