丑陋的面具
前言:这篇文章是我在Herb Sutter正式在网上发表http://www.gotw.ca/gotw/086.htm的解答前两个星期写的,在正式解答中,难度调低了。就像在下文中说的,我本以为我这篇文章只是正餐之前的开胃酒,但正餐出来了,我感觉我的这杯开胃酒已经让读者打饱嗝了。:)题目名字改了,更符合事实,也更有吸引力一些。:)
C++由于横跨学术研究以及工程实践,它的复杂以及摆脱各种控制的自由之心实在在一般人的思考局限之外。
在学术研究领域,经过了从出生到现在的二十几年,C++在与Fortran,C,Pascal以及诞生没有多久的Java,C#的各种竞争中,无论是各种编程范式(paradigm)的灵活运用,还是作为程序语言设计各种灵感的源头活水,C++可以说是天命所归!但是在工程实践的领域中,由于C++不受控制的自由,导致了它巨大的复杂性,西方有一句话:自由从来就不是没有代价的。(Freedom is not free!)除了对实时性,运行效率,兼容性有严格要求的领域之外,在如今的各种企业运用,网络运用等各种上层运用程序中,Java可以说是武林至尊,宝刀屠龙,风光好几年了。只是现在可与争锋的倚天宝剑C#已经出鞘了。在企业级运用中,到底选择那个,是你的自由,不过,令人欣慰的是,就像屠龙刀,倚天剑都出自玄铁宝剑一样,Java和C#也都出自名门C++,因此,他们就像双胞胎,即使有差别,那也极小,一门语言会了,另外一个的也差不太多。
我是不是偏离主题太远了,那让我把风筝收回来吧。今天我想解决Herb Sutter在《天师捉鬼录》:)(Guru of the week我的戏称)中的Gotw86的两个问题。其实说是解决,不如说是总结新闻组上的答案,再加上自己的一点小小见解。下面我将Gotw86用中文表述如下:http://www.gotw.ca/gotw/086.htm
细微的打字错误?图形语言和另外一些稀奇古怪的难题:8/10(Slight Typos? Graphic Language and Other Curiosities Difficulty: 8 / 10)
有时候甚至一些小的、难以察觉的打字错误可能偶然对代码意义有巨大的影响。为了举例说明打字错误多么难以察觉,以及让你产生幻觉的打字错误多么容易被看见即使它们并不存在,请看下面的例子:
难题
大虾级问题
不要使用编译器,请问答下面的问题。
1. 在一个标准兼容的C++编译器上,下面程序的输出是什么?
#include <iostream>
int main()
{
int x = 1;
for( int i = 0; i < 100; ++i );
// What will the next line do? Increment???????????/
++x;
std::cout << x;
}
2. 在一个标准兼容的C++编译器上,编译下面代码时,会有多少个不同的错误被报告?
struct X {
static bool f( int* p )
{
return p && 0[p] and not p[1:>>p[2];
};
};
答案
很快来临……
好像很多人对答案已经等的不耐烦了,在最终的标准答案出来之前,我来尝试一下,算是给大家的一个大餐前的开胃酒J。这两个问题的实质被掩盖在面具之下。
1.Answer:首先我来抓住罪恶心灵前面的一个小妖
(1)for( int i = 0; i < 100; ++i );
我想很多人已经看到了,是的,你是对的,分号“;”。这是一个单独的语句,因为后面有一个分号,对代码后面的语句毫无影响。在现代的编译器中,这一句甚至会被优化的无影无踪。对这种句子,我们程序员应该让它更真诚一些!我一般这样写:
for( int i = 0; i < 100; ++i )
; //明白表示空语句
Let’s go on!
(2)后面的一句就是真正的罪恶心灵了!这是一句注释,没错,天经地义!可惜的是它正是罪恶心灵躲藏的巢穴。它之所以能逃过你火眼金睛的原因在于我们的文化太过于单一,在这里我们有几个人懂几国洋话的?这样的精英估计不太会对C++有兴趣,不太会看到我这篇文章。
如果你是一个非英语系的欧洲人,那你的键盘就很可能不是我们现在这个样子的,字符集不同!ASCII码的特殊字母[,],{,},|,和\占据的字符集位置是由ISO设计的,但是在大多数欧洲国家的ISO—646字符集中,这些位置是由非英语字母占据的。因此,许多欧洲人使用的键盘没有这几个字母,但不巧的是,这几个字母在C++中却是频繁使用的。所以一个妥协的方案被使用:
C++中,有几个图符用来表示这几个字符,如下表:
关键字:图符对照表
Keywords
Digraphs
Trigraphs
and
&&
<%
{
??=
#
and_eq
&=
%>
}
??(
[
bitand
&
<:
[
??<
{
bitor
|
:>
]
??/
compl
~
%:
#
??)
]
not
!
%:&:
##
??>
}
or
||
??’
^
or_eq
|=
??!
|
xor
^
??-
~
xor_eq
^
not_eq
!=
比如,你在C++中就可以用“??/”来取代“\”。
OK,风筝又必须扯回来了。现在我们来看看本来的代码中的
// What will the next line do? Increment???????????/
这一句,再认真看一看,你一定会有如醍醐灌顶。用trigraph(三图符)【注】替换。结果为:
// What will the next line do? Increment?????????
注:(Interestingly, if you look at the Gnu g++ documentation for the -Wtrigraphs command-line switch, you will encounter the following statement:
"Warnings are not given for trigraphs within comments, as they do not affect the meaning of the program." )
有趣的是,如果你用命令行-Wtrigraphs查看一下Gnu g++文档,会发现下面的说明:
“在注释里面的三图符(trigraphs)不会有警告发生,因为它们不会影响程序的意义。”
感谢taodm的提醒,用DevC++4.9.7.0编译的确是这样,会直接忽略。
OK,看到最后一个字符了,这个时候应该想想宏(macro)了,看下面一个例子:
#define MYMACRO(a,b) \
if (xyzzy) asdf(); \
else
“\”的作用是什么,换行连接符号,下一行是这一行的继续。所以
// What will the next line do? Increment???????????/
++x;
就等于
// What will the next line do? Increment????????? ++x;
++x;被注释了。修正后的程序如下:
#include <iostream>
int main()
{
int x = 1;
for( int i = 0; i < 100; ++i )
;
// What will the next line do? Increment?????????++x;
std::cout << x;
}
现在代码中的妖气是不是已经荡然无存了?
九层之台,始于垒土。有了这题的基础,下面的路就比较好走了。
2.Answer:对照上面的表格,这道题就好像庖丁眼中的一条牛,说不上易如反掌,那也是游刃有余!我们将图符换为正规形式:
and 是 &&
not 是 !
:> 是 ]
大家还记得a[i]表示什么吗?(我在《妖藏巨细》中有提到),中国有句古语:庆父不死,鲁难未已。(庆父是春秋时代鲁过的一个贵族,如果他不死的话,鲁国的灾难就不会结束。)在我们这里,面具不除,人心不安!
a[i]是*(a+i)的简写,OK!那0[p]呢?我们举一反三,0[p]是*(0+p)的简写,它等于*(p+0),简写为p[0]。现在的水是不是已经很清了!J
现在对这一句return p && 0[p] and not p[1:>>p[2];我们为它剥去所有丑陋恶心的面具,还给它美丽的真身!
return p && p[0] && ! p[1]>p[2];
最后一个尾巴:“!”的优先级要大于“>”,所以上面一句意思为:
return p && p[0] && ((!p[1]) > p[2]);
也许还有人会说还有一个错误,那就是多了一个分号,函数既然使用了大括号,后面就不应该画蛇添足多一个分号,其实无所谓的,加不加都对,不加更简洁一些。现在代码的本来面目为:
struct X {
static bool f( int* p )
{
return p && p[0] && ((!p[1]) > p[2]);
}
};
解答完毕,大家可以补充。
是不是刚刚从恶梦中醒来!恭喜你,在C++这个浓密的森林中,有无数的魔鬼,也有无数的宝藏!它欢迎无畏的勇士!
只是在我们自己写的代码中,我们更应该记住的是真诚,不是特立独行,哗众取宠!(电影《大腕》的台词)。因为你是给客户看的代码,也许过不了多久你就是这个客户了!
没有人不喜欢真诚,至少他会希望别人真诚!
喜欢别人真诚,自己也应该真诚!
吴桐写于2003.4.28
最近修改2003.6.20