在CSDN上發(fā)帖討論關(guān)于靜態(tài)函數與全局靜態(tài)函數的區別時(shí),有網(wǎng)友指出沒(méi)有全局靜態(tài)函數之說(shuō),既然是全局函數又怎么能是靜態(tài)的那?關(guān)于這個(gè)問(wèn)題,我不想正面回答,static是C++關(guān)鍵字之一,它的作用就是:把有static修飾的函數或者變量變?yōu)殪o態(tài)的,這里的靜態(tài)而不僅僅表示存儲在靜態(tài)區,同時(shí)也標示這個(gè)函數或者變量為文件內編譯有效,也就是本單元編譯有效,從這一點(diǎn)上來(lái)理解,全局靜態(tài)函數其實(shí)就是本編譯單元有效的函數。而這時(shí)和static對應關(guān)系應該就是extern,由extern聲明,表示外部有效,可以直接調用,那么從這個(gè)角度看extern修飾的函數或者變量才是全局的。Extern在C編碼中非常多的出現。不過(guò)我們這里討論的是static問(wèn)題,所以不想討論過(guò)多的extern的問(wèn)題。
同時(shí)通過(guò)這篇帖子,我也了解網(wǎng)友們想多了解些什么?大家覺(jué)得從理論方面討論這些問(wèn)題很沒(méi)有意思?甚至人懷疑我是不是從事C++工作的,提到static函數的應用,無(wú)非是類(lèi)名::函數。對于這些我在發(fā)帖中也提到“每周一貼的目的是整理出來(lái)在各個(gè)角度看待問(wèn)題的觀(guān)點(diǎn),深刻的了解議題中討論問(wèn)題的應用環(huán)境”,我在這里不是想征求一個(gè)問(wèn)題的解決方案,也不是為吸引人眼球而故意考察某項特性。我想討論的是大家能從自己的開(kāi)發(fā)環(huán)境中出發(fā),討論在實(shí)際開(kāi)發(fā)中的應用。我也不希望通過(guò)這篇引導大家只討論面試題或者算法或者語(yǔ)言特性的。而是更多的從實(shí)際出發(fā),談?wù)勛约旱膽?,講講自己的設計,更加關(guān)心的是語(yǔ)言特性給我們帶來(lái)設計思想的變化。大家很討厭這種說(shuō)教式的人,以免引起別人惡心,所以我還不說(shuō)了。接下來(lái)我談?wù)勎覍o態(tài)函數的認識,不正確的地方,歡迎指正。
查遍了Cpp2003,也沒(méi)有找到關(guān)于靜態(tài)函數的幾乎完美的解釋?zhuān)哉堅试S我用大白話(huà)的形式簡(jiǎn)單說(shuō)明一下,在函數或者變量前面加上static修飾符號,以便把函數或者變量在類(lèi)內或者文件范圍內共享,那么我們把這種函數和變量叫靜態(tài)函數和靜態(tài)變量。
The static specifier can be applied only to names of objects and functions and to anonymous unions There can be no static function declarations within a block, nor any static function parameters.
我們把函數和變量聲明為靜態(tài)的有什么優(yōu)點(diǎn)那,從靜態(tài)變量的角度看,更容易理解一些
使用靜態(tài)數據成員可以節省內存,因為它是所有對象所公有的,因此,對多個(gè)對象來(lái)說(shuō),靜態(tài)數據成員只存儲一處,供所有對象共用。靜態(tài)數據成員的值對每個(gè)對象都是一樣,但它的值是可以更新的。只要對靜態(tài)數據成員的值更新一次,就可以保證所有對象都能夠訪(fǎng)問(wèn)到被更新后的值,這樣可以提高效率和節省內存空間。這是靜態(tài)變量的優(yōu)點(diǎn)同時(shí)也是他的一個(gè)缺點(diǎn),在類(lèi)中聲明了靜態(tài)變量,無(wú)論用戶(hù)使用使用這個(gè)類(lèi),而這個(gè)靜態(tài)變量都會(huì )申請他需要的內存容量。對于多線(xiàn)程的情況下,訪(fǎng)問(wèn)靜態(tài)變量我們需要加一些異步機制,防止多個(gè)線(xiàn)程同時(shí)修改靜態(tài)變量。
類(lèi)內靜態(tài)函數比全局靜態(tài)函數的用處要多的多,在這里先講簡(jiǎn)單的
全局靜態(tài)函數的應用比較常見(jiàn)的就是
static int fun()
{
...;
return 1;
}
當我們希望在多個(gè)類(lèi)中調用fun函數時(shí),我們必須把fun聲明為static類(lèi)型,不然在link時(shí)編譯器會(huì )發(fā)現多個(gè)關(guān)于fun的定義。這種函數的應用,多少帶有C的色彩,尤其當我們在C環(huán)境寫(xiě)好的函數,移植到C++中時(shí),需要在函數前面需要加上static,而如果我們需要移植多個(gè)函數時(shí),更通用的一種方法是使用未命名名字空間
namespace{
int fun()
{
...;
return 1;
}
…
}
也許有人問(wèn)如果在未命名名字空間中的函數再加上static修飾符號會(huì )怎么樣?這個(gè)就完全取決以使用的編譯器,可能會(huì )直接報錯,也可能可以編譯并正常使用。不過(guò)這樣寫(xiě),代碼從語(yǔ)言層次上就很難移植編譯環(huán)境,所以盡量不要這樣寫(xiě)。
關(guān)于這樣的應用,在我從前的項目中實(shí)施過(guò)。當時(shí)我需要定義很多函數,執行三角形,已經(jīng)像素轉換方面的計算,所以把這些函數聲明為static的。
對于與全局靜態(tài)函數,我們繼續類(lèi)內靜態(tài)函數和靜態(tài)變量的討論,我想他們的應用是兩者區別的最好體現。
對于類(lèi)內靜態(tài)變量,我們需要在類(lèi)外初始化,這是在使用靜態(tài)變量需要注意的地方,有些初學(xué)者很容易在這里出錯。類(lèi)內靜態(tài)變量使用最多的就是計算類(lèi)的實(shí)例化個(gè)數。
class A
{
static int i; // 外部初始化為0
public:
A() // 構造函數
{
++i;
}
~A() // 析構函數
{
--i;
}
static int CreateObjectNumber() // 或者創(chuàng )建對象的數目
{
return i;
}
};
也許讀者認為這種方法,只是用來(lái)學(xué)習而已,實(shí)際很少使用。我不贊同這個(gè)觀(guān)點(diǎn)。
在程序設計中,比如當前我做的皮膚系統中,我需要對所有創(chuàng )建的對象進(jìn)行,并統計創(chuàng )建和銷(xiāo)毀的對象是否相等,從而判斷是否存在泄漏問(wèn)題。所以我定義這樣的一個(gè)類(lèi)
// 由于版權,我只保留說(shuō)明問(wèn)題的部分
class SkinBaseImpl
{
public:
SkinBaseImpl(void);
virtual ~SkinBaseImpl(void);
public:
…
#ifdef SKINTRACE
private:
static int _ObjNum;
#endif
};
#ifdef SKINTRACE
int SkinBaseImpl::_ObjNum = 0;
#endif
/*=======================================================================*/
/**
* @bri: SkinBaseImpl構造函數
* @param: void
*/
SkinBaseImpl::SkinBaseImpl(void):_useSkin(USE)
{
#ifdef SKINTRACE
TRACE("Create new object, the number is %d\n",++_ObjNum);
#endif
}
/*=======================================================================*/
/**
* @bri: SkinBaseImpl析構函數
* @param: void
*/
SkinBaseImpl::~SkinBaseImpl(void)
{
#ifdef SKINTRACE
TRACE("free the %d object\n",_ObjNum--);
#endif
}
其他需要管理和統計創(chuàng )建銷(xiāo)毀對象的類(lèi)就可以從這個(gè)類(lèi)繼承。
在這里我定義了宏SKINTRACE,當需要使用trace,以便獲取創(chuàng )建對象的數量時(shí),定義這個(gè)宏就可以。而不需要時(shí),比如發(fā)布版時(shí),只需注銷(xiāo)到SKINTRACE的定義,這樣不會(huì )在發(fā)布頒布留下任何痕跡。
通過(guò)上面的例子,我們知道靜態(tài)函數和靜態(tài)變量的幾個(gè)特性
1:靜態(tài)變量受public,protected ,private限制,也就是如果靜態(tài)變量是protected或者private類(lèi)型的,在類(lèi)外不能訪(fǎng)問(wèn),比如
A::i是錯誤的
這條規則同樣適用于靜態(tài)函數
2:靜態(tài)變量在類(lèi)內聲明,而必須在類(lèi)外初始化,模版類(lèi)中應用也是這樣。這里我們在static后面加上const類(lèi)型,可以直接初始化。比如
Class A
{
// Static int I = 5; // error
Static const int I = 5; // ok
Int m_list[I];
}
而這里I的應用也無(wú)非是Int m_list[I];
3:靜態(tài)成員函數只能訪(fǎng)問(wèn)類(lèi)的靜態(tài)變量,而類(lèi)的成員函數也可以訪(fǎng)問(wèn)類(lèi)的靜態(tài)變量,這樣就可以通過(guò)靜態(tài)成員變量建立類(lèi)的靜態(tài)成員函數和類(lèi)對象的關(guān)聯(lián)關(guān)系。
4:還存在一種靜態(tài)變量,他不是全局靜態(tài)變量,而是函數內的靜態(tài)變量,如下例中的i,這算是對全局靜態(tài)變量的一種補充。
int fun()
{
static int i = 3;
++i;
return i;
}
這種方式的好處時(shí),只用調用fun函數時(shí),靜態(tài)變量i才申請內存,這也符合lazy evaluation的設計要求。只有當需要時(shí),才去申請。
同樣作為破壞封裝的一種技術(shù)應用是友元函數或者友元類(lèi)的應用,很多人形象比喻這種方式是在封裝的物體上開(kāi)了一個(gè)小小的洞,不提倡使用這種技術(shù)。其實(shí)任何技術(shù)都有他應用的場(chǎng)所,不然就不會(huì )出現這種技術(shù)。不過(guò)不去了解這種特性,也許永遠我們不會(huì )知道這些技術(shù)的重要性。碰見(jiàn)這些技術(shù)也只會(huì )使用有色眼鏡去看。友元函數的特征基本如下
<!--[if !supportLists]-->1) <!--[endif]-->必須在類(lèi)的說(shuō)明中說(shuō)明友元函數,說(shuō)明時(shí)以關(guān)鍵字friend開(kāi)頭,后跟友元函數的函數原型,友元函數的說(shuō)明可以出現在類(lèi)的任何地方,包括在private和public部分,不受private限制
<!--[if !supportLists]-->2) <!--[endif]-->友元函數不是類(lèi)的成員函數,所以友元函數的實(shí)現和普通函數一樣,在實(shí)現時(shí)不用"::"指示屬于哪個(gè)類(lèi),只有成員函數才使用"::"作用域符號;
<!--[if !supportLists]-->3) <!--[endif]-->友元函數不能直接訪(fǎng)問(wèn)類(lèi)的成員,只能訪(fǎng)問(wèn)對象成員,所以在調用友元函數時(shí),確保友元類(lèi)的必須實(shí)例化。
<!--[if !supportLists]-->4) <!--[endif]-->友元函數可以訪(fǎng)問(wèn)對象的私有成員,但普通函數不行,這個(gè)需要注意,尤其是在友元類(lèi)中,有時(shí)候發(fā)現兩個(gè)類(lèi)互相為友元類(lèi),確不能調用成員函數,就是這個(gè)原因。
<!--[if !supportLists]-->5) <!--[endif]-->調用友元函數時(shí),在實(shí)際參數中需要指出要訪(fǎng)問(wèn)的對象,也可以把對象聲明為全局對象而在友元函數中調用,當然在友元函數中可以調用其他全局函數,或者實(shí)例對象等操作。
使用友員函數最大的優(yōu)點(diǎn)就是,不用對類(lèi)中的每個(gè)變量寫(xiě)Get/Set接口函數。尤其是當類(lèi)中有大量的私有成員變量,而又不想為每個(gè)變量設置接口,同時(shí)又需要外部的某個(gè)函數調用。這樣最好就是把這個(gè)函數聲明為友元函數,我們在一些開(kāi)源項目中很常見(jiàn)這種技術(shù),比如阿agentx++。
說(shuō)道現在我一直沒(méi)有提到模版中靜態(tài)函數的應用,其實(shí)對于模版的應用,我不是很熟練。只能簡(jiǎn)單的說(shuō)明一下
class AU
{
public:
AU(){
};
string GetAU()
{
return "Base--GetAU";
}
virtual string GetAUU()
{
return "Base--GetAUU";
};
virtual ~AU(){};
};
template <class T,class TBase>
class TEMU:public TBase
{
public:
string GetAA()
{
T* pt = static_cast<T*>(this);
return pt->GetA(); // 這里調用的是static string GetA()函數
}
string GetBB()
{
T* pt = static_cast<T*>(this);
return pt->GetB(); // 這里調用的是string GetB()
}
public:
string GetA()
{
return "TEMU - GetA";
}
string GetB()
{
return "TEMU - GetB";
}
};
class DeriveTEMU : public TEMU<DeriveTEMU,AU>
{
public:
static string GetA() // 注意這里是靜態(tài)函數
{
return "DeriveTEMU - GetA";
}
string GetB()
{
return "DeriveTEMU - GetB";
}
};
測試用力
DeriveTEMU u;
TEMU<DeriveTEMU,AU> *p = &u;
cout << p->GetAA() << endl;
cout << p->GetBB() << endl;
輸出結果
DeriveTEMU - GetA
DeriveTEMU – GetB
在這里我們看到,調用類(lèi)內靜態(tài)函數的方式并不是簡(jiǎn)單的類(lèi)名::函數的形式,而是通過(guò)模版父類(lèi)調用子類(lèi)靜態(tài)函數,同樣也給出了,調用普通函數的方式。這種機制可以理解為模版繼承關(guān)系中的虛繼承關(guān)系。當認識到模版中的靜態(tài)函數使用,也許會(huì )更大的改變我們對靜態(tài)函數的印象,這種機制在A(yíng)TL,WTL中有廣泛的應用,幾乎每種涉及到消息影射關(guān)系的類(lèi)中,都使用這種方式。
以上是我對靜態(tài)函數,靜態(tài)變量的簡(jiǎn)單認識。也沒(méi)有面面俱到的講道靜態(tài)的細節,這樣說(shuō)估計很多人會(huì )反感的。如果有錯誤,不妥,或者您認為重要但沒(méi)有提到的,歡迎指正。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1800095




