Trackback: http://tb.donews.net/TrackBack.aspx?PostId=510590
话说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对着火车头道:"别看我对你的这些件们的内部不了解,但我能把你们拼起来。"。
于是众人上了火车,又前行了。
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
}
一直在更新Bioneta的1.1的版本,希望做的尽可能比上一次的好一些。结构上更加清晰了我感觉。
还有就是ACCESS和ADO的,已经到了最后阶段。希望可以按期交工:)
理论:
工具提示是当鼠标在某特定区域上停留时显示的一个矩形窗口.工具提示窗口包含一些编程者想要显示的文本.在这点上,工具提示同状态栏的作用是一样的,所不同的是工具提示当单击或者远离指定区域的时候就会消逝,你可能熟悉与工具栏相关联的工具提示,那些"提示"是工具栏控件提供的便利.如果你想要在其它窗口、控件中显示工具提示的话,就不得不自己创建他们.
既然已经了解了什么是工具提示,就让我们来看看如何创建他们.大致步骤如下:
用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
WORD,:DWORD,:DWORD,:DWORD
EnumChild proto
WORD,:DWORD
SetDlgToolArea proto
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 中设置Entry为wWinMainCRTStartup
反之为MBCS(ANSI)编译。
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公司将COM从16位Windows转换成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数据类型为CHAR,LPSTR和LPCSTR。
ANSI/Unicode通用数据类型为TCHAR,PTSTR,LPCTSTR。
6. 如何对Unicode进行操作?
字符集 特性 实例
ANSI 操作函数以str开头 strcpy
Unicode 操作函数以wcs开头 wcscpy
MBCS 操作函数以_mbs开头 _mbscpy
ANSI/Unicode 操作函数以_tcs开头 _tcscpy(C运行期库)
ANSI/Unicode 操作函数以lstr开头 lstrcpy(Windows函数)
所有新的和未过时的函数在Windows2000中都同时拥有ANSI和Unicode两个版本。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。
如:StrCat,StrChr,StrCmp和StrCpy等。
9. 如何编写符合ANSI和Unicode的应用程序?
(1) 将文本串视为字符数组,而不是chars数组或字节数组。
(2) 将通用数据类型(如TCHAR和PTSTR)用于文本字符和字符串。
(3) 将显式数据类型(如BYTE和PBYTE)用于字节、字节指针和数据缓存。
(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?
判断如果文本文件的开头两个字节是0xFF和0xFE,那幺就是Unicode,否则是ANSI。
12. 如何判断一段字符串是ANSI还是Unicode?
用IsTextUnicode进行判断。IsTextUnicode使用一系列统计方法和定性方法,以便猜测缓存的内容。由于这不是一种确切的科学方法,因此 IsTextUnicode有可能返回不正确的结果。
13. 如何在Unicode与ANSI之间转换字符串?
Windows函数MultiByteToWideChar用于将多字节字符串转换成宽字符串;函数WideCharToMultiByte将宽字符串转换成等价的多字节字符串。
14. Unicode和DBCS之间的区别
Unicode使用(特别在C程序设计语言环境里)“宽字符集”。「Unicode中的每个字符都是16位宽而不是8位宽。」在Unicode中,没有单单使用8位数值的意义存在。相比之下,在“双位组字符集”中我们仍然处理8位数值。有些位组自身定义字符,而某些位组则显示需要和另一个位组共同定义一个字符。
处理DBCS字符串非常杂乱,但是处理Unicode文字则像处理有秩序的文字。您也许会高兴地知道前128个Unicode字符(16位代码从0×0000到0×007F)就是ASCII字符,而接下来的128个Unicode字符(代码从0×0080到0×00FF)是ISO 8859-1对ASCII的扩展。Unicode中不同部分的字符都同样基于现有的标准。这是为了便于转换。希腊字母表使用从0×0370到0×03FF的代码,斯拉夫语使用从0×0400到0×04FF的代码,美国使用从0×0530到0×058F的代码,希伯来语使用从0×0590到0×05FF的代码。中国、日本和韩国的象形文字(总称为CJK)占用了从0×3000到0×9FFF的代码。Unicode的最大好处是这里只有一个字符集,没有一点含糊。
在安装Visual Studio时,在选择VC++时需要加入unicode选项,保证相关的库文件可以拷贝到system32下。
UNICODE编译设置:
C/C++, Preprocessor difinitions 去除_MBCS,加_UNICODE,UNICODE
在ProjectSetting/link/output 中设置Entry为wWinMainCRTStartup
反之为MBCS(ANSI)编译。
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公司将COM从16位Windows转换成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数据类型为CHAR,LPSTR和LPCSTR。
ANSI/Unicode通用数据类型为TCHAR,PTSTR,LPCTSTR。
6. 如何对Unicode进行操作?
字符集 特性 实例
ANSI 操作函数以str开头 strcpy
Unicode 操作函数以wcs开头 wcscpy
MBCS 操作函数以_mbs开头 _mbscpy
ANSI/Unicode 操作函数以_tcs开头 _tcscpy(C运行期库)
ANSI/Unicode 操作函数以lstr开头 lstrcpy(Windows函数)
所有新的和未过时的函数在Windows2000中都同时拥有ANSI和Unicode两个版本。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。
如:StrCat,StrChr,StrCmp和StrCpy等。
9. 如何编写符合ANSI和Unicode的应用程序?
(1) 将文本串视为字符数组,而不是chars数组或字节数组。
(2) 将通用数据类型(如TCHAR和PTSTR)用于文本字符和字符串。
(3) 将显式数据类型(如BYTE和PBYTE)用于字节、字节指针和数据缓存。
(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?
判断如果文本文件的开头两个字节是0xFF和0xFE,那幺就是Unicode,否则是ANSI。
12. 如何判断一段字符串是ANSI还是Unicode?
用IsTextUnicode进行判断。IsTextUnicode使用一系列统计方法和定性方法,以便猜测缓存的内容。由于这不是一种确切的科学方法,因此 IsTextUnicode有可能返回不正确的结果。
13. 如何在Unicode与ANSI之间转换字符串?
Windows函数MultiByteToWideChar用于将多字节字符串转换成宽字符串;函数WideCharToMultiByte将宽字符串转换成等价的多字节字符串。
14. Unicode和DBCS之间的区别
Unicode使用(特别在C程序设计语言环境里)“宽字符集”。「Unicode中的每个字符都是16位宽而不是8位宽。」在Unicode中,没有单单使用8位数值的意义存在。相比之下,在“双位组字符集”中我们仍然处理8位数值。有些位组自身定义字符,而某些位组则显示需要和另一个位组共同定义一个字符。
处理DBCS字符串非常杂乱,但是处理Unicode文字则像处理有秩序的文字。您也许会高兴地知道前128个Unicode字符(16位代码从0×0000到0×007F)就是ASCII字符,而接下来的128个Unicode字符(代码从0×0080到0×00FF)是ISO 8859-1对ASCII的扩展。Unicode中不同部分的字符都同样基于现有的标准。这是为了便于转换。希腊字母表使用从0×0370到0×03FF的代码,斯拉夫语使用从0×0400到0×04FF的代码,美国使用从0×0530到0×058F的代码,希伯来语使用从0×0590到0×05FF的代码。中国、日本和韩国的象形文字(总称为CJK)占用了从0×3000到0×9FFF的代码。Unicode的最大好处是这里只有一个字符集,没有一点含糊。
- 作者: zfqcn
|
学习编译原理的相关建议 |
| 编译原理一般认为是较难的一门课.从网上的评论来看,有人说学了一年半软件理论,就一门编译看不懂;有人甚至说它是大本软件课程里最难的一门;有人抱怨国内的编译教材没有一本容易懂的。 从笔者学习实践来看,第一次学了一个多月,理论部分一知半解,第二次学了一星期,基本看懂词法分析的理论部分,语法分析就一知半解了,第三次学了一星期,才基本看懂词法分析和语法分析.由此看来,这门课确实有难度.网上有的帖子,把编译器的编写搞得高深莫测一般,似乎难度极大,非常人能及. 编译原理究竟难在哪里?笔者的体会,主要在这几点: 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++之父… |
|
老外为啥一直学不会麻将 ? zt |
