﻿<?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>C++博客-依旧的博客-随笔分类-编程</title><link>http://www.cppblog.com/zliner/category/1641.html</link><description>技术学习</description><language>zh-cn</language><lastBuildDate>Mon, 19 May 2008 17:50:43 GMT</lastBuildDate><pubDate>Mon, 19 May 2008 17:50:43 GMT</pubDate><ttl>60</ttl><item><title>用例分析基础</title><link>http://www.cppblog.com/zliner/archive/2006/06/01/8038.html</link><dc:creator>依旧的博客</dc:creator><author>依旧的博客</author><pubDate>Thu, 01 Jun 2006 09:18:00 GMT</pubDate><guid>http://www.cppblog.com/zliner/archive/2006/06/01/8038.html</guid><wfw:comment>http://www.cppblog.com/zliner/comments/8038.html</wfw:comment><comments>http://www.cppblog.com/zliner/archive/2006/06/01/8038.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zliner/comments/commentRss/8038.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zliner/services/trackbacks/8038.html</trackback:ping><description><![CDATA[
		<p>1. 一个用例可以有多个参与者，并且可以同时有多个参与者。<br />用例和参与者的关联可以是双向的，参与者和用例都可以发起通信。<br /><br />2. 用例之间的基本关系有：泛化，包含和扩展。<br />用例A到B的泛化关系表示A和B是具体与抽象的关系。<br />用例A到B的包含关系表示A使用了B提供的功能。<br />用例A到B的扩展关系表示A向B提供的可用的功能。<br />但从A到B的包含关系和从B到A的扩展关系是不同的：<br />A包含B说明B是从A中分解出来的公共行为；B自身是独立的，但对于A来说是不可缺少的一部分。<br />B扩展A说明B是从A中分解出来的变体行为，必须指定扩展点，也就是在基本用例中执行变体行为的具体条件。B仅仅是A的补充，而不是不可缺少的部分，B自身也不是独立的。A可以单独执行，表示通常的情况，在特定的情况下，用B来补充它。<br />抽象用例不能被实例化，不能被实际执行，它的作用在于更好地组织用例关系。<br /><br /><br /><br /><br />参考书：<br />《UML用户指南》/Grady Booch，James Rumbaugh，Ivar Jacobson著 邵维忠等译 机械工业出版社<br />《统一软件开发过程》/Ivar Jacobson，Grady Booch，James Rumbaugh著 周伯生等译 机械工业出版社<br /></p>
<img src ="http://www.cppblog.com/zliner/aggbug/8038.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zliner/" target="_blank">依旧的博客</a> 2006-06-01 17:18 <a href="http://www.cppblog.com/zliner/archive/2006/06/01/8038.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>MFC的五种基本机制</title><link>http://www.cppblog.com/zliner/archive/2006/05/15/7218.html</link><dc:creator>依旧的博客</dc:creator><author>依旧的博客</author><pubDate>Mon, 15 May 2006 11:33:00 GMT</pubDate><guid>http://www.cppblog.com/zliner/archive/2006/05/15/7218.html</guid><wfw:comment>http://www.cppblog.com/zliner/comments/7218.html</wfw:comment><comments>http://www.cppblog.com/zliner/archive/2006/05/15/7218.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zliner/comments/commentRss/7218.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zliner/services/trackbacks/7218.html</trackback:ping><description><![CDATA[<p>我们知道MFC的作用在于封装Windows的编程接口，并提供应用程序框架的开发模式。为了完成从前者到后者的过渡，MFC实现了几种基本机制，它们是消息映射，命令传递，运行时类信息(RTCI)，动态创建和序列化。<br><br>消息映射和命令传递是对SDK程序交互机制的封装。序列化是应用程序需要的一种基本特性，即把数据保存到磁盘和从磁盘打开数据。通过RTCI和动态创建，可以把软件的对象数据保存到磁盘，反过来从这些数据识别和恢复对象，从而实现对象的序列化。基于数据库的序列化机制和这种方式不同，应用程序和数据库之间有一个约定，以什么样的格式保存什么样的数据，再以同样的方式打开，并且如何重建对象数据也定下来了，在打开数据时，应用程序不需要有适应性，不需要识别数据类型，也不需要根据在运行期才确定的类型名称创建其对象。</p>
<p>动态创建就是创建某种类型的对象，具体类型在运行时确定，编译时可能不知道。比如运行时用户输入一个类型名称，如果该类型是程序类型体系中的一员，则程序中将能够创建该类型的对象。下面的代码是使用MFC动态创建机制的一个简化的例子：</p>
<p>CRuntimeClass* g_pFirstClass;<br>void func()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; char szClassName[64];<br>&nbsp;&nbsp;&nbsp;&nbsp; CRuntimeClass* pClass;<br>&nbsp;&nbsp;&nbsp;&nbsp; CObject* pObject;<br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "enter a class name...&nbsp; ";<br>&nbsp;&nbsp;&nbsp;&nbsp; cin &gt;&gt; szClassName;<br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; for (pClass = g_pFirstClass; pClass != NULL; pClass = pClass-&gt;m_pNextClass)<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (strcmp(szClassName, pClass-&gt;m_lpszClassName) == 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pObject = pClass-&gt;CreateObject();<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>}</p>
<p>实现动态创建的思路是把动态的类型名称与程序类型体系中的每一个进行比较，与某个类型吻合时让该类型创建自身的对象。这样，支持动态创建的类库中的每一个类都要额外实现一些功能，即判别一个名称是否与自身相符，以及创建自身的对象。</p>
<p>判别一个名称是否与自身相符，这是运行时类识别的内容，所以MFC动态创建是在RTCI基础上实现的。</p>
<p>RTCI是一个对象能够判定自己是否属于某种类型，该类型的名称在运行时确定，编译时可能不知道。从下面的例子很容易理解RTCI，</p>
<p>void Func()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; char szClassName[64];<br>&nbsp;&nbsp;&nbsp;&nbsp; CDocument* pDoc = new CDocument;<br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "enter a class name...&nbsp; ";<br>&nbsp;&nbsp;&nbsp;&nbsp; cin &gt;&gt; szClassName;<br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; pDoc-&gt;IsKindOf(szClassName); //是返回1，否返回0<br>}</p>
<p>有一点需要说明的是，因为CDocument派生于CObject，所以IsKindOf对于CObject也要返回1。因为我们是从动态创建出发的，所以如果是这样可能会有一点背离初衷。但是RTCI明显和动态创建有密切联系，RTCI也可能有单独的价值，所以先把RTCI实现起来。</p>
<p>实现RTCI的思路是让每一个类记录自身的类型信息，并提供IsKindOf(char*)函数进行所给类型与自身类型的比较，而且还要能访问基类的类型信息，进行比较，一直到根类。所以记录的类型信息要按继承关系连起来，每个类的IsKindOf()还要调用基类的IsKindOf()。MFC把要记录的类型信息抽取到一个CRuntimeClass结构体中，每个类中加入一个CRuntimeClass成员即可。</p>
<p>现在回到动态创建，在RTCI建立的数据结构基础上将可实现它。动态创建从不同于IsKindOf()的角度使用这一数据结构，它要遍历所有类型的CRuntimeClass。那么仅仅有继承关系的类的CRuntimeClass相连还不够，要把所有类的CRuntimeClass连成一个链表。其实动态创建并不关心类间的继承关系，它平等看待每个类。现在以CRuntimeClass为结点构成一个纵横两个方向的链表，IsKindOf()和动态创建分别使用它不同的侧面。</p>
<p>序列化的概念是在文件中存储对象信息，并能根据它恢复对象。对于文档视图结构的软件，用户需要保存所编辑的文档和打开已编辑的文档，这正是序列化的应用，所以序列化是非常重要的一种特性。在序列化恢复对象时，就可以用到动态创建。</p>
<p>使用MFC序列化的例子如下，</p>
<p>void CMyDocument::Serialize(CArichive &amp;ar)<br>{<br>&nbsp;&nbsp;&nbsp; if (ar.IsStoring())<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ar &lt;&lt; m_pMyClass; //CMyClass m_pMyClass;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ar &gt;&gt; m_pMyClass;<br>&nbsp;&nbsp;&nbsp; }<br>}</p>
<p>一个支持序列化的类提供Serialize(CArchive &amp;)函数，重载&lt;&lt;和&gt;&gt;操作。注意两者是不同的，在上例中，CMyDocument类的信息并不被序列化，而CMyClass类的信息被序列化。实际上一个序列化类的&lt;&lt;和&gt;&gt;操作，其不涉及类信息的部分是调用Serialize()完成的，它必须同时实现这两者。</p>
<p>按照MFC的要求，需要在支持序列化的类定义中使用DECLARE_SERIAL宏，在类实现中使用IMPLEMENT_SERIAL宏。我们看一下这两个宏实现了什么，</p>
<p>#define DECLARE_SERIAL(class_name) \<br>&nbsp;_DECLARE_DYNCREATE(class_name) \<br>&nbsp;AFX_API friend CArchive&amp; AFXAPI operator&gt;&gt;(CArchive&amp; ar, class_name* &amp;pOb);</p>
<p>#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \<br>&nbsp;CObject* PASCAL class_name::CreateObject() \<br>&nbsp;&nbsp;{ return new class_name; } \<br>&nbsp;_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \<br>&nbsp;&nbsp;class_name::CreateObject) \<br>&nbsp;AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \<br>&nbsp;CArchive&amp; AFXAPI operator&gt;&gt;(CArchive&amp; ar, class_name* &amp;pOb) \<br>&nbsp;&nbsp;{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \<br>&nbsp;&nbsp;&nbsp;return ar; } \</p>
<p>主要是加入了对&gt;&gt;的重载，但是没有重载&lt;&lt;，MFC仅提供了CObject对&lt;&lt;的重载，如下，</p>
<p>_AFX_INLINE CArchive&amp; AFXAPI operator&lt;&lt;(CArchive&amp; ar, const CObject* pOb)<br>&nbsp;{ ar.WriteObject(pOb); return ar; }</p>
<p>这是因为在序列化读和写的时候，都需要具体类的CRuntimeClass信息。相应的GetRuntimeClass()是一个虚函数，CObject重载&lt;&lt;，在写类信息时调用到该函数，由于虚函数机制，写入的是具体类的信息。但是这里隐含着一个条件，就是调用&lt;&lt;和GetRuntimeClass()时，具体类对象已经存在了，而调用&gt;&gt;和读入类信息时，该类对象还未被创建，所以无法利用这种机制，只能在每个具体类中都重载一次&gt;&gt;。我觉得《深入解析MFC》对这个问题的解释不正确。</p>
<p>这里有一个问题需要明确一下，序列化为什么要写入类信息？一是它应该保存完整的能够独立恢复对象的信息，二是在程序读入对象时，要把它的类信息和程序中期望的(所能处理的)类信息相比较，进行检验。</p>
<p>看IMPLEMENT_SERIAL宏对重载&gt;&gt;的实现，是提供一个期望的CRuntimeClass结构(用于检验)，委托CArchive进行对象读取。因为读对象时首先要跟文件打交道，所以交给CArchive处理，随后把读出的数据写入对象时，CArchive再调用具体类的Serialize()，如此合作是十分恰当的。在这里，CArchive还负责了读出和检验类信息，然后创建对象的过程。因为一方面具体类对象还不存在，另一方面这些操作对所有具体类都没有分别，应该提出来，在类级别实现或者让合作者实现。实际上，MFC先把这个过程交给CArchive::ReadClass()，后者又调用CRuntimeClass::Load()。&nbsp;</p>
<p>对于序列化来说，搞清它的概念以后，就是实现Serialzie()，重载&lt;&lt;和&gt;&gt;。对&lt;&lt;和&gt;&gt;的重载涉及很多工作，MFC已经帮我们实现了，我们也看见了大概的设计，主要是与CArchive分工合作，其次是CRuntimeClass。</p>
<p>现在看到CRuntimeClass结构体在MFC对RTCI，动态创建和序列化的实现中都起着重要的作用，重新认识一下这个数据结构很有必要。</p>
<p>CRuntimeClass包含了关于类的各种信息和有关操作。把类及其基类的CRuntimeClass连成一个链表，就可以很方便地实现RTCI的IsKindOf()；把所有类的CRuntimeClass连成一个链表，再加上一个简单的CreateObject函数，就可以对以任意类名进行动态创建的企图做出反应；CRuntimeClass还实现了向文件读写类信息的Load()，Store()，配合序列化的实现。</p>
<p>在分析消息映射和命令传递机制之前，需要对<a title=Windows程序模型 href="http://www.cppblog.com/zliner/archive/2007/04/15/21942.html">Windows程序模型</a>有很好的理解。</p>
<p>未完待续...<br><br><br>参考：<br><br>《深入解析MFC》/中国电力出版社<br>《深入浅出MFC》/华中科大出版社<br>《Windows程序设计》/北大出版社</p>
<img src ="http://www.cppblog.com/zliner/aggbug/7218.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zliner/" target="_blank">依旧的博客</a> 2006-05-15 19:33 <a href="http://www.cppblog.com/zliner/archive/2006/05/15/7218.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>思路欣赏</title><link>http://www.cppblog.com/zliner/archive/2006/05/12/7045.html</link><dc:creator>依旧的博客</dc:creator><author>依旧的博客</author><pubDate>Fri, 12 May 2006 15:14:00 GMT</pubDate><guid>http://www.cppblog.com/zliner/archive/2006/05/12/7045.html</guid><wfw:comment>http://www.cppblog.com/zliner/comments/7045.html</wfw:comment><comments>http://www.cppblog.com/zliner/archive/2006/05/12/7045.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/zliner/comments/commentRss/7045.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zliner/services/trackbacks/7045.html</trackback:ping><description><![CDATA[欣赏好的思路是一件愉快的事，特别是对我不会做的题目。<br /><br />1. 问题：对32位的二进制整数，不用循环，求出其中1的个数。<br /><br /><div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" /><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000"> POW(c) (1&lt;&lt;(c))</span><span style="COLOR: #000000"><br /><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" /></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000"> MASK(c) (((unsigned long)-1) / (POW(POW(c)) + 1))</span><span style="COLOR: #000000"><br /><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" /></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000"> ROUND(n, c) (((n) &amp; MASK(c)) + ((n) &gt;&gt; POW(c) &amp; MASK(c)))</span><span style="COLOR: #000000"><br /><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" /><br /><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" /></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> bit_count(unsigned </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> n)<br /><img id="Codehighlighter1_179_300_Open_Image" style="DISPLAY: inline" onclick="this.style.display='none'; Codehighlighter1_179_300_Open_Text.style.display='none'; Codehighlighter1_179_300_Closed_Image.style.display='inline'; Codehighlighter1_179_300_Closed_Text.style.display='inline';" src="http://kaikai.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif" align="top" /><img id="Codehighlighter1_179_300_Closed_Image" style="DISPLAY: none" onclick="this.style.display='none'; Codehighlighter1_179_300_Closed_Text.style.display='none'; Codehighlighter1_179_300_Open_Image.style.display='inline'; Codehighlighter1_179_300_Open_Text.style.display='inline';" src="http://kaikai.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif" align="top" /></span><span id="Codehighlighter1_179_300_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"><img src="http://www.cnblogs.com/Images/dot.gif" /></span><span id="Codehighlighter1_179_300_Open_Text" style="DISPLAY: inline"><span style="COLOR: #000000">{<br /><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />    n </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> ROUND(n, </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">);<br /><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />    n </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> ROUND(n, </span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">);<br /><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />    n </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> ROUND(n, </span><span style="COLOR: #000000">2</span><span style="COLOR: #000000">);<br /><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />    n </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> ROUND(n, </span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">);<br /><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />    n </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> ROUND(n, </span><span style="COLOR: #000000">4</span><span style="COLOR: #000000">);<br /><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/InBlock.gif" align="top" />    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> n;<br /><img src="http://kaikai.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif" align="top" />}</span></span></div><br />基本的想法是把所有的1加起来，得到的就是1的个数。我们需要把这些1分离出来，每个1都是平等的，与其位置无关。难题在于不能一个一个去取，那就用到了循环，当然递归也是不允许的。需要有一种统一的办法，可是很难想象具体该怎样。我们逐步地做这件事，假设前16位和后16位分别求得了1的个数，那么加起来就行了。16位二进制中的1仍然是未知的，随机出现的，问题的性质没有变，但我们可以继续分解，这种逐步的做法不一定就意味着递归。每个16位分解为两个8位，...,每个2位分解为两个1位，把两个1位上的数相加就是这两位上1的个数。现在需要取出每一位上的数吗？如果想到了这个问题，就离最终的思路不远了。现在32位已经分成了16个两位，很容易将其看作两个16位，一个是所有奇数位，一个是所有偶数位。我们不难把这两个16位分开，然后移位相加，就求出了每两位中1的个数。到了这一步，以后的思路就很自然了。<br /><br /><br />参考：<br /><br />《计算二进制位'1'的个数》来自 <a href="http://kaikai.cnblogs.com/">http://kaikai.cnblogs.com</a><img src ="http://www.cppblog.com/zliner/aggbug/7045.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zliner/" target="_blank">依旧的博客</a> 2006-05-12 23:14 <a href="http://www.cppblog.com/zliner/archive/2006/05/12/7045.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>几种排序方法的实现</title><link>http://www.cppblog.com/zliner/archive/2006/05/09/6825.html</link><dc:creator>依旧的博客</dc:creator><author>依旧的博客</author><pubDate>Tue, 09 May 2006 08:37:00 GMT</pubDate><guid>http://www.cppblog.com/zliner/archive/2006/05/09/6825.html</guid><wfw:comment>http://www.cppblog.com/zliner/comments/6825.html</wfw:comment><comments>http://www.cppblog.com/zliner/archive/2006/05/09/6825.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zliner/comments/commentRss/6825.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zliner/services/trackbacks/6825.html</trackback:ping><description><![CDATA[
		<div>1. 冒泡排序<br /><br /> 思想：<br /><br /></div>
		<ol>
				<li>从现有元素中取出最大的元素，移到相应的位置，直到所有元素都就位。 
</li>
				<li>通过比较和交换逐步调整现有序列，最终找出最大元素并使其就位。 </li>
		</ol>
		<p> 设计：<br /><br /></p>
		<p>  输入是待排数组及其长度，输出排序后的数组。<br />  在冒泡过程中对数组的有序情况进行检查，在数组已经有序时便结束算法。<br /><br />代码：<br /><br />void BubbleSort(int nArray[], int nLength)<br />{<br />     bool bSorted = false;<br />    <br />     if (nArray == NULL)<br />         throw -1;<br />    <br />     if (nLength &lt; 2)<br />         return;<br />    <br />    for (int i = nLength; !bSorted &amp;&amp; i &gt; 1; i--)<br />    {<br />         bSorted = true;<br />        <br />         for (int j = 1; j &lt; i; j++)<br />        {<br />             if (nArray[j] &lt; nArray[j-1])<br />            {<br />                 int n;<br />                 n = nArray[j];<br />                 nArray[j] = nArray[j-1];<br />                 nArray[j-1] = n;<br />        <br />                 bSorted = false;<br />             }//if<br />         }<br />     }<br /><br />}<br /><br />2. 双向冒泡排序<br /><br />void BiBubbleSort(int nArray[], int nLength)<br />{<br />    int  low, high;<br /> <br />    if (nArray == NULL)<br />       throw -1;</p>
		<p>    if (nLength &lt; 2)<br />       returnt;</p>
		<p>    low = 0;<br />    high = nLength - 1;<br />    while (low &lt; high)<br />   {<br />       int t;</p>
		<p>       t = low;<br />       for (int i = low; i &lt; high; i++)<br />       {<br />           if (nArray[i] &gt; nArray[i+1])<br />          {<br />              int n;<br />              n = nArray[i];<br />              nArray[i] = nArray[i+1];<br />              nArray[i+1] = n;<br /><br />              t = i + 1;<br />          }<br />       }<br />       high = t - 1;</p>
		<p>      t = high;<br />      for (int j = high; j &gt; low; j--)<br />      {<br />          if (nArray[j] &lt; nArray[j-1])<br />          {<br />             int n;<br />             n = nArray[j];<br />             nArray[j] = nArray[j-1];<br />             nArray[j-1] = n;<br />            <br />             t = j - 1;<br />          }<br />      }</p>
		<p>     low = t + 1;</p>
		<p>  }//while<br /><br />}<br /><br />3. 快速排序<br /><br /> 思想：<br /><br /> 选一个枢轴元素，把待排序列划分成两段，前一段不大于枢轴， 后一段不小于枢轴。如果这两段分别有序，原序列也随之有序。通过划分，一个段的排序可以转化为两个子段的排序，即同样性质但较小规模的问题。当段的长度为1时，本身是有序的，转化可以终止。<br /><br />设计：<br /><br />用一个递归函数来实现快速排序算法，递归终止条件是段的长度小于等于1。<br />一次划分过程设计如下：取段的第一个元素为枢轴，从最后一个元素向前与枢轴比较，发现小于枢轴的元素时，与枢轴交换位置，从第二个元素向后与枢轴比较，这样两端是已完成划分的部分，中间是待划分的部分，枢轴始终处于中间部分的一端，比较从另一端向该端进行，发现分类不同的元素就同枢轴交换。随着比较和交换的进行，中间部分不断收缩(每次长度缩短1)，当收缩到长度为1时，划分终止。<br /><br />实现要点：<br /><br />递归函数的参数是待排序列及前后边界。<br />划分过程需要用两个变量记录中间部分的边界。<br /><br />代码：<br /><br />void QuickSort(int nArray[], int low, int high)<br />{<br />     int pivot = nArray[low];<br />     int i = low，j = high;<br />    <br />     if (high &lt; low)<br />           return;   <br />    <br />     while (i &lt; j)<br />     {<br />          while (i &lt; j &amp;&amp; nArray[j] &gt;= pivot) j--;<br />          if (i &lt; j) <br />               nArray[i++] = nArray[j];<br /> <br />          while (i &lt; j &amp;&amp; nArray[i] &lt;= pivot) i++;<br />          if (i &lt; j) <br />               nArray[j--] = nArray[i];<br />     }<br />    <br />     nArray[i] = pivot;<br />    <br />     QuickSort(nArray, low, i - 1);<br />     QuickSort(nArray, i + 1, high);<br />}<br /><br />测试要点： </p>
		<ol>
				<li>递归终止条件。必须是high &lt; low，而不能是 high == low。递归的终止是很重要的边界情况，在实现之前有一个概念上的终止条件，但在实现时处理必须准确。终止条件和递推方式有关，需要结合实际的递推方式来确定。 
</li>
				<li>递归的递推方式。 
</li>
				<li>分划的终止条件。分划过程在i == j时终止，虽然在比较的过程中可能进行交换，但是每次未分划部分的长度减1，用该长度控制分划的终止。 
</li>
				<li>分划过程中改变方向时的交接。 </li>
		</ol>
		<p>算法分析：<br /><br />假设原序列有2<sup>n</sup>个元素，每次分划把一个段等分成两段，则经过n级递归算法终止，每一级递归的比较总数为n, 所以QuickSort()的时间为O(nlog(n))，这是平均情况。当原序列本身有序时，QuickSort()出现最坏情况，时间为O(n<sup>2</sup>)。</p>
<img src ="http://www.cppblog.com/zliner/aggbug/6825.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zliner/" target="_blank">依旧的博客</a> 2006-05-09 16:37 <a href="http://www.cppblog.com/zliner/archive/2006/05/09/6825.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>多线程通信的机制</title><link>http://www.cppblog.com/zliner/archive/2006/05/05/6630.html</link><dc:creator>依旧的博客</dc:creator><author>依旧的博客</author><pubDate>Thu, 04 May 2006 16:15:00 GMT</pubDate><guid>http://www.cppblog.com/zliner/archive/2006/05/05/6630.html</guid><wfw:comment>http://www.cppblog.com/zliner/comments/6630.html</wfw:comment><comments>http://www.cppblog.com/zliner/archive/2006/05/05/6630.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zliner/comments/commentRss/6630.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zliner/services/trackbacks/6630.html</trackback:ping><description><![CDATA[最原始的多线程通信机制是全局变量，但是两个线程对同一变量的并发操作可能是冲突的，所谓冲突是指一种并发调度不等价于任何串行调度。可以通过特定的系统调用保证线程对全局变量的操作具有原子性。这时全局变量成为一种可用的通信机制，根据通信的需要设计通信的协议，两个线程分别执行，就可以完成合作。比如辅线程重复某个过程直到主线程通知它终止，可以约定全局变量为特定值时辅线程终止，让辅线程在循环中检测全局变量，主线程设置全局变量通知辅线程终止。这种机制的缺点是接收通知的一方必须不断检测全局变量，事件机制由此进行了改进。接收方可以在事件上阻塞，等待特定的通知，等待的时间可以设定，如果设为0，则事件退化为全局变量。事件只有两种状态，有信号或无信号，这相对全局变量是一个限制。事件实现了单向的消息，这是最基本的机制。在实际通信活动中还有许多同步问题，每一方不但根据特定的消息采取相应的动作，还在收到消息后发出反馈消息。通信是一个交互和持续的过程。临界段机制就是为此而提供的。临界段的局限是只有两种状态：锁定和解锁，在很多的通信中，需要进一步量化的状态，这就产生了信号量机制。<img src ="http://www.cppblog.com/zliner/aggbug/6630.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zliner/" target="_blank">依旧的博客</a> 2006-05-05 00:15 <a href="http://www.cppblog.com/zliner/archive/2006/05/05/6630.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>COM基本概念和COM模型</title><link>http://www.cppblog.com/zliner/archive/2006/05/03/6601.html</link><dc:creator>依旧的博客</dc:creator><author>依旧的博客</author><pubDate>Wed, 03 May 2006 13:54:00 GMT</pubDate><guid>http://www.cppblog.com/zliner/archive/2006/05/03/6601.html</guid><wfw:comment>http://www.cppblog.com/zliner/comments/6601.html</wfw:comment><comments>http://www.cppblog.com/zliner/archive/2006/05/03/6601.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zliner/comments/commentRss/6601.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zliner/services/trackbacks/6601.html</trackback:ping><description><![CDATA[
		<p>1. 接口<br /><br />接口是一组函数的集合(更一般情况下，是一组函数和变量的集合)，对象和客户(程序的两个不同部分)可通过它进行通信。接口有特定的内存结构，一个接口指针指向一个虚表(vtbl)指针，虚表是一个函数指针的数组，每项指向一个接口函数。<br /><br />接口是概念性的程序元素，它具有继承和多态性。继承性是指子接口继承了基接口的所有函数，子接口可以转型为基接口。在实现上，子接口的虚表包括了基接口的虚表，子接口的虚表指针可以转型为基接口的虚表指针。多态性是指一个基接口的不同子接口可以有不同的行为。<br /><br />2. COM接口(组件模型对接口的要求)</p>
		<p>COM作为一种二进制组件模型，要求对象和客户尽可能分离，它们的一切联系都通过接口进行。一个对象可以有多个接口，那么，客户在获得第一个接口指针后，应当可以从一个接口指针查询下一个接口指针，以保持对象的使用。客户应当可以通过接口管理对象的生命期，以结束对象的使用。作为一种设计，COM规定从对象的一个接口可以查询它的所有接口，对象生命期管理的责任分散到每个接口(只要客户为每个接口进行生命期管理，就可以实现对象的生命期管理)。在实现上，COM将接口查询和生命期管理的责任集中到一个IUnknown接口，所有接口都从IUnknown派生。COM接口就是从IUnknown派生的接口。</p>
		<p>2. COM的面向对象特征</p>COM在二进制上提供了一种软件结构模型，并且带有面向对象的特征。<br /><br /><ol><li>封装 
<p>COM对象是有状态的，数据和操作封装在一起。COM接口和普通API函数的不同，就在于COM对象是有状态的。比如一个宇宙飞船对象(实现IMotion接口，IMotion包含void Fly(double dTime)和double GetPosition()函数)，让它飞行一段时间(通过IMotion接口调用Fly()函数)以后它的位置就改变了(在飞行前后调用GetPosition()得到不同结果)。</p></li><li>多态 
<p>同样的接口可以由不同的COM对象实现，客户程序用统一的方法进行处理，却可以得到不同的结果。接口也可以派生，不同的子接口对基接口的函数有不同的实现。</p><p>在这里解释一下MFC实现COM对象的机制。一个COM对象可以实现多个接口，而这些接口都是IUnknown的子接口，它们对QueryInterface(),  AddRef(),  Release()各有一份实现代码，而在同一对象内，这三个函数的内容完全相同，因此可以抽出来，委派给该对象。又由于对任何COM对象，AddRef()和Release()的实现本质上也相同，因此可以进一步，抽取这两个函数及其操作的数据(m_Ref)，放到CCmdTarget中去。QueryInterface()的情况有所不同，它操作的数据是依赖于具体COM对象的接口映射表，可以在把函数放进CCmdTarget的同时，实现一个返回接口映射表的虚函数，QueryInterface()调用此函数获得具体的接口映射表。</p></li><li>重用 
<p></p><p>COM对象可以用包容和聚合两种方式重用已有的COM对象。</p><p>聚合方式实现重用比较复杂。<br /><br />在实现对象聚合时，要解决的一个主要问题是在接口查询上对用户保持透明。客户从暴露出来的内部对象接口进行查询，应当查到的是外部对象的接口。那么收到查询时，内部对象的IUnknown应当去委托外部对象的IUnknown。但是内部对象也可能不被用于聚合，应该有一个正常的IUnknown。这样可以考虑把内部对象最初收到查询的IUnknown设成一个代理，它根据聚合与否把查询请求转交给外部对象IUnknown或内部对象的正常IUnknown，即内部对象实现两个IUnknown，作为代理的委托IUnknown和正常的非委托IUnknown。内部对象还要知道外部对象IUnknown，并且能判别自身是否被聚合。可以在创建内部对象时把外部对象IUnknown指针传给它，不是聚合时传递一个空指针，这样内部对象就得到了足够信息。</p><p>引用计数的管理也是一样，内部对象的委托IUnknown区别被聚合与否，调用外部对象IUnknown或自身的非委托IUnknown。</p><p>当然，从外部对象接口要能查到内部对象接口。外部对象需要知道内部对象的IUnknown，以查询所要暴露给客户程序的接口。这个IUnknown应当是内部对象的非委托IUnknown。</p></li></ol><img src ="http://www.cppblog.com/zliner/aggbug/6601.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zliner/" target="_blank">依旧的博客</a> 2006-05-03 21:54 <a href="http://www.cppblog.com/zliner/archive/2006/05/03/6601.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>排序的方法</title><link>http://www.cppblog.com/zliner/archive/2006/05/03/6595.html</link><dc:creator>依旧的博客</dc:creator><author>依旧的博客</author><pubDate>Wed, 03 May 2006 09:40:00 GMT</pubDate><guid>http://www.cppblog.com/zliner/archive/2006/05/03/6595.html</guid><wfw:comment>http://www.cppblog.com/zliner/comments/6595.html</wfw:comment><comments>http://www.cppblog.com/zliner/archive/2006/05/03/6595.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zliner/comments/commentRss/6595.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zliner/services/trackbacks/6595.html</trackback:ping><description><![CDATA[如果有一个随机排列的整数表，怎样将它排序呢？这是生活中也经常碰到的问题。比如给一组牌排序，我们通常会怎样做呢？<br /><br />不断从原序列中取出元素来排成一个新序列，在新序列形成的时候保证它有序，这就是插入排序的办法。插入排序需要有空间存放和操作新序列，这可以在原序列的空间中满足。插入排序需要大量的比较和移动，量级是O(n*n)。我们也可以每次从现有元素中取出最小元素，这样新序列排下来自然是有序的，这就是选择排序的办法。选择排序需要O(n*n)的比较，最坏最好情况下都一样。这是一个缺点，和与之对称的插入排序比较，选择排序对原序列的情况缺乏适应性，冒泡排序是对此的改进。冒泡排序也基本使用原序列的空间，每次在现有元素中进行比较以寻找最小元素，并通过交换逐步把它移到相应位置。冒泡排序在原序列的基础上逐步调整得到新序列，它可以更加适应原序列的情况，在最好情况下时间为O(n)。这三种排序是最基本的，其它方法都是从各种角度对它们进行改进。<br /><br />三种基本排序都着眼于从原序列形成新序列的过程。这是最基本的，但还可以把整个过程用分治或渐进的思想来处理。具体的改进方法有多种，希尔排序，快速排序，归并排序等等，我们现在可以欣赏它们的思想，但是当初每种方法的发现都是重要的成果，需要对排序问题有扎实的认识，也需要创造的灵感。<br /><br />希尔排序把原序列分组，每一组进行插入排序，从较小的分组开始逐渐扩大，直到整个序列作为一组。分组元素是间隔选取的，较小的分组排序完成后，整个序列就在一个较大的尺度上有序了，随着分组的扩大，序列在越来越小的尺度上有序，直到完全有序。希尔排序利用插入排序对乐观情况的适应性，自顶向下渐进处理，避免了直接在小尺度上进行处理的盲目性。<br /><br />归并排序反映出一种自底向上的分治思想，其时间为O(n*log(n))。<br /><br />快速排序采用了自顶向下的分治思想，其做法和归并排序有某种对称性。<br /><br />基数排序也是自顶向下的分治思想，它从关键字本身衡量问题的解决程度。<br /><br /><br /><img src ="http://www.cppblog.com/zliner/aggbug/6595.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zliner/" target="_blank">依旧的博客</a> 2006-05-03 17:40 <a href="http://www.cppblog.com/zliner/archive/2006/05/03/6595.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>数据库范式及其涵义</title><link>http://www.cppblog.com/zliner/archive/2006/05/02/6562.html</link><dc:creator>依旧的博客</dc:creator><author>依旧的博客</author><pubDate>Tue, 02 May 2006 09:15:00 GMT</pubDate><guid>http://www.cppblog.com/zliner/archive/2006/05/02/6562.html</guid><wfw:comment>http://www.cppblog.com/zliner/comments/6562.html</wfw:comment><comments>http://www.cppblog.com/zliner/archive/2006/05/02/6562.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zliner/comments/commentRss/6562.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zliner/services/trackbacks/6562.html</trackback:ping><description><![CDATA[
		<p dir="ltr" style="MARGIN-RIGHT: 0px">2NF以上的范式都是对关系上的依赖进行限制，其中最重要的是3NF和BCNF。</p>
		<ul>
				<li> BCNF：所有非平凡依赖都以超键为决定子。一个属性集只有包含了整个的键，才能决定集外的属性。 
</li>
				<li>3NF：其非平凡依赖X-&gt;A必须满足：X是超键，或者A是主属性。3NF比BCNF有所放松，允许含键不完全的属性集决定集外的属性，但必须是主属性。 
</li>
				<li>不符合3NF的情况有两种: 
<ol><li>键的真子集决定非主属性，即非主属性对键的部分依赖； 
</li><li>既非超键也非键的真子集决定非主属性，由此将可证明，存在非主属性对键的传递依赖。</li></ol>如果一个关系不满足2但满足1，称此关系符合2NF。 </li>
		</ul>
		<p>2NF和3NF的涵义是：键是关系的标识信息，非主属性是附属信息。如果附属信息对标识信息的依赖不够紧密，关系的语义单纯性就差，从而容易出现各种更新异常。 </p>
		<p>如果违反2NF，既存在非主属性对键的部分依赖，会有什么问题？例如关系模式SCGT(S#,C#,G,TN)，S#是学生号，C#是课程号，G是成绩，TN是任课教师姓名，假设每门课只有一个教师。(S#,C#)是键，C#-&gt;TN是非主属性对键的部分依赖，因为它的存在会产生三种更新异常：1). 不开课的教师姓名无法插入；2). 一门课的所有学生都退选，则任课教师姓名无法保留；3). 一门课更换教师时，必须对选该课的所有学生进行修改。非主属性对键的部分依赖反映了附属信息和标识信息的缺乏整体一致性，所以会产生以上问题。</p>
		<p>如果符合2NF，但违反3NF，即存在非主属性对键的传递依赖，会有什么问题？例如关系模式SDL(S#,DEPT,LOC)，S#是学生号，DEPT是所在系，LOC是系的办公地，这里S#是键，S#-&gt;DEPT，DEPT-/&gt;S#，DEPT-&gt;LOC，LOC传递依赖于S#，因为它的存在会产生三种更新异常：1). 如果一个系新成立尚未招生，则无法插入；2). 如果一个系不再招生，但仍为其他系开课，则现有学生毕业后，系的信息无法保留；3). 一个系更换办公地时，必须对该系的所有学生进行修改。非主属性对键的传递依赖反映了附属信息和标识信息缺乏直接一致性，所以会产生以上问题。缺乏直接一致不如缺乏整体一致那样严重，所以到了3NF才排除。</p>
		<p>那么BCNF的涵义在哪里呢？<br /><br />2NF和3NF对一个关系模式中的非主属性加以限制，而忽略键之间的关系。如果一个主属性依赖含键不完全的属性组意味着什么呢？可以证明，该依赖涉及不止一个键，其决定子有两种情况，一种是部分键，一种是含部分键和键外的属性。第一种情况下存在一个键之外的属性对该键的部分依赖；第二种情况下，取一个不含前述主属性的键，易知存在该属性对该键的传递依赖，即一个键外的属性对该键的传递依赖，排除这两种情况就得到BCNF。为什么要这样做呢？因为有多个键的情况下，必须照顾每一个键，如果键之外的属性和该键不能保持整体和直接的一致，也可能产生更新异常。例如SCZ(S,C,Z)，S，C，Z分别表示街道，城市，邮编，关系模式上的依赖集为{SC-&gt;Z,Z-&gt;C}，SC和SZ都是键。如果插入一个城市的总邮编，必须借助一个街道，删除这个街道，城市的总邮编也被删除，出现这种情况是因为C与SZ键缺乏整体一致性。<br /><br /><br />参考：</p>
		<p>王能斌《数据库系统教程》/电子工业出版社</p>
<img src ="http://www.cppblog.com/zliner/aggbug/6562.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zliner/" target="_blank">依旧的博客</a> 2006-05-02 17:15 <a href="http://www.cppblog.com/zliner/archive/2006/05/02/6562.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C/S通信和Winsock编程</title><link>http://www.cppblog.com/zliner/archive/2006/05/02/6553.html</link><dc:creator>依旧的博客</dc:creator><author>依旧的博客</author><pubDate>Tue, 02 May 2006 04:46:00 GMT</pubDate><guid>http://www.cppblog.com/zliner/archive/2006/05/02/6553.html</guid><wfw:comment>http://www.cppblog.com/zliner/comments/6553.html</wfw:comment><comments>http://www.cppblog.com/zliner/archive/2006/05/02/6553.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zliner/comments/commentRss/6553.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zliner/services/trackbacks/6553.html</trackback:ping><description><![CDATA[<div class=postText>
<p>1. 客户-服务器通信中的基本问题</p>
<p>客户和服务器通信是为了使用服务，为此在传输机制的基础上设计协议，通过对通信行为的规范，实现通信的目的，并解决传输中的问题。</p>
<p>传输机制通常由下层协议提供，根据不同的通信需要选择不同的下层协议，这是一个基本的问题。对应用协议来说，可用的传输机制有可靠连接的字节流类型和不可靠无连接的数据报类型。</p>
<p>服务器处理大量客户的请求，从而并发是服务器的一个基本问题，如何处理这个问题也取决于通信需要。处理方式上，服务器可以是循环的或并发的，并发服务器有多种实现方式(异步I/O，多线程和多进程)。</p>
<p>一件事情能无重复地连续进行，通常会获得更好的效率，这要求主体始终知道当前的状态。一次通信过程的连续性取决于通信双方，它们都要知道通信进行的状态。这对客户一般不成问题，但服务器要和大量客户通信，不一定能为每个客户的每次通信保存状态。如果服务器是有状态的，那么就更快地计算响应，减少通信的数据量。但是传输和客户的故障使有状态服务器面临很大问题，当传输不可靠(报文重复，丢失，乱序)时，服务器维护的状态会和客户失去一致，一个不断崩溃重启的客户会造成状态信息不能发挥作用，而维护开销却极大增加。</p>
<p>这就提出了客户-服务器通信中的三个基本问题，它们的解决方案都取决于实际需要，客户-服务器通信中有哪些情况的需要呢？</p>
<ul>
    <li>是否要求可靠传输；
    <li>是否需要服务器进行大量处理。对循环服务器进行分析可以知道，需要大量处理的通信用循环方案可能会丢失请求，用并发方案还可以提高服务器资源利用率，改善性能，只要少量处理的通信则无法忍受开销大的解决方案。
    <li>在局域网还是互联网环境下，局域网中传输很少出错，互联网环境则不然； </li>
</ul>
<p><br>通常根据前两个基本问题把服务器实现分为四种类型，它们的适用范围如下：</p>
<ul>
    <li>循环无连接服务器，少量处理的通信时，并且在局域网中或不要求可靠传输。这种做法主要是为了避免开销。
    <li>循环连接服务器，较少用，主要是循环的方式不够高效，因为连接有一定开销，响应时间可能不低。在少量处理并要求可靠性的情况下使用。
    <li>并发无连接服务器，很少用，因为要给每个请求开线程，开销太大。在不要求可靠性的情况下，如果线程开销远小于计算响应开销，或者并发可以让各请求的I/O并行，或者循环方案会丢失请求时可以考虑。
    <li>并发连接服务器，常用。 </li>
</ul>
<p><br>2. winsock基本函数的使用</p>
<p>winsock的基本函数有WSAStartup()，WSACleanup()，socket()，closesocket()，bind()，listen()，accept()， connect()，send()和recv()。</p>
<p>使用这些函数，客户端的大概算法是，</p>
<ol>
    <li>&nbsp;调用WSAStartup()初始化winsock库。
    <li>&nbsp;调用socket()创建套接字，返回套接字描述符s。
    <li>&nbsp;指定远程套接字地址sa，对s调用connect()，向sa标识的服务进程请求连接。
    <li>&nbsp;连接成功后，对s调用send()发送请求，调用recv()接收响应，如此反复直到完成任务。
    <li>&nbsp;对s调用closesocket()关闭连接。
    <li>&nbsp;不再发起连接处理新的任务时，调用WSACleanup()释放winsock库。 </li>
</ol>
<p>服务器端的大概算法是，</p>
<ol>
    <li>调用WSAStartup()初始化winsock库。
    <li>调用socket()创建套接字，返回套接字描述符s。
    <li>对s调用bind()，将其绑定到本地的套接字sa。
    <li>调用listen()，将s置为被动模式。此时开始侦听客户端的连接请求，将其放入一个队列。
    <li>对s调用accept()，即从请求队列中取出一项，接受该连接后返回一个新的套接字描述符s'，以及对应客户端的套接字地址sa'。
    <li>对s'调用recv()接收请求，调用send()发送响应，如此反复直到完成任务。
    <li>对s'调用closesocket()关闭该连接。
    <li>重复5到7的过程。
    <li>从8退出后，调用WSACleanup()释放winsock库。 </li>
</ol>
<p>有以下几点需要进一步说明，</p>
<p>1). 客户端调用connect()和服务器端调用accept()成功后将在客户进程和服务器进程之间建立一个TCP连接。连接两端的每个套接字描述符都包含一个本地端点地址和一个远程端点地址。所以在使用连接套接字发送数据时不用指示目的地址。</p>
<p>2). 多宿主主机的IP地址选择问题。从上面的算法容易提出这样的问题，为什么客户端在使用套接字时不绑定端点地址？通常的主机只有一个IP，但是多宿主主机有多个IP地址，在这种情况下，客户端为套接字指定的IP可能与实际发送时经过的IP不符，所以允许客户端不指定套接字地址，而由TCP/IP软件在实际发送时指定IP，同时选择一个未用过的端口号，这正是在connect()调用中完成的。那么服务器端就不存在同样的情况吗？不是，在它调用bind()时指定一个套接字地址，其端口部分采用应用协议的熟知端口，而IP地址部分有着同样的问题。为此定义了一个代表统配地址的常量INADDR_ANY，用它指示IP地址部分。实际使用的IP仍然是由TCP/IP软件分配。</p>
<p>3). TCP为一个连接的发送端和接收端各维护一个缓冲区。当发送端缓冲区满的时候，send()调用会阻塞，在接收端缓冲区为空的时候，recv()调用会阻塞。为什么要在通信进程和TCP连接之间维护一个间接层呢？可能是为了在一端有多个进程要使用信道的情况下，在多个进程之间进行信道分配的协调。比如在发送端，信道传输数据时send()调用可以继续执行，多个进程的send()调用同缓冲区打交道，彼此影响不大，因为读写缓冲区速度很快，而信道同缓冲区打交道，这时可以对各进程的发送数据进行协调，实现公平的信道分配。另外，在TCP中有滑动窗口概念，是用于流量控制的，前述缓冲区和滑动窗口有什么关系？我现在不太清楚。</p>
<p>4). 套接字的关闭问题。在客户机和服务器通过TCP连接完成数据交换后，有一个安全关闭的问题。一方面，服务器不能关闭连接，因为客户机可能还有请求，另一方面，客户机虽然知道何时不再请求，但是它不知道服务器的响应何时发送完，因为有些应用协议的响应数据量不确定。为此采用部分关闭的办法，虽然连接是双向的，但是允许在一个方向上关闭它，当客户端不再请求时，可以部分关闭连接，使服务器收到一个信号，如果响应发送完了，服务器就可以关闭连接，此时连接被完全关闭<br><br>3. 套接字接口中的端点地址</p>
<p>端点地址用来表示通信的进程，是传输层协议及其套接字接口中的重要概念。不同的协议族可以用不同方式表示端点地址，一个协议族还可以有多个地址族，每个地址族的地址格式不同。TCP/IP只有一个地址族，它的端点地址包括一个32位IP地址和一个16位端口号。在协议族和地址族的基础上，套接字接口用更为具体的结构来表示端点地址。</p>
<p>套接字是一种适用于多个协议族的接口，并允许一个协议族使用多个地址族。TCP/IP协议族及其唯一地址族的标识分别是PF_INET和AF_INET。由于套接字接口的通用性，它提供一个通用的地址结构，其格式为(地址族，该族中的套接字地址)。套接字作为一个接口标准，可以有不同实现，以下我们只讨论windows套接字。</p>
<p>如下定义的sockaddr实现了前述通用地址结构，</p>
<p>// winsock2.h</p>
<p>struct sockaddr {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; u_short sa_family;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* address family */<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char&nbsp;&nbsp;&nbsp; sa_data[14];&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* up to 14 bytes of direct address */<br>};</p>
<p>sockaddr的通用性是相对的，某些地址族不适合这个结构。</p>
<p>尽管sockaddr适合于TCP/IP协议族，但是winsock还定义了TCP/IP专用的地址格式，</p>
<p>// winsock2.h</p>
<p>struct sockaddr_in {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; short&nbsp;&nbsp; sin_family;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; u_short sin_port;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct&nbsp; in_addr sin_addr;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char&nbsp;&nbsp;&nbsp; sin_zero[8];<br>};</p>
<p>sin_family域取值恒为AF_INET。其中的in_addr结构表示IP地址，定义如下，</p>
<p>//winsock2.h</p>
<p>struct in_addr {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; union {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct { u_short s_w1,s_w2; } S_un_w;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; u_long S_addr;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } S_un;<br>#define s_addr&nbsp; S_un.S_addr<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* can be used for most TCP &amp; IP code */<br>#define s_host&nbsp; S_un.S_un_b.s_b2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* host on imp */<br>#define s_net&nbsp;&nbsp; S_un.S_un_b.s_b1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* network */<br>#define s_imp&nbsp;&nbsp; S_un.S_un_w.s_w2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* imp */<br>#define s_impno S_un.S_un_b.s_b4<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* imp # */<br>#define s_lh&nbsp;&nbsp;&nbsp; S_un.S_un_b.s_b3<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* logical host */<br>};</p>
<p>为了保证软件的可移植性与可维护性，访问TCP/IP的代码不应使用sockaddr。只使用TCP/IP的应用程序可以只使用sockaddr_in，而永远不用sockaddr。</p>
<p>4. winsock程序实例</p>
<p>《vc6技术内幕》的例程ex34a包括一个web服务器和三个客户，服务器用winsock实现，一个客户用winsock，另两个用wininet。我们以winsock实现的服务器和客户为例。</p>
<p>CBlockingSocket对各接口函数进行封装，使它们的调用可以统一报错。把错误检查和函数调用一起封装可以避免每次调用这些函数时都检错。为统一报错，采用了异常机制，在检出错误后抛出异常，然后统一进行异常处理。异常机制使我们可以把错误检查和错误处理分开，检查必须是分散的，但是处理可以适当集中，使代码简化。</p>
<p>CHttpBlockingSocket根据接收http报文的特点对CBlockingSocket进行了扩展。成员函数ReadHttpHeaderLine()可以从TCP连接中按行接收字符(它引入了一个缓冲区，缓冲区中收到的字符构成一行后再输出。该缓冲区的长度需要能够容纳每一行字符，溢出时报错。接收输出行的缓冲区也可能长度不足，这时接收的数据不足一行，在调用ReadHttpHeaderLine()时要注意这一点)，ReadHttpResponse()用于接收首部行之后所有的响应，当调用者提供的缓冲区不足时会报错。缓冲区不足的情况都是在CBlockingSocket::Receive()函数中检测到的，该函数调用以上层次中的代码按照正常情况编写。</p>
<p>CSockAddr是一个与sockaddr_in同样用途的类，但是用法更方便。winsock函数使用的端点地址结构是sockaddr，sockaddr_in本身用来代替它，所以CSockAddr需要能够替代sockaddr。sockaddr可能用在传值或传址参数中，CSockAddr必须在逻辑上和存储上都和sockaddr有等价性，并实现有关强制类型转换。CSockAddr还实现了和sockaddr, sockaddr_in互相转换的成员函数，因为一种结构很难在所有情况下都好用，新结构也需要和旧结构保持兼容。<br></p>
<p>&#160;</p>
<p>本例中采用服务器关闭套接字的办法，因为每次连接只处理一个请求。<br><br>参考：</p>
<p>《用TCP/IP进行网际互联第三卷(windows套接字版)》/清华出版社</p>
<p>《vc6技术内幕 5th ed》/希望电子出版社</p>
</div>
<img src ="http://www.cppblog.com/zliner/aggbug/6553.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zliner/" target="_blank">依旧的博客</a> 2006-05-02 12:46 <a href="http://www.cppblog.com/zliner/archive/2006/05/02/6553.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>论面向对象</title><link>http://www.cppblog.com/zliner/archive/2006/05/02/6552.html</link><dc:creator>依旧的博客</dc:creator><author>依旧的博客</author><pubDate>Tue, 02 May 2006 04:44:00 GMT</pubDate><guid>http://www.cppblog.com/zliner/archive/2006/05/02/6552.html</guid><wfw:comment>http://www.cppblog.com/zliner/comments/6552.html</wfw:comment><comments>http://www.cppblog.com/zliner/archive/2006/05/02/6552.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zliner/comments/commentRss/6552.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zliner/services/trackbacks/6552.html</trackback:ping><description><![CDATA[<p>软件中的对象同领域中的概念有着密切的关系。</p>
<p>我们知道概念是人们在某个领域中实践经验的总结，并可能发展为理论。概念是以客观事物为基础，但不是对客观事物的刻板反映。它来自于实践，所以包含主体因素，这是很重要的。实践是概念的根本来源，理论上的需要对概念形成也有一些作用。</p>
<p>软件中使用的对象类似于领域中使用的概念。《UML与模式应用》中说，面向对象就是按照概念而不是功能进行分解。为什么软件要使用概念性元素呢？因为人的认识是概念性的，而软件由人来使用，人来开发。为了提供有良好概念性的用户接口，软件本身适宜采用概念性元素。由人来开发的软件则更需要采用概念性元素，软件本身和领域实践都有巨大的复杂性，人不是按照功能性元素来思考的，面向对象可以让开发人员用概念性元素思考，从而增加对复杂性的适应和控制能力。</p>
<p>领域概念是对象的重要来源，但是对象也形成于软件开发的过程。一方面，软件的使用没有改变领域实践的本质，至少没有完全改变，而概念反映了领域中已经成熟的认识，所以是非常重要的指导和参考。另一方面，领域实践由软件进行和由人进行确实非常不同，要求进行新的认识，软件本身的需要也会影响到对象的形成。</p>
<p>面向对象的基本特征反映着概念的基本特征。</p>
<ol>
    <li>封装：概念不是静态的，它的属性和操作不可分。
    <li>通信：概念不是孤立的，概念的属性可以其他概念构成，也可以在属性中引用其他概念，概念的操作过程中会用到其它的概念。
    <li>抽象：从具体概念可以进一步产生抽象概念。
    <li>多态：抽象概念有着多样的具体表现。</li>
</ol>
<p>Stroustrup的《C++程序设计语言》中说：</p>
<p>&#8220;类应该用于模拟程序员的和应用的世界里的那些概念。...一个概念不会孤立地存在，它总与一些相关的概念共存，并在与相关概念的相互关系中表现出它的大部分力量。...因为我们要用类表示概念，问题就变成了如何去表示概念之间的关系。然而，我们无法在程序语言里表述任意的关系。即使能这样做，我们也未必想去做它。我们的类应该定义得比日常概念更窄一些——而且也更精确。&#8221;</p>
<p>我们可以体会这段话的深刻性。</p>
<ol>
    <li>类用来定义概念。首先，概念不是静态的，它的属性和操作不可分，封装是面向对象的第一个特征。然后，概念不是孤立的，一个概念总是与相关概念共存，最基本的关系有两对：封装和通信，抽象和多态。前者反映了分工与合作关系，后者反映了抽象与具体关系。类是面向对象的核心，从探讨类的作用出发，就引出了面向对象的四个基本特征。
    <li>类所定义的概念不但来自应用领域，也来自程序员引入的东西。
    <li>&#8220;我们无法在程序语言里表述任意的关系。即使能这样做，我们也未必想去做它。&#8221; </li>
</ol>
<img src ="http://www.cppblog.com/zliner/aggbug/6552.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zliner/" target="_blank">依旧的博客</a> 2006-05-02 12:44 <a href="http://www.cppblog.com/zliner/archive/2006/05/02/6552.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>