醜陋的面具
前言:這篇文章是我在Herb Sutter正式在網上發表http://www.gotw.ca/gotw/086.htm的解答前兩個星期寫的,在正式解答中,難度調低了。就像在下文中說的,我本以爲我這篇文章只是正餐之前的開胃酒,但正餐出來了,我感覺我的這杯開胃酒已經讓讀者打飽嗝了。:)題目名字改了,更符合事實,也更有吸引力一些。:)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
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