引子
余好程序,喜BCB(Borland C++ Builder)官觅。一日見C#之屬性聲明纵菌,頓覺清爽。其后偶有所啟休涤,遂整以條理咱圆、載以文字,且冠文章之名功氨。于夫同好而喜BCB者序苏,或可有益。若是疑故,其旨達(dá)矣杠览。
背景描述——宏的歷史地位
面向?qū)ο蟮腃++語言推出后,曾經(jīng)在C中極其重要的宏命令似乎變得很少使用了纵势。連C++大師Bjarne Stroustrup在他的經(jīng)典C++教程《The C++ Programming Language》中也如是說:“關(guān)于宏的第一項規(guī)則是:絕對不應(yīng)該去使用它踱阿,除非你不得不這樣做。因為它將在編譯器看到程序的正文之前重新擺布這些正文钦铁∪砩啵” 就連以前用的最多的定義常量在C++中也有了新關(guān)鍵字const了,在加上其他的inline牛曹、template佛点、enum、namespace機(jī)制等都是為了用做預(yù)處理結(jié)構(gòu)的許多傳統(tǒng)使用方式的替代品。
不過超营,Bjarne Stroustrup自己也承認(rèn)鸳玩,利用宏我們可以設(shè)計出自己的私有語言。只要它能讓我們的工作簡化演闭,那它就是一個有用的宏不跟。
“可以設(shè)計出自己的私有語言”,這對于我們而言意味著什么米碰?又如何使用它來好好改造現(xiàn)有的語言窝革,以便簡化我們的工作呢?當(dāng)然吕座,所謂簡化必然是對繁雜而言虐译。那么只要是原有的語法顯得繁雜的地方我們都可以使用宏來簡化它。
一個很好的典型就是BCB中的屬性聲明語法吴趴。
背景描述——BCB和C#的屬性聲明語法
要在BCB的類聲明中聲明一個屬性漆诽,首先必須聲明一個屬性數(shù)據(jù)的存放者——一個數(shù)據(jù)成員。當(dāng)然一般而言是在私有部分聲明的史侣。然后如果在讀或?qū)憣傩詴r還有一些其他的操作拴泌,那就得聲明讀或?qū)憣傩缘姆椒ㄎ荷怼R话銇碇v為了保證數(shù)據(jù)的合法行惊橱,往往至少需要一個寫屬性方法。最后還得在public或__publish(組件用)中發(fā)布出來箭昵,才能被類的使用者可見税朴。
還是來看看具體的代碼吧。
假設(shè)一個女孩類CGirl家制,有個漂亮指數(shù)屬性Prettiness正林,并且取值范圍是0—3。
0:其丑無比颤殴;
1:馬馬虎虎觅廓;
2:相貌端莊
3:傾國傾城。
那么在BC中大抵都得這么寫:
頭文件CGirl.h中
class CGirl
{
public :
__property int Prettiness={read=FGetPrettiness,write=FSetPrettiness};
...//(以下略去其他公共成員若干)
private:
int FPrettiness;
...//(以下略去其他私有數(shù)據(jù)成員若干)
int __fastcall FGetPrettiness();
void __fastcall FSetPrettiness (int vPrettiness)
...//(以下略去其他私有方法成員若干)
};
代碼文件CGirl.cpp中
int __fastcall CGirl:: FGetPrettiness()
{
return FPrettiness;
}
void __fastcall CGirl:: FSetPrettiness (int vPrettiness)
{
if (vPrettiness<=3 && vPrettiness>=0)
FPrettiness=v FPrettiness;
}
……
對于簡單的類涵但,這樣的定義也許還不算太麻煩杈绸。屬性的聲明定義還能一目了然。但如果類稍微復(fù)雜一點——具體一點如果是一個1000行的類呢矮瘟?(冒汗)結(jié)果往往是在諾大的代碼里翻來翻去瞳脓,為的就是查看和某個屬性相關(guān)的讀或?qū)懛椒ǖ亩x。
而現(xiàn)在澈侠,在微軟.Net的發(fā)布后劫侧,.Net Framework一下子成了最流行的東東。它以不可阻擋之勢襲來。而且微軟為了和SUN的Java爭個高下烧栋,也不顧現(xiàn)在編程語言的泛濫之勢写妥,在Visual Studio中又加入了一種新的語言。我想不用我說大家早就知道了——對就是它:C#审姓,又一個和C++語法十分相似的語言耳标。
對于.Net和C#的故事實在太多了,不可能也不需要在這里說了邑跪。但是C#的對于屬性聲明的語法卻給筆者留下了深刻的影響次坡。這恰恰也正是今天我們所關(guān)心的。那么來看看C#的屬性聲明語法吧画畅。同樣以上面的CGirl類作為例子砸琅,用C#寫來可能是下面這個樣子:
class CGirl
{
private int FPrettiness;
public int Prettiness //定義屬性
{
get //讀屬性方法
{
return FPrettiness;
}
set //寫屬性方法
{
if (value<=3 && value>=0)
FPrettiness=value;
}
}
}
不用我多說,孰繁孰簡已經(jīng)很明顯了轴踱。
下面症脂,我們就來動手看看如何通過宏命令把BCB繁雜的屬性聲明語法變的一如C#般簡潔。這不是變戲法但其精彩卻一點也不亞于它淫僻。
技術(shù)基礎(chǔ)——宏定義語法
首先诱篷,我們得了解宏的定義格式:#define <宏名> [展開內(nèi)容]
其實這樣的東西幾乎是什么都不能做的,但要是給它帶上參數(shù)就不一樣了:
//帶參數(shù)的宏定義格式
#define <宏名(參數(shù)1[雳灵,參數(shù)2棕所,參數(shù)3……])<展開內(nèi)容>
比如一個帶參宏的聲明:#define WRITEN(a) cout<<a<<endl
在代碼中假如這么寫:WRITEN("巧用宏命令,改造C++ Builder");
那么悯辙,其經(jīng)過宏擴(kuò)展后就會變成這樣:cout<<"巧用宏命令琳省,改造C++ Builder"<<endl;
這就是帶參宏的擴(kuò)展規(guī)律了,并不復(fù)雜躲撰。為了要把BCB的屬性聲明語法變成C#针贬,我們另外還需要知道兩個操作符:/
和 ##
。
-
/
操作符:當(dāng)宏定義的擴(kuò)展內(nèi)容不止一行時的連接符拢蛋。
要換行寫宏定義的話桦他,在行尾加上它就行了。如:
#define TITLE 巧用宏命/
令谆棱,改造C++ Builder
就等效于:
#define TITLE 巧用宏命令快压,改造C++ Builder
-
##
操作符:參數(shù)擴(kuò)展連接符。
它可以把前面的內(nèi)容和參數(shù)連接成一個沒有間隔的整體础锐。還是來看看例子:
定義一個宏:#define VAR(i, j) (i##j)
那么如果在代碼中這樣寫:VAR(x, 6)
嗓节,它將會被擴(kuò)展為x6
。利用這個特性我們就可以對給定的宏參數(shù)加以修飾皆警,以便適應(yīng)我們的要求了拦宣。
技術(shù)實現(xiàn)——開始改造BCB
基礎(chǔ)的東西就這么多了,那就來看看需要怎么做來改造BCB。
首先鸵隧,確定宏的名稱绸罗,這里暫時就以PROPERTY為宏名。
其次豆瘫,確定宏的參數(shù)珊蟀。
我們的目的要使宏使用起來和C#的語法盡量相似,那么宏使用起來多少應(yīng)該像這樣:
PROPERTY
(
屬性的可見性 屬性類型 屬性名
{
get
{
//讀屬性代碼
}
set
{
//寫屬性代碼
}
}
)
但上面這樣的偽代碼雖然最接近C#語法外驱,卻無法定義出適合的宏育灸。原因是宏所需要的一些重要的參數(shù)元素?zé)o法從整體的代碼中分隔出來。所謂重要的參數(shù)元素就是上面?zhèn)未a中描述的 “屬性的可見性”昵宇、“屬性類型”磅崭、“元素名”、“讀屬性代碼部分”以及“寫屬性部分”這些東西瓦哎。而上面?zhèn)未a中從“屬性的可見性”開始一直到最后一個大括號是一個整體砸喻,成為一個參數(shù)。這自然無法正確擴(kuò)展了蒋譬。我們應(yīng)該把這五個重要的元素分開為一個個的參數(shù)割岛,原則當(dāng)然是和上面的偽代碼形式越相似越好:
PROPERTY
(
屬性的可見性, 屬性類型, 屬性名,
get
{
//讀屬性代碼
}
,
set
{
//寫屬性代碼
}
)
這樣就形式上而言達(dá)到了把每個元素分開而獨立成為一個宏參數(shù)的要求。
然后犯助,就上面的使用形式癣漆,我們來設(shè)計這個宏。
先給上面的每個元素取個標(biāo)識也切,分別是:
屬性的可見性 pRegion
屬性類型 pType
屬性名 pName
讀屬性代碼 pGetMethod
寫屬性代碼 pSetMethod
還記得在BCB聲明一個屬性需要的兩個方法函數(shù)嗎扑媚,分別對應(yīng)讀和寫腰湾。在宏定義里必須采用固定的函數(shù)名雷恃,而且必須和屬性名相關(guān)。這樣我們定為在屬性名前分別加FGet和FSet來構(gòu)成讀寫方法函數(shù)名费坊,其中寫方法的參數(shù)(對應(yīng)屬性的新值)始終是value(一如在C#中)倒槐。
在宏中的定義:
//讀方法函數(shù)
pType __fastcal FGet##pName () pGetMethod
//寫方法函數(shù)
void __fastcall FSet##pName(pType value) pSetMethod
別忘了還有一個屬性發(fā)布語句:
__property pType pName={read= FGet##pName ,write= FSet##pName };
整理一下,宏的聲明最后變成下面這樣:
PROPERTY(pRegion,pType,pName,pGetMethod,pSetMethod) /
private: /
pType FGet##pName () /
pGetMethod /
void FSet##pName (pType value) /
pSetMethod /
pRegion: /
__property pType pName={ /
read=FGet ##pName /
,write=FSet ##pName /
};
請注意:在宏的使用代碼中附井,讀或?qū)懛椒ê瘮?shù)還有g(shù)et或set標(biāo)識符讨越,而它們必須被屏蔽,否則無法通過把pGetMethod或pSetMethod附加在函數(shù)聲明后而形成完整的函數(shù)實現(xiàn)永毅。
通過下面的兩句宏聲明來屏蔽get或set:
#define get
#define set
到這里把跨,這個宏定義就算基本成功了。然而沼死,由于屬性可能有只讀的着逐、只寫的或不需要讀方法函數(shù)的(很多屬性只要直接讀取成員數(shù)據(jù)的值就可以了,不需要額外的代碼)種類區(qū)別,我們還需要另外幾個可以適用于這些情況的宏耸别。參看具體代碼健芭。
最后,使用這樣的宏有幾個值得特別注意的地方:
- 注意分隔各個宏參數(shù)的逗號秀姐,特別是get和set方法之間的不能漏掉慈迈。
- 注意get和set方法實現(xiàn)不能顛倒位置。
完整實現(xiàn)——具體的代碼
////////////////////////////////////Goldroc Opus///////////////////////////////////////
//屬性定義宏(For C++ Builder)
//說明:仿照C#中的屬性聲明語法
//(頭文件中)
// 定義可讀寫屬性:PROPERTY(屬性可見性省有,屬性類型痒留,屬性名,讀屬性代碼,寫屬性代碼)
// 定義只讀屬性: PROPERTY_READONLY(屬性可見性蠢沿,屬性類型狭瞎,屬性名,讀屬性代碼)
// 定義只寫屬性: PROPERTY_WRITEONLY(屬性可見性,屬性類型搏予,屬性名,寫屬性代碼)
// 定義不需要讀方法的屬性宏:PROPERTY_DIRECT_READ(屬性可見性熊锭,屬性類型,屬性名,屬性對應(yīng)的數(shù)據(jù)雪侥,寫屬性代碼)
// 定義不需要讀方法的只讀屬性宏:PROPERTY_DIRECT_READ(屬性可見性碗殷,屬性類型,屬性名,屬性對應(yīng)的數(shù)據(jù))
//
/////////////////////////////////////////////////////////////////////////////////////////
//下列宏定義一般的規(guī)則如下:
//在private 中放記錄屬性數(shù)據(jù)的成員變量速缨,取名為在屬性名前加F
//在private 中放設(shè)置屬性數(shù)據(jù)的成員變量的函數(shù)锌妻,取名為在成員變量名前加FGet或FSet
// 行參取名為value
//如:屬性 int Left
// PROPERTY
// ( public,int,Left,
// get
// {
// return _Left; //在屬性名前加前綴_表示屬性的當(dāng)前值
// }
// , //注意這個逗號不能丟!Q仿粹!
// set
// {
// _Left=value; //新的值為value
// }
// )
//
//注意:get和set方法不能顛倒位置!
//
// 只讀屬性 int Left
// PROPERTY_READONLY
// ( public,int,Left,
// get
// {
// return _Left; //在屬性名前加前綴_表示屬性的當(dāng)前值
// }
// )
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#ifndef PROPERTY_MACROH
#define PROPERTY_MACROH
//---------------------------------------------------------------------------
#define get /*get*/
#define set /*set*/
#define PROPERTY(pRegion,pType,pName,pGetMethod,pSetMethod) /
private: /
pType FGet##pName () const /
pGetMethod /
void FSet##pName (const pType& value) /
pSetMethod /
pRegion: /
__property pType pName={ /
read=FGet ##pName /
,write=FSet ##pName /
};
//只讀屬性宏
#define PROPERTY_READONLY(pRegion,pType,pName,pGetMethod) /
private: /
pType FGet##pName () const /
pGetMethod /
pRegion: /
__property pType pName={ /
read=FGet ##pName /
};
//只寫屬性宏
#define PROPERTY_WRITEONLY(pRegion,pType,pName, pSetMethod) /
private: /
void FSet##pName (const pType& value) /
pSetMethod /
pRegion: /
__property pType pName={ /
write=FSet ##pName /
};
//不需要讀方法的屬性宏
#define PROPERTY_DIRECT_READ(pRegion,pType,pName,pData,pSetMethod) /
private: /
void FSet##pName (const pType& value) /
pSetMethod /
pRegion: /
__property pType pName={ /
read= pData /
,write=FSet ##pName /
};
//不需要讀方法的只讀屬性宏
#define PROPERTY_DIRECT_READONLY(pRegion,pType,pName,pData) /
pRegion: /
__property pType pName={ /
read= pData /
};
#endif
//---------------------------------------------------------------------------
(以上代碼在BCB6中調(diào)試通過)
這樣原茅,把以上代碼保存為一個頭文件吭历,如property_macro.h。以后在自己的代碼中#include
包含進(jìn)這個頭文件即可擂橘。根據(jù)同樣的原理晌区,我們甚至可以把BCB的整個類聲明語法都改造成類C#的,這里不再累述了通贞,讀者可以自己動手試試朗若。
現(xiàn)在,讓我們來嘗嘗在BCB中寫“C#”的感覺吧昌罩。