程序代码也有风格,这算不得什么新鲜事。早在20世纪80年代, C语言程序员就必须在K&R风格和ANSI风格之间择善而从。但平心而论,我确实没有见过哪一种语言能像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++语言在塑造新形象、引进新观念方面的决心和勇气。
3. OWL和MFC——窗口环境下的风格变异
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在语言风格上的贡献远不如它在窗口框架技术方面的贡献大。