程序代碼也有風格,這算不得什麼新鮮事。早在20世紀80年代, C語言程序員就必須在K&R風格和ANSI風格之間擇善而從。
<!-- frame contents -->
<!-- /frame contents -->
但平心而論,我確實沒有見過哪一種語言能像C++這樣,在代碼風格方面表現得如此詭谲和難以捉摸:誰也說不清C++代碼究竟能衍生出多少種迥異的風格,但我知道,有許多C++初學者在面對不同風格的C++代碼時,經常會誤以為自己看到的是好幾種完全不同的編程語言——僅此一點就足以提醒我們,研究和廓清C++語言風格的演化和發展規律已是當務之急了。 和文體學家們研究歷朝歷代文體變遷的工作相仿,研究C++語言風格的流變史也沒有什麼捷徑可走。我們只能依據劉勰在《文心雕龍》中提倡的“原始以表末”[1]的研究方法,循著歷史的脈絡,推求代碼風格的來源,探尋風格演化的內因,並借以闡明技術發展的趨勢和規律。
1. 帶類的C——對C語言風格的因襲
在1983年12月Bjarne Stroustrup采納Rick Mascitti的建議,將其發明的新語言命名為“C++”之前,人們一直用“帶類的C(C with Classes)”來稱呼這種脫胎於C語言的,帶有數據抽象機制的“方言”。雖然帶類的C在本質上僅僅是一種可以被預處理程序Cpre轉換為傳統C語言代碼(這類似於我們在Oracle中見到的Pro*C語言的預處理過程)的擴展性語言,但它的確在風格上奠定了後來所有C++代碼的基礎。class stack {
char s[SIZE];
char* min;
char* top;
char* max;
void new();
public:
void push(char);
char pop();
}; 這段“帶類的C”代碼錄自Stroustrup所著的《C++語言的設計和演化》。代碼中的new()其實是類stack的構造函數,這與後來的C++語言有很大的不同。 顯而易見,帶類的C在風格上幾乎完整地承襲了C語言的衣缽。代碼中的聲明語句看上去與C語言一模一樣,class的結構也與C語言中strUCt的結構大致相仿,這些跡象反映出C++語言來源於C又盡量與C保持兼容的設計思想——這種設計思想既為C++的迅速普及提供了便利(C++語言的順利推廣顯然得益於C語言已有的龐大用戶群),也在C++的語言風格中深深地烙上了C語言的印記,以至於在若干年後,當C++語言已經基本具備了“獨立人格”的時候,Stroustrup還不得不時常提醒人們要盡量拋開C語言的思維方式。 另一方面,Stroustrup從Simula語言借用的類、派生、訪問控制等面向對象概念在帶類的C中牢牢地扎下了根。據Stroustrup介紹,他為C語言引入面向對象機制的本意在於尋找一種“合適的工具”[2],以便實現分布式系統或解決類似的復雜問題。但無論怎樣,Stroustrup將C的高效和Simula的優雅捆綁在一起的做法都在事實上為C++語言埋下了“雙重性格”的種子——很難說這不是C++語言風格多樣化的直接誘因。
2. I/O流——C++的新形象
假如說C++語言的生身父母分別是C語言和Simula語言的話,那麼,1984年出現的,借助操作符重載實現的I/O流技術就是C++這個幼童甩開父母的庇護,向新的代碼風格邁出的第一步了。ostream& operator<<(ostream&s, const complex& z)
{
return s << '(' << z.real()
<< ',' << z.imag() << ')';
} 上面幾行代碼來自Stroustrup所著《C++程序設計語言》中的示例程序。注重那一行由“<<”連接的代碼,I/O流、變量、字符常量在代碼中被巧妙地串聯在一起。從技術角度看,這種全新語法的引入彌補了C語言中printf()函數族缺乏類型安全機制和擴展能力的弱點。從代碼風格上說,“<<”等通俗易懂的運算符大大改變了程序員對C++語言的第一印象。我自己第一次接觸C++ I/O流庫時,就曾清楚地感覺到,一個試圖擺脫C語言風格束縛的C++精靈正順著“<<”和“>>”組成的溪水“流淌”而來——這種行雲流水般的代碼風格在十幾年前就已經顯示出了C++語言在塑造新形象、引進新觀念方面的決心和勇氣。
更多內容請看C/C++技術專題專題,或
3. OWL和MFC——窗口環境下的風格變異
<!-- frame contents -->
<!-- /frame contents -->
20世紀80年代末到90年代初,X Window、Mac OS、Windows等窗口環境的先後出現為程序設計提出了新的課題,而C++語言兼顧面向對象和傳統開發方法的特性無疑使其成為了窗口環境下編程語言的最佳選擇。一批基於C++語言的窗口框架不僅在商業上取得了成功,也在很大程度上改變了C++語言本身的風格特點。 最早在窗口開發中贏得大多數程序員青睐的C++框架是Borland公司於1992年內置在Borland C++ 3.1中的OWL(Object Windows Library)框架庫。下面這段代碼取自Borland C++ 3.1的示例程序:class TGDIDemoWindow : public TMDIFrame
{
public:
TGDIDemoWindow( LPSTR ATitle, LPSTR MenuName )
: TMDIFrame(ATitle, MenuName) {};
virtual void SetupWindow();
virtual void ArtyDemo( TMessage& ) =
[CM_FIRST + ArtyDemoID];
virtual void Quit( TMessage& ) =
[CM_FIRST + QuitID];
virtual void WMTimer( TMessage& ) =
[WM_FIRST + WM_TIMER];
virtual void WMDestroy( TMessage& ) =
[WM_FIRST + WM_DESTROY];
}; 為了解決窗口編程中最要害的消息映射問題,OWL的設計者為C++語言的成員函數引入了“=[…]”的古怪語法,這是許多用過Borland C++的程序員至今都無法忘懷的一種語言風格。我承認,Borland公司在C++語言的發展初期為我們提供了最好的編譯器和最出色的集成開發環境(IDE),但Borland通過OWL框架為C++引入的另類語言風格的確讓人不敢恭維(客觀地講,這筆賬也不應全算在Borland頭上,因為OWL的前身是Borland從White Water公司購買的框架代碼)。 就在Borland C++ 3.1統治市場兩年以後,Microsoft憑借其當仁不讓的霸氣和聞名的Visual C++系列產品逐漸奪回了Windows開發工具市場的主導權。與Borland不同的是,Visual C++中的MFC(Microsoft Foundation Class)框架庫沒有向OWL那樣肆意篡改C++的語法,而是采用了下面這樣的方式來實現消息映射(代碼取自MSDN示例程序):// Example for BEGIN_MESSAGE_MAP
BEGIN_MESSAGE_MAP( CMyWindow, CFrameWnd )
ON_WM_PAINT()
ON_COMMAND( IDM_ABOUT, OnAbout )
END_MESSAGE_MAP( ) 事實上,用MFC框架編寫的C++代碼在大量使用宏定義等預編譯指令的同時,還把WIN32平台下常見的匈牙利風格(有關標識符大小寫和前綴的書寫規范)發揮到了極限。這一點用不著我多費口舌,許多程序員僅從代碼的大小寫特征上就能百分之百地確定代碼中是否使用了MFC框架。 很遺憾,MFC為C++打造的語言風格並沒有得到C++專家們的首肯。例如,包括Stroustrup在內的許多學者都建議我們盡量少用甚至不用宏定義等預處理指令。在這一點上,MFC的做法顯然和專家們的論調背道而馳。應當說,是Microsoft的霸氣造就了MFC的巨大成功;但從純粹的語言學角度看,MFC在語言風格上的貢獻遠不如它在窗口框架技術方面的貢獻大。
4. 模板——現代C++風格的基礎
Stroustrup於1988年首次公布了與模板(template)有關的語法設計。毫無疑問,這是一項對現代C++的語言風格影響最大的技術改進。模板的概念來自Clu語言,並綜合了Smalltalk和Ada語言中相關技術的優點。1991年後,包含模板機制的開發環境(DEC C++、IBM C++、Borland C++等)陸續問世。但直到1995年STL(Standard Template Library)模板庫逐漸發展成熟以後,模板技術才在程序員中迅速普及開來。 下面的例子取自SGI STL的示例代碼,它基本反映了使用模板技術後C++代碼的整體風格:template <class InputIterator, class T>
InputIterator find(InputIterator first,
InputIterator last, const T& value)
{
while (first != last && *first != value)
++first;
return first;
} 在這樣的C++代碼中,除了少數幾個要害字和操作符以外,我們幾乎找不到多少C語言的痕跡了。模板技術兼顧了類型安全和編碼靈活性的雙重需求,但它同時也為C++語言引入了一種更加精妙但也較難理解(相對於沒有模板的代碼而言)的代碼風格。許多傳統的C語言擁護者討厭這種風格的代碼,但更多的新生代程序員對其鐘愛有加。1998年,在ANSI/ISO標准化委員會的支持下,STL被作為標准C++庫(Standard C++ Library)的一部分收入了C++國際標准之中。今天,以模板、異常等現代C++技術為代表的語言風格也已在事實上成為了C++世界的“官方風格”。
更多內容請看C/C++技術專題專題,或
5. ATL——COM時代的另類C++
除了STL模板庫之外,還有一個與模板風格相關的例子。下面的代碼片斷取自Visual C++自動生成的ATL控件工程:
<!-- frame contents -->
<!-- /frame contents -->
class ATL_NO_VTABLE CMyATLObj :
public IMyATLObj,
public IpersistStreamInitImpl
<CMyATLObj>,
public IOleControlImpl<CMyATLObj>,
public IOleObjectImpl<CMyATLObj>,
public IoleInPlaceActiveObjectImpl
<CMyATLObj>,
public IViewObjectExImpl<CMyATLObj>,
public IoleInPlaceObjectWindowlessImpl
<CMyATLObj>,
public IPersistStorageImpl<CMyATLObj>,
public IspecifyPropertyPagesImpl
<CMyATLObj>,
public IQuickActivateImpl<CMyATLObj>,
public IDataObjectImpl<CMyATLObj>,
public IProvideClassInfo2Impl
<&__uuidof(CMyATLObj), NULL>,
public CComControl<CMyATLObj>
...... 注重控件類CMyATLObj的代碼,CMyATLObj類居然是從N個接口類和控件類中派生出來的,類的聲明語句中隨處可見模板的身影——這就是Microsoft為我們設計的別具一格的ATL風格的代碼了。之所以要不惜代價地大量使用模板、多重繼續等語言特性,這主要為了適應COM、OLE、ActiveX等在架構上本來就相對復雜的技術體系。但這樣一來,使用ATL的代碼在所有C++代碼中,就擁有了一副異乎平常的長相了:到處都是尖括號,到處都是以“I”打頭的標識符,甚至還有多重尖括號的嵌套……假如要求一個剛學會C++語言的程序員馬上讀懂一大段ATL代碼,我想,用不了幾分鐘,他就會被代碼中那些晦澀、離奇的語言風格折磨得精神崩潰了。
6. 標准C++——一種全新的語言?
C++語言的標准化進程遠遠落後於語言本身的普及速度。1990年以後,ANSI/ISO的C++標准化委員會才將包括Stroustrup在內的大批專家以及包括Apple、Borland、DEC、HP、IBM、Microsoft、Sun、Unisys在內的知名公司召集在一起,像所有國家的議會或人民代表大會一樣通過沒完沒了的會議、討論和投票制定C++的國際標准。標准直到1998年9月才正式發布。在國際標准化組織的檔案庫裡,C++標准的代號是ISO/IEC 14882:1998。 Stroustrup建議我們把標准C++當作一種全新的語言來學習[3]。這一說法顯然是基於這樣一個事實:標准C++語言已經擁有了一種穩定的、可以推廣的語言風格,即,通過對STL等既有技術的肯定,ANSI/ISO委員會在1998年的標准中正式認可了包括模板、容器類、I/O流庫、異常處理等典型語言特征的現代C++風格。風格的穩定意味著語言本身的進步和成熟,也意味著程序員們對C++的熟悉必須上升到一個新的層次——那些至今還在編寫僅由類和C語言庫函數組成的C++代碼的程序員,一定會成為Stroustrup及其同仁們的取笑對象的。 Stroustrup的《C++程序設計語言》第3版對標准C++風格做了最權威的闡釋。在Stroustrup等專家學者的號召下,越來越多的項目開始編寫符合標准C++風格的代碼。這一點在許多開放源代碼的項目中體現得非凡明顯。這多半是由於,使用C++語言的開源項目大多都不會像大企業裡的項目組那樣,在語言風格上會受到公司背景或歷史習慣的羁絆。在具體的編程實踐中,開源項目的程序員們一方面可以果斷地貫徹標准C++的語言風格,另一方面也可以根據自己的喜好為代碼增添一些感情色彩。例如,在OpenOffice的源碼中,標識符的前綴規范就相當有特點,連指針和引用類型的變量都由不同的前綴字母區分;下面給出的Linux桌面治理器KDE 3.1.4的源代碼片斷則顯示出,開發KDE的程序員在代碼風格上或多或少受到了Java語言風格的影響:class delUser: public KDialogBase {
Q_OBJECT
public:
delUser(KUser *AUser, QWidget *parent = 0,
const char *name = 0);
bool getDeleteHomeDir()
{ return m_deleteHomeDir->isChecked(); }
bool getDeleteMailBox()
{ return m_deleteMailBox->isChecked(); }
private:
QCheckBox *m_deleteHomeDir;
QCheckBox *m_deleteMailBox;
};
更多內容請看C/C++技術專題專題,或
7. 讀不懂的代碼——兼容並包的語言風格
說到標准C++語言風格,有必要給大家看一段非常古怪但也非常有趣的代碼。你看得懂下面這段C++代碼嗎?它是真正的C++代碼嗎?
<!-- frame contents -->
<!-- /frame contents -->
%:include <iostream>
using namespace std;
%:define MAX 5
void main()
<%
int m<:MAX:>;
int i = 1;
for (i = 0; i < MAX; i++)
<%
m<:i:> = i;
if (i not_eq 3 and i < 5)
cout << i << endl;
%>
%> 這是我自己編寫的一段代碼。你也許無法在Visual C++環境下運行它,但它的語法的確符合1998年C++標准的規定。在GNU C++環境下,我曾成功地將其編譯為可執行程序。 簡單說來,這段風格詭異的C++代碼其實是根據C++標准中關於可替換標記(Alternative Tokens)的規定而編寫的。該規定的設計初衷是要適應歐洲某些國家的標准字符集缺少“{”、“#”等標點符號(非凡是在一些傳統的終端設備上)的現狀。嚴格地講,這算不得一種真正的語言風格,但類似的規定的確體現了ANSI/ISO委員會在語言設計上兼容並包的寬廣胸襟。
8. C++Builder——Borland的復興之路
Borland公司在發布了Borland C++ 3.1之後,就因為不思進取而將C++開發工具的市場拱手讓給了Microsoft[4]。在經歷了Borland C++ 4.0、4.5和5.0等版本的失敗後,1997年,Borland推出了全新的C++開發工具C++Builder。這個在市場上為Borland挽回了顏面的產品不但在界面風格上與Borland的支柱產品Delphi別無二致,甚至還在產品內部直接照搬了Delphi的VCL(Visual Component Library)庫。結果,使用C++Builder開發的代碼天生就受到了Delphi風格的傳染,長相酷似Pascal語言了(以下代碼取自C++Builder 6.0的示例代碼):class TFormClrDlg : public TForm
{
published: // IDE-managed Components
TColorDialog *ColorDialog;
TButton *Button;
TPanel *Panel1;
void fastcall ButtonClick(TObject *Sender);
private: // User declarations
public: // User declarations
virtual fastcall TFormClrDlg(TComponent* Owner);
}; 說實話,盡管C++Builder在市場上的表現不錯,但我還是不喜歡Borland將C++語言與Delphi中的Object Pascal語言刻意混淆的做法。也許在Borland這種做法的背後有提高產品通用性、縮短產品開發周期等體面的理由,但使用C++Builder開發出的代碼在外表上已經離標准C++風格越來越遠了。 值得注重的是,Borland於2003年推出了其下一代C++開發工具 ——C++BuilderX。讓人哭笑不得的是,這一次Borland居然將C++開發環境構築在了用Java語言實現的PrimeTime平台上,這多少將C++語言推向了一種極為尴尬的處境。不過,C++BuilderX也為我們帶來了一些好消息:在後續的版本中,C++BuilderX將集成vxWindows框架庫[5],在這種框架下開發的C++代碼顯然要比使用VCL的代碼具備更多的標准C++風格。
9. Visual C++ .NET——革命還是叛逆?
Microsoft將C++引入.NET環境的舉動其實比Borland還要激進。單從風格上說,使用Visual C++ .NET開發的代碼可能兼具MFC、ATL、標准C++、.NET托管代碼等多種不同的風格。其中,對C++語言本身影響最大的,當然要數.NET托管代碼為C++注入的若干新鮮血液了:#using <mscorlib.dll>
using namespace System;
using namespace System::Reflection;
using namespace System::Security::Permissions;
public __value enum SomeStuff {
e1 = 1,
e17 = 17
};
[attribute(AttributeTargets::Class, AllowMultiple=true)]
public __gc class ABC {
public:
ABC(int __gc[]) {}
ABC() {}
ABC(int) {}
ABC(int, float) {}
ABC(SomeStuff) {}
ABC(String*) {}
int rgnField __gc [];
double rgdField __gc [];
double dField;
}; 上述代碼來自MSDN中的示例程序。看到Microsoft大刀闊斧地為C++語言引入的垃圾收集、Attribute屬性等新特性和新技術,看到.NET托管代碼新奇得近乎離經叛道的語言風格,我不知道是應該為Microsoft在發展通用語言平台上的努力而歡呼雀躍,還是應該為C++在C#語言陰影下日漸屈居.NET大戲中的配角而灰心喪氣。也許,語言風格和程序員的感受在Microsoft眼中,都是些不值一提的小事,它們哪能和.NET的宏偉戰略及Microsoft的強大帝國相提並論呢?
10. 回顧和展望
語言風格的變遷從一個側面反映了技術思想和產業需求的嬗變規律。從1979年Stroustrup完成第一個Cpre預處理程序算起,C++語言來到這個世界上已經快滿25個年頭了。這是一種在實踐中誕生、成長和發展起來的語言。也許,Stroustrup從一開始就壓根兒也沒想把它設計成像Smalltalk那樣純粹的面向對象語言。開放性、高效率、兼容性和擴展性的需求將C++語言塑造成了一種典型的多模式(Multiparadigm)語言。無論是C++早期對Simula語言的繼續,還是後來對Smalltalk、Ada、Clu等語言的借鑒,無論是ANSI/ISO標准風格的迅速普及,還是Visual C++ .NET在技術創新上的不懈努力,所有這些歷史變遷都說明,C++在風格上的多樣性主要源自C++語言本身“海納百川”的胸襟和氣概。 5年以後,當C++步入而立之年的時候,它會給我們帶來新的驚喜嗎?我們還會看到更加新奇的C++語言風格嗎?也許,沒有誰能給出准確的答案。但作為程序員,我們至少應該知道:無論面對什麼樣的軟件需求,無論使用什麼樣的思維方式,C++語言都賦予了我們選擇語言風格的最大自由;當我們真正理解了C++語言的精神實質之後,這種自由也必將成為所有優秀軟件和優雅代碼的堅實基礎。
參考文獻
[1] 劉勰. 文心雕龍·序志.
[2] Stroustrup B. C++ 語言的設計和演化. 北京: 機械工業出版社, 2002
[3] Stroustrup B. Learning Standard C++ as a New Language. C/C++ Users Journal. pp 43-54. May 1999.
[4] 李維. Borland 傳奇. 北京: 電子工業出版社, 2003
[5] 李維. 細說Borland C++BuilderX. 程序員. 2003.11
更多內容請看C/C++技術專題專題,或