劍指offer和網上有很多考察類的面試題,我覺得很具有代表性,在這裏做一個總結:
簡答題一:我們可以用static修飾一個類的成員函數,也可以用const修飾類的成員函數?
一。劍指offer面試題2:實現一個單例模式的類。
要求:設計一個類,我們只能生成該類的一個實例。
該題目要求是這個類只能生成一個實例,所以我們要想辦法控制類的構造函數,因爲生成實例的入口都是構造函數。
解法一:將構造函數設置爲私有的,對於類的外部將是不可見的。並且將它設置爲靜態的,只有當條件滿足時唯一的調用一次
<pre name="code" class="cpp"><span style="font-family:Microsoft YaHei;font-size:14px;">class Singleton
{
public:
static Singleton GetInstance()
{
if(instance==NULL)
{
instance=new Singleton();
}
return instance;
}
private:
static Singleton(){};
static Singleton instance;
}</span>
這種解法思路清晰且直觀,但是這隻適合單線程的模式下,如果多線程的情況下,會出現兩個線程同時訪問這個類,當遇到if語句時instance的值還爲NULL,兩個線程就都創建出一個實例,這就與題目要求不符合。
基於上面的解法一,我們可以得到可行解法二:
<pre name="code" class="cpp"><span style="font-family:Microsoft YaHei;font-size:14px;">class Singleton
{
public:</span>
<span style="font-family:Microsoft YaHei;font-size:14px;"> Singleton getInstance()</span>
{ if (instance == NULL) {lock(); if (instance == NULL) { instance = new Singleton(); } unlock(); } return instance;}<span style="font-family:Microsoft YaHei;font-size:14px;"> private:
static Singleton(){};
static Singleton instance;
}</span>
這樣只夠極低的機率下,通過越過了if (instance == NULL)的線程纔會有進入鎖定臨界區的可能性,這種機率還是比較低的,不會阻塞太多的線程,但爲了防止一個線程進入臨界區創建實例,另外的線程也進去臨界區創建實例,又加上了一道防禦if (instance == NULL),這樣就確保不會重複創建了。二。劍指offer面試題48:不能被繼承的類
要求:用C++設計一個不能被繼承的類。
要想要實現一個不能繼承的類在C#中是比較簡單的,使用關鍵字sealed就行,所以在這裏我們既要實現一個簡易版的sealed關鍵字,是不是很牛的感覺,其實並不是很難得一件事。
解法一:要想要一個類不能繼承,我們首先應該想到的是將類的構造函數設置爲私有的,如果一個類的構造函數被設置爲私有的,那麼它的子類將會無法調用它的構造函數,這和不能繼承是一樣的效果。同題一一樣,我們還想要得到這個類的一個實例並且釋放它,這是作爲一個類所應該具有的功能。我們可以通過定義公有的靜態函數來創建和釋放類的實例。
<span style="font-family:Microsoft YaHei;font-size:14px;">class A
{
public:
static A * Construct(int n)
{
A *pa = new A;
pa->num = n;
cout<<"num is:"<<pa->num<<endl;
return pa;
}
static void Destruct(A * pIntance)
{
delete pIntance;
pIntance = NULL;
}
private:
A(){}
~A(){}
private:
int num;
};
</span>
這種方法只可以創建堆上的對象,不可以構建棧上的對象。
這種方法相信大家都可以想到,但是爲了打動安靜的面試官,我們得拿出點料來才行。
解法二:虛擬繼承+友元類
<span style="font-family:Microsoft YaHei;font-size:14px;">#include<iostream>
using namespace std;
template <typename T>
class Base
{
friend T;
private:
Base() {}
~Base() {}
};
class Finalclass : virtual public Base<Finalclass>
{
public:
Finalclass() {}
~Finalclass() {}
};
</span>
Finalclass就是那個不能被繼承的類。
繼承於Base,Base爲虛基類,因爲它是Base的友元,所以,它可以訪問基類的私有構造函數,以及析構函數。編譯運行時是正確的。也就是說,可以創建堆上的對象,並且可以構建棧上的對象。
三。含有指針成員的類的拷貝
題目:下面是一個數組類的聲明和實現,分析這個類有什麼問題,並針對問標提出集中解決方案。
<span style="font-family:Microsoft YaHei;font-size:14px;"> Template<typename T> class Array
{
public:
Array(unsigned arraySize):data(0), size(arraySize)
{
if(size > 0)
data = new T[size];
}
~Array()
{
if(data) delete[] data;
}
void setValue(unsigned index, const T& value)
{
if(index < size)
data[index] = value;
}
T getValue(unsigned index) const
{
if(index < size)
return data[index];
else
return T();
}
private:
T* data;
unsigned size;
}</span>
眼尖的朋友應該看出來一點端倪,此類只定義了帶參數的構造函數,沒有定義無參數的構造函數,還有沒有定義拷貝構造函數和複製運算符的重載,系統將會自動生成一個,但是,恰恰系統就會坑了你,此類中有指針的數據成員,這是很危險的
編譯器生成的缺省的構造拷貝函數和拷貝運算符的重載函數,對指針實行的是按位拷貝,僅僅只是拷貝指針的地址,而不會拷貝指針的內容。若執行Array A(10);Array B(A)時。A.data和B.data指向的同一地址。當A或者B中任意一個結束其生命週期調用析構函數時,會刪除data。由於他們的data指向的是同一個地方,兩個實例的data都被刪除了。但另外一個實例並不知道它的data已經被刪除了,當企圖再次用它的 data的時候,程序就會不可避免地崩潰。
<span style="font-family:Microsoft YaHei;font-size:14px;">private:
Array(const Array& copy);
const Array& operator = (const Array& copy);</span>
但是這樣的類並不是一個功能齊全的類,所以我們只能自己實現着兩個函數來解決問題了:<span style="font-family:Microsoft YaHei;font-size:14px;">rray(const Array& copy):data(0), size(copy.size)
{
if(size > 0)
{
data = new T[size];
for(int i = 0; i < size; ++ i)
setValue(i, copy.getValue(i));
}
}
const Array& operator = (const Array& copy)
{
if(this == ©)
return *this;
if(data != NULL)
{
delete []data;
data = NULL;
}
size = copy.size;
if(size > 0)
{
data = new T[size];
for(int i = 0; i < size; ++ i)
setValue(i, copy.getValue(i));
}
}</span>