??xml version="1.0" encoding="utf-8" standalone="yes"?> OnPaint是对q个消息的反应函?/font> mfc ?CWnd::OnPaint 没做什么,只是丢给pȝ处理?/font> 一 Q?/strong> OnEraseBkGnd与OnPaint的区别与联系 在OnEraseBkGnd?如果你不调用原来~省的OnEraseBkGnd只是重画背景则不?x)有闪?而在OnPaint里面,׃它隐含的调用?jin)OnEraseBkGnd,而你又没有处理OnEraseBkGnd 函数,q时和H口~省的背景刷相关?~省?OnEraseBkGnd操作使用H口的缺省背景刷h背景(一般情况下是白?,而随后你又自己重画背景造成屏幕闪动. OnEraseBkGnd不是每次都会(x)被调用的.如果?font style="BACKGROUND-COLOR: #ccff00">调用Invalidate的时候参Cؓ(f)TRUE,那么在OnPaint里面隐含调用BeginPaint的时候就?font style="BACKGROUND-COLOR: #ccff00">生WM_ERASEBKGND消息,如果参数是FALSE,则不?x)重刯? ZYP解释Q?/strong>void Invalidate( BOOL bErase = TRUE ); 该函数的作用是整个H口客户区无效。窗口的客户区无效意味着需要重l,参数bErase为TRUEӞ重绘区域内的背景被重绘x除,否则Q背景将保持不变。调用Invalidate{函数后H口不会(x)立即重绘Q这是由于WM_PAINT消息的优先很低Q它需要等消息队列中的其它消息发送完后才能被处理?/font> OnPaint里面?x)调?/font>BeginPaint函数自动讄昄讑֤内容的剪切区域而排除Q何更新区域外的区域更新区域。如果更新区域被标记为可擦除的,BeginPaint发送一个WM_ERASEBKGND消息l窗口。WM_ERASEBKGND消息的响应函数既?font style="BACKGROUND-COLOR: #ccff00" color=#000099>OnEraseBkGndQ)(j) 在MFC?M一個window元g的繪?都是攑֜這兩個member function?br>在設定上 OnEraseBkgnd()是用來畫底圖?而OnPaint()是用來畫主要物g?br>舉例說明 一個按鈕是灰色?上面還有文字 BOOL CMyDlg::OnEraseBkgnd(CDC* pDC) 以上本來是會呼叫CDialog::OnEraseBkgnd() 但是如果我們不呼叫的話 Q:Z对话框的E序中如何重载OnEraseBkGnd()函数 A:q是一个消息WM_ERASEBKWND ?Q?/font> MFC中OnDraw与OnPaint的区?/font> 在OnPaint中调用OnDrawQ一般来_(d)用户自己的绘图代码应攑֜OnDraw中?/font> OnPaint()是CWnd的类成员Q负责响应WM_PAINT消息。OnDraw()是CVIEW的成员函敎ͼ没有响应消息的功?当视囑֏得无效时Q包括大的改变Q移动,被遮盖等{)(j)QW(xu)indows发送WM_PAINT消息。该视图的OnPaint 处理函数通过创徏CPaintDCcȝDC对象来响应该消息q调用视囄OnDraw成员函数.OnPaint最后也要调用OnDraw,因此一般在OnDraw函数中进行绘制?/font> 在OnPaint中,调用BeginPaintQ用来获得客户区的显C备环境,q以此调用GDI函数执行l图操作。在l图操作完成后,调用EndPaint以释放显C备环境。而OnDraw在BeginPaint与EndPaint间被调用。(一个应用程序除?jin)响应WM_PAINT消息外,不应该调用BeginPaint。每ơ调用BeginPaint都应该有相应的EndPaint函数?/font>Q?/font> 1) 在mfcl构里OnPaint是CWnd的成员函? OnDraw是CView的成员函? OnDraw()和OnPaint()有什么区别呢Q?br>首先Q我们先要明CViewcL生自CWndcR而OnPaint()是CWnd的类成员Q同时负责响应WM_PAINT消息。OnDraw()是CVIEW的成员函敎ͼq且没有响应消息的功能。这是Z么你用VC成的E序代码Ӟ在视囄只有OnDraw没有OnPaint?/strong>原因。而在Z对话框的E序中,只有OnPaint?br>其次Q我们在W《每天跟我学MFC?的开始部分已l说C(jin)。要惛_屏幕上绘图或昄囑ŞQ首先需要徏立设备环境DC。其实DC是一个数据结构,它包含输备(不单指你17寸的U屏昄器,q包括打印机之类的输备)(j)的绘囑ֱ性的描述。MFC提供?jin)CPaintDCcdCWindwoDCcL实时的响应,而CPaintDC支持重画。当视图变得无效Ӟ包括大小的改变,UdQ被遮盖{等Q,W(xu)indows ?WM_PAINT 消息发送给它。该视图的OnPaint 处理函数通过创徏 CPaintDC cȝDC对象来响应该消息q调用视囄 OnDraw 成员函数。通常我们不必~写重写?OnPaint 处理成员函数?br>///CView默认的标准的重画函数 CPaintDC dc(this); 既然OnPaint最后也要调用OnDraw,因此我们一般会(x)在OnDraw函数中进行绘制。下面是一个典型的E序?br>///视图中的l图代码首先(g)索指向文档的指针Q然后通过DCq行l图调用?br>void CMyView::OnDraw( CDC* pDC ) CMyDoc* pDoc = GetDocument(); OnDraw中可以绘制用户区域。OnPaint中只是当H口无效旉l不?x)保留C(j)ClientDCl制的内宏V?/font> q两个函数有区别也有联系Q?/font> 1、区别:(x)OnDraw是一个纯虚函敎ͼ定义为virtual void OnDraw( CDC* pDC ) = 0; 而OnPaint是一个消息响应函敎ͼ它响应了(jin)WMQPANIT消息Q也是是H口重绘消息?/font> 2、联p:(x)我们一般在视类中作囄时候,往往不直接响应WMQPANIT消息Q而是重蝲OnDrawU虚函数Q这是因为在CVIEWcM的WMQPANIT消息响应函数中调用了(jin)OnDraw函数Q如果在CMYVIEWcM响应?jin)WMQPAINT消息Q不昑ּ地调用OnDraw函数的话Q是不会(x)在窗口重l的时候调用OnDraw函数的?/font> 应用E序中几乎所有的l图都在视图?OnDraw 成员函数中发生,必须在视囄中重写该成员函数。(鼠标l图是个特例Q这在通过视图解释用户输入中讨论。)(j) 的确QOnPaint()用来响应WM_PAINT消息Q视cȝOnPaint()内部Ҏ(gu)是打印还是屏q绘制分别以不同的参数调用OnDraw()虚函数。所以在OnDraw()里你可以区别对待打印和屏q绘制?br>其实QMFC在进行打印前后还做了(jin)很多工作Q调用了(jin)很多虚函敎ͼ比如OnPreparePrint(){?/font> 视图H口完全建立后第一个被框架调用的函数。框架在W一ơ调用OnDraw前会(x)调用OnInitialUpdateQ因此OnInitialUpdate是设|滚动视囄逻辑寸和映模式的最合适的地方?/font> 因ؓ(f)Qmakefile关系C(jin)整个工程的编译规则。一个工E中的源文g不计敎ͼ其按cd、功能、模块分别放在若q个目录中,makefile定义?jin)一pd的规则来指定Q哪些文仉要先~译Q哪些文仉要后~译Q哪些文仉要重新编译,甚至于进行更复杂的功能操作,因ؓ(f)makefile像一个Shell脚本一P其中也可以执行操作系l的命o(h)?
makefile带来的好处就是—?#8220;自动化编?#8221;Q一旦写好,只需要一个make命o(h)Q整个工E完全自动编译,极大的提高了(jin)软g开发的效率。make是一个命令工P是一个解释makefile中指令的命o(h)工具Q一般来_(d)大多数的IDE都有q个命o(h)Q比如:(x)Delphi的makeQVisual C++的nmakeQLinux下GNU的make。可见,makefile都成Z(jin)一U在工程斚w的编译方法?
现在讲述如何写makefile的文章比较少Q这是我惛_q篇文章的原因。当?dng)不同产商的make各不相同Q也有不同的语法Q但其本质都是在“文g依赖?#8221;上做文章Q这里,我仅对GNU的makeq行讲述Q我的环境是RedHat Linux 8.0Qmake的版本是3.80。必竟,q个make是应用最为广泛的Q也是用得最多的。而且其还是最遵@于IEEE 1003.2-1992 标准的(POSIX.2Q?
在这文档中Q将以C/C++的源码作为我们基Q所以必然涉?qing)一些关于C/C++的编译的知识Q相关于q方面的内容Q还请各位查看相关的~译器的文档。这里所默认的编译器是UNIX下的GCC和CC?
~译Ӟ~译器需要的是语法的正确Q函C变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位|(头文件中应该只是声明Q而定义应该放在C/C++文g中)(j)Q只要所有的语法正确Q编译器可以编译出中间目标文g。一般来_(d)每个源文仉应该对应于一个中间目标文ӞO文g或是OBJ文gQ?
链接Ӟ主要是链接函数和全局变量Q所以,我们可以使用q些中间目标文gQO文g或是OBJ文gQ来链接我们的应用程序。链接器q不函数所在的源文Ӟ只管函数的中间目标文ӞObject FileQ,在大多数时候,׃源文件太多,~译生成的中间目标文件太多,而在链接旉要明昑֜指出中间目标文g名,q对于编译很不方便,所以,我们要给中间目标文g打个包,在Windows下这U包?#8220;库文?#8221;QLibrary File)Q也是 .lib 文gQ在UNIX下,是Archive FileQ也是 .a 文g?
ȝ一下,源文仉先会(x)生成中间目标文gQ再׃间目标文件生成执行文件。在~译Ӟ~译器只(g)程序语法,和函数、变量是否被声明。如果函数未被声明,~译器会(x)l出一个警告,但可以生成Object File。而在链接E序Ӟ链接器会(x)在所有的Object File中找d数的实现Q如果找不到Q那到就?x)报链接错误码(Linker ErrorQ,在VC下,q种错误一般是QLink 2001错误Q意思说是说Q链接器未能扑ֈ函数的实现。你需要指定函数的Object File.
好,a归正传,GNU的make有许多的内容Q闲a叙Q还是让我们开始吧?
首先Q我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册Q在q个CZ中,我们的工E有8个C文gQ和3个头文gQ我们要写一个Makefile来告诉make命o(h)如何~译和链接这几个文g。我们的规则是:(x)
只要我们的Makefile写得够好Q所有的q一切,我们只用一个make命o(h)可以完成,make命o(h)?x)自动智能地?gu)当前的文件修改的情况来确定哪些文仉要重~译Q从而自q译所需要的文g和链接目标程序?
prerequisites是Q要生成那个target所需要的文g或是目标?
command也就是make需要执行的命o(h)。(L的Shell命o(h)Q?
q是一个文件的依赖关系Q也是_(d)targetq一个或多个的目标文件依赖于prerequisites中的文gQ其生成规则定义在command中。说白一点就是说Qprerequisites中如果有一个以上的文g比target文g要新的话Qcommand所定义的命令就?x)被执行。这是Makefile的规则。也是Makefile中最核心(j)的内宏V?
说到底,Makefile的东西就是这样一点,好像我的q篇文档也该l束?jin)。呵c(din)还不尽?dng)q是Makefile的主U和核心(j)Q但要写好一个Makefileq不够,我会(x)以后面一点一点地l合我的工作l验l你慢慢到来。内容还多着呢。:(x)Q?
在这个makefile中,目标文gQtargetQ包含:(x)执行文gedit和中间目标文Ӟ*.oQ,依赖文gQprerequisitesQ就是冒号后面的那些 .c 文g?.h文g。每一?.o 文g都有一l依赖文Ӟ而这?.o 文g又是执行文g edit 的依赖文件。依赖关pȝ实质上就是说明了(jin)目标文g是由哪些文g生成的,换言之,目标文g是哪些文件更新的?
在定义好依赖关系后,后箋的那一行定义了(jin)如何生成目标文g的操作系l命令,一定要以一个Tab键作为开头。记住,makeq不命令是怎么工作的,他只执行所定义的命令。make?x)比较targets文g和prerequisites文g的修Ҏ(gu)期,如果prerequisites文g的日期要比targets文g的日期要斎ͼ或者target不存在的话,那么Qmake׃(x)执行后箋定义的命令?
q里要说明一点的是,clean不是一个文Ӟ它只不过是一个动作名字,有点像C语言中的lable一P其冒号后什么也没有Q那么,make׃?x)自动去找文件的依赖性,也就不会(x)自动执行其后所定义的命令。要执行其后的命令,p在make命o(h)后明昑־指出q个lable的名字。这L(fng)Ҏ(gu)非常有用Q我们可以在一个makefile中定义不用的~译或是和编译无关的命o(h)Q比如程序的打包Q程序的备䆾Q等{?
q就是整个make的依赖性,make?x)一层又一层地L文g的依赖关p,直到最l编译出W一个目标文件。在扑֯的过E中Q如果出现错误,比如最后被依赖的文件找不到Q那么make׃(x)直接退出,q报错,而对于所定义的命令的错误Q或是编译不成功QmakeҎ(gu)不理。make只管文g的依赖性,卻I如果在我找了(jin)依赖关系之后Q冒号后面的文gq是不在Q那么对不vQ我׃工作啦?
通过上述分析Q我们知道,像cleanq种Q没有被W一个目标文件直接或间接兌Q那么它后面所定义的命令将不会(x)被自动执行,不过Q我们可以显Cmake执行。即命o(h)—?#8220;make clean”Q以此来清除所有的目标文gQ以侉K~译?
于是在我们编E中Q如果这个工E已被编译过?jin),当我们修改?jin)其中一个源文gQ比如file.cQ那么根据我们的依赖性,我们的目标file.o?x)被重编译(也就是在q个依性关pd面所定义的命令)(j)Q于是file.o的文件也是最新的啦,于是file.o的文件修Ҏ(gu)间要比edit要新Q所以edit也会(x)被重新链接了(jin)Q详见edit目标文g后定义的命o(h)Q?
而如果我们改变了(jin)“command.h”Q那么,kdb.o、command.o和files.o都会(x)被重~译Qƈ且,edit?x)被重链接?
比如Q我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJQ反正不什么啦Q只要能够表Cobj文gp?jin)。我们在makefile一开始就q样定义Q? 于是如果有新?.o 文g加入Q我们只需单地修改一?objects 变量可以了(jin)?
关于变量更多的话题,我会(x)在后l给你一一道来?
只要make看到一个[.o]文gQ它?yu)׃?x)自动的把[.c]文g加在依赖关系中,如果make扑ֈ一个whatever.oQ那么whatever.cQ就?x)是whatever.o的依赖文件。ƈ?cc -c whatever.c 也会(x)被推导出来,于是Q我们的makefile再也不用写得q么复杂。我们的是新的makefile又出炉了(jin)? 关于更ؓ(f)详细?#8220;隐晦规则”?#8220;伪目标文?#8221;Q我?x)在后箋l你一一道来?
上面是一个makefile的概貌,也是makefile的基Q下面还有很多makefile的相关细节,准备好了(jin)吗?准备好了(jin)来?
最后,q值得一提的是,在Makefile中的命o(h)Q必要以[Tab]键开始?
当然Q你可以使用别的文g名来书写MakefileQ比如:(x)“Make.Linux”Q?#8220;Make.Solaris”Q?#8220;Make.AIX”{,如果要指定特定的MakefileQ你可以使用make?#8220;-f”?#8220;--file”参数Q如Qmake -f Make.Linux或make --file Make.AIX?
在include前面可以有一些空字符Q但是绝不能是[Tab]键开始。include?filename>可以用一个或多个I格隔开。D个例子,你有q样几个MakefileQa.mk、b.mk、c.mkQ还有一个文件叫foo.makeQ以?qing)一个变?(bar)Q其包含?jin)e.mk和f.mkQ那么,下面的语句:(x)
如果有文件没有找到的话,make?x)生成一条警告信息,但不?x)马上出现致命错误。它?x)l蝲入其它的文gQ一旦完成makefile的读取,make?x)再重试q些没有扑ֈQ或是不能读取的文gQ如果还是不行,make才会(x)出现一条致命信息。如果你惌make不理那些无法d的文Ӟ而l执行,你可以在include前加一个减?#8220;-”。如Q?
但是在这里我q是不要使用q个环境变量Q因为只要这个变量一被定义,那么当你使用makeӞ所有的Makefile都会(x)受到它的影响Q这l不是你想看到的。在q里提这个事Q只是ؓ(f)?jin)告诉大Ӟ也许有时候你的Makefile出现?jin)怪事Q那么你可以看看当前环境中有没有定义q个变量?
1-5步ؓ(f)W一个阶D,6-7为第二个阶段。第一个阶D中Q如果定义的变量被用了(jin)Q那么,make?x)把其展开在用的位置。但makeq不?x)完全马上展开Qmake使用的是拖g战术Q如果变量出现在依赖关系的规则中Q那么仅当这条依赖被军_要用了(jin)Q变量才?x)在其内部展开?
当然Q这个工作方式你不一定要清楚Q但是知道这个方式你也会(x)对make更ؓ(f)熟?zhn)。有?jin)这个基Q后l部分也容易看懂了(jin)?
在Makefile中,规则的顺序是很重要的Q因为,Makefile中只应该有一个最l目标,其它的目标都是被q个目标所q带出来的,所以一定要让make知道你的最l目标是什么。一般来_(d)定义在Makefile中的目标可能?x)有很多Q但是第一条规则中的目标将被确立ؓ(f)最l的目标。如果第一条规则中的目标有很多个,那么Q第一个目标会(x)成ؓ(f)最l的目标。make所完成的也是q个目标?
好了(jin)Q还是让我们来看一看如何书写规则?
command是命令行Q如果其不与“target:prerequisites”在一行,那么Q必M[Tab键]开_(d)如果和prerequisites在一行,那么可以用分号做为分隔。(见上Q?
prerequisites也就是目标所依赖的文Ӟ或依赖目标)(j)。如果其中的某个文g要比目标文g要新Q那么,目标p认ؓ(f)?#8220;q时?#8221;Q被认ؓ(f)是需要重生成的。这个在前面已经讲过?jin)?
如果命o(h)太长Q你可以使用反斜框(‘\’Q作为换行符。make对一行上有多个字符没有限制。规则告诉make两g事,文g的依赖关pd如何成成目标文g?
一般来_(d)make?x)以UNIX的标准ShellQ也是/bin/sh来执行命令?
如果我们惛_义一pd比较cM的文Ӟ我们很自然地想起用通配W。make支持三各通配W:(x)“*”Q?#8220;?”?#8220;[...]”。这是和Unix的B-Shell是相同的?
波浪P“~”Q字W在文g名中也有比较Ҏ(gu)的用途。如果是“~/test”Q这pC当前用L(fng)$HOME目录下的test目录。?#8220;~hchen/test”则表C用户hchen的宿ȝ录下的test目录。(q些都是Unix下的知识了(jin)Qmake也支持)(j)而在Windows或是MS-DOS下,用户没有宿主目录Q那么L号所指的目录则根据环境变?#8220;HOME”而定?
通配W代替了(jin)你一pd的文Ӟ?#8220;*.c”表示所以后~为c的文件。一个需要我们注意的是,如果我们的文件名中有通配W,如:(x)“*”Q那么可以用转义字符“\”Q如“\*”来表C真实的“*”字符Q而不是Q意长度的字符丌Ӏ?
好吧Q还是先来看几个例子吧:(x) 在一些大的工E中Q有大量的源文gQ我们通常的做法是把这许多的源文g分类Qƈ存放在不同的目录中。所以,当make需要去扑֯文g的依赖关pLQ你可以在文件前加上路径Q但最好的Ҏ(gu)是把一个\径告诉makeQ让make在自动去找?
Makefile文g中的Ҏ(gu)变量“VPATH”是完成q个功能的,如果没有指明q个变量Qmake只会(x)在当前的目录中去扑֯依赖文g和目标文件。如果定义了(jin)q个变量Q那么,make׃(x)在当当前目录找不到的情况下,到所指定的目录中LL件了(jin)? 另一个设|文件搜索\径的Ҏ(gu)是用make?#8220;vpath”关键字(注意Q它是全写的)(j)Q这不是变量Q这是一个make的关键字Q这和上面提到的那个VPATH变量很类|但是它更为灵zR它可以指定不同的文件在不同的搜索目录中。这是一个很灉|的功能。它的用方法有三种Q?
vapth使用Ҏ(gu)中的< pattern>需要包?#8220;%”字符?#8220;%”的意思是匚w零或若干字符Q例如,“%.h”表示所有以“.h”l尾的文件?lt; pattern>指定?jin)要搜?ch)的文仉Q?lt; directories>则指定了(jin)
我们可以q箋C用vpath语句Q以指定不同搜烦(ch){略。如果连l的vpath语句中出C(jin)相同?lt; pattern>Q或是被重复?jin)?lt; pattern>Q那么,make?x)按照vpath语句的先后顺序来执行搜烦(ch)。如Q? 最早先的一个例子中Q我们提到过一?#8220;clean”的目标,q是一?#8220;伪目?#8221;Q? 因ؓ(f)Q我们ƈ不生?#8220;clean”q个文g?#8220;伪目?#8221;q不是一个文Ӟ只是一个标{,׃“伪目?#8221;不是文gQ所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过昄地指明这?#8220;目标”才能让其生效。当?dng)?#8220;伪目?#8221;的取名不能和文g名重名,不然其就失去?#8220;伪目?#8221;的意义了(jin)?
当然Qؓ(f)?jin)避免和文g重名的这U情况,我们可以使用一个特D的标记“.PHONY”来显C地指明一个目标是“伪目?#8221;Q向make说明Q不是否有q个文gQ这个目标就?#8220;伪目?#8221;? 随便提一句,从上面的例子我们可以看出Q目标也可以成ؓ(f)依赖。所以,伪目标同样也可成Z赖。看下面的例子:(x)
Makefile的规则中的目标可以不止一个,其支持多目标Q有可能我们的多个目标同时依赖于一个文Ӟq且其生成的命o(h)大体cM。于是我们就能把其合qv来。当?dng)多个目标的生成规则的执行命o(h)是同一个,q可能会(x)可我们带来麻?ch),不过好在我们的可以用一个自动化变量“$@”Q关于自动化变量Q将在后面讲qͼ(j)Q这个变量表C着目前规则中所有的目标的集合,q样说可能很抽象Q还是看一个例子吧?
静态模式可以更加容易地定义多目标的规则Q可以让我们的规则变得更加的有弹性和灉|。我们还是先来看一下语法:(x)
targets定义?jin)一pd的目标文Ӟ可以有通配W。是目标的一个集合?
target-parrtern是指明了(jin)targets的模式,也就是的目标集模式?
prereq-parrterns是目标的依赖模式Q它对target-parrtern形成的模式再q行一ơ依赖目标的定义?
q样描述q三个东西,可能q是没有说清楚,q是举个例子来说明一下吧。如果我们的<target-parrtern>定义?#8220;%.o”Q意思是我们?target>集合中都是以“.o”l尾的,而如果我们的<prereq-parrterns>定义?#8220;%.c”Q意思是?lt;target-parrtern>所形成的目标集q行二次定义Q其计算Ҏ(gu)是,?lt;target-parrtern>模式中的“%”Q也是L?jin)[.o]q个l尾Q,qؓ(f)其加上[.c]q个l尾QŞ成的新集合?
所以,我们?#8220;目标模式”或是“依赖模式”中都应该?#8220;%”q个字符Q如果你的文件名中有“%”那么你可以用反斜杠“\”q行转义Q来标明真实?#8220;%”字符?
看一个例子:(x) $(filter %.o,$(files))表示调用Makefile的filter函数Q过?#8220;$filter”集,只要其中模式?#8220;%.o”的内宏V其的它内容Q我׃用多说了(jin)吧。这个例字展CZ(jin)Makefile中更大的Ҏ(gu)?
在Makefile中,我们的依赖关pd能会(x)需要包含一pd的头文gQ比如,如果我们的main.c中有一?#8220;#include "defs.h"”Q那么我们的依赖关系应该是:(x) gcc -M main.c的输出是Q? 于是Q我们可以写出[.c]文g和[.d]文g的依赖关p,q让make自动更新或自成[.d]文gQƈ把其包含在我们的主Makefile中,q样Q我们就可以自动化地生成每个文g的依赖关pM(jin)?
q里Q我们给Z(jin)一个模式规则来产生[.d]文gQ? q个规则的意思是Q所有的[.d]文g依赖于[.c]文gQ?#8220;rm -f $@”的意思是删除所有的目标Q也是[.d]文gQ第二行的意思是Qؓ(f)每个依赖文g“$<”Q也是[.c]文g生成依赖文gQ?#8220;$@”表示模式“%.d”文gQ如果有一个C文g是name.cQ那?#8220;%”是“name”Q?#8220;$$$$”意ؓ(f)一个随机编PW二行生成的文g有可能是“name.d.12345”Q第三行使用sed命o(h)做了(jin)一个替换,关于sed命o(h)的用法请参看相关的用文档。第四行是删除临时文g?
总而言之,q个模式要做的事是在编译器生成的依赖关pM加入[.d]文g的依赖,x依赖关系Q? 每条规则中的命o(h)和操作系lShell的命令行是一致的。make?x)一按顺序一条一条的执行命o(h)Q每条命令的开头必M[Tab]键开_(d)除非Q命令是紧跟在依赖规则后面的分号后的。在命o(h)行之间中的空格或是空行会(x)被忽略,但是如果该空格或I是以Tab键开头的Q那么make?x)认为其是一个空命o(h)?
我们在UNIX下可能会(x)使用不同的ShellQ但是make的命令默认是?#8220;/bin/sh”——U(ku)NIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。Makefile中,“#”是注释符Q很像C/C++中的“//”Q其后的本行字符都被注释?
通常Qmake?x)把其要执行的命令行在命令执行前输出到屏q上。当我们?#8220;@”字符在命令行前,那么Q这个命令将不被make昄出来Q最具代表性的例子是,我们用这个功能来像屏q显CZ些信息。如Q? 而make参数“-s”?#8220;--slient”则是全面止命o(h)的显C?
当依赖目标新于目标时Q也是当规则的目标需要被更新Ӟmake?x)一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的l果应用在下一条命令时Q你应该使用分号分隔q两条命令。比如你的第一条命令是cd命o(h)Q你希望W二条命令得在cd之后的基上运行,那么你就不能把这两条命o(h)写在两行上,而应该把q两条命令写在一行上Q用分号分隔。如Q? 当我们执?#8220;make exec”ӞW一个例子中的cd没有作用Qpwd?x)打印出当前的Makefile目录Q而第二个例子中,cdpv作用?jin),pwd?x)打印?#8220;/home/hchen”?
make一般是使用环境变量SHELL中所定义的系lShell来执行命令,默认情况下用UNIX的标准Shell—?bin/sh来执行命令。但在MS-DOS下有点特D,因ؓ(f)MS-DOS下没有SHELL环境变量Q当然你也可以指定。如果你指定?jin)UNIX风格的目录Ş式,首先Qmake?x)在SHELL所指定的\径中扑֯命o(h)解释器,如果找不刎ͼ其会(x)在当前盘W中的当前目录中LQ如果再找不刎ͼ其会(x)在PATH环境变量中所定义的所有\径中L。MS-DOS中,如果你定义的命o(h)解释器没有找刎ͼ其会(x)l你的命令解释器加上诸如“.exe”?#8220;.com”?#8220;.bat”?#8220;.sh”{后~?
每当命o(h)q行完后Qmake?x)检每个命令的q回码,如果命o(h)q回成功Q那么make?x)执行下一条命令,当规则中所有的命o(h)成功q回后,q个规则q是成功完成了(jin)。如果一个规则中的某个命令出错了(jin)Q命令退出码非零Q,那么make׃(x)l止执行当前规则Q这有可能l止所有规则的执行?
有些时候,命o(h)的出错ƈ不表C就是错误的。例如mkdir命o(h)Q我们一定需要徏立一个目录,如果目录不存在,那么mkdir成功执行,万事大吉Q如果目录存在,那么出错了(jin)。我们之所以用mkdir的意思就是一定要有这L(fng)一个目录,于是我们׃希望mkdir出错而终止规则的q行?
Z(jin)做到q一点,忽略命o(h)的出错,我们可以在Makefile的命令行前加一个减?#8220;-”Q在Tab键之后)(j)Q标Cؓ(f)不管命o(h)Z出错都认为是成功的。如Q? q有一个要提一下的make的参数的?#8220;-k”或是“--keep-going”Q这个参数的意思是Q如果某规则中的命o(h)出错?jin),那么q目该规则的执行,但l执行其它规则?
在一些大的工E中Q我们会(x)把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的MakefileQ这有利于让我们的Makefile变得更加地简z,而不至于把所有的东西全部写在一个Makefile中,q样?x)很隄护我们的MakefileQ这个技术对于我们模块编译和分段~译有着非常大的好处?
例如Q我们有一个子目录叫subdirQ这个目录下有个Makefile文gQ来指明?jin)这个目录下文g的编译规则。那么我们L的Makefile可以q样书写Q? 我们把这个Makefile叫做“LMakefile”QLMakefile的变量可以传递到下的Makefile中(如果你显C的声明Q,但是不会(x)覆盖下层的Makefile中所定义的变量,除非指定?#8220;-e”参数?
如果你要传递变量到下Makefile中,那么你可以用这L(fng)声明Q?
需要注意的是,有两个变量,一个是SHELLQ一个是MAKEFLAGSQ这两个变量不管你是否exportQ其L要传递到下层Makefile中,特别是MAKEFILES变量Q其中包含了(jin)make的参C息,如果我们执行“LMakefile”时有make参数或是在上层Makefile中定义了(jin)q个变量Q那么MAKEFILES变量会(x)是这些参敎ͼq会(x)传递到下层Makefile中,q是一个系l的环境变量?
但是make命o(h)中的有几个参数ƈ不往下传递,它们?#8220;-C”,“-f”,“-h”“-o”?#8220;-W”Q有关Makefile参数的细节将在后面说明)(j)Q如果你不想往下层传递参敎ͼ那么Q你可以q样来:(x) q有一个在“嵌套执行”中比较有用的参数Q?#8220;-w”或是“--print-directory”?x)在make的过E中输出一些信息,让你看到目前的工作目录。比如,如果我们的下Umake目录?#8220;/home/hchen/gnu/make”Q如果我们?#8220;make -w”来执行,那么当进入该目录Ӟ我们?x)看刎ͼ?x) 如果Makefile中出C些相同命令序列,那么我们可以些相同的命o(h)序列定义一个变量。定义这U命令序列的语法?#8220;define”开始,?#8220;endef”l束Q如Q? 许多操纵W我们能够改变输出的外观。有两大cȝ输出控制Q控制数值的表示Q以?qing)控制填充符的数量和布局?/p>
改变对象格式化状态的操纵W的一个例子是 boolalpha 操纵W。默认情况下Q将 bool 值显CZؓ(f) 1 ?0Q?tt>true 值显CZؓ(f) 1Q?false 值显CZؓ(f) 0。可以通过的 boolalpha 操纵W覆盖这个格式化Q?/p>
执行Ӟq段E序产生下面的输出:(x) 一旦将 boolalpha “?#8221;?coutQ从q个点v改变了(jin) cout 怎样昄 bool |后箋昄 bool 值的操作用 true ?false q行昄?/p>
要取?cout 的格式状态改变,必须应用 noboolalphaQ?/p>
现在只改?bool 值的格式化来昄 bool_valQƈ且立卛_重|ؓ(f)原来的状态?/p>
默认情况下,用十q制d整型倹{通过使用操纵W?hex?tt>oct ?decQ程序员可以表C制改为八q制、十六进制或恢复十进Ӟ点值的表示不受影响Q:(x) ~译和执行的时候,E序产生下面的输出:(x) 注意Q像 boolalpha 一Pq些操纵W改变格式状态。它们媄(jing)响紧接在后面的输出,以及(qing)所有后l的整型输出Q直到通过调用另一操纵W重围格式ؓ(f)止?/p>
默认情况下,昄数值的时候,不存在关于所用基数的可见记号。例如,20 ?20,q是 16 的八q制表示Q按十进制模式显C数值的时候,?x)按我们期待的格式打印数倹{如果需要打印八q制或十六进制|可能应该也?showbase 操纵W?tt>showbase 操纵W导致输出流使用的约定,与指定整型常量基数所用的相同Q?/p>
?0x 为前DC十六进制?/p>
?0 为前DC八q制?/p>
没有M前导表示十进制?/p>
修改E序使用 showbase 如下Q?/p>
修改后的输出使得基础值到底是什么很清楚Q?/p>
noshowbase 操纵W重|?coutQ以便它不再昄整型值的表示基数?/p>
默认情况下,十六q制值用带小?x 的小写Ş式打印。可以应?uppercase 操纵W显C?X q将十六q制数字 a - f 昄为大写字母?/p>
前面的程序生下面的输出Q?/p>
要恢复小写,应?nouppercase 操纵W?/p>
对于点值的格式化,可以控制下面三个斚wQ?/p>
_ֺQ显C多位数字?/p>
记数法:(x)用小数还是科学记法法昄?/p>
Ҏ(gu)整数的Q点值的数点的处理?/p>
默认情况下,使用六位数字的精度显CQ点倹{如果值没有小数部分,则省略小数点。用小数Ş式还是科学记数法昄数值取决于被显C的点数的|标准库选择增强数值可L的格式Q非常大和非常小的g用科学记数法昄Q其他g用小数Ş式?/p>
默认情况下,_ֺ控制昄的数字M数。显C的时候,Q点值四舍五入到当前_ֺ。因此,如果当前_ֺ?4Q则 3.14159 成ؓ(f) 3.142Q如果精度是 3Q打Cؓ(f) 3.14?/p>
通过名ؓ(f) precision 的成员函敎ͼ或者通过使用 setprecision 操纵W,可以改变_ֺ?tt>precision 成员是重载的Q?a >W?7.8 ?/font>Q:(x)一个版本接受一?int 值ƈ精度设|ؓ(f)那个新|它返?span>先前的精度|另一个版本不接受实参q返回当前精度倹{?tt>setprecision 操纵W接受一个实参,用来讄_ֺ?/p>
下面的程序说明控制显CQ点值所用精度的不同Ҏ(gu)Q?/p>
~译q执行后Q程序生下面的输出Q?/p>
q个E序调用标准库中?sqrt 函数Q可以在头文?cmath 中找到它?tt>sqrt 函数量重载的Q可以用 float?tt>double ?long double 实参调用Q它q回实参的^Ҏ(gu)?/p>
操纵W和其他接受实参的操U늬定义在头文g iomanip 中?/p>
默认情况下,用于昄点值的记数法取决于数的大小Q如果数很大或很,按U学记数法显C,否则Q用固定位数的数。标准库选择使得数容易阅ȝ记数法?/p>
QҎ(gu)昄为普通数Q相对于昄货币、百分比Q那时我们希望控制值的外观Q的时候,通常最好让标准库来选择使用的记数法。要强制U学记数法或固定位数数的一U情冉|在显C的时候,表中的小数点应该寚w?/p>
如果希望强制U学记数法或固定位数数表示Q可以通过使用适当的操U늬做到q一点:(x)scientific 操纵W将变Z用科学记数法。像在十六进制g昄 x 一P也可以通过 uppercase 操纵W控制科学记数法中的 e?tt>fixed 操纵W将ؓ(f)使用固定位数数表示?/p>
q些操纵W改变流_ֺ的默认含义。执?scientific ?fixed 之后Q精度值控制小数点之后的数位。默认情况下Q精度指定数字的M数——小数点之前和之后。?fixed ?scientific 命名我们能够按列寚w来显C数Q这一{略保证数Ҏ(gu)L在相对于被显C的数部分固定的位|?/p>
与其他操U늬不同Q不存在流恢复为根据被昄值选择记数法的默认状态的操纵W,相反Q我们必调?unsetf 成员来取?scientific ?fixed 所做的改变。要流恢复为Q点值的默认处理Q将名ؓ(f) floatfield 的标准库定义gl?unsetf 函数Q?/p>
除了(jin)取消它们的效果之外,使用q些操纵W像使用L其他操纵W一P(x) 产生如下输出Q?/p>
默认情况下,当Q点值的数部分?0 的时候,不显C小数点?tt>showpoint 操纵W强制显C小数点Q?/p>
noshowpoint 操纵W恢复默认行为。下一个输辑ּ具有默认行为,卻I如果点值小数部分ؓ(f) 0,取消小数点?/p>
按栏昄数据的时候,l常很希望很好地控制数据的格式化。标准库提供下面几个操纵帮助我们实现需要的控制Q?/p>
setwQ指定下一个数值或字符串的最间隔?/p>
leftQ左寚w输出?/p>
rightQ右寚w输出。输出默认ؓ(f)叛_齐?/p>
internalQ控制负值的W号位置?tt>internal 左对齐符号且叛_齐|用空格填充介于其间的I间?/p>
setfillQ我们能够指定填充输出时用的另一个字W。默认情况下Q值是I格?/p>
?endl 一Psetw 不改变输出流的内部状态,它只军_下一个输出的长度?/p>
下面E序D说明了(jin)q些操纵W:(x) 执行Ӟ该程序段产生如下输出Q?/p>
默认情况下,输入操作W忽略空白(I格、制表符、换行符、进U和回RQ。对下面的@环:(x) l定输入序列 循环执行四次从字W?a d dQ蟩q介于其间的I格、可能的制表W和换行W。该E序D늚输出是:(x) noskipws 操纵W导致输入操作符读(而不是蟩q)(j)I白。要q回默认行ؓ(f)Q应?skipws 操纵W:(x) l定与前面相同的输入Q该循环q行 7 ơP代,读输入中的空白以?qing)字W。该循环产生如下输出Q?/p>
q今为止Q示例程序中只用过格式化的 IO 操作。输入和输出操作W(<< ?>>Q根据被处理数据的类型格式化所d的数据。输入操作符忽略I白Q输出操作符应用填充、精度等?/p>
标准库还提供?jin)丰富的支持未格式?IO 的低U操作,q些操作使我们能够将作为未解释的字节序列处理,而不是作为数据类型(?char?tt>int?tt>string {)(j)的序列处理?/p>
所以解x法有三个?
1.用OnEraseBkGnd实现,不要调用原来的OnEraseBkGnd函数.
2.用OnPaint实现,同时重蝲OnEraseBkGnd,其中直接q回.
3.用OnPaint实现,创徏H口时设|背景刷为空
4.用OnPaint实现,但是要求h时用Invalidate(FALSE)q样
的函?(不过q种情况?H口覆盖{造成的刷新还是要闪一
?所以不是彻底的解决Ҏ(gu))
都挺单的.
則OnEraseBkgnd()所做的事就是把按鈕畫成灰色
而OnPaint()所做的?是畫上文字
既然這兩個member function都是用來畫出元g?br>那為何還要分OnPaint() ?OnEraseBkgnd() ?br>其實OnPaint() ?OnEraseBkgnd() Ҏ(gu)是有差?br>1.
2.
3.
如果我們是一個在做圖形化使用者介面的?br>常會需要把一늾的圖片a為我們dialog的底?br>把繪圖的E式放在OnPaint() 之中 可能會常到一些問?br>比方說拖曳一個視H在我們做的dialog上面一直移?br>則dialog會變成灰?直到動作停止才恢?br>這是因為每次需要重J的時?E式都會馬上呼叫OnEraseBkgnd()
OnEraseBkgnd()把dialog畫成灰色
而只有動作停止之?E式才會呼叫OnPaint() 這時才會把我們要畫的底圖g?br>
這個問的解法 比較差點的方法是把OnEraseBkgnd() 改寫成不做事的function
如下所C?/font>
{
}
E式便不會畫上灰色的底色?/font>
比較好的做法是直接將J圖的程式從OnPaint()UdOnEraseBkgnd()來做
如下所C?/font>
// m_bmpBKGND ZCBitmap物g 且事先早已載(j)入我們的底圖
// 底圖的大與我們的視窗client大小一?br>
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
}
特別要注意的?取得重畫大小是用GetUpdateRect() 而不是GetClientRect()
如果使用GetClientRect() 會把不該重畫的地斚w?/font>
The WM_PAINT message is sent when the UpdateWindow or RedrawWindow member function is called.
2) OnPaint()调用OnDraw()QOnPrint也会(x)调用OnDraw()Q所以OnDraw()是显C和打印的共同操作?br>
OnPaint是WM_PAINT消息引发的重l消息处理函敎ͼ在OnPaint中会(x)调用OnDraw来进行绘图。OnPaint中首先构造一个CPaintDCcd实例Q然后一q个实例为参数来调用虚函数OnPrepareDC来进行一些绘制前的一些处理,比设|映模式,最后调用OnDraw。?/font>OnDraw?/strong>OnPrepareDC不是消息处理函数。所以在不是因ؓ(f)重绘消息所引发的OnPaintDOnDraw被调用时Q比如在OnLButtonDown{消息处理函Cl图Ӟ要先自己调用OnPrepareDC?br>至于CPaintDC和CClientDCҎ(gu)是两回事?CPaintDC是一个设备环境类Q在OnPaint中作为参C递给OnPrepareDC来作讑֤环境的设|。真正和CClientDCh可比性的是CWindowDCQ他们一个是描述客户区域Q一个是描述整个屏幕?br>如果是对CVIEW或从CVIEWcL生的H口l图时应该用OnDraw?/font>
void CView::OnPaint() //见VIEWCORE.CPP
{
OnPrepareDC(&dc)Q?br>OnDraw(&dc);
}
///CView默认的标准的OnPrint函数
void CView::OnPrint(CDC* pDC, CPrintInfo*)
{
ASSERT_VALID(pDC);
OnDraw(pDC);
}
{
CString s = pDoc->GetData();
GetClientRect( &rect ); // Returns a CString CRect rect;
pDC->SetTextAlign( TA_BASELINE | TA_CENTER );
pDC->TextOut( rect.right / 2, rect.bottom / 2, s, s.GetLength() );
}
最后:(x)现在大家明白q哥俩之间的关系?jin)吧。因此我们一般用OnPaintl护H口的客户区Q例如我们的H口客户区加一个背景图片)(j)Q用OnDrawl护视图的客户区Q例如我们通过鼠标在视图中dQ。当然你也可以不按照上面规律来,只要辑ֈ目的q且没有问题Q怎么q都成。补充:(x)我们q可以利用Invalidate(),ValidateRgn(),ValidateRect()函数强制的重ȝ口,具体的请参考MSDN吧?/font>
OnDraw 重写Q?br>通过调用(zhn)提供的文档成员函数获取数据?br>通过调用框架传递给 OnDraw 的设备上下文对象的成员函数来昄数据?br>当文档的数据以某U方式更改后Q必重l视图以反映该更攏V默认的 OnUpdate 实现使视囄整个工作区无效。当视图变得无效ӞW(xu)indows ?WM_PAINT 消息发送给它。该视图?OnPaint 处理函数通过创徏 CPaintDC cȝ讑֤上下文对象来响应该消息ƈ调用视图?OnDraw 成员函数?br>
当没有添加WM_PAINT消息处理?H口重绘?由O(jin)nDraw来进行消息响?..当添加WM_PAINT消息处理?H口重绘?WM_PAINT消息被投?由O(jin)nPaint来进行消息响?q时׃能隐式调用OnDraw?必须昑ּ调用(
隐式调用:当由O(jin)nPaint来进行消息响应时,pȝ自动调用CView::OnDraw(&pDC).
惌一下,H口昄的内容和打印的内Ҏ(gu)差不多的Q所以,一般情况下Q统一由O(jin)nDraw来画。窗口前景需要刷新时Q系l会(x)?x)调用到OnPaintQ而OnPaint一般情况下是对DC作一些初始化操作后,调用OnDraw()?/font>
OnEraseBkGnd()Q是H口背景需要刷新时ql调用的。明昄一个例子是讄H口的背景颜Ԍ你可以把q放在OnPaint中去做,但是?x)产生闪烁的现象?j)?
至于怎么界定背景和前景,那要具体问题具体分析?jin),一般情况下Q你q是很容易区别的吧?/font>
另外OnInitialUpdate
]]>
]]>0 Makefile概述
什么是makefileQ或许很多Winodws的程序员都不知道q个东西Q因为那些Windows的IDE都ؓ(f)你做?jin)这个工作,但我觉得要作一个好的和professional的程序员Qmakefileq是要懂。这好像现在有q么多的HTML的编辑器Q但如果你想成ؓ(f)一个专业h士,你还是要?jin)解HTML的标识的含义。特别在Unix下的软g~译Q你׃能不自己写makefile?jin),会(x)不会(x)写makefileQ从一个侧面说明了(jin)一个h是否具备完成大型工程的能力?
0.1 关于E序的编译和链接
在此Q我惛_说关于程序编译的一些规范和Ҏ(gu)Q一般来_(d)无论是C、C++、还是pasQ首先要把源文g~译成中间代码文Ӟ在Windows下也是 .obj 文gQUNIX下是 .o 文gQ即 Object FileQ这个动作叫做编译(compileQ。然后再把大量的Object File合成执行文gQ这个动作叫作链接(linkQ?
1 Makefile 介绍
make命o(h)执行Ӟ需要一?Makefile 文gQ以告诉make命o(h)需要怎么L(fng)ȝ译和链接E序?
1.1 Makefile的规?
在讲q这个Makefile之前Q还是让我们先来_略地看一看Makefile的规则?
target ... : prerequisites ...
command
...
...
target也就是一个目标文Ӟ可以是Object FileQ也可以是执行文件。还可以是一个标{(LabelQ,对于标签q种Ҏ(gu),在后l的“伪目?#8221;章节中会(x)有叙q?
1.2 一个示?
正如前面所说的Q如果一个工E有3个头文gQ和8个C文gQ我们ؓ(f)?jin)完成前面所q的那三个规则,我们的Makefile应该是下面的q个样子的?
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
反斜杠(\Q是换行W的意思。这h较便于Makefile的易诅R我们可以把q个内容保存在文件ؓ(f)“Makefile”?#8220;makefile”的文件中Q然后在该目录下直接输入命o(h)“make”可以生成执行文件edit。如果要删除执行文g和所有的中间目标文gQ那么,只要单地执行一?#8220;make clean”可以了(jin)?
1.3 make是如何工作的
在默认的方式下,也就是我们只输入make命o(h)。那么,
1.4 makefile中用变?
在上面的例子中,先让我们看看edit的规则:(x)
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
我们可以看到[.o]文g的字W串被重复了(jin)两次Q如果我们的工程需要加入一个新的[.o]文gQ那么我们需要在两个地方加(应该是三个地方,q有一个地方在clean中)(j)。当?dng)我们的makefileq不复杂Q所以在两个地方加也不篏Q但如果makefile变得复杂Q那么我们就有可能会(x)忘掉一个需要加入的地方Q而导致编译失败。所以,Z(jin)makefile的易l护Q在makefile中我们可以用变量。makefile的变量也是一个字W串Q理解成C语言中的宏可能会(x)更好?
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
于是Q我们就可以很方便地在我们的makefile中以“$(objects)”的方式来使用q个变量?jin),于是我们的改良版makefile变成下面这个样子:(x)
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)
1.5 让make自动推导
GNU的make很强大,它可以自动推导文件以?qing)文件依赖关pd面的命o(h)Q于是我们就没必要去在每一个[.o]文g后都写上cM的命令,因ؓ(f)Q我们的make?x)自动识别,q自己推导命令?
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
q种Ҏ(gu)Q也是make?#8220;隐晦规则”。上面文件内容中Q?#8220;.PHONY”表示Qclean是个伪目标文件?
1.6 另类风格的makefile
即然我们的make可以自动推导命o(h)Q那么我看到那堆[.o]和[.h]的依赖就有点不爽Q那么多的重复的[.h]Q能不能把其收拢hQ好吧,没有问题Q这个对于make来说很容易,谁叫它提供了(jin)自动推导命o(h)和文件的功能呢?来看看最新风格的makefile吧?
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
rm edit $(objects)
q种风格Q让我们的makefile变得很简单,但我们的文g依赖关系显得有点凌׃(jin)。鱼和熊掌不可兼得。还看你的喜好了(jin)。我是不喜欢q种风格的,一是文件的依赖关系看不清楚Q二是如果文件一多,要加入几个新?o文gQ那q不清楚了(jin)?
1.7 清空目标文g的规?
每个Makefile中都应该写一个清I目标文Ӟ.o和执行文Ӟ(j)的规则,q不仅便于重~译Q也很利于保持文件的清洁。这是一?#8220;修养”Q呵呵,q记得我的《编E修充R吗Q。一般的风格都是Q?
clean:
rm edit $(objects)
更ؓ(f)E_的做法是Q?
.PHONY : clean
clean :
-rm edit $(objects)
前面说过Q?PHONY意思表Cclean是一?#8220;伪目?#8221;Q。而在rm命o(h)前面加了(jin)一个小减号的意思就是,也许某些文g出现问题Q但不要,l箋做后面的事。当?dng)clean的规则不要放在文件的开_(d)不然Q这׃(x)变成make的默认目标,怿谁也不愿意这栗不成文的规矩是—?#8220;clean从来都是攑֜文g的最?#8221;?
2 Makefile 总述
2.1 Makefile里有什么?
Makefile里主要包含了(jin)五个东西Q显式规则、隐晦规则、变量定义、文件指C和注释?
2.2Makefile的文件名
默认的情况下Qmake命o(h)?x)在当前目录下按序扑֯文g名ؓ(f)“GNUmakefile”?#8220;makefile”?#8220;Makefile”的文Ӟ扑ֈ?jin)解释这个文件。在q三个文件名中,最好?#8220;Makefile”q个文g名,因ؓ(f)Q这个文件名W一个字Wؓ(f)大写Q这h一U显目的感觉。最好不要用“GNUmakefile”Q这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文g名敏感,但是基本上来_(d)大多数的make都支?#8220;makefile”?#8220;Makefile”q两U默认文件名?
2.3 引用其它的Makefile
在Makefile使用include关键字可以把别的Makefile包含q来Q这很像C语言?includeQ被包含的文件会(x)原模原样的放在当前文件的包含位置。include的语法是Q?
include <filename>
include foo.make *.mk $(bar)
{h(hun)于:(x)
include foo.make a.mk b.mk c.mk e.mk f.mk
make命o(h)开始时Q会(x)把找寻include所指出的其它MakefileQƈ把其内容安置在当前的位置。就好像C/C++?include指o(h)一栗如果文仉没有指定l对路径或是相对路径的话Qmake?x)在当前目录下首先寻找,如果当前目录下没有找刎ͼ那么Qmakeq会(x)在下面的几个目录下找Q?
-include <filename>
2.4 环境变量 MAKEFILES
如果你的当前环境中定义了(jin)环境变量MAKEFILESQ那么,make?x)把q个变量中的值做一个类ginclude的动作。这个变量中的值是其它的MakefileQ用I格分隔。只是,它和include不同的是Q从q个环境变中引入的Makefile?#8220;目标”不会(x)起作用,如果环境变量中定义的文g发现错误Qmake也会(x)不理?
2.5 make的工作方?
GNU的make工作时的执行步骤入下Q(x其它的make也是cMQ?
3 Makefile书写规则
规则包含两个部分Q一个是依赖关系Q一个是生成目标的方法?
3.1 规则举例
foo.o : foo.c defs.h # foo模块
cc -c -g foo.c
看到q个例子Q各位应该不是很陌生?jin),前面也已说过Qfoo.o是我们的目标Qfoo.c和defs.h是目标所依赖的源文gQ而只有一个命?#8220;cc -c -g foo.c”Q以Tab键开_(d)(j)。这个规则告诉我们两件事Q?
3.2 规则的语?
targets : prerequisites
command
...
或是q样Q?
targets : prerequisites ; command
command
...
targets是文件名Q以I格分开Q可以用通配W。一般来_(d)我们的目标基本上是一个文Ӟ但也有可能是多个文g?
3.3 在规则中使用通配W?
clean:
rm -f *.o
上面q个例子我不不多说了(jin)Q这是操作系lShell所支持的通配W。这是在命o(h)中的通配W?
print: *.c
lpr -p $?
touch print
上面q个例子说明?jin)通配W也可以在我们的规则中,目标print依赖于所有的[.c]文g。其中的“$?”是一个自动化变量Q我?x)在后面l你讲述?
objects = *.o
上面q个例子Q表CZ(jin)Q通符同样可以用在变量中。ƈ不是说[*.o]?x)展开Q不Qobjects的值就?#8220;*.o”。Makefile中的变量其实是C/C++中的宏。如果你要让通配W在变量中展开Q也是让objects的值是所有[.o]的文件名的集合,那么Q你可以q样Q?
objects := $(wildcard *.o)
q种用法由关键字“wildcard”指出Q关于Makefile的关键字Q我们将在后面讨论?
3.4 文g搜寻
VPATH = src:../headers
上面的的定义指定两个目录Q?#8220;src”?#8220;../headers”Qmake?x)按照这个顺序进行搜索。目录由“冒号”分隔。(当然Q当前目录永q是最高优先搜索的地方Q?
为符合模?lt; pattern>的文件指定搜索目?lt; directories>?
清除W合模式< pattern>的文件的搜烦(ch)目录?
清除所有已被设|好?jin)的文g搜烦(ch)目录? vpath %.h ../headers
该语句表C,要求make?#8220;../headers”目录下搜索所有以“.h”l尾的文件。(如果某文件在当前目录没有扑ֈ的话Q?
vpath %.c foo
vpath % blish
vpath %.c bar
其表C?#8220;.c”l尾的文Ӟ先在“foo”目录Q然后是“blish”Q最后是“bar”目录?
vpath %.c foo:bar
vpath % blish
而上面的语句则表C?#8220;.c”l尾的文Ӟ先在“foo”目录Q然后是“bar”目录Q最后才?#8220;blish”目录?
3.5 伪目?
clean:
rm *.o temp
正像我们前面例子中的“clean”一P即然我们生成?jin)许多文件编译文Ӟ我们也应该提供一个清除它们的“目标”以备完整地重~译而用?Q以“make clean”来用该目标Q?
.PHONY : clean
只要有这个声明,不管是否?#8220;clean”文gQ要q行“clean”q个目标Q只?#8220;make clean”q样。于是整个过E可以这样写Q?
.PHONY: clean
clean:
rm *.o temp
伪目标一般没有依赖的文g。但是,我们也可以ؓ(f)伪目标指定所依赖的文件。伪目标同样可以作ؓ(f)“默认目标”Q只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文gQ但你只想简单地敲一个make完事Qƈ且,所有的目标文g都写在一个Makefile中,那么你可以?#8220;伪目?#8221;q个Ҏ(gu):(x)
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
我们知道QMakefile中的W一个目标会(x)被作为其默认目标。我们声明了(jin)一?#8220;all”的伪目标Q其依赖于其它三个目标。由于伪目标的特性是QL被执行的Q所以其依赖的那三个目标L不如“all”q个目标新。所以,其它三个目标的规则L?x)被册。也pC(jin)我们一口气生成多个目标的目的?#8220;.PHONY : all”声明?#8220;all”q个目标?#8220;伪目?#8221;?
.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
“make clean”清除所有要被清除的文g?#8220;cleanobj”?#8220;cleandiff”q两个伪目标有点?#8220;子程?#8221;的意思。我们可以输?#8220;make cleanall”?#8220;make cleanobj”?#8220;make cleandiff”命o(h)来达到清除不同种cL件的目的
3.6 多目?
bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@
其中Q?$(subst output,,$@)中的“$”表示执行一个Makefile的函敎ͼ函数名ؓ(f)substQ后面的为参数。关于函敎ͼ在后面讲述。这里的q个函数是截取字W串的意思,“$@”表示目标的集合,像一个数l,“$@”依次取出目标Qƈ执于命o(h)?
上述规则{h(hun)于:(x)
bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput
3.7 静态模?
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...
objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
上面的例子中Q指明了(jin)我们的目标从$object中获取,“%.o”表明要所有以“.o”l尾的目标,也就?#8220;foo.o bar.o”Q也是变量$object集合的模式,而依赖模?#8220;%.c”则取模式“%.o”?#8220;%”Q也是“foo bar”Qƈ为其加下“.c”的后~Q于是,我们的依赖目标就?#8220;foo.c bar.c”。而命令中?#8220;$<”?#8220;$@”则是自动化变量,“$<”表示所有的依赖目标集(也就?#8220;foo.c bar.c”Q,“$@”表示目标集(也褪恰癴oo.o bar.o”Q。于是,上面的规则展开后等价于下面的规则:(x)
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
试想Q如果我们的“%.o”有几百个Q那U我们只要用q种很简单的“静态模式规?#8221;可以写完一堆规则,实在是太有效率了(jin)?#8220;静态模式规?#8221;的用法很灉|Q如果用得好Q那?x)一个很强大的功能。再看一个例子:(x)
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
3.8 自动生成依赖?
main.o : main.c defs.h
但是Q如果是一个比较大型的工程Q你必需清楚哪些C文g包含?jin)哪些头文gQƈ且,你在加入或删除头文gӞ也需要小?j)地修改MakefileQ这是一个很没有l护性的工作。ؓ(f)?jin)避免这U繁重而又Ҏ(gu)出错的事情,我们可以使用C/C++~译的一个功能。大多数的C/C++~译器都支持一?#8220;-M”的选项Q即自动扑֯源文件中包含的头文gQƈ生成一个依赖关pR例如,如果我们执行下面的命令:(x)
cc -M main.c
其输出是Q?
main.o : main.c defs.h
于是q译器自动生成的依赖关p,q样一来,你就不必再手动书写若q文件的依赖关系Q而由~译器自动生成了(jin)。需要提醒一句的是,如果你用GNU的C/C++~译器,你得?#8220;-MM”参数Q不?dng)?#8220;-M”参数?x)把一些标准库的头文g也包含进来?
main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
/usr/include/bits/sched.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/include/bits/wchar.h /usr/include/gconv.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
/usr/include/bits/stdio_lim.h
gcc -MM main.c的输出则是:(x)
main.o: main.c defs.h
那么Q编译器的这个功能如何与我们的Makefile联系在一起呢。因样一来,我们的Makefile也要Ҏ(gu)q些源文仉新生成,让Makefile自已依赖于源文gQ这个功能ƈ不现实,不过我们可以有其它手D|q回地实现这一功能。GNUl织把编译器为每一个源文g的自动生成的依赖关系攑ֈ一个文件中Qؓ(f)每一?#8220;name.c”的文仉生成一?#8220;name.d”的Makefile文gQ[.d]文g中就存放对应[.c]文g的依赖关pR?
%.d: %.c
@set -e; rm -f $@; \
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
main.o : main.c defs.h
转成Q?
main.o main.d : main.c defs.h
于是Q我们的[.d]文g也会(x)自动更新?jin),q会(x)自动生成?jin),当然Q你q可以在q个[.d]文g中加入的不只是依赖关p,包括生成的命令也可一q加入,让每个[.d]文g都包含一个完赖的规则。一旦我们完成这个工作,接下来,我们p把这些自动生成的规则放进我们的主Makefile中。我们可以用Makefile?#8220;include”命o(h)Q来引入别的Makefile文gQ前面讲q)(j)Q例如:(x)
sources = foo.c bar.c
include $(sources:.c=.d)
上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变?(sources)所有[.c]的字串都替换成[.d]Q关于这?#8220;替换”的内容,在后面我?x)有更?f)详细的讲q。当?dng)你得注意ơ序Q因为include是按ơ来载入文gQ最先蝲入的[4 Makefile 书写命o(h)
4.1 昄命o(h)
@echo 正在~译XXX模块......
当make执行Ӟ?x)输?#8220;正在~译XXX模块......”字串Q但不会(x)输出命o(h)Q如果没?#8220;@”Q那么,make输出:(x)
echo 正在~译XXX模块......
正在~译XXX模块......
如果make执行Ӟ带入make参数“-n”?#8220;--just-print”Q那么其只是昄命o(h)Q但不会(x)执行命o(h)Q这个功能很有利于我们调试我们的MakefileQ看看我们书写的命o(h)是执行v来是什么样子的或是什么顺序的?
4.2 命o(h)执行
CZ一Q?
exec:
cd /home/hchen
pwd
CZ二:(x)
exec:
cd /home/hchen; pwd
4.3 命o(h)出错
clean:
-rm -f *.o
q有一个全局的办法是Q给make加上“-i”或是“--ignore-errors”参数Q那么,Makefile中所有命令都?x)忽略错误。而如果一个规则是?#8220;.IGNORE”作ؓ(f)目标的,那么q个规则中的所有命令将?x)忽略错误。这些是不同U别的防止命令出错的Ҏ(gu)Q你可以Ҏ(gu)你的不同喜欢讄?
4.4 嵌套执行make
subsystem:
cd subdir && $(MAKE)
其等价于Q?
subsystem:
$(MAKE) -C subdir
定义$(MAKE)宏变量的意思是Q也许我们的make需要一些参敎ͼ所以定义成一个变量比较利于维护。这两个例子的意思都是先q入“subdir”目录Q然后执行make命o(h)?
export <variable ...>
unexport <variable ...>
CZ一Q?
export variable = value
其等价于Q?
variable = value
export variable
其等价于Q?
export variable := value
其等价于Q?
variable := value
export variable
CZ二:(x)
export variable += value
其等价于Q?
variable += value
export variable
如果你要传递所有的变量Q那么,只要一个exportp?jin)。后面什么也不用跟,表示传递所有的变量?
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=
如果你定义了(jin)环境变量MAKEFLAGSQ那么你得确信其中的选项是大安?x)用到的Q如果其中有“-t”,“-n”,?#8220;-q”参数Q那么将?x)有让你意想不到的结果,或许会(x)让你异常地恐慌?
make: Entering directory `/home/hchen/gnu/make'.
而在完成下层make后离开目录Ӟ我们?x)看刎ͼ?x)
make: Leaving directory `/home/hchen/gnu/make'
当你使用“-C”参数来指定make下层MakefileӞ“-w”?x)被自动打开的。如果参C?#8220;-s”Q?#8220;--slient”Q或?#8220;--no-print-directory”Q那么,“-w”L失效的?
4.5 定义命o(h)?
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
q里Q?#8220;run-yacc”是这个命令包的名字,其不要和Makefile中的变量重名。在“define”?#8220;endef”中的两行是命o(h)序列。这个命令包中的W一个命令是q行YaccE序Q因为YaccE序L生成“y.tab.c”的文Ӟ所以第二行的命令就是把q个文gҎ(gu)名字。还是把q个命o(h)包放C个示例中来看看吧?
foo.c : foo.y
$(run-yacc)
我们可以看见Q要使用q个命o(h)包,我们好像用变量一栗在q个命o(h)包的使用中,命o(h)?#8220;run-yacc”中的“$^”是“foo.y”Q?#8220;$@”是“foo.c”Q有兌U以“$”开头的Ҏ(gu)变量Q我们会(x)在后面介l)(j)Qmake在执行命令包Ӟ命o(h)包中的每个命令会(x)被依ơ独立执行?
]]>A.3.3. 控制输出格式
控制布尔值和格式
cout << "default bool values: "
<< true << " " << false
<< "\nalpha bool values: "
<< boolalpha
<< true << " " << false
<< endl;default bool values: 1 0
alpha bool values: true falsebool bool_val;
cout << boolalpha // sets internal state of cout
<< bool_val
<< noboolalpha; // resets internal state to default formatting指定整型值的基数
const int ival = 15, jval = 1024; // const, so values never change
cout << "default: ival = " << ival
<< " jval = " << jval << endl;
cout << "printed in octal: ival = " << oct << ival
<< " jval = " << jval << endl;
cout << "printed in hexadecimal: ival = " << hex << ival
<< " jval = " << jval << endl;
cout << "printed in decimal: ival = " << dec << ival
<< " jval = " << jval << endl;default: ival = 15 jval = 1024
printed in octal: ival = 17 jval = 2000
printed in hexadecimal: ival = f jval = 400
printed in decimal: ival = 15 jval = 1024指出输出的基?/h5>
const int ival = 15, jval = 1024; // const so values never change
cout << showbase; // show base when printing integral values
cout << "default: ival = " << ival
<< " jval = " << jval << endl;
cout << "printed in octal: ival = " << oct << ival
<< " jval = " << jval << endl;
cout << "printed in hexadecimal: ival = " << hex << ival
<< " jval = " << jval << endl;
cout << "printed in decimal: ival = " << dec << ival
<< " jval = " << jval << endl;
cout << noshowbase; // reset state of the streamdefault: ival = 15 jval = 1024
printed in octal: ival = 017 jval = 02000
printed in hexadecimal: ival = 0xf jval = 0x400
printed in decimal: ival = 15 jval = 1024cout << uppercase << showbase << hex
<< "printed in hexadecimal: ival = " << ival
<< " jval = " << jval << endl
<< nouppercase << endl;printed in hexadecimal: ival = 0XF jval = 0X400
控制点值的格式
指定昄_ֺ
// cout.precision reports current precision value
cout << "Precision: " << cout.precision()
<< ", Value: " << sqrt(2.0) << endl;
// cout.precision(12) asks that 12 digits of precision to be printed
cout.precision(12);
cout << "Precision: " << cout.precision()
<< ", Value: " << sqrt(2.0) << endl;
// alternative way to set precision using setprecision manipulator
cout << setprecision(3);
cout << "Precision: " << cout.precision()
<< ", Value: " << sqrt(2.0) << endl;Precision: 6, Value: 1.41421
Precision: 12, Value: 1.41421356237
Precision: 3, Value: 1.41
控制记数?/h5>
恢复点值的默认记数?/h5>
// reset to default handling for notation
cout.unsetf(ostream::floatfield);cout << sqrt(2.0) << '\n' << endl;
cout << "scientific: " << scientific << sqrt(2.0) << '\n'
<< "fixed decimal: " << fixed << sqrt(2.0) << "\n\n";
cout << uppercase
<< "scientific: " << scientific << sqrt(2.0) << '\n'
<< "fixed decimal: " << fixed << sqrt(2.0) << endl
<< nouppercase;
// reset to default handling for notation
cout.unsetf(ostream::floatfield);
cout << '\n' << sqrt(2.0) << endl;1.41421
scientific: 1.414214e+00
fixed decimal: 1.414214
scientific: 1.414214E+00
fixed decimal: 1.414214
1.41421昄数?/h5>
cout << 10.0 << endl; // prints 10
cout << showpoint << 10.0 // prints 10.0000
<< noshowpoint << endl; // revert to default handling of decimal point填充输出
int i = -16;
double d = 3.14159;
// pad first column to use minimum of 12 positions in the output
cout << "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n';
// pad first column and left-justify all columns
cout << left
<< "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n'
<< right; // restore normal justification
// pad first column and right-justify all columns
cout << right
<< "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n';
// pad first column but put the padding internal to the field
cout << internal
<< "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n';
// pad first column, using # as the pad character
cout << setfill('#')
<< "i: " << setw(12) << i << "next col" << '\n'
<< "d: " << setw(12) << d << "next col" << '\n'
<< setfill(' '); // restore normal pad characteri: -16next col
d: 3.14159next col
i: -16 next col
d: 3.14159 next col
i: -16next col
d: 3.14159next col
i: - 16next col
d: 3.14159next col
i: -#########16next col
d: #####3.14159next colA.3.4. 控制输入格式?/h4>
while (cin >> ch)
cout << ch;a b c
dabcd
cin >> noskipws; // set cin so that it reads whitespace
while (cin >> ch)
cout << ch;
cin >> skipws; // reset cin to default state so that it discards whitespacea b c
dA.3.5. 未格式化的输入/输出操作
比如Q当你进行基于事件的~程的时候,你可以创建函数来响应用户定义的事Ӟ比如OnClick事gQ。在利用SAXq行~程的时候,需要注意的是,是解析器而不是用户生事件?br>
比如考虑下面一个简单的文档?br>
Q?xml version="1.0"?Q?br> QpartsQ?br> QpartQTurboWidgetQ?partQ?br> Q?partsQ?br>
当SAX2在处理这个文档的时候,它生如下的一pd的事Ӟ(x)
StartDocument( )
StartElement( "parts" )
StartElement( "part" )
Characters( "TurboWidget" )
EndElement( "part" )
EndElement( "parts" )
EndDocument( )
可以把SAX2看成是一个有拉特点(PUSHQ的解析器,SAX2产生事gQ然后你可以自己d理些事g。实际上Q当SAX2在解析一个文档的时候,SAXXMLReader读该文档q生一pd的事Ӟ你可以选择一些事件进行处理?br>
创徏一个应用SAX的应用程序框?br>
SAX2产生的事件包括如下的U类Q?br>
¨ 和XML文档内容相关的事?ISAXContentHandler)
¨ 和DTD相关的事?ISAXDTDHandler)
¨ 出现错误时发生的事g(ISAXErrorHandler)
Z(jin)处理q些事gQ你需要实C个相关的处理c,该处理类需要包含一些方法来处理相关的事件。你必须对你惌处理的事件实现相关的处理。如果你不想处理某一个事件的话,只需要简单的忽略它就可以。在实际应用中,我们首先要承这些接口,用C++我们可以创徏一个类Q在q个cȝҎ(gu)中,我们可以告诉应用E序在接收到一个事件的时候如何进行处理。下面是建立一个基于SAX的应用的基本步骤Q?br>
1Q?创徏头文件当使用SAX2的时候,我们需要用到动态连接库MSXML.DLL,Z(jin)使用MSXML中包含的SAX2接口Q你必须在程序的头文Ӟ一般在stdafx.h中)(j)中包含下列的代码Q?br>
#import
using namespace MSXML2;
2Q?建立具体的操作(handlerQ类QSAX2主要定义?jin)三个基本的操作c,它们分别是ISAXContentHandlerQISAXDTDHandler和ISAXErrorHandler?br>
ISAXContentHandler是用来处理SAX2解析器对文档内容q行解析时所产生的消息的QISAXXMLReader通过Ҏ(gu)putContentHandler来注册这个实例。而ISAXDTDHandler是用来处理和DTD相关的基本的消息的,ISAXXMLReader通过Ҏ(gu)putDTDHandler来注册这个实例。ISAXErrorHandler提供?jin)对在解析过E中遇到错误时生的错误事g的处理,ISAXXMLReader通过Ҏ(gu)putErrorHandler来注册这个实?br>
因ؓ(f)q三个类都是用来对事件进行处理的Qƈ且需要在接口ISAXXMLReader中进行注册。但是它们的基本使用Ҏ(gu)cMQ所以我们这里只详细描述Ҏ(gu)口ISAXContentHandler 的操作?br>
ISAXContentHandler接口接收关于文档的内容变化的事gQ这是实现SAX应用所需要的最重要的接口,如果应用在遇到基本的解析事g的时候需要被通知的话QISAXXMLReader通过Ҏ(gu)putContentHandler来注册这个实例,然后ISAXXMLReader׃用这个实例来报告Z文档的事Ӟ比如元素的开始,元素的结束和相关的字W串数据{等。ISAXContentHandler 包括?jin)很多的?gu)Q比如startDocumentQendDocumentQstartElementQendElement{等。实际上它包含了(jin)好接个startXXX和endXXXҎ(gu)建立不同的信息集合的抽象。比如startDocumentҎ(gu)在文档信息开始的时候被调用Q而在startDocument以后被调用的Ҏ(gu)p认ؓ(f)是文档信息项QitemQ的子项。在文档信息内容l束的时候endDocumentp调用Q表C文档信息的l束?实际上是SAX2在解析文档的时候,当处于文档某一位置的时候,?x)激发相应的Ҏ(gu)Q比如当一个文档开始的时候,׃(x)Ȁ发startDocumentҎ(gu)Q在实际实现的时候,我们可以在我们承ISAXContentHandlercȝ实现cMQ重载该Ҏ(gu)Q实现我们自己想要的处理。我们可以把q些Ҏ(gu)看成是ISAXContentHandler接口提供l我们的。需要注意的是事件被处理的顺序和信息在文档中的位|是一致的?br>
同时需要注意的是,如果我们需要在我们的应用中对这些消息进行处理的话,我们pl承处理q些消息的类Q比如我们只需要对文档内容q行处理Q而忽略对DTD和解析过E中错误(Error)的处理,那么我们只需要创Z个新的类Q该cȝ承ISAXContentHandler接口Q因为ISAXContentHandler中定义了(jin)很多的事件处理方法,而事实上我们只需要对我们所兛_(j)事g的处理方法进行重载,Ҏ(gu)们不兛_(j)的事件可以简单的忽略它?br>
比如我们只关?j)startElement和endElement事gQ而且我们假设我们建立的类的名UCؓ(f)CXMLContentDealQ我们的cd可以如下面所C:(x)
class CXMLContentDeal : public ISAXContentHandler
{
public:
CXMLContentDeal();
virtual CXMLContentDeal ();
virtual HRESULT STDMETHODCALLTYPE startElement(
/* [in] */ wchar_t __RPC_FAR *pwchNamespaceUri,
/* [in] */ int cchNamespaceUri,
/* [in] */ wchar_t __RPC_FAR *pwchLocalName,
/* [in] */ int cchLocalName,
/* [in] */ wchar_t __RPC_FAR *pwchRawName,
/* [in] */ int cchRawName,
/* [in] */ ISAXAttributes __RPC_FAR *pAttributes);
virtual HRESULT STDMETHODCALLTYPE endElement(
/* [in] */ wchar_t __RPC_FAR *pwchNamespaceUri,
/* [in] */ int cchNamespaceUri,
/* [in] */ wchar_t __RPC_FAR *pwchLocalName,
/* [in] */ int cchLocalName,
/* [in] */ wchar_t __RPC_FAR *pwchRawName,
/* [in] */ int cchRawName);
}
然后我们可以重蝲Ҏ(gu)startElement和endElement来进行和应用相关的特D的处理?br>
3Q?通过接口ISAXXMLReader创徏一个解析器。XMLReader是SAX应用实现的主要的接口QXMLReader的作用是q样的?首先QXML的开发h员用这个接口来注册他们对其他SAX接口的实玎ͼ比如ContentHandler,DTDHandler,ErrorHandler{等Q,另外QXMLREADER通过setFeature和setProperty两个Ҏ(gu)来配|SAX解析器的行ؓ(f)Q最后,XMLReader装?jin)解析的功能。示例代码如下:(x)
ISAXXMLReader* pRdr = NULL;
HRESULT hr = CoCreateInstance(
__uuidof(SAXXMLReader),
NULL,
CLSCTX_ALL,
__uuidof(ISAXXMLReader),
(void **)&pRdr);
4Q?创徏相应的事ӞhandlerQ处理类Q这里不妨假设我们只处理和文档内容相关的事g。示例代码如下:(x)
CXMLContentDeal * pMc = new CXMLContentDeal();
注意q里CXMLContentDeal是承接口ISAXContentHandler的类?br>
5Q在解析器中注册事g处理c,CZ代码如下Q?br>
hr = pRdr->putContentHandler(pMc);
6Q开始进行文档的解析Q示例代码如?br>
hr = pRdr->parseURL(URL); file://q里的URL是指一个具体XML文档的位|?br>
7Q释放解析器对象
pRdr->Release();
以上是ZSAX的应用程序的框架l构Q我们可以看刎ͼ实际的事件处理是在我们的l承cCXMLContentDeal中实现的Q在我们q个CZ代码中,每当文档中一个新的元素开始的时候,都会(x)ȀzL法startElementQ每当一个元素结束的时候,都会(x)ȀzL法endElement。我们可以在startElement和endElement中写入和应用相关的特定的代码?
]]>
STL中有多种排序法Q各有各的适用范围Q下面听我一一道来Q?br>
I、完全排?/span>
sort()
首先要隆重推出的当然是最最常用的sort?jin),sort有两UŞ式,W一UŞ式有两个q代器参敎ͼ构成一个前开后闭的区_(d)按照元素?less 关系排序Q第二种形式多加一个指定排序准则的谓词。sort基本是最通用的排序函敎ͼ它用快速排序算法,q且在递归q程中,当元素数目小于一个阈|一般是16Q我的试验是24Q时Q{成直接插入排序。伟大的数学家Knuth已经证明Q在q_意义上,快速排序是最快的?jin);当然Q最坏复杂性比较差。sort要求随机q代器,因此对于很多~译器来_(d)对于前向q代器(如listQ用sort是一个编译错误?不过Q在vc2005里面Q这个错误信息实在很p糕)
sort的基本用方式如下:(x)
l常有h问如何从大到逆排序,q个其实有很多中方式实现Q如下面的例子:(x)
最后一U方法是我比较欣赏的Q可以不能直接对原生数组使用Q也是_(d)如果ar的定义是int ar[MAXN]Q上面其他的排序法都可以简单的Ҏ(gu)sort(ar, ar+MAXN, ...Q,但最后一个不行,要用另外一U比较丑陋的方式Q?br>
stable_sort
sort优点一大堆Q一个缺点就是它不是一U稳定的排序。什么是排序的稳定性,是如果出现两个元素相等Ӟ要求排序之后他们之间保持原来的次序(比如我们先按学号排序Q然后按成W排序Q这时就希望成W相同的还是按照学L(fng)ơ序排)(j)。很可惜Q快速排序算法就不是E_的,要追求这个,只好用stable_sort?jin)?br>
在各U排序算法中Q合q排序是E_的,但一般的合ƈ排序需要额外的O(N)的存储空_(d)而这个条件不是一定能够满的Q可能是比较奢侈的)(j)。所以在stable_sort内部Q首先判断是否有_的额外空_(d)如vecotr中的cap-size()部分Q,有的话就使用普通合q函敎ͼȝ旉复杂性和快速排序一个数量Q都是O(N*logN)。如果没有额外空_(d)使用?jin)一个merge_without_buffer的关键函数进行就地合qӞ如何实现是比较有技巧的Q完全可以专门谈一谈)(j)Q这个合q过E不需要额外的存储I间Q但旉复杂度变成O(N*logN)Q这U情况下Qȝstable_sort旉复杂度是O(N*logN*logN)?br>
MQstable_sortE微慢一点儿Q但能够保证E_Q用方法和sort一栗但很多时候可以不用这U方式和q个函数Q比如上面的例子Q完全可以在排序比较准则中写入成l和学号两个条gO(jin)K?br>
sort_heap
堆排序也是一U快速的排序法Q复杂度也是O(N*logN)。STL中有一些和堆相关的函数Q能够构造堆Q如果在构造好的堆上每ơ取出来根节Ҏ(gu)在尾部,所有元素@环一遍,最后的l果也就有序?jin)。这是sort_heap?jin)。它的用要求区间前面已l构造成堆,如:(x)
list.sort
对于list容器Q是不能直接使用sort的(包括stable_sortQ,从技术的角度来说是由于sort要求随机q代器;从算法的角度来说Qlistq种链表l构׃适合用快速排序。因此,list容器内部实现?jin)专门的sort法Q这个算法采用的是合q排序,应该是稳定的Q不定Q?br>
其他
优先队列Qpriority_queueQ每ơ弹出的都是max倹{实际上是heap的一个容器方式的包装?
兌式容器自w就必须是有序的Q针对keyQ,对其q代Ӟkey是递增的?
II、部分排?/span>
q些部分排序功能能够完成一D|据(而不是所有)(j)的排序,在适当的适合使用可以节省计算量。不q用的h不多?br>
partial_sort(), partial_sort_copy()
q两个函数能够将整个区间中给定数目的元素q行排序Q也是_(d)l果中只有最的M个元素是有序的。你当然也可以用sortQ区别就在于效率。如果M显著地小于NQ时间就比较短;当然M太小?jin)也不好Q那q不如挨个找最g(jin)?br>
partial_sort接受三个参数Q分别是区间的头Q中间和l尾。执行后Q将前面MQM=中间Q头Q个元素有序地放在前面,后面的元素肯定是比前面的大,但他们内部的ơ序没有保证。partial_sort_copy的区别在于把l果攑ֈ另外指定的P代器区间中:(x)
q两个函数的实现使用的是堆的Ҏ(gu)Q先前M个元素构造成堆,然后挨个(g)查后面的元素Q看看是否小于堆的最大|是的话就彼此交换Q然后重排堆Q最后将前面已经是最的M个元素构成的堆作一ơsort_heap可以了(jin)。算法的复杂度差不多是O(N*logM)
nth_element
q个函数只真正排序出一个元素来Q就是第n个。函数有三个q代器的输入Q当然还可以加上一个谓词)(j)Q执行完毕后Q中间位|指向的元素保证和完全排序后q个位置的元素一_(d)前面区间的元素都于Q精地_(d)是不大于Q后面区间的元素?br>
熟?zhn)快速排序的马上p发现Q这实际上是一个按位置划分的算法。STL的规范中要求此函数的q_复杂度是U性的Q和快速排序一Pq种法的最坏复杂度比较差。在一般的实现Q如SGIQ中Q采用三U取1的方法寻扑ֈ分元素,最坏复杂度是O(N^N)。虽然理Z有一些算法可以保证最坏线性复杂度Q但法q于复杂QSTL一般也不采用?
III、排序辅助功?/span>
partition, stable_partition
merge, inplace_merge
IV、有序区间操?/span>
q个准备单独写一?br>
让我们l期待郭老师的补充!
郭老师的boke地址 www.skywind.name/blog
]]>
~译通过?jin),可连接怎么也不能成功。?/font>
查了(jin)一些书才知道,模板cȝ定义和实现必L在同一文gQ?/font>
《c++~程思想》中_(d)(x)模板cd义很Ҏ(gu)Q由template<...>定义的Q何东襉K意味着~译器在当时不ؓ(f)它分配内存空_(d)它一直处于等待状态,直到被一个模板实例告知,x板参数是q译器来替换的?nbsp;
Z(jin)Ҏ(gu)使用Q几乎L在头文g中放|全部的模板声明和定义。有Ӟ也可能ؓ(f)?jin)满特D需要而要在独立的cpp中放|模板的实现。但大部分现在的~译器还不支持模板类的定义和实现分开
以下?/span>函数生成一个长度ؓ(f)0x500Q合10q制敎ͼ(x)1280Q的cryptTable[0x500]
void prepareCryptTable()
{
unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i;
for( index1 = 0; index1 < 0x100; index1++ )
{
for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100 )
{
unsigned long temp1, temp2;
seed = (seed * 125 + 3) % 0x2AAAAB;
temp1 = (seed & 0xFFFF) << 0x10;
seed = (seed * 125 + 3) % 0x2AAAAB;
temp2 = (seed & 0xFFFF);
cryptTable[index2] = ( temp1 | temp2 );
}
}
}以下函数计算lpszFileName 字符串的hash|其中dwHashType ?/span>hash的类型,在下?/span>GetHashTablePos函数里面调用本函敎ͼ其可以取的gؓ(f)0?/span>1?/span>2Q该函数q回lpszFileName 字符串的hash|
unsigned long HashString( char *lpszFileName, unsigned long dwHashType )
{
unsigned char *key = (unsigned char *)lpszFileName;
unsigned long seed1 = 0x7FED7FED;
unsigned long seed2 = 0xEEEEEEEE;
int ch;
while( *key != 0 )
{
ch = toupper(*key++);
seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2);
seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3;
}
return seed1;
}typedef struct
{
int nHashA;
int nHashB;
char bExists;
......
} SOMESTRUCTRUE;
一U可能的l构体定义?lpszString ?/span>hash表中查找的字W串Q?/span>lpTable 为存储字W串hash值的hash?/span>
int GetHashTablePos( har *lpszString, SOMESTRUCTURE *lpTable )
{
int nHash = HashString(lpszString);
int nHashPos = nHash % nTableSize;
if ( lpTable[nHashPos].bExists && !strcmp( lpTable[nHashPos].pString, lpszString ) )
{
return nHashPos;
}
else
{
return -1;
}看到此,我想大家都在想一个很严重的问题:(x)“如果两个字符串在哈希表中对应的位|相同怎么办?”,毕竟一个数l容量是有限的,q种可能性很大。解册问题的方法很多,我首先想到的是?#8220;链表”,感谢大学里学的数据结构教?x)?jin)q个百试癄的法宝,我遇到的很多法都可以{化成链表来解冻I只要在哈希表的每个入口挂一个链表,保存所有对应的字符串就OK?jin)。事情到此似乎有?jin)完的l局Q如果是把问题独自交l我解决Q此时我可能p开始定义数据结构然后写代码?jin)。然?/span>Blizzard的程序员使用的方法则是更_֦的方法。基本原理就是:(x)他们在哈希表中不是用一个哈希D是用三个哈希值来校验字符丌Ӏ?/span>
MPQ使用文g名哈希表来跟t内部的所有文件。但是这个表的格式与正常的哈希表有一些不同。首先,它没有用哈希作Z标,把实际的文g名存储在表中用于验证Q实际上它根本就没有存储文g名。而是使用?/span>3U不同的哈希Q一个用于哈希表的下标,两个用于验证。这两个验证哈希替代?jin)实际文件名?/span>
当然?jin),q样仍然?x)出?/span>2个不同的文g名哈希到3个同L(fng)哈希。但是这U情况发生的概率q_?/span>1:18889465931478580854784Q这个概率对于Q何h来说应该都是_的。现在再回到数据l构上,Blizzard使用的哈希表没有使用链表Q而采?/span>"g"的方式来解决问题Q看看这个算法:(x)lpszString ?/span>hash表中查找的字W串Q?/span>lpTable 为存储字W串hash值的hash表;nTableSize ?/span>hash表的长度Q?/span>
int GetHashTablePos( char *lpszString, MPQHASHTABLE *lpTable, int nTableSize )
{
const int HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;
int nHash = HashString( lpszString, HASH_OFFSET );
int nHashA = HashString( lpszString, HASH_A );
int nHashB = HashString( lpszString, HASH_B );
int nHashStart = nHash % nTableSize;
int nHashPos = nHashStart;
while ( lpTable[nHashPos].bExists )
{
/*如果仅仅是判断在该表中时候存在这个字W串Q就比较q两?/span>hash值就可以?jin),不用?/span>
*l构体中的字W串q行比较。这样会(x)加快q行的速度Q减?/span>hash表占用的I间Q这U?/span>
*Ҏ(gu)一般应用在什么场合?*/
if ( lpTable[nHashPos].nHashA == nHashA
&& lpTable[nHashPos].nHashB == nHashB )
{
return nHashPos;
}
else
{
nHashPos = (nHashPos + 1) % nTableSize;
}
if (nHashPos == nHashStart)
break;
}
return -1;
}/*keyZ个字W串Q?/span>nTableLength为哈希表的长?/span>
*该函数得到的hash值分布比较均匀*/
unsigned long getHashIndex( const char *key, int nTableLength )
{
unsigned long nHash = 0;
while (*key)
{
nHash = (nHash<<5) + nHash + *key++;
}
return ( nHash % nTableLength );
}补充2Q?/span>
?希表的数l是定长的,如果太大Q则费Q如果太,体现不出效率。合适的数组大小是哈希表的性能的关键。哈希表的尺寸最好是一个质数。当?dng)?gu)不同的数 据量Q会(x)有不同的哈希表的大小。对于数据量时多时少的应用,最好的设计是用动态可变尺寸的哈希表,那么如果你发现哈希表寸太小?jin),比如其中的元素是?希表寸?/font>2倍时Q我们就需要扩大哈希表寸Q一般是扩大一倍。下面是哈希表尺寸大的可能取|(x)17, 37, 79, 163, 331,
673, 1361, 2729, 471, 10949,
21911, 43853, 87719, 175447, 350899,
701819, 1403641, 2807303, 5614657, 11229331,
22458671, 44917381, 89834777, 179669557, 359339171,
718678369, 1437356741, 2147483647一?/span>C?/span>const的用法ȝh主要分ؓ(f)以下两种Q?/span>
在定义变量时使用Q注意:(x)在定义变量时使用constQ一定要q行初始化操作)(j)Q?/span>
最单的用法Q说明变量ؓ(f)一个常变量Q?/span>
const int a=100;
int const b=100;
说明指针为指向常数的指针Q即指针本n的值是可以改变的:(x)
const int *a=&bQ?/span>
int const *a=&bQ?/span>
说明指针本n的g可改变,但指向的内容可改?/span>Q?/span>
int * const a = &bQ?/span>
说明指针为指向常数的常指?/span>,x针本w与指针指向的内定w不可改变Q?/span>
const int * const a = &bQ?/span>
说明引用为常数引?/span>,即不能改变引用的|(x)
const int &a=100?/span>
在定义函数时使用Q?/span>
做ؓ(f)参数使用Q说明函C内是不能修改该参数的Q?/span>
void func(const int a)Q?/span>
void func(int const a)Q?/span>
做ؓ(f)q回g用,说明函数的返回值是不能被修改的Q?/span>
const int func()Q?/span>
在函C使用const,情况与定义变量的情况基本一_(d)(x)
int func()
{
Const int a=10Q?/span>
…
}
二?/span>C++中区别于C?/span>const用法主要分ؓ(f)以下两种
constcL?/span>
constcL员在对象构造期间允许被初始化ƈ且在以后不允许被改变?/span>constcL员和一般的const 变量有所不同?/span>constcL员是对应于每个对象而言Q它在对象构造期间被初始化,在这个对象的生存周期中不允许被改变?/span>
const 成员函数
const 成员函数不允许在此函C内对此函数对应的cȝ所有成员变量进行修改,q样可以提高E序的健壮性?/span>Const一般放在函C后:(x)
void fun() const?/span>
我L鼓励 C/C++ 的学?fn)者,在刚接触q个E式语言的时候,先以 console modeQDOS-likeQ程式ؓ(f)目标。换a之,不要一开始就惛_ GUI E式、想开视窗、想有眩目亮丽的画面 -- 那只是未走先飞,揠苗助长|了(jin)?br>所?console E式Q就是文字模式的E式Q我们可以在其中好好?C/C++ 的语a根基l好Q而不?x)分心(j)於其他暂无必要?GUI 枝节上?br>我一直以为,q是理所当然的事情,却也一直发玎ͼ有不大专院校的大一 C/C++ 评Q同学们必须写个作家、小d、小盘┅做为期中或期末作业?br>果然世界不能大同Q各人看法殊?:)
我不但认?C/C++ E式开发对象初期要?console mode ZQ我也认为,C/C++ 的程式开发环境,初期也要?console mode Z。换a之,不要一开始就q入整合环境QIDEQ。整合环境中那麽多视H、那麽多功能、那麽多预设|?x)让E式新手D撩ؕQ无法掌握程式编译过E中一些有价值的知识与经验?br>{我们对~译E序有了(jin)L(fng)的了(jin)解,再来使用整合环境Q我认ؓ(f)q才最好?br>所以不论在 <深入出 MFC> ?<多型与虚?gt; 书籍中,我都?x)简qconsole mode 下的作业方式?lt;深入出 MFC> ?p.224 列出Q?lt;多型与虚?gt; ?p.233 列出?br>但仍然偶而会(x)收到|友Q不论是否上两本书的读者)(j)的询问,询问console mode 的编译方式,或询问他们所遭遇的问题?br>我再ơ整理这个题目。再有类似问题,我就可以整篇 mail l发问者了(jin)?br>★★ 注意Q以下适合 PC 环境 ★★
●C/C++ ~译器需要的环境变数讑֮
古早以来QPC 上的 C ~译器,需要两个环境变敎ͼ(x)
LIBQ这个环境变数告诉编译器_(d)必要?libraries 在哪里(哪个碟目录下)(j)
INCLUDEQ告诉编译器_(d)必要?header files 在哪里(哪个碟目录下)(j)
另外Qؓ(f)?jin)让我们能够在Q?working directory 都叫得到~译器,当然我们必须讑֮ PATH?br>从古早以来,一直到现在QC/C++ ~译器都需要这三个环境变数?br>●以 Visual C++ Z
?Visual C++ ZQ如果安装後的档案布局如下Q?br>C:\MSDEV\VC98\BIN : q里放有~译?CL.EXE
C:\MSDEV\VC98\INCLUDE : q里放有 C/C++ header files
C:\MSDEV\VC98\LIB : q里放有 C/C++ standard libraries
那麽你可以写一个批ơ档如下Q?br>set PATH=C:\MSDEV\VC98\BIN;C:\MSDEV\COMMON\MSDEV98\BIN
set INCLUDE=C:\MSDEV\VC98\INCLUDE
set LIB=C:\MSDEV\VC98\LIB
之所以需要另外设?PATH=C:\MSDEV\COMMON\MSDEV98\BINQ是因ؓ(f)~译?CL.EXE 执行旉?MSPDB60.DLLQ而它被安装於 C:\MSDEV\COMMON\MSDEV98\BIN 之中?br>如果你写的程式不只是单纯?C/C++ E式Q还用到?MFCQ一样可以在 console mode 下编译,q时候你的环境变数应该如此设定:(x)
set PATH=C:\MSDEV\VC98\BIN;C:\MSDEV\COMMON\MSDEV98\BIN
set INCLUDE=C:\MSDEV\VC98\INCLUDE;C:\MSDEV\VC98\MFC\INCLUDE
set LIB=C:\MSDEV\VC98\LIB;C:\MSDEV\VC98\MFC\LIB
多指定了(jin) MFC\INCLUDE ?MFC\LIBQ就可以让编译器和联l器扑ֈ MFC ?header files ?libraries。如果你q需要用?ATLQ就得在 INCLUDE 环境变数中再加上 C:\MSDEV\VC98\ATL\INCLUDE?br>●以 Borland C++Builder Z
?Borland C++Builder ZQ如果安装後的档案布局如下Q?br>C:\BORLAND\CBuilder3\BIN : q里放有~译?BCC32.EXE
C:\BORLAND\CBuilder3\INCLUDE : q里放有 C/C++ header files
C:\BORLAND\CBuilder3\LIB : q里放有 C/C++ standard libraries
那麽你可以写一个批ơ档如下Q?br>set PATH=C:\BORLAND\CBuilder3\BIN
set INCLUDE=C:\BORLAND\CBuilder3\INCLUDE
set LIB=C:\BORLAND\CBuilder3\LIB
●如何在 console 中编?C/C++ E式
首先Q开启一?DOS BoxQDOS Prompt, DOS VMQ,然後在该 DOS box 中执行上q写好的Ҏ(gu)档,完成环境变数的设定。你可以再在 DOS 提示号下键入 set 命o(h)Q看看环境变数的讑֮内容正确与否?br>然後可以直接在 DOS 提示号下键入~译器名Uͼ开始编译了(jin)。如果你使用 Visual C++Q就q麽做:(x)
C:\> CL test.cpp
如果你?C++BuilderQ就q麽做:(x)
C:\> BCC32 test.cpp
xҎ(gu)情况下需要什麽特D的 optionsQ就必须自己查一下啦。只要执?CL /? ?BCC32Q其後不加Q何引敎ͼ(j)Q便可看到所有的 compile options?br>●编译器与联l器的关p?br>早期的编译过E与联结q程是分开的。换句话说我们必d两个动作Q?br>C:\> Cl test.cpp
C:\> LINK test.obj xxx Qxxx 代表各个必要?librariesQ?/small>
或是Q?br>C:\> BCC32 test.cpp
C:\> TLINK32 test.obj xxx Qxxx 代表各个必要?librariesQ?br>
如今的编译过E与联结q程当然q是分开的,但是我们的动作只需一个:(x)
C:\> CL test.cpp
或是Q?br>C:\> BCC32 test.cpp
q是因ؓ(f)~译器变聪明?jin),除非你指?/c optionQ表C只~译不联l)(j)Q否则它便自动ؓ(f)你呼叫联l器q行联结动作。过M来颇?programmer?ch)恼的「该使用哪些 libraries」的问题Q编译器也有?jin)聪明的解决?gu)Q它?yu)程式中用到?library functions 记录hQ同时也录下它们所属的library 名称Q於是联l器可以从q个表格中知道要联结哪些 libraries ?jin)?br>●环境变C DOS VMQVirtual MachineQ的关系
你可以同时开起多?DOS BoxQ但是你不能够在某个 DOS Box 中执行上q批ơ档而在另一?DOS VM 中n受其环境讑֮?br>q是因ؓ(f)每个 DOS Box 都是一?Virtual MachineQ彼此谁也看不到谁,互不相干?br>除非你在 autoexec.bat 中就讑֮好上q那些环境变数。这麽一来,M一个新开启的 DOS VM 便会(x)因ؓ(f)l承最原始?DOS VM 环境Q而承了(jin)那些变数讑֮?br>●环境空_(d)environment spaceQ不?br>最易造成大家困扰的,是环境I间Qenvironment spaceQ不的问题?br>当你安装?Visual C++Q会(x)在其 BIN 子目录中发现一个名?VCVARS32.BAT 的档案。这个档案其实就是做上述的环境变数设定动作(q在 Visual C++ 安装q程的最後一个步骤有说明。哎Q有多少人安装Y体不看说明!Q。所以,你可以在M DOS Box 中执行此档,取代前述我们自己的批ơ档?br>但是通常大家都有p|的经验,得到 "Out of environment space" 的错误讯息。这是因?VCVARS32.BAT 使用以下句法Q?br>set INCLUDE=%MSVCDir%\ATL\INCLUDE;%MSVCDir%\INCLUDE;%MSVCDir%\MFC\INCLUDE;%INCLUDE%
set LIB=%MSVCDir%\LIB;%MSVCDir%\MFC\LIB;%LIB%
意思是?INCLUDE 的原始设定(%INCLUDE%Q再附加其他讑֮Qƈ把LIB 的原始设定(%LIB%Q再附加其他讑֮。如果原始设定已l很长,多来q麽几次Q便 "Out of environment space" 啦!
做法之一是调高环境空间的大小。请?c:\config.sys 档中加上q行Q?br>shell=C:\COMMAND.COM C:\ /E:1024 /P
其中 /E:1024 便是表示环境空间调?1024 bytes。(不够Q再调)(j)
做法之二是不要?VCVARS32.BAT 的那U「附加」句型,改用前述我们自己的批ơ档。要知道Q我们可能有好几个编译器环境QVC、BCB、G++ ┅)(j)Q需要轮番测试我们的E式Q如果用「附加」句型,多来几次Q再大的环境I间也会(x)消磨D尽?br>Ҏ(gu)一和方法二要双齐下唷?br>●有M规模上的限制吗?
使用 console 模式Q或U?command line 模式Q来~译联结E式Q程式的大小可否有Q何规模上的限Ӟ{案是没有!
它的~点是没有工具帮你管理档案、没有预讑րD你少打几个字、没有分析工具帮你整?objectsQ让你浏?objects、symbols┅。所以一旦你基本功学?x)?jin)Q要开始中大型E式的设计,当然以整合环境(IDEQؓ(f)佟?br>●不要误?br>我这不是开倒RQ要大家回到Ҏ(gu)饮血的时代,都回头去做山洞人。而是我觉得,Ҏ(gu)一?C/C++ 初学者,整合环境QIDEQ的q用恐怕带来一头雾_(d)不如先在 console mode 下作业。一斚w多认识一些环境设定方面的常识Q满好的Q一斚w比较方便好用Q也不必写个 1000 行的小l习(fn)q得启动 五五加农炮,一斚w求知的力量可以全部放在语a的练?fn)上头?br>{有?jin)一定的E度Q再使用整合环境Q就不会(x)如坠五里雾了(jin)?/p>
]]>
POSIX是Portable Operating System Interface of Unix的羃写。由IEEEQInstitute of Electrical and Electronic EngineeringQ开发,由ANSI和ISO标准化?br> POSIX的诞生和Unix的发展是密不可分的,Unix?0q代诞生于Bell labQƈ?0q代向美各大高校分发V7版的源码以做研究。UC Berkeley在V7的基上开发了(jin)BSD Unix。后来很多商业厂家意识到Unix的h(hun)gUL(fng)以Bell Lab的System V或BSD为基来开发自qUnixQ较著名的有Sun OSQAIXQVMS。由于各厂家对Unix的开发各自ؓ(f)政,造成?jin)Unix的版本相当乱,lY件的可移植性带来很大困难,对Unix的发展极Z利。ؓ(f)l束q种局面,IEEE开发了(jin)POSIXQPOSIX在源代码U别上定义了(jin)一l最的Unix(cUnix)操作pȝ接口?br> POSIX 表示可移植操作系l接口(Portable Operating System Interface Q羃写ؓ(f) POSIX 是ؓ(f)?jin)读x?UNIXQ。电(sh)气和?sh)子工程师协会(x)(Institute of Electrical and Electronics EngineersQIEEEQ最初开?POSIX 标准Q是Z(jin)提高 UNIX 环境下应用程序的可移植性。然而,POSIX q不局限于 UNIX。许多其它的操作pȝQ例?DEC OpenVMS ?Microsoft Windows NTQ都支持 POSIX 标准Q尤其是 IEEE Std. 1003.1-1990Q?995 q修订)(j)?POSIX.1QPOSIX.1 提供?jin)源代码U别?C 语言应用~程接口QAPIQ给操作pȝ的服务程序,例如d文g。POSIX.1 已经被国际标准化l织QInternational Standards OrganizationQISOQ所接受Q被命名?ISO/IEC 9945-1:1990 标准?
POSIX 现在已经发展成ؓ(f)一个非常庞大的标准族,某些部分正处在开发过E中。表 1-1 l出?POSIX 标准的几个重要组成部分。POSIX ?IEEE 1003 ?2003 家族的标准是可互换的。除 1003.1 之外Q?003 ?2003 家族也包括在表中?
?Q标准的重要l成部分
1003.0
理 POSIX 开攑ּpȝ环境QOSEQ。IEEE ?1995 q通过?jin)这?gu)准?ISO 的版本是 ISO/IEC 14252:1996?
1003.1
被广泛接受、用于源代码U别的可UL性标准?003.1 提供一个操作系l的 C 语言应用~程接口QAPIQ。IEEE ?ISO 已经?1990 q通过?jin)这个标准,IEEE ?1995 q重C订了(jin)该标准?
1003.1b
一个用于实时编E的标准Q以前的 P1003.4 ?POSIX.4Q。这个标准在 1993 q被 IEEE 通过Q被合ƈq?ISO/IEC 9945-1?
1003.1c
一个用于线E(在一个程序中当前被执行的代码D)(j)的标准。以前是 P1993.4 ?POSIX.4 的一部分Q这个标准已l在 1995 q被 IEEE 通过Q归?ISO/IEC 9945-1:1996?
1003.1g
一个关于协议独立接口的标准Q该接口可以使一个应用程序通过|络与另一个应用程序通讯?1996 q_(d)IEEE 通过?jin)这个标准?
1003.2
一个应用于 shell ?工具软g的标准,它们分别是操作系l所必须提供的命令处理器和工L(fng)序?1992 q?IEEE 通过?jin)这个标准。ISO 也已l通过?jin)这个标准(ISO/IEC 9945-2:1993Q?
1003.2d
改进?1003.2 标准?
1003.5
一个相当于 1003.1 ?Ada 语言?API。在 1992 q_(d)IEEE 通过?jin)这个标准。ƈ?1997 q对其进行了(jin)修订。ISO 也通过?jin)该标准?
1003.5b
一个相当于 1003.1bQ实时扩展)(j)?Ada 语言?API。IEEE ?ISO 都已l通过?jin)这个标准。ISO 的标准是 ISO/IEC 14519:1999?
1003.5c
一个相当于 1003.1qQ协议独立接口)(j)?Ada 语言?API。在 1998 q_(d) IEEE 通过?jin)这个标准。ISO 也通过?jin)这个标准?
1003.9
一个相当于 1003.1 ?FORTRAN 语言?API。在 1992 q_(d)IEEE 通过?jin)这个标准,q于 1997 q对其再ơ确认。ISO 也已l通过?jin)这个标准?
1003.10
一个应用于计算应用环境框架QApplication Environment ProfileQAEPQ的标准。在 1995 q_(d)IEEE 通过?jin)这个标准?
1003.13
一个关于应用环境框架的标准Q主要针对?POSIX 接口的实时应用程序。在 1998 q_(d)IEEE 通过?jin)这个标准?
1003.22
一个针?POSIX 的关于安全性框架的指南?
1003.23
一个针对用L(fng)l的指南Q主要是Z(jin)指导用户开发和使用支持操作需求的开攑ּpȝ环境QOSEQ框?
2003
针对指定和用是否符?POSIX 标准的测试方法,有关其定义、一般需求和指导斚w的一个标准。在 1997 q_(d)IEEE 通过?jin)这个标准?
2003.1
q个标准规定?jin)针?1003.1 ?POSIX 试Ҏ(gu)的提供商要提供的一些条件。在 1992 q_(d)IEEE 通过?jin)这个标准?
2003.2
一个定义了(jin)被用来检查与 IEEE 1003.2Qshell ?工具 APIQ是否符合的试Ҏ(gu)的标准。在 1996 q_(d)IEEE 通过?jin)这个标准?
除了(jin) 1003 ?2003 家族以外Q还有几个其它的 IEEE 标准Q例?1224 ?1228Q它们也提供开发可UL应用E序?API。要惛_到关?IEEE 标准的最C息,可以讉K IEEE 标准的主,|址?http://standards.ieee.org/。有?POSIX 标准的概qC息,误?Web 站点 http://standards.ieee.org/reading/ieee/stad_public/description/posix/?/p>
Linuxpȝ下的多线E遵循POSIXU程接口Q称为pthread。从上面的描qC隄道,POSIXU程接口是POSIX众多标准中的一个(POSIX 1003.1-2001Q?/strong>?/p>
~写Linux下的多线E程序,需要用头文gpthread.hQ连接时需要用库libpthread.a。顺便说一下,Linux下pthread的实现是通过pȝ调用 clone() 来实现的。clone() 是Linux所Ҏ(gu)的系l调用,它的使用方式cMforkQ关?clone() 的详l情况,有兴的读者可以去查看有关文档说明?/p>
下面是一?POSIX U程的简单示例程?thread1.c)Q? 要编译这个程序,只需先将E序存ؓ(f) thread1.cQ然后输入:(x) q行则输入:(x) Windows本n没有提供对POSIX的支持。但有一个叫 POSIX Threads for Win32 的开源项目给Z(jin)一个功能比较完善的Windows下pthreads API的实现。目前的最新版本是Pthreads-w32 release 2.8.0 (2006-12-22)?/p>
我没有测试过q个最新版本,q里只给?.7.0版的链接Q?a title=ftp://sources.redhat.com/pub/pthreads-win32/pthreads-w32-2-7-0-release.exe href="ftp://sources.redhat.com/pub/pthreads-win32/pthreads-w32-2-7-0-release.exe">ftp://sources.redhat.com/pub/pthreads-win32/pthreads-w32-2-7-0-release.exe?/p>
关于该开源项目的详细介绍见:(x)http://sources.redhat.com/pthreads-win32/?/p>
上面的exe文g是一个自解压文gQ解压后QPre-built.2目录中有~译所需要的头文Ӟinclude子目录)(j)和库文gQlib子目录)(j)?/p>
一个简单的试E序(main.cpp)Q?/p>
使用 cl.exe ~译Q不熟?zhn)?cl.exe 的请参考:(x)http://blog.csdn.net/liuyongjin1984/archive/2008/01/07/2029405.aspx 或者参见下?.2部分Q:(x) 3.2 使用VC++ 6.0或Visual Studio 2005来运行上面的E序 关键有两点:(x) 1. 是将头文Ӟinclude子目录)(j)和库文gQlib子目录)(j)中的内容d到VC++ 6.0或Visual Studio 2005开发环境对应的include和lib目录下?/p>
具体来说Q?strong>以添加include目录ZQ添加lib目录cM#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
void *thread_function(void *arg) ...{
int i;
for ( i=0; i<20; i++) ...{
printf("Thread says hi! ");
sleep(1);
}
return NULL;
}
int main(void) ...{
pthread_t mythread;
if ( pthread_create( &mythread, NULL, thread_function, NULL) ) ...{
printf("error creating thread.");
abort();
}
if ( pthread_join ( mythread, NULL ) ) ...{
printf("error joining thread.");
abort();
}
exit(0);
}
3. Windows下POSIXU程~程
3.1 单?/strong>
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
void* Function_t(void* Param)
...{
printf("I am a thread! ");
pthread_t myid = pthread_self();
printf("thread ID=%d ", myid);
return NULL;
}
int main()
...{
pthread_t pid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_PROCESS);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&pid, &attr, Function_t, NULL);
printf("======================================== ");
getchar();
pthread_attr_destroy(&attr);
return 1;
}
?span style="COLOR: #000000">rem cl.bat
》cl.exe main.cpp /c /I"c:pthreads-w32-2-7-0-releasePre-built.2include"
》link.exe /out:main_cl.exe main.obj /LIBPATH:"c:pthreads-w32-2-7-0-releasePre-built.2lib" pthreadVC2.lib
?QVC++ 6.0Q添加include目录Q工?-》选项--》目录)(j)
?QVisual Studio 2005(dinclude目录Qtools--》options)
2. 指定link时要q接的库的名UͼpthreadVC2.libQ?/strong>
?QVC++ 6.0Q工E?-》设|?-》连接)(j)
?QVisual Studio 2005(project-->* property pages)
对象池的用途在q里׃介绍?jin),本例中只是其一个简单的实现?
#include <list>
#include <iostream>
using namespace std;
template<class T>
class object_pool
{
list<void *> data_list;
public:
void* malloc_data()
{
if(!data_list.empty())
{
list<void *>::iterator it = data_list.begin();
void *p = *it;
data_list.pop_front();
return p;
}
else
{
void* p = malloc(sizeof(T));
return p;
}
}
void free_data(void* p)
{
data_list.push_back(p);
}
};
class A
{
public:
int value;
A(int a):value(a){cout<<"A("<<a<<") called"<<endl;}
~A() {cout<<"~A("<<value<<") called"<<endl;}
static object_pool<A> pool_;
void* operator new (size_t size)
{
return pool_.malloc_data();
}
void operator delete(void* p)
{
pool_.free_data(p);
}
};
object_pool<A> A::pool_;
void main()
{
A* a1=new A(1);
delete a1;
A* a2=new A(2);
delete a2;
}
׃U种原因Q我们无法保证所有通过new在heap中申L(fng)内存资源都安全地得到释放Q例如:(x)
class base{...};
void f()
{
base* ptr = new base;
...
delete ptr;
}
q样一pd操作中,?..."中可能会(x)抛出异常Q最l导致delete无法得到执行Q于是就造成?jin)内存泄霌Ӏ尽我们可以设法避免在"..."中抛出异常,但是Q对于后来维护和修改q段代码?jin)h员来_(d)很可能加入一些控制流Q从而过早地从函C中返回,内存泄露再次发生?/p>
一U解决之道就是,资源封装在一个类中,利用cȝ析构函数来释放该资源Q这样一来,无论是何U方式退出f()函数的作用域Q类的析构函数L?x)被调用Q从而有效地避免?jin)资源泄霌Ӏ?/p>
auto_ptr是q种Ҏ(gu)的一U典型应用。它被称为智能指针,是一个模板类。声明一个只能指针的Ҏ(gu)是:(x)auto_ptr linux下,g++~译器的输出l果如下Q?br>~$ g++ test.cpp -o test#include <iostream>
#include <memory> //Z(jin)使用auto_ptr你必d含头文g:#include <memory>
using namespace std;
class demo
{
public:
demo ()
{
cout << "demo()" << endl;
}
~demo ()
{
cout << "~demo()" << endl;
}
};
void
test_org ()
{
cout << "test_org():" << endl;
demo *ptr = new demo;
return;
}
void
test_auto ()
{
cout << "test_auto():" << endl;
auto_ptr < demo > ptr (new demo);
return;
}
int
main (int narg, char **arg)
{
test_org ();
test_auto ();
return 0;
}
~$ ./test
test_org():
demo()
test_auto():
demo()
~demo()
]]>
使用标准C++的类型{换符Qstatic_cast、dynamic_cast、reinterpret_cast、和const_cast?/font>
3.1 static_cast
用法Qstatic_cast < type-id > ( expression )
该运符把expression转换为type-idcdQ但没有q行时类型检查来保证转换的安全性。它主要有如下几U用法:(x)
①用于类层次l构中基cd子类之间指针或引用的转换?br> q行上行转换Q把子类的指针或引用转换成基c表C)(j)是安全的Q?br> q行下行转换Q把基类指针或引用{换成子类表示Q时Q由于没有动态类型检查,所以是不安全的?br>②用于基本数据类型之间的转换Q如把int转换成charQ把int转换成enum。这U{换的安全性也要开发h员来保证?br>③把I指针{换成目标cd的空指针?br>④把Mcd的表辑ּ转换成voidcd?/font>
注意Qstatic_cast不能转换掉expression的const、volitale、或者__unaligned属性?/font>
3.2 dynamic_cast
用法Qdynamic_cast < type-id > ( expression )
该运符把expression转换成type-idcd的对象。Type-id必须是类的指针、类的引用或者void *Q?br>如果type-id是类指针cdQ那么expression也必L一个指针,如果type-id是一个引用,那么expression也必L一个引用?/font>
dynamic_cast主要用于cdơ间的上行{换和下行转换Q还可以用于cM间的交叉转换?br>在类层次间进行上行{换时Qdynamic_cast和static_cast的效果是一L(fng)Q?br>在进行下行{换时Qdynamic_casthcd(g)查的功能Q比static_cast更安全?br>class B{
public:
int m_iNum;
virtual void foo();
};
class D:public B{
public:
char *m_szName[100];
};
void func(B *pb){
D *pd1 = static_cast<D *>(pb);
D *pd2 = dynamic_cast<D *>(pb);
}
在上面的代码D中Q如果pb指向一个Dcd的对象,pd1和pd2是一L(fng)Qƈ且对q两个指针执行Dcd的Q何操作都是安全的Q?br>但是Q如果pb指向的是一个Bcd的对象,那么pd1是一个指向该对象的指针,对它q行Dcd的操作将是不安全的(如访问m_szNameQ,
而pd2是一个空指针?/font>
另外要注意:(x)B要有虚函敎ͼ否则?x)编译出错;static_cast则没有这个限制?br>q是׃q行时类型检查需要运行时cd信息Q而这个信息存储在cȝ虚函数表Q?br>关于虚函数表的概念,详细可见<Inside c++ object model>Q中Q只有定义了(jin)虚函数的cL有虚函数表,
没有定义虚函数的cL没有虚函数表的?/font>
另外Qdynamic_castq支持交叉{换(cross castQ。如下代码所C?br>class A{
public:
int m_iNum;
virtual void f(){}
};
class B:public A{
};
class D:public A{
};
void foo(){
B *pb = new B;
pb->m_iNum = 100;
D *pd1 = static_cast<D *>(pb); //compile error
D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL
delete pb;
}
在函数foo中,使用static_castq行转换是不被允许的Q将在编译时出错Q而?dynamic_cast的{换则是允许的Q结果是I指针?/font>
3.3 reinpreter_cast
用法Qreinpreter_cast<type-id> (expression)
type-id必须是一个指针、引用、算术类型、函数指针或者成员指针?br>它可以把一个指针{换成一个整敎ͼ也可以把一个整数{换成一个指针(先把一个指针{换成一个整敎ͼ
在把该整数{换成原类型的指针Q还可以得到原先的指针|(j)?/font>
该运符的用法比较多?/font>
3.4 const_cast
用法Qconst_cast<type_id> (expression)
该运符用来修改cd的const或volatile属性。除?jin)const 或volatile修饰之外Q?type_id和expression的类型是一L(fng)?br>帔R指针被{化成非常量指针,q且仍然指向原来的对象;
帔R引用被{换成非常量引用,q且仍然指向原来的对象;帔R对象被{换成非常量对象?/font>
Voiatile和constc试。D如下一例:(x)
class B{
public:
int m_iNum;
}
void foo(){
const B b1;
b1.m_iNum = 100; //comile error
B b2 = const_cast<B>(b1);
b2. m_iNum = 200; //fine
}
上面的代码编译时?x)报错,因?f)b1是一个常量对象,不能对它q行改变Q?br>使用const_cast把它转换成一个常量对象,可以对它的数据成员L改变。注意:(x)b1和b2是两个不同的对象?/font>