# [27] 编码规范
## FAQs in section [18]:
* [27.1] 有哪些好的C++编码规范?
* [27.2] 编码规范是必需的吗?有它就够了么?
* [27.3] 我们机构应该根据以前用C的经验来制定编码规范么?
* [27.4] `<xxx>`和`<xxx.h>`这两种头文件有何不同?
* [27.5] 我应该在我的代码中使用using namespace std么?
* [27.6] ?:操作符能够写出难以阅读的代码,它是邪恶的么?
* [27.7] 我应该把变量声明放在函数体中间还是开头?
* [27.8] 哪种源代码文件名最好?foo.cpp?foo.C?foo.cc?
* [27.9] 哪种头文件名最好?foo.H?foo.hh?foo.hpp?
* [27.10] C++有没有一些像lint一样的规范原则?
* [27.11] 为何人们对指针转换和/或引用转换如此担忧?
* [27.12]这两种标识符的名字:that_look_like_this和thatLookLikeThis,哪种更好?
* [27.13]从哪里可以找到一些编码规范么?
* [27.14] 我应该用“不常见”的语法么?
## 27.1 有哪些好的C++编码规范?
很高兴你在这里找答案,而不仅仅是试图建立自己的编码规范。
但要注意在[comp.lang.c++](news:comp.lang.c++)上有一些人对这个话题非常敏感。几乎所有的软件工程师在某个时候,都曾经被一些人利用过,这些人把编码规范当作是一种“权力游戏”。另外,有些不知道自己在说些什么的人也设定了一些C++编码规范,因此当这些标准的制定者实际写代码时,标准往往就成了只是用来观赏的东西。这些情况促使人们不相信编码规范。
很明显,问这个问题的人们是想要得到训练,因此他们并_不_逃避他们在知识上的欠缺。但虽然如此,在[comp.lang.c++](news:comp.lang.c++)上发帖问这个问题常常会导致争吵,而不是解决方案。
Sutter和Alexandrescu对这个问题有本非常好的书叫"C++ Coding Standards"(220页, Addison-Wesley出版,2005,ISBN 0-321-11358-6)。里面提供了101条规则、指导和最佳时间。作者和编辑们提供了一些确切的材料,并且对结对审查团队很有帮助。所有这些都使得此书更有价值。值得购买。
## 27.2 编码规范是必需的吗?有它就够了么?
编码规范不会把一个不懂OO的程序员变得懂OO了,这需要培训和经验。编码规范的好处是,当大型机构协调不同群体的程序员时,有助于降低分化的产生。
但仅有编码规范是不够的。编码规范减少了新人的自由度,使他们不用操心一些事情,这是好的。但仅有编码规范是不够的,还需要更多实用的指南。机构需要一种一致的设计和实现的_哲学_。例如,是使用强类型还是弱类型?在接口中使用引用还是指针?用stream I/O还是stdio?C++代码能够调用C代码么?反过来可以么?抽象基类应该怎么使用?继承是应该采用实现方法还是特化方法?应采用何种测试策略和审查策略?接口应该统一为每个数据成员提供get()和/或set()么?接口应该是从外向内设计还是从内向外设计?错误是应该用try/catch/throw处理还是用错误码?等等。
需要有一份有关详细_设计_的“伪标准”。我推荐一种三头并进的方法来达到这种标准化程度:培训、[指导](http://www.sunistudio.com/cppfaq/how-to-learn-cpp#[28.1])和库。培训能够提供“强化的指示”,指导能够让OO在实际中得到应用而不仅仅是教过就没事了。而高质量的C++类库则是一种“长期的指示”。针对这三种“训练”的商业市场正不断扩大。经历过这些困难的机构给出的意见非常统一:_购买现成的,不要试图构建自己的_。购买库、培训、工具和咨询。如果一个公司试图提供自足的工具,同时又制作应用程序或系统,那么它将发现很难取得成功。
很少人会认为编码规范是“理想的”,甚至都算不上“良好”。但在上述的机构中,编码规范又是必要的。
以下条目给出了一些基本的约定和风格方面的指南。
## 27.3 我们机构应该根据以前用C的经验来制定编码规范么?
不要!
不管你用C的经验多么丰富,不管你对C掌握的多么熟练,一名好的C程序员不一定就会是一名好的C++程序员。从C转换到C++,并不只是学习一下C++中++部分的语法和语义。那些希望获得OO好处的机构,如果不在“OO编程”中真正运用“OO”技术,那么他们就是在自欺欺人;他们的蠢行最终会在财报上表现出来。
应该由C++专家来打造C++的编码规范。开始可以在[comp.lang.c++](news:comp.lang.c++)上问问题。寻找能够帮助你避开陷阱的专家。购买程序库,然后看“好”的库能否通过你的编码规范。在获得足够的C++经验之前,_不要_自己制定编码规范。没有标准要好过一份糟糕的标准,因为不合适的“官方”标准会让错误的做法一直存在。现在C++培训和程序库的市场正不断壮大,可以从中吸取经验。
还有:只要对某件事物有需求,就会增加出现伪专家的机会。要三思而后行。同时还要从过去的公司中寻求反馈,因为有娴熟技术的人未必是一名好的沟通者。最后,选一名会教学生的专业人,注意这可不是有足够语言/编程范式相关知识的全职教师。
## 27.4 `<xxx>`和`<xxx.h>`这两种头文件有何不同?
ISO C++标准的头文件不包含`.h`后缀。标准委员会修改了以前的做法。C的头文件和C++头文件在细节上不同。
C++标准库保证包含来自C语言的那18个标准头文件。这些头文件有两种标准风格:`<cxxx>`和`<xxx.h>`(这里`xxx`是头文件的基本文件名,例如`stdio`,`stdlib`等等)。这两种风格的头文件是一样的,但有一点不同:`<cxxx>`风格的头文件将所有声明放在`std`名字空间中,而`<xxx.h>`除了将声明放在`std`名字空间,同时还放在了全局名字空间。委员会这么做是为了已有的C代码能够被C++编译器编译。但`<xxx.h>`是已经过时了,虽然现在仍被标准接受,但可能在以后的标准中不再支持。(参见ISO C++标准的D.5节)。
C++标准库还增加了32个C里面没有直接对应的标准头文件,例如`<iostream>`, `<string>`和`<new>`。你可能会在老的代码中看到#include `<iostream.h>`这种写法,因此一些编译器厂商还提供这些`.h`版本的头文件。但要注意,`.h`版本的头文件可能与标准版本不一样。如果一个程序里有些用`<iostream>`,有些用`<iostream.h>`,那这个程序可能无法正常运行。
在新项目中,应当用`<xxx>`,而不该用`<xxx.h>`。
当修改或扩展使用旧式头文件名的代码时,最好还是遵从那些代码的做法,除非有很重要的理由换用标准头文件(例如标准`<iostream>`提供了一些功能,而厂商的`<iostream.h>`里没有)。如果想要使以后代码符合标准,那么要确保在所有被链接起来的代码中包括外部库,里面所用的所有C++头文件都修改了。
以上内容只对标准头文件有影响。你自己的头文件可以随便怎么命名,参见[27.9]。
## 27.5 我应该在我的代码中使用`using namespace std`么?
可能不该。
人们不喜欢一边又一遍地键入`std::`。他们发现`using namespace std`能够使编译器看到任何`std`中的名字,即使名字前没有被`std`限定也能看到。问题是这会使编译器看到_**所有**_在`std`中的名字,包括那些你没想到的名字。换句话说,这可能导致名字冲突和二义性。
例如,假设你的代码需要计数,然后你定义了一个名为`count`的变量或函数。但`std`库也使用`count`这个名字(这是一个`std`算法),这就可能导致二义性。
你看,名字空间的用处就是用来防止两部分独立开发的代码产生名字冲突。`using`指令(描述`using namespace XYZ`的术语)实际上是把一个名字空间的内容全部引入到另外一个名字空间了,这就违背了名字空间的本意。`using`指令是为了使遗留的C++代码容易迁移到名字空间上来,但至少在新的C++代码中,不该大范围这么做。
如果真的不想敲`std::`,可以使用`using`声明,或使自己适应`std::`(不是办法的办法)
* **使用`using`声明**,这可以引入特定的名字。例如,为了能够在代码中使用`count`同时还不必写`std::`,可以在代码中插入一行`using std::count`。这不大可能会带来混乱或二义性,因为是显式引入名字的。
```
#include <vector>
#include <iostream>
void f(const std::vector<double>& v)
{
using std::cout; // ← using声明允许直接使用cout,前面不必限定。
cout << "Values:";
for (std::vector<double>::const_iterator p = v.begin(); p != v.end(); ++p)
cout << ' ' << *p;
cout << '\n';
}
```
* **让自己适应`std::`**(不是办法的办法):
```
#include <vector>
#include <iostream>
void f(const std::vector<double>& v)
{
std::cout << "Values:";
for (std::vector<double>::const_iterator p = v.begin(); p != v.end(); ++p)
std::cout << ' ' << *p;
std::cout << '\n';
}
```
我个人觉得与其为每个不同的`std`名字决定是否使用`using`声明、并且找到最适合放置这个声明的地方,不如直接敲"`std::`",这样还更快。但这两种方法都不错。记住你是一个团队的一分子,所以要确保使用的方法和其它人保持一致。
## 27.6 `?:`操作符能够写出难以阅读的代码,它是邪恶的么?
不是。但和往常一样,记住可读性是最重要的事情之一。
有人觉得应避免使用`?:`运算符,因为和`if`语句相比,它有时会令人困惑。在很多情况下,`?:`常会使代码更难读懂(因此应该替换为`if`语句)。但有时用`?:`更清晰,因为这会强调到底在干什么事情,而不是强调那里有个`if`。
让我们先来看一个很简单的例子。假设你需要打印出一个函数调用的结果。这时你应该把真正的目的(打印结果)放在开头,然后把函数调用放在后面,因为函数调用是相对次要的(直觉上大多数开发者认为一行开头的内容是最重要的)。
```
// 更好(强调主要目的-打印):
std::cout << funct();
// 不那么好(强调次要目的-函数调用):
functAndPrintOn(std::cout);
```
现在我们把这个观点扩展到`?:`上来。假设你的真正目的是要打印一些东西,但需要做一些额外操作来决定打印的内容。因为在概念上打印是更重要的事,所以我们倾向于把它放在开头,而把用于判断的逻辑放在后面。在下面的例子中,变量`n`代表消息发送者的数量;消息本身被打印到`std::cout`上:
```
int n = /*...*/; // 发送者的数量
// 更好(强调主要目的-打印):
std::cout << "Please get back to " << (n==1 ? "me" : "us") << " soon!\n";
// 不那么好(强调次要目的-函数调用):
std::cout << "Please get back to ";
if (n == 1)
std::cout << "me";
else
std::cout << "us";
std::cout << " soon!\n";
```
已经说过了,通过不同组合使用`?:`、`&&`和`||`等运算符,可以写出很过分且难以阅读的代码(“只写代码”)。例如
```
// 更好(意思很明显):
if (f())
g();
// 不太好(更难理解):
f() && g();
```
我个人觉得这里明确写出`if`会更清晰,因为这强调主要的事情(至于要做什么是根据`f()`的结果来决定的),而不是强调次要的事情(调用`f()`)。换句话说,在这里使用`if`是_恰当_的,原因和上面用`if`是_不恰当_的一样:我们希望把主要事情放在在显眼位置,次要事情放到次要位置。
不管怎样,别忘了可读性是最终目的(至少是目的之一)。你的目标_不_应是为了避免类似`?:`、`||`、`if`或者甚至是`goto`这样的语法结构。如果你变成一个“唯标准论者”,那么你最终会另自己蒙羞,因为任何基于语法的规则总是存在反例。如果你是强调大的目标和指导原则(例如“主要的事情要放在显眼位置”,或者“把重要事情放在开头”,甚至是“让你的代码含义明显容易阅读”),那就好多了。
写出来的代码是要被其它人读的,不是给编译器看的。
## 27.7 我应该把变量声明放在函数体中间还是开头?
在第一次使用的附近声明。
对象是在声明时被初始化(构造)的。如果在函数中间才有足够的信息来初始化一个对象,那就应该把声明放在中间,以便对象可以正确初始化。不要现在开头给对象初始化为一个“空值”,然后在其它地方给它实际“赋值”。这么做是为了运行时的效率。与其先把对象构造到一个错误状态,然后再修正,不如开始就把对象构造正确,这样速度更快。简单的例子表明对像`String`这样简单的类,也会有350%的速度差别。具体的数值可能有所不同,而且整个系统的效率损失肯定小于350%,但的确_会有_效率损失。_不必要_的效率损失。
对这个问题一个常见的回应是:“我们要为对象中的每个数据提供一个`set()`成员函数,这样构造对象的代价就被平摊开了。”这就不仅是损失效率了,因为还导致维护困难。为每个数据成员提供`set()`函数和`public`数据一样糟糕:你把实现技术暴露给外部了。你唯一隐藏掉的是成员对象的物理_名字_,而具体的实现细节(比方说用了一个`List`、一个`String`和一个`float`),则还是给外面知道了。
底线是:局部变量应在靠近第一次使用的地方声明。对C语言专家来说可能不太习惯,但新事物不一定就不好。
## 27.8 哪种源代码文件名最好?`foo.cpp`?`foo.C`?`foo.cc`?
如果已经有一种命名约定了,那就继续使用。否则,需要查一下所使用的编译器接受哪种文件名。常用的是:`.cpp`, `.C`, `.cc`或`.cxx`(当然如果用`.C`的话,那么文件系统需要能够区分大小写,以便不会混淆`.C`和`.c`)。
我们已经使用`.cpp`做为C++源文件名的后缀了,我们也用`.C`。如果用`.C`,那么在把代码移植到大小写不敏感的文件系统时,需要告诉编译器将`.c`文件做为C++源文件对待(例如IBM CSet++是用`-Tdp`选项,Zortech C++编译器用`-cpp`,Borland C++编译器用`-P`)。
关键在于这些文件扩展名中,并不存在说哪个比其它更好。我们通常根据客户的要求来选择(这些问题应当依据商业上的考量,而不是技术)。
## 27.9 哪种头文件名最好?`foo.H`?`foo.hh`?`foo.hpp`?
如果已经有一种命名约定了,那就继续使用。如果还没有,而且不需要让编辑器区分C和C++文件,那就用`.h`好了。否则,就按编辑器的要求来,例如`.H`、`lhh`或`.hpp`。
我们倾向于使用`.h`或`.hpp`做为C++头文件的后缀名。
## 27.10 C++有没有一些像`lint`一样的规范原则?
有的。有些做法一般被认为是危险的。但没有一个是总是“不好”的,因为最糟糕的做法有时也有用武之地。
* `class Fred`的赋值运算符应该将`*this`做为`Fred&`返回(允许将赋值运算串起来)
* 一个类如果有虚。
* 一个类如果有{析构函数、赋值运算符、拷贝构造函数}中的任何一个,一般也需要另外两个。
* `class Fred`的拷贝构造函数和赋值运算符的参数应该用`const`来限定,即`Fred::Fred(const Fred&)`和`Fred& Fred::operator=(const Fred&)`
* 当在构造函数中初始化对象成员时,总是使用初始化列表,而不是用赋值。对于用户定义的类来说,这两种办法在性能上可能会有很大差别(3倍!)
* 赋值运算符需要保证当对自身赋值时不做任何操作,否则可能会有麻烦。有时这要求做显式的判断。
* 重载运算符时,要遵守指导原则。例如,如果类里面重载了`+=`和`+`,那么`a += b`和`a = a + b`一般来说应该是做相同的操作。其它内建/基本的类型也是如此(例如`a += 1`和`++a`;`p[i]`和`*(p+i);`等等)。在编写二元运算符时,可以使用`op=`这种形式来强制达到这个目的。例如:
```
Fred operator+ (const Fred& a, const Fred& b)
{
Fred ans = a;
ans += b;
return ans;
}
```
用这种办法,那些“构造性的”[译注1]二元运算符就不必成为类的[友元](http://www.sunistudio.com/cppfaq/friends.html)了。但有时可以更高效地实现一些普通的操作(例如,如果`class Fred`是`std::string`类型,并且`+=`需要重新分配/拷贝字符串内存,那么最好在开始就能够知道最终的长度)。
[译注1]: 这里“构造性”的指二元运算符的结果是一个新构造的对象。
## 27.11 为何人们对指针转换和/或引用转换如此担忧?
因为它们是邪恶的!(这说明在使用它们时需要很小心谨慎)。
不知为什么,程序员在转换指针时不太注意。他们到处转换指针类型,然后还奇怪为什么会出问题。最糟糕的是,当编译器给出一条错误消息时,他们就添加一个类型转换“让编译器闭嘴”,然后他们再“测试一下”看能否运行。如果你做了很多指针或引用的类型转换,请继续往下读。
当你转换指针类型和/或引用类型时,编译器通常不会产生任何信息。指针类型转换(和引用类型转换)会使编译器保持沉默。我把他们当作是一种错误信息的过滤器:编译器_想要_抱怨,因为它发现你正在做蠢事,但同时也发现它不该抱怨因为你用了类型转换,所以编译器就把错误消息丢掉了。这就像用密封胶带封住编译器的嘴:它试图告诉你一些重要事情,而你却故意让它闭嘴。
指针类型转换告诉编译器:“别想了,赶紧生成代码;我很聪明,你太笨了;我很伟大,你很渺小;我知道我在做什么,所以就假装这是汇编语言,然后生成代码吧。”当你转换类型时,编译器就盲目地生成代码-由你来控制(和负责)生成的结果。编译器和语言会缩减(甚至是消除)你所能得到的保证。你只能靠自己了。
做个类比,虽然手抛链锯玩完全合法,但这么做却很蠢。如果出了问题,别向链锯制造商抱怨-你做了他们没有保证的事情。你只能靠自己。
为公平起见,语言的确在类型转换时做了一些保证,至少是在一个有限的子集内有保证。例如,语言保证当从对象指针(指向一块数据的指针,不是指向函数,也不是指向成员)转换到void*,并且再转换_原_数据类型时,是没有问题的。但很多时候,你只能靠自己。)
## 27.12这两种标识符的名字:`that_look_like_this`和`thatLookLikeThis`,哪种更好?
这个要看以前是怎么做的。如果你有Pascal或Smalltalk背景,那么会喜欢`youProbablySquashNamesTogether`。如果有Ada背景,那么会喜欢`You_Probably_Use_A_Large_Number_Of_Underscores`. 如果有微软Windows背景,那么可能倾向于“匈牙利”命名法,即在标识符前面添加表示类型的前缀[译注1]。对于Unix C背景的人来说,会喜欢用缩写[译注2]。
所以没有普遍适用的标准。如果你在的项目团队已经有一份命名规范了,就照上面说的做。如果硬要推翻重来,可能更多会带来争吵而不是解决问题。从商业角度来看,只有两件事是重要的:代码可读性好,团队中的每个成员都使用相同风格。
除此之外,差别很小。
还有,在使用平台相关的代码时,不要用一种完全不同的风格。例如,一种编码风格在使用微软的库时可能看起来很自然,但在和UNIX库一起使用时就会看起来很奇异。别这么做。为不同的平台使用不同的风格。(为避免有人不仔细看,别给我发email询问那些要移植到(或是用在)不同平台上的通用代码,因为这些代码不是平台相关的,所以刚才说的“为不同的平台使用不同的风格”在这里并不适用。)
好吧,还有。真的。别跟自动生成的代码(例如通过工具产生的代码)过不去。一些人对编码规范抱有一种宗教般的狂热,他们试图让工具产生的代码符合他们的风格。别这么做,即使工具产生的代码风格不同,也别管它。记住钱和时间才重要?!?整个编码规范目的就是为了省钱省时间。别把这个变成烧钱的陷阱。
[译注1]:原文是jkuidsPrefix vndskaIdentifiers ncqWith ksldjfTheir nmdsadType
[译注2]: 原文是abbr evthng n use vry srt idntfr nms. (AND THE FORTRN PRGMRS LIMIT EVRYTH TO SIX LETTRS.)
## 27.13从哪里可以找到一些编码规范么?
有好几个地方可以找到。
在我看来,[Sutter和Alexandrescu的"C++ Coding Standards"(220页,Addison-Wesley出版,2005, ISBN 0-321-11358-6)](http://www.amazon.com/exec/obidos/ASIN/0321113586/)是最好的。 我有理由推荐此书,并且本书作者很能激发推荐者的热情。所有人都大力推荐,以前我可没见过这事。
这里有一些编码规范,可以以此为起点来制定机构的编码规范。(列表顺序是随机的)(有些已经过时了,有些可能非常糟糕。我不会推荐任何一种。使用者自己注意。)
* [`www.codingstandard.com/`](http://www.codingstandard.com/ "www.codingstandard.com/")
* [`cdfsga.fnal.gov/computing/coding_guidelines/CodingGuidelines.html`](http://cdfsga.fnal.gov/computing/coding_guidelines/CodingGuidelines.html "cdfsga.fnal.gov/computing/coding_guidelines/CodingGuidelines.html")
* [`www.nfra.nl/~seg/cppStdDoc.html`](http://www.nfra.nl/~seg/cppStdDoc.html "www.nfra.nl/~seg/cppStdDoc.html")
* [`www.cs.umd.edu/users/cml/resources/cstyle`](http://www.cs.umd.edu/users/cml/resources/cstyle "www.cs.umd.edu/users/cml/resources/cstyle")
* [`www.cs.rice.edu/~dwallach/CPlusPlusStyle.html`](http://www.cs.rice.edu/~dwallach/CPlusPlusStyle.html "www.cs.rice.edu/~dwallach/CPlusPlusStyle.html")
* [`cpptips.hyperformix.com/conventions/cppconventions_1.html`](http://cpptips.hyperformix.com/conventions/cppconventions_1.html "cpptips.hyperformix.com/conventions/cppconventions_1.html")
* [`www.objectmentor.com/resources/articles/naming.htm`](http://www.objectmentor.com/resources/articles/naming.htm "www.objectmentor.com/resources/articles/naming.htm")
* [`www.arcticlabs.com/codingstandards/`](http://www.arcticlabs.com/codingstandards/ "www.arcticlabs.com/codingstandards/")
* [`www.possibility.com/cpp/CppCodingStandard.html`](http://www.possibility.com/cpp/CppCodingStandard.html "www.possibility.com/cpp/CppCodingStandard.html")
* [`www.cs.umd.edu/users/cml/cstyle/Wildfire-C++Style.html`](http://www.cs.umd.edu/users/cml/cstyle/Wildfire-C++Style.html "www.cs.umd.edu/users/cml/cstyle/Wildfire-C++Style.html")
* [Industrial Strength C++](http://hem.passagen.se/erinyq/industrial/ "hem.passagen.se/erinyq/industrial/")
* Ellemtel的编码规范在这里可以找到:
* [`membres.lycos.fr/pierret/cpp2.htm`](http://membres.lycos.fr/pierret/cpp2.htm "membres.lycos.fr/pierret/cpp2.htm")
* [`www.cs.umd.edu/users/cml/cstyle/Ellemtel-rules.html`](http://www.cs.umd.edu/users/cml/cstyle/Ellemtel-rules.html "www.cs.umd.edu/users/cml/cstyle/Ellemtel-rules.html")
* [`www.doc.ic.ac.uk/lab/cplus/c++.rules/`](http://www.doc.ic.ac.uk/lab/cplus/c++.rules/ "www.doc.ic.ac.uk/lab/cplus/c++.rules/")
* [`www.mgl.co.uk/people/kirit/cpprules.html`](http://www.mgl.co.uk/people/kirit/cpprules.html "www.mgl.co.uk/people/kirit/cpprules.html")
注意:
* Ellement的标准已经过时了,但鉴于其重要地位,所以仍然列出。它是第一个广泛传播并被采用的C++编码规范,也是第一个批判使用保护成员的。
* Industrial Strength的C++规范也过时了,但在那些提到在基类中使用保护非虚析构函数的规范中,它是第一个被广泛发行的。
## 27.14 我应该用“不常见”的语法么?
只有当有足够的理由时再去用。换句话说,就是通过“普通”的语法无法获得同样的结果。
决定软件方面决策的是钱。除非你是在象牙塔中,否则,当你的做法会增加费用、增加风险、增加时间,或者是在一个受限环境中增加产品的时空开销,那么你的做法就不好。在意识中,你应该把这些都转换为钞票。
根据这种以实用为目的、以钞票为导向的观点,只要有等价的“正常”语法,程序员就应避免实用非主流的语法。如果一个程序员写下隐晦的代码,其它程序员看了会困惑,这就会耗费金钱。其它程序员可能会引入bug(会花钱),可能会需要更长的时间来维护(钱),修改起来可能会很困难(错过了市场机遇等于损失了钱),可能在优化时更困难(在受限的环境中,有人会需要为更大内存、更快CPU和/或更大电池来买单),另外客户可能还不满意(钱)。这是一种有关风险和回报的权衡。但如果有等价的“正常”语法能够达到同样目的,那么再努力降低使用“非正常”语法所带来的风险,就没有任何“回报”。
例如,在[混乱C代码大赛](http://www.ioccc.org/)中使用的技术,礼貌来讲是不正常的。没错,其中很多是合法的,但不是所有合法的事情都合理。使用奇怪的技巧会使其它程序员感到困惑。一些程序员喜欢“秀”他们挑战极限的能力,但这是把自我的虚荣心放在了比钱更重要的位置,是不专业的表现。坦白说,任何这么干的人都该被开除。(如果你觉得我太“刻薄”或“残忍”,我建议你调整一下态度。记住:公司雇你来是为了来帮助它而不是来伤害它的。那些把自我放到公司最佳利益上的人应该被开除出去)。
举个非主流语法的例子,`?:`运算符一般不作为语句来用。(一些人甚至不喜欢把它用在表达式里。但必须承认有_很多_地方用到了`?:`,所以不管喜欢不喜欢,(用作表达式)是“正常”的。这里有个把?:用作语句的例子:
```
blah();
blah();
xyz() ? foo() : bar(); // 应该用if/else
blah();
blah();
```
还有把`||`和`&&`当作"if-not"和"if"语句来用也是一样道理。是的,Perl里面有这些惯用法,但C++不是Perl,用这些来代替`if`语句(而不是用在表达式中)在C++中是“不正常”的。例如:
```
foo() || bar(); // 应该用if (!foo()) bar();
foo() && bar(); // 应该用if (foo()) bar();
```
这里还有个例子,好像是能够运行,甚至是合法的,但绝不是正常的。
```
void f(const& MyClass x) // 应该用const MyClass& x
{
...
}
```
- C++ FAQ Lite
- [1] 复制许可
- [2] 在线站点分发本文档
- [3] C++-FAQ-Book 与 C++-FAQ-Lite
- [6] 综述
- [7] 类和对象
- [8] 引用
- [9] 内联函数
- [10] 构造函数
- [11] 析构函数
- [12] 赋值算符
- [13] 运算符重载
- [14] 友元
- [15] 通过 &lt;iostream&gt; 和 &lt;cstdio&gt;输入/输出
- [16] 自由存储(Freestore)管理
- [17] 异常和错误处理
- [18] const正确性
- [19] 继承 — 基础
- [20] 继承 — 虚函数
- [21] 继承 — 适当的继承和可置换性
- [22] 继承 — 抽象基类(ABCs)
- [23] 继承 — 你所不知道的
- [24] 继承 — 私有继承和保护继承
- [27] 编码规范
- [28] 学习OO/C++
- [31] 引用与值的语义
- [32] 如何混合C和C++编程
- [33] 成员函数指针
- [35] 模板
- [36] 序列化与反序列化
- [37] 类库