巧用宏命令译仗,改造BCB——在BCB中實現(xiàn)類C#屬性聲明語法

引子

余好程序,喜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#针贬,我們另外還需要知道兩個操作符:/##

  1. / 操作符:當(dāng)宏定義的擴(kuò)展內(nèi)容不止一行時的連接符拢蛋。
    要換行寫宏定義的話桦他,在行尾加上它就行了。如:
 #define TITLE 巧用宏命/
 令谆棱,改造C++ Builder

就等效于:

 #define TITLE 巧用宏命令快压,改造C++ Builder
  1. ## 操作符:參數(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ū)別,我們還需要另外幾個可以適用于這些情況的宏耸别。參看具體代碼健芭。
最后,使用這樣的宏有幾個值得特別注意的地方:

  1. 注意分隔各個宏參數(shù)的逗號秀姐,特別是get和set方法之間的不能漏掉慈迈。
  2. 注意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#”的感覺吧昌罩。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哭懈,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子茎用,更是在濱河造成了極大的恐慌遣总,老刑警劉巖你虹,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異彤避,居然都是意外死亡傅物,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門琉预,熙熙樓的掌柜王于貴愁眉苦臉地迎上來董饰,“玉大人,你說我怎么就攤上這事圆米∽湓荩” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵娄帖,是天一觀的道長也祠。 經(jīng)常有香客問我,道長近速,這世上最難降的妖魔是什么诈嘿? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮削葱,結(jié)果婚禮上奖亚,老公的妹妹穿的比我還像新娘。我一直安慰自己析砸,他們只是感情好昔字,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著首繁,像睡著了一般作郭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弦疮,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天夹攒,我揣著相機(jī)與錄音,去河邊找鬼挂捅。 笑死芹助,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的闲先。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼无蜂,長吁一口氣:“原來是場噩夢啊……” “哼伺糠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起斥季,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤训桶,失蹤者是張志新(化名)和其女友劉穎累驮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舵揭,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡谤专,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了午绳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片置侍。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拦焚,靈堂內(nèi)的尸體忽然破棺而出蜡坊,到底是詐尸還是另有隱情,我是刑警寧澤赎败,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布秕衙,位于F島的核電站,受9級特大地震影響僵刮,放射性物質(zhì)發(fā)生泄漏据忘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一搞糕、第九天 我趴在偏房一處隱蔽的房頂上張望若河。 院中可真熱鬧,春花似錦寞宫、人聲如沸萧福。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鲫忍。三九已至,卻和暖如春钥屈,著一層夾襖步出監(jiān)牢的瞬間悟民,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工篷就, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留射亏,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓竭业,卻偏偏與公主長得像智润,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子未辆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容