2006年06月20日

Trackback: http://tb.donews.net/TrackBack.aspx?PostId=510590

2006年03月03日

话说PD国VB, VC,, Delphi, ,SDK一齐乘火车,走着走着,忽听咣一声响,火车头忽然裂为八瓣儿。火车走不了了。于是大家下车商讨对策。,SDK说:"这好办,看我的。",说着,,SDK从身后拿出铁镐,斧头,大锅等一干工具。别人都以为,SDK先要解决温饱问题,不想,SDK一边架锅一边提出自己的解决方案:"先砍一棵树,劈成木柴点起来,再在旁边岩壁上,挖一个矿井,产出铁矿石,练成钢后,便可以再造一个火车头了。"。我考,其余人一齐恶到在地,在地上齐喊Cancel。

这时VC,说:"火车头虽已四分五裂,但我们可以将这些部件的有用部分继承下来,做成一个新的。"。 Delphi点头称是,VB半懂不懂。VC,一看基本获得通过,挽起袖子准备干活,不想走到近前,发现对火车头不甚了解,但这也难不倒VC,,只见VC,轻点F1,本想获得一些帮助,不想弹出一对话框:本系统没有安装MSDN。正当VC,准备晕倒之时, Delphi从煤堆中翻出两张D版MSDN交给VC,,VC,用颤抖的双手将MSDN装上以后发现,这个版本的MSDN居然还是简体PD文版的,内容非常丰富,不仅有火车头,还有飞机头,汽车头等,另外还有各种动物的头,比如gui头。VC,点中火车头的热链接。"啊,天哪!",VC,终于昏倒在地。 Delphi与VB凑近一看,原来简体PD文版只是个噱头,真正的文档都是M$文版的。VC,的方案终告失败。此时天空蔚蓝的仿佛篮屏。

VB眼珠一转想出了一个办法,砍倒了一些树,VB居然用树做了一个象么象样但却比原来大许多倍的火车头。四人欢天喜地上了车,没想到火车纹丝不动, Delphi问:"这火车头怎么不动啊?"。VB说:"兄弟,您还不了解我么,我做的东东什么时候能自己动啊?","那怎么办!","不着急,有上帝呐"。这是天空中传来上帝耶和华·比尔盖茨的声音:"VB,我从小看着你长大的孩子啊,我赐予你的火车头32个人的力量,让它run吧。"。这时火车居然吱吱嘎嘎的行进起来,可是速度又慢又不稳定,没过多久火车又停了。

这时 Delphi正在车厢中溜达,他发现,车厢的最后端有些大大小小的可以控制的件。于是 Delphi招呼大家把这些件搬下车看看是什么东西,没想到这个中国式的车厢门太窄,于是大家就像蚂蚁似的续传着搬下了车。 Delphi照着读我把这些件拼凑起来,没想到居然是一个崭新的火车头, Delphi对着火车头道:"别看我对你的这些件们的内部不了解,但我能把你们拼起来。"。
于是众人上了火车,又前行了。

2006年02月24日

Most   DLL   developers   have   faced   the   challenge   of   detecting   a   HMODULE/HINSTANCE   handle   within   the   module   you’re   running   in.   It   may   be   a   difficult   task   if   you   wrote   the   DLL   without   a   DLLMain()   function   or   you   are   unaware   of   its   name.   For   example:

Your   DLL   was   built   without   ATL/MFC,   so   the   DLLMain()   function   exists,   but   it’s   hidden   from   you   code   and   you   cannot   access   the   hinstDLL   parameter.   You   do   not   know   the   DLL’s   real   file   name   because   it   could   be   renamed   by   everyone,   so   GetModuleHandle()   is   not   for   you.

This   small   code   can   help   you   solve   this   problem:

#if   _MSC_VER   >=   1300         //   for   VC   7.0
    //   from   ATL   7.0   sources
    #ifndef   _delayimp_h
    extern   "C"   IMAGE_DOS_HEADER   __ImageBase;
    #endif
#endif

HMODULE   GetCurrentModule()
{
#if   _MSC_VER   <   1300         //   earlier   than   .NET   compiler   (VC   6.0)

    //   Here’s   a   trick   that   will   get   you   the   handle   of   the   module
    //   you’re   running   in   without   any   a-priori   knowledge:
    //   http://www.dotnet247.com/247reference/msgs/13/65259.aspx

    MEMORY_BASIC_INFORMATION   mbi;
    static   int   dummy;
    VirtualQuery(   &dummy,   &mbi,   sizeof(mbi)   );

    return   reinterpret_cast<HMODULE>(mbi.AllocationBase);

#else         //   VC   7.0

    //   from   ATL   7.0   sources

    return   reinterpret_cast<HMODULE>(&__ImageBase);
#endif
}

2005年12月16日

一直在更新Bioneta的1.1的版本,希望做的尽可能比上一次的好一些。结构上更加清晰了我感觉。

还有就是ACCESS和ADO的,已经到了最后阶段。希望可以按期交工:)

2005年11月22日

理论:
工具提示是当鼠标在某特定区域上停留时显示的一个矩形窗口.工具提示窗口包含一些编程者想要显示的文本.在这点上,工具提示同状态栏的作用是一样的,所不同的是工具提示当单击或者远离指定区域的时候就会消逝,你可能熟悉与工具栏相关联的工具提示,那些"提示"是工具栏控件提供的便利.如果你想要在其它窗口、控件中显示工具提示的话,就不得不自己创建他们.

既然已经了解了什么是工具提示,就让我们来看看如何创建他们.大致步骤如下:

用CreateWindowEx函数创建工具提示控件.
定义一个工具提示控件将要监视鼠标移动的区域.
将区域传递给工具提示控件
将传递区域的鼠标消息转送给工具提示控件.(这步或许更早,具体依据转播消息的方法)
下面我们就来详细的讨论每一步.
工具提示控件的创建
工具提示控件是一种通用控件.同样,要在源代码某处调用InitCommonControls以便MASM能够将你的程序和comctl32.dll连接. 用CreateWindowEx创建工具提示控件,典型代码如下:
.data
TooltipClassName db "Tooltips_class32",0
.code
…..
invoke InitCommonControls
invoke CreateWindowEx, NULL, addr TooltipClassName, NULL, TIS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL
注意窗口风格:TIS_ALWAYSTIP指定了工具提示不管包含指定区域的窗口状态如何,当鼠标移过指定区域的时候,工具提示总是显示.简单的说就是,即使窗口处于非激活状态,鼠标移过工具提示指定区域的时候,工具提示也会出现.
你不必在CreateWindowEx中包括WS_POPUP 和 WS_EX_TOOLWINDOW风格,因为工具提示处理过程会自动加上,你也不必指定工具提示窗口的坐标和宽高,控件会依据要显示的文字自动调节.四个参数,均使用CW_USEDEFAULT ,其余的参数都不太重要.
指定工具
工具提示控件创建了但还没有显示,我们想要当鼠标指针在某个区域之上时显示工具提示窗口.现在需要指定这个区域.我们称这样的区域为"工具",“工具”就是工具提示控件监视鼠标指针是否移过的位于窗口客户区的一个方形区域.如果鼠标指针移过"工具",工具提示窗口就显示."工具"可覆盖整个客户区或者仅仅是它的一部分.因此我们把"工具"分成两种类型,一种是作为一个窗口,另一种则是某窗口客户区的一部分.两种各有所用.覆盖整个客户区的"工具"通常用于按钮、编辑控件等,你不必指定焦点域的坐标和大小:它被假定为窗口的整个客户区.仅覆盖窗口客户区一部分的"工具"在你想把窗口客户区分成几个部分但又不想使用子窗口时特别有用,但需要指定左上角的坐标和宽高.

使用如下的 TOOLINFO 结构定义"工具":

TOOLINFO STRUCT
cbSize DWORD ?
uFlags DWORD ?
hWnd DWORD ?
uId DWORD ?
rect RECT <>
hInst DWORD ?
lpszText DWORD ?
lParam LPARAM ?
TOOLINFO ENDS
域名 说明
cbSize TOOLINFO结构的大小.必须填充, 如果这个区域不被正确填充Windows并不会报错,但你会得到不可预料的奇怪结果.
uFlags 指定焦点域的属性,可以是如下标志的联合:
TTF_IDISHWND "ID is hWnd".如果你指定了这个标志,就意味着你要使用覆盖整个客户区的"工具" (上面第一种"工具"). 如果你使用了这个标志,你必须用你要使用的窗口句柄填充uId成员,如果你不指定这个成员,就意味着你要使用第二种"工具"、客户区窗口的一方形区域.在这种情况下,你就必须以方形区域的大小填充rect成员.
TTF_CENTERTIP 通常工具提示窗口显示在鼠标的右下方,如果你指定了这个标志,不管鼠标的位置如何,工具提示总显示在焦点域总的中下方.
TTF_RTLREADING .如果你的程序不是为阿拉伯或者希伯来语系统设计的,你完全可以不理它,它使得提示文本以从右至左的顺序显示,在其它系统中无效.
TTF_SUBCLASS 如果你使用了这个标志,工具提示控件将子类化"工具"所在窗口以便截取发送给它的的鼠标消息,这个标志非常有用,否则你将不得不做更多的工作来向工具提示控件转发消息.

hWnd 包含"工具"的窗口句柄,如果你指定了TTF_IDISHWND标志,Windows将忽略该值,而使用uId成员的值作为窗口句柄.你需要填充这个域域如果:
你不使用 TTF_IDISHWND标志 (换句话说,你使用局部"工具")
你在 lpszText 成员中指定了LPSTR_TEXTCALLBACK .这个值告诉工具提示控件当需要显示提示窗口时,必须向包含"工具"的窗口查询应该显示什么. 这是一种实时的控件文本更新.如果你需要动态改变提示文本,你应当在 lpszText成员中指定LPSTR_TEXTCALLBACK值,控件就会向hWnd指定的窗口发送TTN_NEEDTEXT 消息.

uId 这个域的值可能有两种含义,依 uFlags 是否包含TTF_IDISHWND.
如果TTF_IDISHWND标志没有被指定就代表应用程序定义的"工具"ID,由于这意味着你使用仅覆盖客户区一部分的"工具",逻辑的推出一个客户区可能存在多个同样的焦点域(不存在交迭),Hwnd成员的一个窗口句柄就不够了,应用程序定义ID以区分他们因此而显得必要,只要唯一ID可以是任何值.
如果TTF_IDISHWND标志被指定就表示整个客户区都作为焦点域的窗口句柄,你或许会奇怪为什么不用上面提到的hWnd成员的值来储存窗口句柄.答案是:如果lpszText指定为LPSTR_TEXTCALLBACK,Hwnd 可能已经被填充了.还有提供提示文本的窗口和包含"工具"的窗口可能不是同一个!(你可以设计一个提供两种服务的窗口程序,但太严格了,在这点上,微软为我们提供了更大的自由,欢呼吧!)

rect 指定"工具"大小的rect结构.这个结构定义了一个以hWnd指定窗口客户区左上角为基点的方形大小,简言之,如果你想指定客户区的一部分作为"工具"就得填充这个结构,如果你指定了TTF_IDISHWND标志 ,控件就会忽略这个值.(你已经选择整个客户区作为"工具")
hInst 如果lpszText指定了字符串资源的标识,包含将作为工具提文本字符串资源的实例句柄.听起来有点费解,阅读一下lpszText的说明就可以明白这个域是干什么用的了.若lpszText不包含字符串资源标识,控件会忽略这个域.
lpszText 这个域可以有如下几个值:
如果指定为LPSTR_TEXTCALLBACK, 工具提示控件就会向HWnd窗口发送TTN_NEEDTEXT消息以获得将要显示的字符串.提示文本的动态更新方法:每次显示提示窗口都改变提示文本.
如果在这个域中指定字符串资源标识,当控件要在提示窗口中显示提示文本时,就搜索hInst成员标识的实例的字符串资源列表.由于字符串资源列表标识总是16位值,这个域的高字节将永远为0,这种方法在你想移植程序时非常有用,由于字符串资源以脚本形式定义,你不必修改源代码.只需要修改字符串列表提示文本就会相应改变,而不必担心引进bugs.
如果这个域的值不是LPSTR_TEXTCALLBACK并且高字节不为零, 控件截取这个值作为提示文本的指针,这是最简单的方法但也最不稳定.通过检查高字节区分字符串资源标识.

总言之,你需要将TOOLINFO结构传递给工具提示控件之前填充填充好,它描述了你期望的"工具"属性.

向工具提示控件注册"工具"
填充完TOOLINFO结构后, 必须将其传递给控件 . 一个工具提示控件可以控制很多"工具",因此不必为一个窗口创建很多控件,为了注册"工具",向控件发送TTM_ADDTOOL消息 wParam不使用,lParam必须包含要注册的TOOLINFO结构的指针
.data?
ti TOOLINFO <>
…….
.code
…….
<fill the TOOLINFO structure>
…….
invoke SendMessage, hwndTooltip, TTM_ADDTOOL, NULL, addr ti
成功返回 TRUE,否则返回 FALSE.
发送 TTM_DELTOOL消息取消注册.
向工具提示控件转发鼠标消息
以上步骤完毕之后,控件知道了应当监视那一块区域和应该在提示窗口显示什么.唯一缺乏的就是激发机制. 想想看:"工具"指定的其它窗口的客户区的区域.控件如何截取发送向该窗口的消息呢?实际中需要截取消息以便了解鼠标停留了多长时间,当指定时间流逝以后,控件显示提示窗口.有两种方法: 一种需要包含"工具"窗口的合作,另一种则不需要.
包含"工具"的窗口必须向控件发送 TTM_RELAYEVENT 以转发消息. lParam是指向要转发消息MSG的指针 控件仅处理如下鼠标消息 :
WM_LBUTTONDOWN
WM_MOUSEMOVE
WM_LBUTTONUP
WM_RBUTTONDOWN
WM_MBUTTONDOWN
WM_RBUTTONUP
WM_MBUTTONUP
其它消息全被忽略,因此包含"工具"的窗口的处理过程必须包含像这样的选择:
WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
…….
if uMsg==WM_CREATE
………….
elseif uMsg==WM_LBUTTONDOWN || uMsg==WM_MOUSEMOVE || uMsg==WM_LBUTTONUP || uMsg==WM_RBUTTONDOWN || uMsg==WM_MBUTTONDOWN || uMsg==WM_RBUTTONUP || uMsg==WM_MBUTTONUP
invoke SendMessage, hwndTooltip, TTM_RELAYEVENT, NULL, addr msg
……….

.你可以在TOOLINFO结构的uFlags成员指定 TTF_SUBCLASS标志。此标志告诉控件子类化包含"工具"的窗口以便无需窗口的协作便可捕获鼠标消息。由于除了控件自己处理截获的鼠标消息和指定TTF_SUBCLASS标志之外不用编写多余的代码,因此很易于使用。
就是这些了,到这步为止,控件已经全功能了.还有几个你应当知道的相关消息.
TTM_ACTIVATE.如果你想动态的允许或者禁止工具提示控件,这个小消息就是为你而备.wParam值为TRUE,允许控件.若为FALSE,禁止控件.控件初始创建的时候无需发送消息激活他,便被自动设为允许状态.
TTM_GETTOOLINFO and TTM_SETTOOLINFO. 如果你想在把TOOLINFO结构传递给控件之后获得或者改变其值,使用此消息.你需要用正确的uId and hWnd值指定要改变的"工具".如果你只想改变rect成员的值,使用TTM_NEWTOOLRECT 消息,如果仅想改变提示文本,使用TTM_UPDATETIPTEXT消息.
TTM_SETDELAYTIME. 使用此消息指定控件显示提示文本时的时间延迟.
例子:
例子是一个有两个按钮的对话框,对话框的客户区分为4部分:左上、右上、左下、右下.每个区域都指定为有自己提示文本的"工具",两个按钮也有自己的提示文本.
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\comctl32.inc
includelib \masm32\lib\comctl32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
DlgProc proto :D WORD,:DWORD,:DWORD,:DWORD
EnumChild proto :D WORD,:DWORD
SetDlgToolArea proto :D WORD,:DWORD,:DWORD,:DWORD,:DWORD
.const
IDD_MAINDIALOG equ 101
.data
ToolTipsClassName db "Tooltips_class32",0
MainDialogText1 db "This is the upper left area of the dialog",0
MainDialogText2 db "This is the upper right area of the dialog",0
MainDialogText3 db "This is the lower left area of the dialog",0
MainDialogText4 db "This is the lower right area of the dialog",0
.data?
hwndTool dd ?
hInstance dd ?
.code
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,IDD_MAINDIALOG,NULL,addr DlgProc,NULL
invoke ExitProcess,eax

DlgProc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
LOCAL ti:TOOLINFO
LOCAL id:DWORD
LOCAL rect:RECT
.if uMsg==WM_INITDIALOG
invoke InitCommonControls
invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
TTS_ALWAYSTIP,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInstance,NULL
mov hwndTool,eax
mov id,0
mov ti.cbSize,sizeof TOOLINFO
mov ti.uFlags,TTF_SUBCLASS
push hDlg
pop ti.hWnd
invoke GetWindowRect,hDlg,addr rect
invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect
inc id
invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText2,id,addr rect
inc id
invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText3,id,addr rect
inc id
invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText4,id,addr rect
invoke EnumChildWindows,hDlg,addr EnumChild,addr ti


.elseif uMsg==WM_CLOSE
invoke EndDialog,hDlg,NULL
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
DlgProc endp

EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD
LOCAL buffer[256]:BYTE
mov edi,lParam
assume edi:ptr TOOLINFO
push hwndChild
pop [edi].uId
or [edi].uFlags,TTF_IDISHWND
invoke GetWindowText,hwndChild,addr buffer,255
lea eax,buffer
mov [edi].lpszText,eax
invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
assume edi:nothing
ret
EnumChild endp

SetDlgToolArea proc uses edi esi hDlg:DWORD,lpti:DWORD,lpText:DWORD,id:DWORD,lprect:DWORD
mov edi,lpti
mov esi,lprect
assume esi:ptr RECT
assume edi:ptr TOOLINFO
.if id==0
mov [edi].rect.left,0
mov [edi].rect.top,0
mov eax,[esi].right
sub eax,[esi].left
shr eax,1
mov [edi].rect.right,eax
mov eax,[esi].bottom
sub eax,[esi].top
shr eax,1
mov [edi].rect.bottom,eax
.elseif id==1
mov eax,[esi].right
sub eax,[esi].left
shr eax,1
inc eax
mov [edi].rect.left,eax
mov [edi].rect.top,0
mov eax,[esi].right
sub eax,[esi].left
mov [edi].rect.right,eax
mov eax,[esi].bottom
sub eax,[esi].top
mov [edi].rect.bottom,eax
.elseif id==2
mov [edi].rect.left,0
mov eax,[esi].bottom
sub eax,[esi].top
shr eax,1
inc eax
mov [edi].rect.top,eax
mov eax,[esi].right
sub eax,[esi].left
shr eax,1
mov [edi].rect.right,eax
mov eax,[esi].bottom
sub eax,[esi].top
mov [edi].rect.bottom,eax
.else
mov eax,[esi].right
sub eax,[esi].left
shr eax,1
inc eax
mov [edi].rect.left,eax
mov eax,[esi].bottom
sub eax,[esi].top
shr eax,1
inc eax
mov [edi].rect.top,eax
mov eax,[esi].right
sub eax,[esi].left
mov [edi].rect.right,eax
mov eax,[esi].bottom
sub eax,[esi].top
mov [edi].rect.bottom,eax
.endif
push lpText
pop [edi].lpszText
invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti
assume edi:nothing
assume esi:nothing
ret
SetDlgToolArea endp


end start

分析:

创建主对话框窗口之后,使用CreateWindowEx创建工具提示控件.

invoke InitCommonControls
invoke CreateWindowEx,NULL,ADDR ToolTipsClassName,NULL,\
TTS_ALWAYSTIP,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
hInstance,NULL
mov hwndTool,eax

之后,我们继续定义对话框四个角作为焦点域.

mov id,0 ; 焦点域ID
mov ti.cbSize,sizeof TOOLINFO
mov ti.uFlags,TTF_SUBCLASS ; 告诉控件子类化窗口.
push hDlg
pop ti.hWnd ; 包含焦点域的窗口句柄
invoke GetWindowRect,hDlg,addr rect ; 获得客户区的大小
invoke SetDlgToolArea,hDlg,addr ti,addr MainDialogText1,id,addr rect

我们初始化TOOLINFO结构. 注意我们要把客户区分成4个焦点域,因此我们需要知道客户区的大小,所以调用GetWindowRect.因为我们不想自己向控件转发消息,因此指定TIF_SUBCLASS 标志.
SetDlgToolArea 是计算焦点域矩形范围的并向控件注册的函数,我不详细解释计算过程.只说明它把对话框分成4个焦点域.然后向控件发送TTM_ADDTOOL 消息, 在lParam参数中传递TOOLINFO结构的地址.

invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,lpti

在四个控件注册之后,我们来看看对话框的按钮,我们可以用ID来处理每个按钮,但是实在太乏味了.我们使用EnumChildWindows函数列举对话框上的所有控件并把他们注册给控件,EnumChildWindows原型如下:

EnumChildWindows proto hWnd:DWORD, lpEnumFunc:DWORD, lParam:DWORD
hWnd 是父窗口句柄.

lpEnumFunc 是每个控件将调用的EnumChildProc函数地址.lParam 是应用程序定义的要传给EnumChildProc 函数的值. EnumChildProc 函数定义如下:

EnumChildProc proto hwndChild:DWORD, lParam:DWORD
hwndChild是EnumChildWindows函数枚举的句柄. lParam 就是你传递给EnumChildWindows函数的同一个lParam.
在例子中.我们如此调用 EnumChildWindows 函数:
invoke EnumChildWindows,hDlg,addr EnumChild,addr ti
我们把TOOLINFO结构的地址放在lParam参数中传递,是因为我们要在EnumChild函数中注册每个子控件.如果我们不使用这种方法,就需要将ti声明为全局变量,但这可能会引入很多bug.
当我们调用 EnumChildWindows时, Windows会枚举出对话框上所有的子控件并为每个子控件调用一次EnumChild f函数. 这样如果我们的对话框有两个控件,EnumChild将被调用两次.
EnumChild 函数填充TOOLINFO 结构的相应成员并向控件注册.
EnumChild proc uses edi hwndChild:DWORD,lParam:DWORD
LOCAL buffer[256]:BYTE
mov edi,lParam
assume edi:ptr TOOLINFO
push hwndChild
pop [edi].uId ; we use the whole client area of the control as the tool
or [edi].uFlags,TTF_IDISHWND
invoke GetWindowText,hwndChild,addr buffer,255
lea eax,buffer ; use the window text as the tooltip text
mov [edi].lpszText,eax
invoke SendMessage,hwndTool,TTM_ADDTOOL,NULL,edi
assume edi:nothing
ret
EnumChild endp
注意在例子中,我们使用了一种不同"工具":覆盖整个客户区的"工具",因此我们需要用包含"工具"窗口的句柄来填充uID成员,也必须在uFlags 成员中指定TTF_IDISHWND标志.

Windows环境下Unicode编程总结- -

                                      

 UNICODE环境设置

在安装Visual Studio时,在选择VC++时需要加入unicode选项,保证相关的库文件可以拷贝到system32下。

UNICODE编译设置:

C/C++, Preprocessor difinitions 去除_MBCS,加_UNICODE,UNICODE

ProjectSetting/link/output 中设置EntrywWinMainCRTStartup

反之为MBCSANSI)编译。

Unicode :宽字节字符集

1. 如何取得一个既包含单字节字符又包含双字节字符的字符串的字符个数?

可以调用Microsoft Visual C++的运行期库包含函数_mbslen来操作多字节(既包括单字节也包括双字节)字符串。

调用strlen函数,无法真正了解字符串中究竟有多少字符,它只能告诉你到达结尾的0之前有多少个字节。

2. 如何对DBCS(双字节字符集)字符串进行操作?

函数 描述

PTSTR CharNext LPCTSTR ; 返回字符串中下一个字符的地址

PTSTR CharPrev LPCTSTR, LPCTSTR ); 返回字符串中上一个字符的地址

BOOL IsDBCSLeadByte( BYTE ) 如果该字节是DBCS字符的第一个字节,则返回非0

3. 为什幺要使用Unicode

1 可以很容易地在不同语言之间进行数据交换。

2 使你能够分配支持所有语言的单个二进制.exe文件或DLL文件。

3 提高应用程序的运行效率。

Windows 2000是使用Unicode从头进行开发的,如果调用任何一个Windows函数并给它传递一个ANSI字符串,那幺系统首先要将字符串转换成Unicode,然后将Unicode字符串传递给操作系统。如果希望函数返回ANSI字符串,系统就会首先将Unicode字符串转换成ANSI字符串,然后将结果返回给你的应用程序。进行这些字符串的转换需要占用系统的时间和内存。通过从头开始用Unicode来开发应用程序,就能够使你的应用程序更加有效地运行。

Windows CE 本身就是使用Unicode的一种操作系统,完全不支持ANSI Windows函数

Windows 98 只支持ANSI,只能为ANSI开发应用程序。

Microsoft公司将COM16Windows转换成Win32时,公司决定需要字符串的所有COM接口方法都只能接受Unicode字符串。

4. 如何编写Unicode源代码?

Microsoft公司为Unicode设计了WindowsAPI,这样,可以尽量减少代码的影响。实际上,可以编写单个源代码文件,以便使用或者不使用Unicode来对它进行编译。只需要定义两个宏(UNICODE_UNICODE),就可以修改然后重新编译该源文件。

_UNICODE宏用于C运行期头文件,而UNICODE宏则用于Windows头文件。当编译源代码模块时,通常必须同时定义这两个宏。

5. Windows定义的Unicode数据类型有哪些?

数据类型 说明

WCHAR Unicode字符

PWSTR 指向Unicode字符串的指针

PCWSTR 指向一个恒定的Unicode字符串的指针

对应的ANSI数据类型为CHARLPSTRLPCSTR

ANSI/Unicode通用数据类型为TCHARPTSTR,LPCTSTR

6. 如何对Unicode进行操作?

字符集 特性 实例

ANSI 操作函数以str开头 strcpy

Unicode 操作函数以wcs开头 wcscpy

MBCS 操作函数以_mbs开头 _mbscpy

ANSI/Unicode 操作函数以_tcs开头 _tcscpyC运行期库)

ANSI/Unicode 操作函数以lstr开头 lstrcpyWindows函数)

所有新的和未过时的函数在Windows2000中都同时拥有ANSIUnicode两个版本。ANSI版本函数结尾以A表示;Unicode版本函数结尾以W表示。Windows会如下定义:

#ifdef UNICODE

#define CreateWindowEx CreateWindowExW

#else

#define CreateWindowEx CreateWindowExA

#endif // !UNICODE

7. 如何表示Unicode字符串常量?

字符集 实例

ANSI “string”

Unicode L“string”

ANSI/Unicode T(string)_TEXT(string)if( szError[0] == _TEXT(J) ){ }

8. 为什幺应当尽量使用操作系统函数?

这将有助于稍稍提高应用程序的运行性能,因为操作系统字符串函数常常被大型应用程序比如操作系统的外壳进程Explorer.exe所使用。由于这些函数使用得很多,因此,在应用程序运行时,它们可能已经被装入RAM

如:StrCatStrChrStrCmpStrCpy等。

9. 如何编写符合ANSIUnicode的应用程序?

1 将文本串视为字符数组,而不是chars数组或字节数组。

2 将通用数据类型(如TCHARPTSTR)用于文本字符和字符串。

3 将显式数据类型(如BYTEPBYTE)用于字节、字节指针和数据缓存。

4 TEXT宏用于原义字符和字符串。

5 执行全局性替换(例如用PTSTR替换PSTR)。

6 修改字符串运算问题。例如函数通常希望在字符中传递一个缓存的大小,而不是字节。这意味着不应该传递sizeof(szBuffer),而应该传递(sizeof(szBuffer)/sizeof(TCHAR)。另外,如果需要为字符串分配一个内存块,并且拥有该字符串中的字符数目,那幺请记住要按字节来分配内存。这就是说,应该调用

malloc(nCharacters *sizeof(TCHAR)),而不是调用malloc(nCharacters)

10. 如何对字符串进行有选择的比较?

通过调用CompareString来实现。

标志 含义

NORM_IGNORECASE 忽略字母的大小写

NORM_IGNOREKANATYPE 不区分平假名与片假名字符

NORM_IGNORENONSPACE 忽略无间隔字符

NORM_IGNORESYMBOLS 忽略符号

NORM_IGNOREWIDTH 不区分单字节字符与作为双字节字符的同一个字符

SORT_STRINGSORT 将标点符号作为普通符号来处理

11. 如何判断一个文本文件是ANSI还是Unicode

判断如果文本文件的开头两个字节是0xFF0xFE,那幺就是Unicode,否则是ANSI

12. 如何判断一段字符串是ANSI还是Unicode

IsTextUnicode进行判断。IsTextUnicode使用一系列统计方法和定性方法,以便猜测缓存的内容。由于这不是一种确切的科学方法,因此 IsTextUnicode有可能返回不正确的结果。

13. 如何在UnicodeANSI之间转换字符串?

Windows函数MultiByteToWideChar用于将多字节字符串转换成宽字符串;函数WideCharToMultiByte将宽字符串转换成等价的多字节字符串。

14. UnicodeDBCS之间的区别

Unicode使用(特别在C程序设计语言环境里)“宽字符集”。「Unicode中的每个字符都是16位宽而不是8位宽。」在Unicode中,没有单单使用8位数值的意义存在。相比之下,在“双位组字符集”中我们仍然处理8位数值。有些位组自身定义字符,而某些位组则显示需要和另一个位组共同定义一个字符。

处理DBCS字符串非常杂乱,但是处理Unicode文字则像处理有秩序的文字。您也许会高兴地知道前128Unicode字符(16位代码从0×00000×007F)就是ASCII字符,而接下来的128Unicode字符(代码从0×00800×00FF)是ISO 8859-1ASCII的扩展。Unicode中不同部分的字符都同样基于现有的标准。这是为了便于转换。希腊字母表使用从0×03700×03FF的代码,斯拉夫语使用从0×04000×04FF的代码,美国使用从0×05300×058F的代码,希伯来语使用从0×05900×05FF的代码。中国、日本和韩国的象形文字(总称为CJK)占用了从0×30000×9FFF的代码。Unicode的最大好处是这里只有一个字符集,没有一点含糊。

在安装Visual Studio时,在选择VC++时需要加入unicode选项,保证相关的库文件可以拷贝到system32下。

UNICODE编译设置:

C/C++, Preprocessor difinitions 去除_MBCS,加_UNICODE,UNICODE

ProjectSetting/link/output 中设置EntrywWinMainCRTStartup

反之为MBCSANSI)编译。

Unicode :宽字节字符集

1. 如何取得一个既包含单字节字符又包含双字节字符的字符串的字符个数?

可以调用Microsoft Visual C++的运行期库包含函数_mbslen来操作多字节(既包括单字节也包括双字节)字符串。

调用strlen函数,无法真正了解字符串中究竟有多少字符,它只能告诉你到达结尾的0之前有多少个字节。

2. 如何对DBCS(双字节字符集)字符串进行操作?

函数 描述

PTSTR CharNext LPCTSTR ; 返回字符串中下一个字符的地址

PTSTR CharPrev LPCTSTR, LPCTSTR ); 返回字符串中上一个字符的地址

BOOL IsDBCSLeadByte( BYTE ) 如果该字节是DBCS字符的第一个字节,则返回非0

3. 为什幺要使用Unicode

1 可以很容易地在不同语言之间进行数据交换。

2 使你能够分配支持所有语言的单个二进制.exe文件或DLL文件。

3 提高应用程序的运行效率。

Windows 2000是使用Unicode从头进行开发的,如果调用任何一个Windows函数并给它传递一个ANSI字符串,那幺系统首先要将字符串转换成Unicode,然后将Unicode字符串传递给操作系统。如果希望函数返回ANSI字符串,系统就会首先将Unicode字符串转换成ANSI字符串,然后将结果返回给你的应用程序。进行这些字符串的转换需要占用系统的时间和内存。通过从头开始用Unicode来开发应用程序,就能够使你的应用程序更加有效地运行。

Windows CE 本身就是使用Unicode的一种操作系统,完全不支持ANSI Windows函数

Windows 98 只支持ANSI,只能为ANSI开发应用程序。

Microsoft公司将COM16Windows转换成Win32时,公司决定需要字符串的所有COM接口方法都只能接受Unicode字符串。

4. 如何编写Unicode源代码?

Microsoft公司为Unicode设计了WindowsAPI,这样,可以尽量减少代码的影响。实际上,可以编写单个源代码文件,以便使用或者不使用Unicode来对它进行编译。只需要定义两个宏(UNICODE_UNICODE),就可以修改然后重新编译该源文件。

_UNICODE宏用于C运行期头文件,而UNICODE宏则用于Windows头文件。当编译源代码模块时,通常必须同时定义这两个宏。

5. Windows定义的Unicode数据类型有哪些?

数据类型 说明

WCHAR Unicode字符

PWSTR 指向Unicode字符串的指针

PCWSTR 指向一个恒定的Unicode字符串的指针

对应的ANSI数据类型为CHARLPSTRLPCSTR

ANSI/Unicode通用数据类型为TCHARPTSTR,LPCTSTR

6. 如何对Unicode进行操作?

字符集 特性 实例

ANSI 操作函数以str开头 strcpy

Unicode 操作函数以wcs开头 wcscpy

MBCS 操作函数以_mbs开头 _mbscpy

ANSI/Unicode 操作函数以_tcs开头 _tcscpyC运行期库)

ANSI/Unicode 操作函数以lstr开头 lstrcpyWindows函数)

所有新的和未过时的函数在Windows2000中都同时拥有ANSIUnicode两个版本。ANSI版本函数结尾以A表示;Unicode版本函数结尾以W表示。Windows会如下定义:

#ifdef UNICODE

#define CreateWindowEx CreateWindowExW

#else

#define CreateWindowEx CreateWindowExA

#endif // !UNICODE

7. 如何表示Unicode字符串常量?

字符集 实例

ANSI “string”

Unicode L“string”

ANSI/Unicode T(string)_TEXT(string)if( szError[0] == _TEXT(J) ){ }

8. 为什幺应当尽量使用操作系统函数?

这将有助于稍稍提高应用程序的运行性能,因为操作系统字符串函数常常被大型应用程序比如操作系统的外壳进程Explorer.exe所使用。由于这些函数使用得很多,因此,在应用程序运行时,它们可能已经被装入RAM

如:StrCatStrChrStrCmpStrCpy等。

9. 如何编写符合ANSIUnicode的应用程序?

1 将文本串视为字符数组,而不是chars数组或字节数组。

2 将通用数据类型(如TCHARPTSTR)用于文本字符和字符串。

3 将显式数据类型(如BYTEPBYTE)用于字节、字节指针和数据缓存。

4 TEXT宏用于原义字符和字符串。

5 执行全局性替换(例如用PTSTR替换PSTR)。

6 修改字符串运算问题。例如函数通常希望在字符中传递一个缓存的大小,而不是字节。这意味着不应该传递sizeof(szBuffer),而应该传递(sizeof(szBuffer)/sizeof(TCHAR)。另外,如果需要为字符串分配一个内存块,并且拥有该字符串中的字符数目,那幺请记住要按字节来分配内存。这就是说,应该调用

malloc(nCharacters *sizeof(TCHAR)),而不是调用malloc(nCharacters)

10. 如何对字符串进行有选择的比较?

通过调用CompareString来实现。

标志 含义

NORM_IGNORECASE 忽略字母的大小写

NORM_IGNOREKANATYPE 不区分平假名与片假名字符

NORM_IGNORENONSPACE 忽略无间隔字符

NORM_IGNORESYMBOLS 忽略符号

NORM_IGNOREWIDTH 不区分单字节字符与作为双字节字符的同一个字符

SORT_STRINGSORT 将标点符号作为普通符号来处理

11. 如何判断一个文本文件是ANSI还是Unicode

判断如果文本文件的开头两个字节是0xFF0xFE,那幺就是Unicode,否则是ANSI

12. 如何判断一段字符串是ANSI还是Unicode

IsTextUnicode进行判断。IsTextUnicode使用一系列统计方法和定性方法,以便猜测缓存的内容。由于这不是一种确切的科学方法,因此 IsTextUnicode有可能返回不正确的结果。

13. 如何在UnicodeANSI之间转换字符串?

Windows函数MultiByteToWideChar用于将多字节字符串转换成宽字符串;函数WideCharToMultiByte将宽字符串转换成等价的多字节字符串。

14. UnicodeDBCS之间的区别

Unicode使用(特别在C程序设计语言环境里)“宽字符集”。「Unicode中的每个字符都是16位宽而不是8位宽。」在Unicode中,没有单单使用8位数值的意义存在。相比之下,在“双位组字符集”中我们仍然处理8位数值。有些位组自身定义字符,而某些位组则显示需要和另一个位组共同定义一个字符。

处理DBCS字符串非常杂乱,但是处理Unicode文字则像处理有秩序的文字。您也许会高兴地知道前128Unicode字符(16位代码从0×00000×007F)就是ASCII字符,而接下来的128Unicode字符(代码从0×00800×00FF)是ISO 8859-1ASCII的扩展。Unicode中不同部分的字符都同样基于现有的标准。这是为了便于转换。希腊字母表使用从0×03700×03FF的代码,斯拉夫语使用从0×04000×04FF的代码,美国使用从0×05300×058F的代码,希伯来语使用从0×05900×05FF的代码。中国、日本和韩国的象形文字(总称为CJK)占用了从0×30000×9FFF的代码。Unicode的最大好处是这里只有一个字符集,没有一点含糊。

- 作者: zfqcn

2005年11月14日

学习编译原理的相关建议

    编译原理一般认为是较难的一门课.从网上的评论来看,有人说学了一年半软件理论,就一门编译看不懂;有人甚至说它是大本软件课程里最难的一门;有人抱怨国内的编译教材没有一本容易懂的。

  从笔者学习实践来看,第一次学了一个多月,理论部分一知半解,第二次学了一星期,基本看懂词法分析的理论部分,语法分析就一知半解了,第三次学了一星期,才基本看懂词法分析和语法分析.由此看来,这门课确实有难度.网上有的帖子,把编译器的编写搞得高深莫测一般,似乎难度极大,非常人能及.

  编译原理究竟难在哪里?笔者的体会,主要在这几点:

  1.错误认识: 很多人以为编译原理只能应用在写程序语言的编译器上,觉得用处不大,学习兴趣不高.而且可能觉得写编译器就必须完全手工来写.

  2.自动机理论: 象NFA,DFA之类,比较抽象,要费些脑子,特别如果学离散数学时没有学自动机理论的话,更是需要多花点时间.

  3.集合论的推演: 主要是一些闭包运算之类,数学基础不好的话,学起来也会感到吃力.

  4.LR文法: 主要是又引入了自动机

  不管哪本编译教材,即使是绝对经典”龙书”也不例外,都要涉及到这几个难点.由于这些内容本身不好懂,作者有再大的本事,也很难把书写得象小说那么流畅好懂.

  明确了难点,接着想对策.大致有这么几种:

  1.端正认识: 编译原理在静态文本处理上有广泛的应用,举个简单的例子,把HTML文件转化为纯文本,利用编译原理来实现”非常”简单.理解了编译原理的实用性,大概可以提高学习兴趣.

  2.反复看书: 这个办法看起来最笨,却是基本的方法.忘了是哪位名人说过,书只要多看,总能看得懂的.

  3.结合源码来看: 这是经典教材Compiler Design in C的作者Allen Hollub建议的方法.这本教材的特色就是包含了大段yacc,lex的代码.这也是个好方法,而且,只有看懂了代码,才能说在根本上理解了理论.当然,要完全看懂yacc的代码,工作量是很大的,而且同样要先理解理论.

  4.删繁就简,避重就轻.网上流传较广的一篇《编译原理学习导论》(作者四川大学唐良)就基本是这种思路,对于词法分析,作者避免了自动机理论和集合论推演的介绍,直接搬出源码来,大大降低了理解难度,对于语法分析,作者介绍了递归下降和LL文法及相应的源码,而对LR文法,只说”理解理论就可以了”.虽然这种方法回避了对于难点的学习,但是用这种方法学习,可以在较短时间内编写出一个能够运行的词法分析器和语法分析器,可以大大提高学习积极性.

笔者的思路大体上类似第4种方法,但也稍有不同.由于一个偶然的原因, 笔者需要编写一个词法分析器和语法分析器,用于程序源代码的静态分析.开始无从下手,硬着头皮看了点编译原理,觉得困难很大.后来偶然找到一个类似的开源程序,是利用一个叫做PCCTS的编译器自动生成工具开发的,大受启发.开源就是好!笔者找来了一个叫做ANTLR的工具(它是PCCTS的新版,支持生成java,c++和c#代码),又下载了一个c语言的语法文件(因为笔者需要处理c代码文件),然后自己编了少量动作(action)语句,界面代码,分析处理代码等,就这样,在对编译原理所知甚少(以前学过的因为理解不深都忘了,只记得正则表达式)的情况下,仅用一个星期就写出了程序.

  这次实践使笔者对编译原理兴趣大增,重新又学了一遍编译原理,并归纳出笔者认为比较实用有效的编译原理学习步骤:

  1.先利用ANTLR之类的编译器生成工具,做一个小程序(如上面提到的HTML文件转化成纯文本文件的程序),所需知识只是正则表达式的基本知识和生成工具本身的使用方法(可以看联机帮助和网上教程(tutorial)来掌握). 这样做的好处是: 

  1)可以体会到编译原理的实用性,提高学习兴趣

  2)入门容易,消除编译原理学习的畏难情绪.

  3)获得词法分析器和语法分析器的感性认识,有利于加深对理论的理解.

  4)获得编译器自动生成工具(compiler compiler)的使用经验,提高解决实际问题的能力.(实际工作很多都不是手编而是利用工具的)

  2.象ANTLR之类的工具是开源(open source)的,可研究其源码,以便必要时自己手编分析程序.

  3.回过头来看编译原理教材. 这时大概会发现,很多理论很容易懂,剩下的只有上面说的几个难点,多看几遍,重点突破.

  4.结合教材所附源码,进一步加深对教材的理解

  这里顺便提一下,有的编译原理的教材,对于输入子系统不单立一章来讲,有的甚至完全忽略,笔者认为, 输入子系统相对于词法分析器和语法分析器来说当然简单地多,但也是两者的基础,故有必要看源码来理解.在这方面,ANTLR的实现机制和Lex是不同的(当然和java与c的差异有关),可对照着看.
  
  笔者学习VC++时,深切体会到好教材的重要.笔者开始吃了劣质光盘版”教材”和”21天学VC++”的祸害,看了一个月还如入云雾之中,后来看了《VC++技术内幕》,方才豁然开朗.但是编译原理的教材却似乎质量相差不是特别大,关键还在于合适的方法.以上方法笔者也是误打误撞总结出来的,希望有所参考价值.

编译原理学习导论

    大学课程为什么要开设编译原理呢?这门课程关注的是编译器方面的产生原理和技术问题,似乎和计算机的基础领域不沾边,可是编译原理却一直作为大学本科的必修课程,同时也成为了研究生入学考试的必考内容。编译原理及技术从本质上来讲就是一个算法问题而已,当然由于这个问题十分复杂,其解决算法也相对复杂。我们学的数据结构与算法分析也是讲算法的,不过讲的基础算法,换句话说讲的是算法导论,而编译原理这门课程讲的就是比较专注解决一种的算法了。在20世纪50年代,编译器的编写一直被认为是十分困难的事情,第一Fortran的编译器据说花了18年的时间才完成。在人们尝试编写编译器的同时,诞生了许多跟编译相关的理论和技术,而这些理论和技术比一个实际的编译器本身价值更大。就犹如数学家们在解决著名的哥德巴赫猜想一样,虽然没有最终解决问题,但是其间诞生不少名著的相关数论。

推荐参考书

虽然编译理论发展到今天,已经有了比较成熟的部分,但是作为一个大学生来说,要自己写出一个像Turboc C,Java那样的编译器来说还是太难了。不仅写编译器困难,学习编译原理这门课程也比较困难。

正是因为编译原理学习相对困难,那么就要求有好的教师和好的教材。教师方面不是我们能自己更改的,而在教材方面我们却可以按自己的意愿来阅读。我下面推荐几本好的编译原理的教材。我推荐的书籍都是国外的经典教材,因为在国内的教材中,确实还没发现什么让人满意的。

第一本书的原名叫《Compilers Principles,Techniques,and Tools》,另外一个响亮的名字就是龙书。原因是这本书的封面上有条红色的龙,也因为獗臼樵诒嘁朐砘×煊蛉肥堤忻?所以很多国外的学者都直接取名为龙书。最近机械工业出版社已经出版了此书的中文版,名字就叫《编译原理》。该书出的比较早,大概是在85或86年编写完成的,作者之一还是著名的贝尔实验室的科学家。里面讲解的核心编译原理至今都没有变过,所以一直到今天,它的价值都非凡。这本书最大的特点就是一开始就通过一个实际的小例子,把编译原理的大致内容罗列出来,让很多编译原理的初学者很快心里有了个底,也知道为什么会有这些理论,怎么运用这些理论。而这一点是我感觉国内的教材缺乏的东西,所以国内的教材都不是写给愿意自学的读者,总之让人看了半天,却不知道里面的东西有什么用。

第二本书的原名叫《Modern Compiler Design》,中文名字叫做《现代编译程序设计》。该书由人民邮电出版社所出。此书比较关注的是编译原理的实践,书中给出了不少的实际程序代码,还有很多实际的编译技术问题等等。此书另外一个特点就是其“现代”而字。在传统的编译原理教材中,你是不可能看到如同Java中的“垃圾回收”等算法的。因为Java这样的解释执行语言是在近几年才流行起来的东西。如果你想深入学习编译原理的理论知识,那么你肯定得看前面那本龙书,如果你想自己动手做一个先进的编译器,那么你得看这本《现代编译程序设计》。

第三本书就是很多国内的编译原理学者都推荐的那本《编译原理及实践》。或许是这本书引入国内比较早吧,我记得我是在高中就买了这本书,不过也是在前段时间才把整本书看完。此书作为入门教程也的确是个不错的选择。书中给出的编译原理讲解也相当细致,虽然不如前面的龙书那么深入,但是很多地方都是点到为止,作为大学本科教学已经是十分深入了。该书的特点就是注重实践,不过感觉还不如前面那本《现代编译程序设计》的实践味道更重。此书的重点还是在原理上的实践,而非前面那本那样的技术实践。《编译原理及实践》在讲解编译原理的各个部分的同时,也在逐步实践一个现代的编译器Tiny C.等你把整本书看完,差不多自己也可以写一个Tiny C了。作者还对Lex和Yacc这两个常用的编译相关的工具进行了很详细的说明,这一点也是很难在国内的教材中看到的。

推荐了这三本教材,都有英文版和中文版的。很多英文好的同学只喜欢看原版的书,不我的感觉是这三本书的翻译都很不错,没有必要特别去买英文版的。理解理论的实质比理解表面的文字更为重要。

编译原理的实质

前面已经说过,学习编译原理其实也就是学习算法而已,没什么特别的。只不过这些算法的产生已经形成了一套理论。下面我来看看编译原理里面到底有什么高深的理论吧。

几乎每本编译原理的教材都是分成词法分析,语法分析(LL算法,递归下降算法,LR算法),语义分析,运行时环境,中间代码,代码生成,代码优化这些部分。其实现在很多编译原理的教材都是按照85,86出版的那本龙书来安排教学内容的,所以那本龙书的内容格式几乎成了现在编译原理教材的定式,包括国内的教材也是如此。一般来说,大学里面的本科教学是不可能把上面的所有部分都认真讲完的,而是比较偏重于前面几个部分。像代码优化那部分东西,就像个无底洞一样,如果要认真讲,就是单独开一个学期的课也不可能讲得清楚。所以,一般对于本科生,对词法分析和语法分析掌握要求就相对要高一点了。

词法分析相对来说比较简单。可能是词法分析程序本身实现起来很简单吧,很多没有学过编译原理的人也同样可以写出各种各样的词法分析程序。不过编译原理在讲解词法分析的时候,重点把正则表达式和自动机原理加了进来,然后以一种十分标准的方式来讲解词法分析程序的产生。这样的做法道理很明显,就是要让词法分析从程序上升到理论的地步。

语法分析部分就比较麻烦一点了。现在一般有两种语法分析算法,LL自顶向下算法和LR自底向上算法。LL算法还好说,到了LR算法的时候,困难就来了。很多自学编译原理的都是遇到LR算法的理解成问题后就放弃了自学。其实这些东西都是只要大家理解就可以了,又不是像词法分析那样非得自己写出来才算真正的会。像LR算法的语法分析器,一般都是用工具Yacc来生成,实践中完全没有比较自己来实现。对于LL算法中特殊的递归下降算法,因为其实践十分简单,那么就应该要求每个学生都能自己写。当然,现在也有不少好的LL算法的语法分析器,不过要是换在非C平台,比如Java,Delphi,你不能运用YACC工具了,那么你就只有自己来写语法分析器。

等学到词法分析和语法分析时候,你可能会出现这样的疑问:“词法分析和语法分析到底有什么?”就从编译器的角度来讲,编译器需要把程序员写的源程序转换成一种方便处理的数据结构(抽象语法树或语法树),那么这个转换的过程就是通过词法分析和语法分析的。其实词法分析并非一开始就被列入编译器的必备部分,只是我们为了简化语法分析的过程,就把词法分析这种繁琐的工作单独提取出来,就成了现在的词法分析部分。除了编译器部分,在其它地方,词法分析和语法分析也是有用的。比如我们在DOS,Unix,Linux下输入命令的时候,程序如何分析你输入的命令形式,这也是简单的应用。总之,这两部分的工作就是把不“规则”的文本信息转换成一种比较好分析好处理的数据结构。那么为什么编译原理的教程都最终把要分析的源分析转换成“树”这种数据结构呢?数据结构中有Stack, Line,List…这么多数据结构,各自都有各自的特点。但是Tree这种结构有很强的递归性,也就是说我们可以把Tree的任何结点Node提取出来后,它依旧是一颗完整的Tree。这一点符合我们现在编译原理分析的形式语言,比如我们在函数里面使用函树,循环中使用循环,条件中使用条件等等,那么就可以很直观地表示在Tree这种数据结构上。同样,我们在执行形式语言的程序的时候也是如此的递归性。在编译原理后面的代码生成的部分,就会介绍一种堆栈式的中间代码,我们可以根据分析出来的抽象语法树,很容易,很机械地运用递归遍历抽象语法树就可以生成这种指令代码。而这种代码其实也被广泛运用在其它的解释型语言中。像现在流行的Java,.NET,其底层的字节码bytecode,可以说就是这中基于堆栈的指令代码的。

关于语义分析,语法制导翻译,类型检查等等部分,其实都是一种完善前面得到的抽象语法树的过程。比如说,我们写C语言程序的时候,都知道,如果把一个浮点数直接赋值给一个整数,就会出现类型不匹配,那么C语言的编译器是怎么知道的呢?就是通过这一步的类型检查。像C++语言这中支持多态函数的语言,这部分要处理的问题就更多更复杂了。大部编译原理的教材在这部分都是讲解一些比较好的处理策略而已。因为新的问题总是在发生,旧的办法不见得足够解决。

本来说,作为一个编译器,起作用的部分就是用户输入的源程序到最终的代码生成。但是在讲解最终代码生成的时候,又不得不讲解机器运行环境等内容。因为如果你不知道机器是怎么执行最终代码的,那么你当然无法知道如何生成合适的最终代码。这部分内容我自我感觉其意义甚至超过了编译原理本身。因为它会把一个计算机的程序的运行过程都通通排在你面前,你将来可能不会从事编译器的开发工作,但是只要是和计算机软件开发相关的领域,都会涉及到程序的执行过程。运行时环境的讲解会让你更清楚一个计算机程序是怎么存储,怎么装载,怎么执行的。关于部分的内容,我强烈建议大家看看龙书上的讲解,作者从最基本的存储组织,存储分配策略,非局部名字的访问,参数传递,符号表到动态存储分配(malloc,new)都作了十分详细的说明。这些东西都是我们编写平常程序的时候经常要做的事情,但是我们却少去探求其内部是如何完成。

关于中间代码生成,代码生成,代码优化部分的内容就实在不好说了。国内很多教材到了这部分都会很简单地走马观花讲过去,学生听了也只是作为了解,不知道如何运用。不过这部分内容的东西如果要认真讲,单独开一学期的课程都讲不完。在《编译原理及实践》的书上,对于这部分的讲解就恰到好处。作者主要讲解的还是一种以堆栈为基础的指令代码,十分通俗易懂,让人看了后,很容易模仿,自己下来后就可以写自己的代码生成。当然,对于其它代码生成技术,代码优化技术的讲解就十分简单了。如果要仔细研究代码生成技术,其实另外还有本叫做《Advance Compiler Desgin and Implement》,那本书现在由机械工业出版社引进的,十分厚重,而且是英文原版。不过这本书我没有把它列为推荐书给大家,毕竟能把龙书的内容搞清楚,在中国已经就算很不错的高手了,到那个时候再看这本《Advance Compiler Desgin and Implement》也不迟。代码优化部分在大学本科教学中还是一个不太重要的部分,就是算是实践过程中,相信大家也不太运用得到。毕竟,自己做的编译器能正确生成执行代码已经很不错了,还谈什么优化呢?

关于实践

编译原理的课程毕竟还只是讲解原理的课程,不是专门的编译技术课程。这两门课程是有很大的区别的。编译技术更关注实际的编写编译器过程中运用到的技术,而原理的课关注讲解其基本理论。但是计算机科学本身就是一门实践性很强的课程,如果能够学以致用,那才叫真正的学会。李阳在讲解疯狂英语的时候就说到,只要当你会实际中运用一个单词一个词组的时候你才能叫学会了这个单词或者词组,而不是只是知道了它的拼写和意思。其实任何学习都是一样的,如果缺少了实践的结合,你不能算学会。

编译原理的课程主要就是讲解编译器产生的理论和原理,那么很简单,自己写个编译器就是最好的实践过程了。不过你得小心,编译系统可能是所有软件系统中最复杂的系统之一,不然为什么大学里面还会把编译器的编写开成一门叫做编译原理的课程来讲?我很佩服那些学了操作系统原理就开始自己写操作系统,学了编译原理就开始自己写编译器的人们,确实,在中国,敢这么做的学生太少了。且不管你这样做能不能做成功,至少有了这个尝试,会让你的程序设计,系统规划安排的功底增进不少。我下面给出一些关于实践过程中可能会遇到的困难,希望能够在你陷入困境的前帮你一把。

1. Lex和Yacc. 这两工具是作为词法分析很语法分析的工具。如果你自己写一个编译器,我十分不建议你连词法分析这种事情都亲手来写。Lex和Yacc应该是作为每本编译原理的教材的必备内容,可是在国内的教材中缺很少看到。这两个工具是Unix系统下的小东西,如果你要在Windows中运用,那么你最好去下在cygwin这个软件。它是个在Windows下模拟Unix的东东,里面就包含了flex.exe和bison.exe(yacc)这两个工具.这两个工具使用起来还挺麻烦的(其实unix 下的很多十分有用的工具都是这样), 不过在《编译原理与实践》这本书上对于这两个工具的讲解十分详细,还列举了不少实际的例子。

2. 做解释型语言比做生成机器代码的编译器简单。虽然说,做解释型的编译器,像Java那样的,你还得自己去写解释器,不过这样你就不必去查找机器代码的资料了。如果你做生成的最终机器代码编译器可能会遇到问题还有就是寄存器为基础的代码生成方法。前面说过,如果你生成的是以堆栈为基础的代码,那么其代码生成过程十分简单,需要考虑的东西也不多,如果你考虑最终的机器代码生成的话,你必须考虑机器的寄存器如何分配等麻烦的问题。

3. 考虑用别人已经生成的语法文件,尽量不要自己动手写词法文件和语法文件.以前一个朋友曾经说过,写出一个好的程序语言的语法定义,就几乎完成了一个编译器的一半.确实是这样,语法文件的编写是个很难的事情.现在网上到处都可以找到比如C语言,C++,Java, Tiny C,Minus C等语言的词法文件和语法文件,你完全可以自己下下来来用.

在《编译原理及实践》的书中,作者给出了一个Tiny C的全部代码.我自我感觉作者的这个编译器做得很不错,相对于其它php,perl等语言的源代码来说,简单得多,容易看懂,而且很清晰地展现了一个完成的编译系统的实现过程.其源代码可以在作者的网站上下载.

后话

编译原理的学习可能算是一个困难的历程,特别是对于那些不对编译系统感兴趣的同学来说.既然它已经作为了大学本科的必修课程,那么就说明的它引申出来的一套理论在整个计算机科学领域还是占有相对重要的地位.

如果我们考究一下历史,就会发现很多被称为程序设计大师的人都是编译领域的高手.写出第一个微型机上运行的Basic语言的比尔盖茨,设计出Delphi的Borland的”世界上最厉害的程序员”, Sun的JAVA之父, 贝尔实验室的C++之父…
2005年11月11日


C++图书热点观察[转]
 

文 CSDN 孟岩  转载自2005年第11期《程序员》杂志

不知不觉,C++迎来了自己20岁的生日。20年来,C++从一个实验室语言成长为软件产业一流主导语言,在实际应用中取得了巨大的成功,同时也催生了大量为技术人员耳熟能详的经典技术著作,比如Bjarne Stroustrup的《TC++PL》和《D&E》,Stan Lippman的《C++ Primer》,Scott Meyers的《Effective C++》,GoF的《Design Patterns》,Andy Koenig的《Ruminations on C++》,Herb Sutter的《Exceptional C++》,Andrei Alexandrescu的《Modern C++ Design》,Addison Wesley的“C++ in Depth”系列等。C++领域的一些经典图书不但对于C++语言的发展起到了巨大的推动作用,而且对于其他相关技术领域也起到了指导和促进作用。例如Scott Meyers的“Effective系列”,开辟了技术图书写作的新风格,而“Design Patterns”的影响,更是远远超出C++的范畴。这些经典的好书,已经成为C++辉煌历史的一部分而被人们铭记。

20年后的今天,软件产业的规模和环境已经发生了深刻的变化。如今企业级应用整合与开发的任务主要由JavaC#Visual Basic以及各种新型动态语言来承担,而C++的应用场合也有所收缩,不再是像1990年代中期那样从上到下包打天下,而是呈现出鲜明的应用领域特色。相应的,近期的C++技术图书也更加注重在特色领域的发挥。下面我们分别从几个角度来了解近期C++图书的热点,并且展望未来一年中C++技术图书中值得注意的选题。

  

经典著作全面翻新

这一两年,一大批经典技术图书都经历了一次更新换代,C++经典图书自然也不例外。第一个要说的就是Scott Meyers的《Effective C++》。这本书在1991年推出第一版,1998年推出第二版,在C++技术的传播与教育方面居功至伟。包括我在内的很多C++开发者都是通过阅读这本书而寻得升堂入室的门径。今年,《Effective C++》推出了第三版。这一版决不是第二版的简单修订,而是根据八年来C++所发生的巨大变化而进行的一次全面改写,几乎是一本全新的书。其内容涵盖了旧版的精髓,也体现了诸如Boost库、TR1标准等C++领域最新成果。如果说这本书的内容发生了重大的变化,那么可以说不变的是这本书的地位——它仍然是每个严肃的C++开发者都应当反复阅读领悟的重要作品。

另一本翻新的著作是Stan Lippman的《C++ Primer》第四版。这本书的历史地位无需赘言,迄今为止仍然是对C++介绍最全面的一本著作。第四版进行了大幅度的删修,篇幅减少近300页,而内容也大幅修改,以反映C++近年来的变化。对于希望系统学习C++的新手,或者希望拥有一本手册以备查的老手来说,这本书的地位是不可取代的。

有传言说GoF打算翻新“Design Patterns”。该书第一版于1995年初版,此后十年,设计模式领域的理论探讨和实践基本上仍然围绕这本书中的观点和内容进行,少有突破。已经有不少开发者对于这种情况表示了不满,认为初版中的一些观点已经过时,甚至对现在的一些技术应用构成思想上的羁绊。但是此书实在声名太盛,真正的突破恐怕只能由GoF自己完成,因此本书新版如果推出,必将是技术界内的一桩大事。不过传言亦称,GoF对于新版“Design Patterns”是否继续使用C++作为示范语言存在分歧。不管怎样,相信广大读者会对这本书抱有高度的关注。

 

新锐佳作剑走偏锋

近年来,一批C++技术新锐崛起,给我们带来了一批令人耳目一新的C++好书。在技术上,这批图书偏重于C++模板风格的巧妙运用,富于智趣而对实践的关注略显不足。这类图书以2001Andrei Alexandrescu的《Modern C++ Design》为开端,之后又有《Boost Graphic Library》,《C++ Template Metap- rogramming》等。这些书一度以其新颖的思想和奇妙的技巧吸引了大家的目光,但由于缺乏实践的支撑,逐渐褪去光环。这两年,这类题材逐渐缩减。即使像Andrei Alexandrescu那样的模板技术奇才,最近与Herb Sutter合作的《C++ Coding Standard》也并没有过多地炫耀模板技巧,而是中规中矩地对C++编码的经验做了贴近实践的总结。这本书与Steve McConnell的经典著作《Code Complete II》相配合,应成为每一位C++开发者必读的基本著作,将帮助读者奠定坚实的编码和微观设计技术基础。

BoostC++社群尽人皆知的“准标准库”,其中大量的组件已经基本成熟并可供应用。最近Bjorn Karlsson撰写的《Beyond C++ Standard Library: An Introduction to Boost》是市面上第一本全面介绍Boost的著作。对于那些勇于吃螃蟹的C++先锋开发者来说,这本书无疑是值得一读的。不过在C++社群,对Boost库还存在争议。大部分C++实践者比较保守,这本书对他们的吸引力恐怕将打折扣。

Stephen Dewhurst是近年来比较活跃的新锐作家,他的两本书《C++ Gotchas》和《C++ Common Knowledge》,单独来看都是值得一读的好书,可惜现在不是1995年,而是2005年,Scott MeyersHerb Sutter煌煌巨著已然危急九五,Dewhurst只好自叹施手略慢。不过我们希望他能够再接再厉,找到创新点,突破前人。

相比之下,中文版即将问世的《Imperfect C++》就比较有新意,很值得一读,以至于Bjarne Stroustrup都相当赞赏。这位C++之父表示,如果不是因为该书太厚,他一定会将其纳入著名的“C++ in Depth”系列。这在事实上肯定了本书的高质量。我个人认为,这本书是一年来出版的最值得精读的C++著作,其中对于不少实践中经常遇到的难题进行了深入的分析,给出了实实在在的解决方案。作者Matt Wilson凭借此书以及其系列模板程序库崛起为C++社群中引人注目的新星,实在可喜可贺。

  

关键领域期待佳作

未来C++将主要在系统级复杂应用程序、高性能、实时中间件及嵌入式领域施展,同时,随着多核CPU的的普及和网络安全重要性的空前提升,在并发程序设计和安全程序设计方面,C++也将获得新的应用空间。因此,在这些具体领域的C++著作更值得关注。

1996年,John Lakos出版了《Large Scale C++》一书,该书与 “C++ In Depth”系列中的《Applied C++》一样,是C++著作中极少数实战派佳作,其中字字句句都来自于作者丰富的实际项目经验,对于一线的C++开发者来说是特别值得咀嚼体味的好书。可惜这本书在国内一直没有得到应有的重视,这可能跟本书中译本出版时间过晚,且翻译质量不佳有关。好消息是,John Lakos即将在2006年推出其新作《Scalable C++》。我本人将此书视为未来一年中C++领域最令人期待的作品。这一方面是出于我对于作者的高度信任,另一方面是由于该书题材的极端重要性。该书副标题为“基于组件的软件开发”,而“组件化”这一主题,是C++十几年来的一块心病。在未来,无论C++应用在何种场合,“组件化”是必不可少的基本要求。而COM技术虽然在组件化方面比较成熟,但是不具有可移植性,而且对于无须跨语言的C++开发项目来说过于复杂。因此,C++社群需要自己探讨经济适用的组件化实用方案。John Lakos本人从事大型复杂应用软件开发多年,在这方面的经验无人能敌,由他来对这个主题进行深入剖析,并且给出实际解决方案,毫无疑问是再合适不过的了。虽然这本书还在写作过程中,但是我们有理由对其抱有充分信心。在此我也呼吁本书中文版未来的出版者认真对待此书的翻译制作,不要重蹈《Large Scale C++》的覆辙。

说到COM,自从.NET推出,COM的书几乎一夜之间绝了种,几乎无人再勇于炒这碗冷饭。但这次出版界的茶实在凉得快了些,事实上直到今天,COM开发仍然是Windows平台上应用开发的一个重要方向。特别是在前两年微软Windows核心向.NET全面转型的左倾冒险主义的尝试以失败告终后,在可见的未来,Windows操作系统将继续构建在C/C++COM的基础之上——这就确保了COMWindows平台上的重要地位将至少延续到2011年之后。因此,COMATLWTL开发技术在未来几年都还是颇具意义的出版题材。特别是近年来ATL/WTL的发展之快,相比之下,技术出版在这方面出现了一个空白点。此外,开源的Mozilla项目提供了一个可移植到所有主流平台的COM实现,这对于C++开发者来说是很具有吸引力的,却一直没有出版资源的关注,令人遗憾。

C++近年来的一个应用热点是复杂网络应用的开发,ACE在这方面已经成为越来越流行的选择,而ICE作为目前可用的最先进的高性能中间件产品,崛起的势头很猛。这两方面目前都有一些好书,特别是最近出版的《ACE Programmers Guide》,对于ACE编程入门很有好处。而ICE 1.3版的手册早已由马维达先生译成中文,可在网上自由获得。不过坦率地说,这方面的图书还远远不够,ACE原作者Doug Schmidt所著的两卷本《C++ Network Programming》可读性和实用性不足,而ICE手册深度和广度都显不够,我们期望这方面能有更好的著作出现。

随着多核CPU的普及,并发程序设计将成为C++技术上的一个新热点。这方面目前的好书几乎没有,不知道这个巨大的空白将由何方神圣来填补。

同样,网络安全重要性的空前提升对C++开发提出了很多新的具体要求,很多C++老手面临一个“再教育”的问题。这方面Microsoft Press的《Writing Safe Code》,OReilly的《Secure Programming Cookbook for C and C++》,以及最近Pearson出版的《Secure Coding in C and C++》都是不错的参考。不过我发现目前C++开发者并未普遍重视这个问题。也许管理层还需要更加“血淋淋的教训”来刺激一下,才会有革新的动力。

在嵌入式方面,由于应用复杂度的逐渐提升和Symbian OS/Windows CE等面向消费的高级嵌入式操作系统的广泛应用,给C++提供了一个广阔的发挥空间。可惜在这方面,真正堪称经典的好书还是凤毛麟角,看来还需要时间和经验的积淀。不过有一本书特别值得一提,北航出版社去年引进的《嵌入式系统的微模块化设计》被国际嵌入式开发领域公推为数年来年度最重要的嵌入式软件开发技术著作,其中含有一些意义深远的创新思想,非嵌入式开发者也有必要了解此书的大致思想。

  

总结

程序设计语言数以千计,能够广为流传的不过几十种,而能够风光20年的更是屈指可数。Fortran已经问世50年,仍然是科学计算首选,C语言辉煌30年,至今老当益壮。C++顺利地度过了自己的20岁生日,相信属于它的日子还很长很长。作为C++开发者,我们也希望看到优秀的C++技术图书不断涌现。历史证明,C++领域内的技术创新,不但对于C++开发具有重大意义,而且对于整个软件开发技术都具有重大意义。我们有理由相信,在下一个十年里,以高水平C++技术专家和作家为代表的C++技术社群能够继续为软件技术做出突出的贡献。

老外为啥一直学不会麻将 ? zt
作者:884443


中国人的聪明是有名的,我们的“国粹”麻将牌即是一例。打麻将的乐趣绝非仅仅在赢钱,而在于打的时候,需要用尽各种心思骗上家盯下家,有时候要故意打好张,有时候又特意要喂一口,欲擒故纵,真真假假,羊头狗肉,声东击西,明修栈道,暗渡陈仓,上下其手,两面三刀……这哪里是打牌,完全是一种心理战。
  

  据说老外一直学不会咱们的麻将,原因就在于,人家没有我们这么多花花肠子,这一套对他们来说太复杂了。他们最典型的赌博,就是轮盘赌,全凭运气或概率,最笨的就是,拿一把左轮手枪,里面装颗子弹,大家轮流向太阳穴射击,看谁命大。最最笨的还有,就是那种决斗,两个人直接用剑残杀,或站在一定距离开外,互相瞄准射击。天底下还有这么笨的人嘛。

  

   看看我们的小小麻将,其中蕴含着多少机巧谋算,里面浓缩的可是我几千年的中华智慧啊!

  

  中国人就是靠这种智慧才生存下来的。中华文明为什么不像别的文明消亡、断裂?就因为我们每个人都会打麻将。我已在多处谈到,中华文明是一种没有血性的文明,一种自我保存至上的文明,一种苟且偷生的文明,一种算计别人的文明,一种不断进行着“人种逆淘汰”的文明。中国的历史越久远,中华的文明越绵延,中国人的质量就越是退化,也就是越活越“滑头”的意思。

  

  大概在几年前吧,看到一则消息,说美国在三军中展开了学习“孙子兵法”热潮。当时看到此消息后一方面深感自豪,看来老美还是得向中国人借智慧;一方面也觉得疑惑,难道像什么“借刀杀人”、“落井下石”、“偷梁换柱”、“过河拆桥”、“瞒天过海”等等计谋还需要正儿八经去学习吗?对中国人来说,这些可都是一种遇事时的自然反应而已。从这里,我们也可以知道,许多老外还是比较老实的。在中国人面前,他们的智力不够用,经常被整得鼻青脸肿,狼狈不堪。

  

  这些年有一件事情颇让我痛心,那就是我们的现实在不断地糟踏好词,比如“小姐”,比如“同志”。还有一个被毁掉的好词是:“老实”,今天如果谁称赞另外一个人“老实”,那他肯定跟你急,因为“老实”几乎已经成为“无能”、“笨”的同义词。这怪不得哪个人,只能说现实在悄悄地调整、塑造着我们的价值观。

  

   同样,我们今天说一个人“能干”,一般指的是这个人会整关系,会玩手段,会下黑手,会搞女人,会捞大钱。我们的社会机制有利于此类人的生存和发展,而我们都很羡慕或景仰此种“能干”的人。

  

  聪明过头就难免玩些欺骗手段。我有一回认识一个卖药的东北人,问他怎么卖的药?他的回答只有一个字:“骗!”东北人骗浙江人,浙江人骗湖南人,湖南人骗四川人,四川人骗广东人,反正是全国人民一大骗……制造商骗进货商,经销商骗各商场,各商场骗零售商,零售商骗顾客,顾客们谁也骗不了,就在自己行业骗别人……老板骗包工头,包工头骗打工仔,打工仔谁也骗不了,拿不到工钱就只能上演“跳楼秀”,以争取媒体关注,争取公众视线(这是最弱者的行为,是被迫着发出的绝望的挣扎,我无意讽刺),可是到后来连这样的“跳楼秀”都无人在意……

  

   谁没有见过,那无处不在的假烟假酒假药假文凭假结婚证假亲戚假避孕药假老鼠药假……我想不出对中国人而言,还有什么是不能假的。我只能说自己的想象力太有限了。

  

  在澳洲,我们听说这么一件事。澳洲的警察是比较愿意为人民服务的,包括为中国人服务,可是后来却提到中国人就冒火,就不太愿意搭理。原来,有一些中国人在那边生活时发现,如果他装作不太懂英文,去向警察打听地方,显出茫然的样子,那警察就会开车把他送到目的地去。于是很多中国人为了省钱就这么干了。但是人家警察毕竟也不至于笨到屡屡上当还毫无知觉的地步,次数多了,也感到被当成“二百五”、“傻大头”愚弄了。从此后就不乐意为中国人服务了。

  

  我还听一个朋友说起欧洲的事情。在欧洲的一些国家,手机是可以免费领取的。欧洲人一般是确有需要才去领,可中国人听说此事后,就一次次地领,多的领了十几个。人家虽然“笨”,但总还是有智力的吧,发现后就出台了规定,凡中国人只能领取一只手机。这还算没有斩尽杀绝,给中国人留了点颜面。

  

   有位网友说他见识过一个50岁的美籍华人,他说起这么一件事:在留学期间,他用批发价花9台的价钱买了10台录音机,而后逐一把9台录音机退回商店,这样他就免费拥有了一台录音机。他讲起这个故事的时候还十分自豪。

  

   还有一位网友则贡献了另一则真实的故事:他在英国遇到过一个同胞,此人先买了一份保险,保险范围中有一台子虚乌有的电脑。然后他对警察谎称自己的手提电脑被盗,结果成功地骗到了保险金。他逢人便吹嘘:“英国人真蠢,没派人来查,就把钱赔了。”

  

  很多中国人在做这些事的时候,一个个都自以为很聪明的。他们还得意洋洋地吹嘘自己脑瓜子聪明、活络。被王彬彬称为“过于聪明的中国作家”王蒙到美国去了一趟,回来后就说美国人真笨,笨到什么程度呢?比如,你到商场买了一样东西,价8元6毛2分,于是你递过去10元6毛2分,美国人睁大了眼睛不知什么意思。中国人都知道这是为了找个2块整的。可老美不懂啦。他硬是只肯收10元,找回零钱给你。

  

  这不是算术好坏的问题,也不是有人说的算法差异,而是思维方式上的差异。正因为我们有这样的思维方式,所以非常自然地会在买了一个软件后,同时在几台电脑上使用,但老美不是这样的。我听新东方副校长、耶鲁MBA钱永强说过,美国人买软件,如果家里有两台电脑,他就花钱买两个软件,虽然没有任何人监督他这么做。就像我上文说到过的,他们尊重规则。而这么干在咱们中国人看来确实傻到家了,所以成了我们的笑柄。那位号称“跨中西文化,写宇宙文章”的林语堂,在其《吾土吾民》一书中,也豪不客气地说西方人有一种stupidity,就是愚蠢。

  

   可是我们真的聪明吗?我们有没有计算过,为了这样的“聪明”,我们人与人之间失去了互信的基础,我们支付了多大的社会成本?澳洲警察从此不肯载中国人、欧洲电信从此限制中国人领手机,这是小事,可是一点小聪明败坏的是一个国家的声誉,祸及的是一个种族的所有人。

  

   这且不管它吧,在我们自己国内,一个缺乏“诚信”的社会至少有两大弊:

  

  一是经济无法健康运行。社会学家郑也夫先生曾在《代价论》、《信任论》两书中深入谈到过这个问题,他在另一篇介绍日本社会学家福山《信任》一书时也谈到过,在一个低信任度的国家里(中国是被福山列为其中之一的),经济增长的质量是很可疑的。就像胡润说中国人的财富品质很低一样。

  

   二是人与人之间的关系被败坏,所有人都生活在一种不安全感之中。我已经说过,“不安全感”是中国人不守规则之因,事实上,大家不守规则、互相欺骗的结果是进一步加剧了不安全感。于是进入恶性循环。

  

  这种互欺、互不信任的最明显表现就是彼此防范,其物化形态是什么?就是城市楼房里家家户户都有的防盗门、防盗窗,就是中国遍地都是的高高的围墙。我们都把自己关在了铁“笼子”里。那些丑陋的铁栅栏、那些一重重的铁门,是一个社会人心腐烂变质的见证!每一个中国人都应该为自己如此防范他人而感到羞愧,应该为自己如此被防范而感到耻辱!

  

  有位德国人到某高校参观,令他不解的是到处都有围墙,不但校园与外界有围墙阻隔,而且校内也有一些自成一体的墙把自己包裹起来。他好奇地问为什么?没有得到什么满意的回答,后来经过苦思冥想终于恍然大悟:“哦,我明白了,我明白了,你们中国人自古就有建Great Wall的传统!”我记不清啦,是黑格尔还是博尔赫斯还是卡夫卡说过,我们引以为自豪的长城,其实是中国人内心里巨大恐惧的象征。

  

  新西兰的奥克兰郊区风景非常优美,那一带住着大批的中产阶级。他们住着一层(最多两层)的house,有些是木结构的,给人感觉用力一脚就能踢开。房子外面一般有一道由灌木和花草建成的篱笆,透过篱笆上的漂亮的“天堂鸟”可以看见小院子里的果树长满了金黄的胡柚,有的人家地上也落了不少。这是他们住宅的基本格局,在全澳洲,大致也是如此。没有铁门,没有高墙,没有防盗窗。在堪培拉,我们只在两个地方看到了混凝土建成的围墙,知道那是什么地方吗?一个地方是驻扎着军队的总理府,还有一个地方是:中国大使馆。

  

  在澳洲期间,我带着一本《小说月报》,在里面读到张抗抗的一个小说《芝麻》,里面写到以骗别人出名的河南人,自己所承受着的苦果,他们彼此之间,即使是乡亲、同村人,也不得不厉行着严格的防范措施:家里有头家畜,男人不得不一夜夜搂着小畜娃睡觉,因为以前曾经大意,天天睡灶房挨着羊睡觉,可七只羊还是被人偷走了!那里“家家的牛都跟人睡,若是头母牛,男人和牛就像是夫妻差不多。”还有一个人,睡觉时把牵牛的绳系在手上,可半夜里那牛仍然被人偷走!这就是中国人与人之间的互信关系的写照。我相信张抗抗这样的作家是不会写荒诞小说的,可她叙述的故事听来却不能不给人一种荒诞感。

  

   前面我说到过西方人比较看重Law is law,他们好像也比较把承诺当回事,所谓promise is promise。在中国,固然也有“一诺千金”的事情,但如果那样真得谢天谢地,而且如果某人坚守诺言还给陌生人多少钱,这样的事可能要上报纸新闻去了。

  

  在澳洲期间,不知何故,我已经逐渐习惯于那种信任的感觉,对于所购物由商家托运一事,没有半点怀疑,我相信他们会准时把货物寄到机场。在某个农场,我与可爱的考拉合拍了一张照片,因为是用数码相机拍的,我向他们提出能否把照片发到我信箱,他们说可以。我就留下了我的Email地址。尽管他们那儿是农场,上网可能也不太方便,但我知道他们会守住诺言,哪怕我很快就会回到遥远的中国,哪怕我这辈子大概也不会再回到布里斯班的那个农场。我回到家,打开电子信箱,看到那封邮件已经静静地等在那儿,邮件名是:koala photo,打开邮件,里面说,“相信你现在已经回到了中国,我们把照片寄给你,希望澳大利亚的旅行令您愉快,并且在这儿所经验的一切都会给你留下美好的回忆。”

  

   那时候,面对着屏幕,我在心里说,是的,我感觉,非常美好