網站首頁 個人範例 行業範例 行政範例 職場範例 校園範例 書信範例 生活範例 節日文化範例

程序員網絡面試題

欄目: 互聯網 / 發佈於: / 人氣:2.76W

題目(一):我們可以用static修飾一個類的成員函數,也可以用const修飾類的成員函數(寫在函數的最後表示不能修改成員變量,不是指寫在前面表示返回值為常量)。請問:能不能同時用static和const修飾類的成員函數?

程序員網絡面試題

分析:答案是不可以。C++編譯器在實現const的成員函數的時候為了確保該函數不能修改類的實例的狀態,會在函數中添加一個隱式的參數const this*。但當一個成員為static的時候,該函數是沒有this指針的。也就是説此時static的用法和static是衝突的。

我們也可以這樣理解:兩者的語意是矛盾的。static的作用是表示該函數只作用在類型的靜態變量上,與類的實例沒有關係;而const的作用是確保函數不能修改類的實例的狀態,與類型的靜態變量沒有關係。因此不能同時用它們。

題目(二):運行下面的代碼,輸出是什麼?

class A

{

};

class B

{

public:

B() {}

~B() {}

};

class C

{

public:

C() {}

virtual ~C() {}

};

int _tmain(int argc, _TCHAR* argv[])

{

printf("%d, %d, %d", 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。

題目(三):運行下面中的代碼,得到的結果是什麼?

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

pA->Print2();

return 0;

}

分析:答案是Print1調用正常,打印出hello world,但運行至Print2時,程序崩潰。調用Print1時,並不需要pA的地址,因為Print1的函數地址是固定的。編譯器會給Print1傳入一個this指針,該指針為NULL,但在Print1中該this指針並沒有用到。只要程序運行時沒有訪問不該訪問的內存就不會出錯,因此運行正常。在運行print2時,需要this指針才能得到m_value的值。由於此時this指針為NULL,因此程序崩潰了。

題目(四):運行下面中的代碼,得到的結果是什麼?

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指針為空指針,因此導致內存訪問出錯。

題目(五):靜態成員函數能不能同時也是虛函數?

分析:答案是不能。調用靜態成員函數不要實例。但調用虛函數需要從一個實例中指向虛函數表的指針以得到函數的地址,因此調用虛函數需要一個實例。兩者相互矛盾。

題目(六):運行下列C++代碼,輸出什麼?

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

return 0;

}

答案:輸出8。由於在pPoint->z的前面加上了取地址符號,運行到此時的時候,會在pPoint的指針地址上加z在類型Point3D中的偏移量8。由於pPoint的地址是0,因此最終offset的值是8。

&(pPoint->z)的語意是求pPoint中變量z的地址(pPoint的地址0加z的偏移量8),並不需要訪問pPoint指向的內存。只要不訪問非法的內存,程序就不會出錯。

題目(七):運行下列C++代碼,輸出什麼?

class A

{

public:

A()

{

Print();

}

virtual void Print()

{

printf("A is constructed.");

}

};

class B: public A

{

public:

B()

{

Print();

}

virtual void Print()

{

printf("B is constructed.");

}

};

int _tmain(int argc, _TCHAR* argv[])

{

A* pA = new B();

pA;

return 0;

}

答案:先後打印出兩行:A is constructed. B is constructed. 調用B的構造函數時,先會調用B的基類及A的構造函數。然後在A的構造函數裏調用Print。由於此時實例的類型B的部分還沒有構造好,本質上它只是A的一個實例,他的虛函數表指針指向的是類型A的虛函數表。因此此時調用的Print是A::Print,而不是B::Print。接着調用類型B的構造函數,並調用Print。此時已經開始構造B,因此此時調用的Print是B::Print。

同樣是調用虛擬函數Print,我們發現在類型A的構造函數中,調用的是A::Print,在B的構造函數中,調用的是B::Print。因此虛函數在構造函數中,已經失去了虛函數的動態綁定特性。

題目(八):運行下列C#代碼,輸出是什麼?

namespace ChangesOnString

{

class Program

{

static void Main(string[] args)

{

String str = "hello";

per();

rt(0, " WORLD");

eLine(str);

}

}

}

答案:輸出是hello。由於在中,String有一個非常特殊的性質:String的實例的狀態不能被改變。如果String的成員函數會修改實例的狀態,將會返回一個新的String實例。改動只會出現在返回值中,而不會修改原來的實例。所以本題中輸出仍然是原來的字符串值hello。

如果試圖改變String的內容,改變之後的值可以通過返回值拿到。用StringBuilder是更好的選擇,特別是要連續多次修改的時候。如果用String連續多次修改,每一次修改都會產生一個臨時對象,開銷太大。

題目(九):在C++和C#中,struct和class有什麼不同?

答案:在C++中,如果沒有標明函數或者變量是的訪問權限級別,在struct中,是public的;而在class中,是private的。

在C#中,如果沒有標明函數或者變量的訪問權限級別,struct和class中都是private的。struct和class的區別是:struct定義值類型,其實例在棧上分配內存;class定義引用類型,其實例在堆上分配內存。

題目(十):運行下圖中的C#代碼,輸出是什麼?

namespace StaticConstructor

{

class A

{

public A(string text)

{

eLine(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。

題目(11):運行下圖中的C#代碼,輸出是什麼?

namespace StringValueOrReference

{

class Program

{

internal static void ValueOrReference(Type type)

{

String result = "The type " + ;

if (lueType)

eLine(result + " is a value type.");

else

eLine(result + " is a reference type.");

}

internal static void ModifyString(String text)

{

text = "world";

}

static void Main(string[] args)

{

String text = "hello";

ValueOrReference(ype());

ModifyString(text);

eLine(text);

}

}

}

答案:輸出兩行。第一行是The type String is reference type. 第二行是hello。類型String的定義是public sealed class String {...},既然是class,那麼String就是引用類型。

在方法ModifyString裏,對text賦值一個新的字符串,此時改變的不是原來text的內容,而是把text指向一個新的字符串"world"。由於參數text沒有加ref或者out,出了方法之後,text還是指向原來的字符串,因此輸出仍然是"hello".

題目(12):運行下圖中的C++代碼,輸出是什麼?

#include

class A

{

private:

int n1;

int n2;

public:

A(): n2(0), n1(n2 + 2)

{

}

void Print()

{

std::cout << "n1: " << n1 << ", n2: " << n2 << std::endl;

}

};

int _tmain(int argc, _TCHAR* argv[])

{

A a;

t();

return 0;

}

答案:輸出n1是一個隨機的數字,n2為0。在C++中,成員變量的初始化順序與變量在類型中的申明順序相同,而與它們在構造函數的初始化列表中的順序無關。因此在這道題中,會首先初始化n1,而初始n1的參數n2還沒有初始化,是一個隨機值,因此n1就是一個隨機值。初始化n2時,根據參數0對其初始化,故n2=0。

題目(13):編譯運行下圖中的C++代碼,結果是什麼?(A)編譯錯誤;(B)編譯成功,運行時程序崩潰;(C)編譯運行正常,輸出10。請選擇正確答案並分析原因。

#include

class A

{

private:

int value;

public:

A(int n)

{

value = n;

}

A(A other)

{

value = e;

}

void Print()

{

std::cout << value << std::endl;

}

};

int _tmain(int argc, _TCHAR* argv[])

{

A a = 10;

A b = a;

t();

return 0;

}

答案:編譯錯誤。在複製構造函數中傳入的參數是A的一個實例。由於是傳值,把形參拷貝到實參會調用複製構造函數。因此如果允許複製構造函數傳值,那麼會形成永無休止的遞歸併造成棧溢出。因此C++的標準不允許複製構造函數傳值參數,而必須是傳引用或者常量引用。在Visual Studio和GCC中,都將編譯出錯。

題目(14):運行下圖中的C++代碼,輸出是什麼?

int SizeOf(char pString[])

{

return sizeof(pString);

}

int _tmain(int argc, _TCHAR* argv[])

{

char* pString1 = "google";

int size1 = sizeof(pString1);

int size2 = sizeof(*pString1);

char pString2[100] = "google";

int size3 = sizeof(pString2);

int size4 = SizeOf(pString2);

printf("%d, %d, %d, %d", size1, size2, size3, size4);

return 0;

}

答案:4, 1, 100, 4。pString1是一個指針。在32位機器上,任意指針都佔4個字節的空間。*pString1是字符串pString1的第一個字符。一個字符佔一個字節。pString2是一個數組,sizeof(pString2)是求數組的大小。這個數組包含100個字符,因此大小是100個字節。而在函數SizeOf中,雖然傳入的參數是一個字符數組,當數組作為函數的參數進行傳遞時,數組就自動退化為同類型的指針。因此size4也是一個指針的大小,為4.

題目(15):運行下圖中代碼,輸出的結果是什麼?這段代碼有什麼問題?

#include

class A

{

public:

A()

{

std::cout << "A is created." << std::endl;

}

~A()

{

std::cout << "A is d." << std::endl;

}

};

class B : public A

{

public:

B()

{

std::cout << "B is created." << std::endl;

}

~B()

{

std::cout << "B is d." << std::endl;

}

};

int _tmain(int argc, _TCHAR* argv[])

{

A* pA = new B();

pA;

return 0;

}

答案:輸出三行,分別是:A is created. B is created. A is d。用new創建B時,回調用B的構造函數。在調用B的構造函數的時候,會先調用A的構造函數。因此先輸出A is created. B is created.

接下來運行語句時,會調用析構函數。由於pA被聲明成類型A的指針,同時基類A的析構函數沒有標上virtual,因此只有A的析構函數被調用到,而不會調用B的析構函數。

由於pA實際上是指向一個B的實例的指針,但在析構的時候只調用了基類A的析構函數,卻沒有調用B的析構函數。這就是一個問題。如果在類型B中創建了一些資源,比如文件句柄、內存等,在這種情況下都得不到釋放,從而導致資源泄漏。

問題(16):運行如下的C++代碼,輸出是什麼?

class A

{

public:

virtual void Fun(int number = 10)

{

std::cout << "A::Fun with number " << number;

}

};

class B: public A

{

public:

virtual void Fun(int number = 20)

{

std::cout << "B::Fun with number " << number;

}

};

int main()

{

B b;

A &a = b;

();

}

答案:輸出B::Fun with number 10。由於a是一個指向B實例的引用,因此在運行的時候會調用B::Fun。但缺省參數是在編譯期決定的。在編譯的時候,編譯器只知道a是一個類型a的引用,具體指向什麼類型在編譯期是不能確定的,因此會按照A::Fun的聲明把缺省參數number設為10。

這一題的關鍵在於理解確定缺省參數的值是在編譯的時候,但確定引用、指針的虛函數調用哪個類型的函數是在運行的時候。

問題(17):運行如下的C代碼,輸出是什麼?

char* GetString1()

{

char p[] = "Hello World";

return p;

}

char* GetString2()

{

char *p = "Hello World";

return p;

}

int _tmain(int argc, _TCHAR* argv[])

{

printf("GetString1 returns: %s.", GetString1());

printf("GetString2 returns: %s.", GetString2());

return 0;

}

答案:輸出兩行,第一行GetString1 returns: 後面跟的是一串隨機的內容,而第二行GetString2 returns: Hello World. 兩個函數的區別在於GetString1中是一個數組,而GetString2中是一個指針。

當運行到GetString1時,p是一個數組,會開闢一塊內存,並拷貝"Hello World"初始化該數組。接着返回數組的首地址並退出該函數。由於p是GetString1內的一個局部變量,當運行到這個函數外面的時候,這個數組的內存會被釋放掉。因此在_tmain函數裏再去訪問這個數組的內容時,結果是隨機的。

當運行到GetString2時,p是一個指針,它指向的是字符串常量區的一個常量字符串。該常量字符串是一個全局的,並不會因為退出函數GetString2而被釋放掉。因此在_tmain中仍然根據GetString2返回的地址得到字符串"Hello World"。

問題(18):運行下圖中C#代碼,輸出的結果是什麼?

namespace StaticVariableInAppDomain

{

[Serializable]

internal class A : MarshalByRefObject

{

public static int Number;

public void SetNumber(int value)

{

Number = value;

}

}

[Serializable]

internal class B

{

public static int Number;

public void SetNumber(int value)

{

Number = value;

}

}

class Program

{

static void Main(string[] args)

{

String assamblyName = ntryAssembly()Name;

AppDomain domain = teDomain("NewDomain");

er = 10;

String nameOfA = typeof(A)Name;

A a = teInstanceAndUnwrap(assamblyName, nameOfA) as A;

umber(20);

eLine("Number in class A is {0}", er);

er = 10;

String nameOfB = typeof(B)Name;

B b = teInstanceAndUnwrap(assamblyName, nameOfB) as B;

umber(20);

eLine("Number in class B is {0}", er);

}

}

}

答案:輸出兩行,第一行是Number in class A is 10,而第二行是Number in class B is 20。上述C#代碼先創建一個命名為NewDomain的應用程序域,並在該域中利用反射機制創建類型A的一個實例和類型B的一個實例。我們注意到類型A是繼承自MarshalByRefObject,而B不是。雖然這兩個類型的結構一樣,但由於基類不同而導致在跨越應用程序域的邊界時表現出的行為將大不相同。

由於A繼承MarshalByRefObject,那麼a實際上只是在缺省的域中的一個代理,它指向位於NewDomain域中的A的一個實例。當umber時,是在NewDomain域中調用該方法,它將修改NewDomain域中靜態變量er的值並設為20。由於靜態變量在每個應用程序域中都有一份獨立的拷貝,修改NewDomain域中的靜態變量er對缺省域中的靜態變量omain沒有任何影響。由於eLine是在缺省的應用程序域中輸出er,因此輸出仍然是10。

B只從Object繼承而來的類型,它的實例穿越應用程序域的邊界時,將會完整地拷貝實例。在上述代碼中,我們儘管試圖在NewDomani域中生成B的實例,但會把實例b拷貝到缺省的域。此時,調用umber也是在缺省的域上進行,它將修改缺省的域上的er並設為20。因此這一次輸出的是20。

問題(19):運行下圖中C代碼,輸出的結果是什麼?

int _tmain(int argc, _TCHAR* argv[])

{

char str1[] = "hello world";

char str2[] = "hello world";

char* str3 = "hello world";

char* str4 = "hello world";

if(str1 == str2)

printf("str1 and str2 are same.");

else

printf("str1 and str2 are not same.");

if(str3 == str4)

printf("str3 and str4 are same.");

else

printf("str3 and str4 are not same.");

return 0;

}

答案:輸出兩行。第一行是str1 and str2 are not same,第二行是str3 and str4 are same。

str1和str2是兩個字符串數組。我們會為它們分配兩個長度為12個字節的空間,並把"hello world"的內容分別拷貝到數組中去。這是兩個初始地址不同的數組,因此比較str1和str2的值,會不相同。str3和str4是兩個指針,我們無需為它們分配內存以存儲字符串的內容,而只需要把它們指向"hello world“在內存中的地址就可以了。由於"hello world”是常量字符串,它在內存中只有一個拷貝,因此str3和str4指向的是同一個地址。因此比較str3和str4的值,會是相同的。

問題(20):運行下圖中C#代碼,輸出的結果是什麼?並請比較這兩個類型各有什麼特點,有哪些區別。

namespace Singleton

{

public sealed class Singleton1

{

private Singleton1()

{

eLine("Singleton1 constructed");

}

public static void Print()

{

eLine("Singleton1 Print");

}

private static Singleton1 instance = new Singleton1();

public static Singleton1 Instance

{

get

{

return instance;

}

}

}

public sealed class Singleton2

{

Singleton2()

{

eLine("Singleton2 constructed");

}

public static void Print()

{

eLine("Singleton2 Print");

}

public static Singleton2 Instance

{

get

{

return ance;

}

}

class Nested

{

static Nested() { }

internal static readonly Singleton2 instance = new Singleton2();

}

}

class Program

{

static void Main(string[] args)

{

t();

t();

}

}

}

答案: 輸出三行:第一行“Singleton1 constructed”,第二行“Singleton1 Print”,第三行“Singleton2 Print”。

當我們調用t時,運行時會自動調用Singleton1的靜態構造函數,並初始化它的靜態變量。此時會創建一個Singleton1的實例,因此會調用它的構造函數。Singleton2的實例是在Nested的靜態構造函數裏初始化的。只有當類型Nested被使用時,才回觸發運行時調用它的靜態構造函數。我們注意到我們只在ance裏面用到了Nested。而在我們的代碼中,只調用了t。因此不會創建Singleton2的實例,也不會調用它的構造函數。

這兩個類型其實都是單例模式(Singleton)的實現。第二個實現Singleton2只在真的需要時,才會創建實例,而第一個實現Singleton1則不然。第二個實現在空間效率上更好。

問題(21):C#是一門託管語言,那麼是不是説明只要用C#,就能保證不會出現內存泄露和其他資源泄漏?如果不是,在哪些情況下可能會出現泄漏?

答案:C#不能保證沒有資源泄漏。比如如下幾種情況可能會造成資源泄漏:(1) 調用Native code,比如用P/Invoke或者調用COM;(2) 讀寫文件時的,沒有及時close stream, 或者連數據庫時,沒有及時關閉連接,也算資源泄漏?(3)註冊事件後沒有remove,導致publisher和subscriber的強依 賴,垃圾回收可能會被推遲;(4)還定義了一些方法直接申請非託管內存,比如cHGlobal和cCoTaskMem。通過這種方式得到的內存,如果沒有及時釋放,也會造成內存泄露。

問題(22):下面的兩段C#有哪些不同?

static void CatchException1()

{

try

{

Function();

}

catch

{

throw;

}

}

static void CatchException2()

{

try

{

Function();

}

catch (Exception e)

{

throw e;

}

}

答案:兩個函數的catch都是重新拋出截獲的exception,但拋出的exception的call stack是不一樣的。對於第一種方法,exception的call stack是從最開始的拋出地點開始的。對於第二種方法,exception的call stack是從CatchException2開始的,最初拋出的地方相關的信息被隱藏了。

問題(23):運行下圖中的C++代碼,打印出的結果是什麼?

bool Fun1(char* str)

{

printf("%s", str);

return false;

}

bool Fun2(char* str)

{

printf("%s", str);

return true;

}

int _tmain(int argc, _TCHAR* argv[])

{

bool res1, res2;

res1 = (Fun1("a") && Fun2("b")) || (Fun1("c") || Fun2("d"));

res2 = (Fun1("a") && Fun2("b")) && (Fun1("c") || Fun2("d"));

return res1 || res2;

}

答案:打印出4行,分別是a、c、d、a。

在C/C++中,與、或運算是從左到右的順序執行的。在計算rest1時,先計算Fun1(“a”) && Func2(“b”)。首先Func1(“a”)打印出內容為a的一行。由於Fun1(“a”)返回的是false, 無論Func2(“b”)的返回值是true還是false,Fun1(“a”) && Func2(“b”)的結果都是false。由於Func2(“b”)的結果無關重要,因此Func2(“b”)會略去而不做計算。接下來計算Fun1(“c”) || Func2(“d”),分別打印出內容c和d的兩行。

在計算rest2時,首先Func1(“a”)打印出內容為a的一行。由於Func1(“a”)返回false,和前面一樣的道理,Func2(“b”)會略去不做計算。由於Fun1(“a”) && Func2(“b”)的結果是false,不管Fun1(“c”) && Func2(“d”)的結果是什麼,整個表達式得到的結果都是false,因此Fun1(“c”) && Func2(“d”)都將被忽略。

問題(24):運行下面的C#代碼,打印出來的結果是什麼?

struct Person

{

public string Name;

public override string ToString()

{

return Name;

}

}

class Program

{

static void Main(string[] args)

{

ArrayList array = new ArrayList();

Person jim = new Person() {Name = "Jim"};

(jim);

Person first = (Person)array[0];

= "Peter";

eLine(array[0]ring());

}

}

答案:Person的定義是一個struct,因此是一個值類型。在運行到語句Person first = (Person)array[0]的時候,first是array[0]的一個拷貝,first和array[0]不是一個實例。因此修改first對array[0]沒有影響。

問題(25):運行下面的C++代碼,打印的結果是什麼?

class Base

{

public:

void print() { doPrint();}

private:

virtual void doPrint() {cout << "Base::doPrint" << endl;}

};

class Derived : public Base

{

private:

virtual void doPrint() {cout << "Derived::doPrint" << endl;}

};

int _tmain(int argc, _TCHAR* argv[])

{

Base b;

t();

Derived d;

t();

return 0;

}

答案:輸出兩行,分別是Base::doPrint和Derived::doPrint。在print中調用doPrint時,doPrint()的寫法和this->doPrint()是等價的,因此將根據實際的類型調用對應的doPrint。所以結果是分別調用的是Base::doPrint和Derived::doPrint2。如果感興趣,可以查看一下彙編代碼,就能看出來調用doPrint是從虛函數表中得到函數地址的。