<?xml version="1.0" encoding="GBK" ?>
<rss version="2.0" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:dcterms="http://purl.org/dc/terms/">
 <channel>
  	  <title><![CDATA[haoyindangdeblog的博客]]></title>
	  <link>http://haoyindangdeblog.blog.163.com</link>
	  <description><![CDATA[游戏学院成都校区程序老师 ]]></description>
	  <language>zh-CN</language>
	  <pubDate>Mon, 21 Jul 2008 13:01:02 +0800</pubDate>
	  <lastBuildDate>Mon, 21 Jul 2008 13:01:02 +0800</lastBuildDate>
	  <docs>http://blogs.law.harvard.edu/tech/rss</docs>
	  <generator><![CDATA[NetEase Space]]></generator>
	  <managingEditor><![CDATA[haoyindangdeblog]]></managingEditor>
	  <webMaster><![CDATA[游戏学院成都校区程序老师]]></webMaster>
		  <ttl>120</ttl>
	  <image>
	  	<title><![CDATA[haoyindangdeblog的博客]]></title>
	  	<url>http://haoyindangdeblog.blog.163.com/style/common/user_default.gif</url>
	  	<link>http://haoyindangdeblog.blog.163.com</link>
	  </image>
  <item>
  	<title><![CDATA[内存池(上)]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/704678092008621059460</link>
    <description><![CDATA[<div><P>成都游戏学院 <A href="http://www.cdgamecollege.org">http://www.cdgamecollege.org</A> 电话：028-85586115 </P>
<P>&nbsp;</P>
<P style="TEXT-INDENT: 2em">本书主要针对的是 C++ 程序的性能优化，深入介绍 C++ 程序性能优化的方法和实例。全书由 4 个篇组成，第 1 篇介绍 C++ 语言的对象模型，该篇是优化 C++ 程序的基础；第 2 篇主要针对如何优化 C++ 程序的内存使用；第 3 篇介绍如何优化程序的启动性能；第 4 篇介绍了三类性能优化工具，即内存分析工具、性能分析工具和 I/O 检测工具，它们是测量程序性能的利器。</P>
<P style="TEXT-INDENT: 2em">在此我们推出了此书的第 <A href="http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index2.html">2</A>、<A href="http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html">6</A> 章供大家在线浏览。更多推荐书籍请访问 <A href="http://www.ibm.com/developerworks/cn/books/index.html">developerWorks 图书频道</A>。 </P>
<P style="TEXT-INDENT: 2em">
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG src="file:///F:/资料/其它/资料/内存池/内存池.files/blue_rule.gif" border=0>
<P></P>
<P style="TEXT-INDENT: 2em"><IMG src="file:///F:/资料/其它/资料/内存池/内存池.files/c.gif" border=0></P></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD><IMG src="file:///F:/资料/其它/资料/内存池/内存池.files/c.gif" border=0>
<P></P>
<P style="TEXT-INDENT: 2em">
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG src="file:///F:/资料/其它/资料/内存池/内存池.files/u_bold.gif" border=0>
<P></P>
<P style="TEXT-INDENT: 2em"></P></TD>
<TD vAlign=top align=right><A href="http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html#main">回页首</A></TD></TR></TBODY></TABLE></P></TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em"><A href="http://www.china-pub.com/computers/common/info.asp?id=34206">中国互动出版网</A> 、<A href="http://www.dearbook.com.cn/book/129969">dearbook</A> </TD></TR><TR><TD colSpan="3"></TD></TR></P>
<P style="TEXT-INDENT: 2em">推荐章节: 
</P><UL>
<LI><A href="http://www.ibm.com/developerworks/cn/books/more.html">前言和目录</A> 
</LI><LI><A href="http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index2.html">第 2 章：C++ 语言特性的性能分析</A> 
</LI><LI><A href="http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html">第 6 章：内存池</A> </LI></UL>
<P></P>
<P style="TEXT-INDENT: 2em">更多推荐书籍，请访问 <A href="http://www.ibm.com/developerworks/cn/books/index.html">developerWorks 图书频道</A>。 </P>
<P style="TEXT-INDENT: 2em">如果您对本书有任何的建议、意见或者问题，欢迎与本书作者和 IBM 技术专家 <A href="http://www.ibm.com/developerworks/cn/books/feedback.html">交流</A>。</TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em">如前所述，读者已经了解到"堆"和"栈"的区别。而在编程实践中，不可避免地要大量用到堆上的内存。例如在程序中维护一个链表的数据结构时，每次新增或者删除一个链表的节点，都需要从内存堆上分配或者释放一定的内存；在维护一个动态数组时，如果动态数组的大小不能满足程序需要时，也要在内存堆上分配新的内存空间。</P>
<P style="TEXT-INDENT: 2em"><A href="http://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html#main">回页首</A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em"><A >6.2 一个内存池的实现实例</A></P>
<P style="TEXT-INDENT: 2em">本节分析在某个大型应用程序实际应用到的一个内存池实现，并详细讲解其使用方法与工作原理。这是一个应用于单线程环境且分配单元大小固定的内存池，一般用来为执行时会动态频繁地创建且可能会被多次创建的类对象或者结构体分配内存。</P>
<P style="TEXT-INDENT: 2em">本节首先讲解该内存池的数据结构声明及图示，接着描述其原理及行为特征。然后逐一讲解实现细节，最后介绍如何在实际程序中应用此内存池，并与使用普通内存函数申请内存的程序性能作比较。</P>
<P style="TEXT-INDENT: 2em"><A >6.2.1 内部构造</A></P>
<P style="TEXT-INDENT: 2em">内存池类MemoryPool的声明如下：</P>
<P style="TEXT-INDENT: 2em">
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P></P>
<P style="TEXT-INDENT: 2em">class MemoryPool{private: MemoryBlock* pBlock; USHORT nUnitSize; USHORT nInitSize; USHORT nGrowSize;public: MemoryPool( USHORT nUnitSize, USHORT nInitSize = 1024, USHORT nGrowSize = 256 ); ~MemoryPool(); void* Alloc(); void Free( void* p );};</P></TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em">MemoryBlock为内存池中附着在真正用来为内存请求分配内存的内存块头部的结构体，它描述了与之联系的内存块的使用信息：</P>
<P style="TEXT-INDENT: 2em">
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P></P>
<P style="TEXT-INDENT: 2em">struct MemoryBlock{ USHORT nSize; USHORT nFree; USHORT nFirst; USHORT nDummyAlign1; MemoryBlock* pNext; char aData[1]; static void* operator new(size_t, USHORT nTypes, USHORT nUnitSize) { return ::operator new(sizeof(MemoryBlock) + nTypes * nUnitSize); } static void operator delete(void *p, size_t) { ::operator delete (p); } MemoryBlock (USHORT nTypes = 1, USHORT nUnitSize = 0); ~MemoryBlock() {}};</P></TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em">此内存池的数据结构如图6-2所示。</P>
<P style="TEXT-INDENT: 2em"><A >图6-2 内存池的数据结构</A></P>
<P style="TEXT-INDENT: 2em"><IMG src="file:///F:/资料/其它/资料/内存池/内存池.files/6_2.gif" border=0> </P>
<P style="TEXT-INDENT: 2em"><A >6.2.2 总体机制</A></P>
<P style="TEXT-INDENT: 2em">此内存池的总体机制如下。</P>
<P style="TEXT-INDENT: 2em">（1）在运行过程中，MemoryPool内存池可能会有多个用来满足内存申请请求的内存块，这些内存块是从进程堆中开辟的一个较大的连续内存区域，它由一个MemoryBlock结构体和多个可供分配的内存单元组成，所有内存块组成了一个内存块链表，MemoryPool的pBlock是这个链表的头。对每个内存块，都可以通过其头部的MemoryBlock结构体的pNext成员访问紧跟在其后面的那个内存块。</P>
<P style="TEXT-INDENT: 2em">（2）每个内存块由两部分组成，即一个MemoryBlock结构体和多个内存分配单元。这些内存分配单元大小固定（由MemoryPool的nUnitSize表示），MemoryBlock结构体并不维护那些已经分配的单元的信息；相反，它只维护没有分配的自由分配单元的信息。它有两个成员比较重要：nFree和nFirst。nFree记录这个内存块中还有多少个自由分配单元，而nFirst则记录下一个可供分配的单元的编号。每一个自由分配单元的头两个字节（即一个USHORT型值）记录了紧跟它之后的下一个自由分配单元的编号，这样，通过利用每个自由分配单元的头两个字节，一个MemoryBlock中的所有自由分配单元被链接起来。</P>
<P style="TEXT-INDENT: 2em">（3）当有新的内存请求到来时，MemoryPool会通过pBlock遍历MemoryBlock链表，直到找到某个MemoryBlock所在的内存块，其中还有自由分配单元（通过检测MemoryBlock结构体的nFree成员是否大于0）。如果找到这样的内存块，取得其MemoryBlock的nFirst值（此为该内存块中第1个可供分配的自由单元的编号）。然后根据这个编号定位到该自由分配单元的起始位置（因为所有分配单元大小固定，因此每个分配单元的起始位置都可以通过编号分配单元大小来偏移定位），这个位置就是用来满足此次内存申请请求的内存的起始地址。但在返回这个地址前，需要首先将该位置开始的头两个字节的值（这两个字节值记录其之后的下一个自由分配单元的编号）赋给本内存块的MemoryBlock的nFirst成员。这样下一次的请求就会用这个编号对应的内存单元来满足，同时将此内存块的MemoryBlock的nFree递减1，然后才将刚才定位到的内存单元的起始位置作为此次内存请求的返回地址返回给调用者。</P>
<P style="TEXT-INDENT: 2em">（4）如果从现有的内存块中找不到一个自由的内存分配单元（当第1次请求内存，以及现有的所有内存块中的所有内存分配单元都已经被分配时会发生这种情形），MemoryPool就会从进程堆中申请一个内存块（这个内存块包括一个MemoryBlock结构体，及紧邻其后的多个内存分配单元，假设内存分配单元的个数为n，n可以取值MemoryPool中的nInitSize或者nGrowSize），申请完后，并不会立刻将其中的一个分配单元分配出去，而是需要首先初始化这个内存块。初始化的操作包括设置MemoryBlock的nSize为所有内存分配单元的大小（注意，并不包括MemoryBlock结构体的大小）、nFree为n-1（注意，这里是n-1而不是n，因为此次新内存块就是为了满足一次新的内存请求而申请的，马上就会分配一块自由存储单元出去，如果设为n-1，分配一个自由存储单元后无须再将n递减1），nFirst为1（已经知道nFirst为下一个可以分配的自由存储单元的编号。为1的原因与nFree为n-1相同，即立即会将编号为0的自由分配单元分配出去。现在设为1，其后不用修改nFirst的值），MemoryBlock的构造需要做更重要的事情，即将编号为0的分配单元之后的所有自由分配单元链接起来。如前所述，每个自由分配单元的头两个字节用来存储下一个自由分配单元的编号。另外，因为每个分配单元大小固定，所以可以通过其编号和单元大小（MemoryPool的nUnitSize成员）的乘积作为偏移值进行定位。现在唯一的问题是定位从哪个地址开始？答案是MemoryBlock的aData[1]成员开始。因为aData[1]实际上是属于MemoryBlock结构体的（MemoryBlock结构体的最后一个字节），所以实质上，MemoryBlock结构体的最后一个字节也用做被分配出去的分配单元的一部分。因为整个内存块由MemoryBlock结构体和整数个分配单元组成，这意味着内存块的最后一个字节会被浪费，这个字节在图6-2中用位于两个内存的最后部分的浓黑背景的小块标识。确定了分配单元的起始位置后，将自由分配单元链接起来的工作就很容易了。即从aData位置开始，每隔nUnitSize大小取其头两个字节，记录其之后的自由分配单元的编号。因为刚开始所有分配单元都是自由的，所以这个编号就是自身编号加1，即位置上紧跟其后的单元的编号。初始化后，将此内存块的第1个分配单元的起始地址返回，已经知道这个地址就是aData。</P>
<P style="TEXT-INDENT: 2em">（5）当某个被分配的单元因为delete需要回收时，该单元并不会返回给进程堆，而是返回给MemoryPool。返回时，MemoryPool能够知道该单元的起始地址。这时，MemoryPool开始遍历其所维护的内存块链表，判断该单元的起始地址是否落在某个内存块的地址范围内。如果不在所有内存地址范围内，则这个被回收的单元不属于这个MemoryPool；如果在某个内存块的地址范围内，那么它会将这个刚刚回收的分配单元加到这个内存块的MemoryBlock所维护的自由分配单元链表的头部，同时将其nFree值递增1。回收后，考虑到资源的有效利用及后续操作的性能，内存池的操作会继续判断：如果此内存块的所有分配单元都是自由的，那么这个内存块就会从MemoryPool中被移出并作为一个整体返回给进程堆；如果该内存块中还有非自由分配单元，这时不能将此内存块返回给进程堆。但是因为刚刚有一个分配单元返回给了这个内存块，即这个内存块有自由分配单元可供下次分配，因此它会被移到MemoryPool维护的内存块的头部。这样下次的内存请求到来，MemoryPool遍历其内存块链表以寻找自由分配单元时，第1次寻找就会找到这个内存块。因为这个内存块确实有自由分配单元，这样可以减少MemoryPool的遍历次数。</P>
<P style="TEXT-INDENT: 2em">综上所述，每个内存池（MemoryPool）维护一个内存块链表（单链表），每个内存块由一个维护该内存块信息的块头结构（MemoryBlock）和多个分配单元组成，块头结构MemoryBlock则进一步维护一个该内存块的所有自由分配单元组成的"链表"。这个链表不是通过"指向下一个自由分配单元的指针"链接起来的，而是通过"下一个自由分配单元的编号"链接起来，这个编号值存储在该自由分配单元的头两个字节中。另外，第1个自由分配单元的起始位置并不是MemoryBlock结构体"后面的"第1个地址位置，而是MemoryBlock结构体"内部"的最后一个字节aData（也可能不是最后一个，因为考虑到字节对齐的问题），即分配单元实际上往前面错了一位。又因为MemoryBlock结构体后面的空间刚好是分配单元的整数倍，这样依次错位下去，内存块的最后一个字节实际没有被利用。这么做的一个原因也是考虑到不同平台的移植问题，因为不同平台的对齐方式可能不尽相同。即当申请MemoryBlock大小内存时，可能会返回比其所有成员大小总和还要大一些的内存。最后的几个字节是为了"补齐"，而使得aData成为第1个分配单元的起始位置，这样在对齐方式不同的各种平台上都可以工作。</P></div>]]></description>
	    <author><![CDATA[游戏学院成都校区程序老师]]></author>
	    <comments>http://haoyindangdeblog.blog.163.com/blog/static/704678092008621059460</comments>
    <slash:comments>0</slash:comments>
    <guid isPermaLink="true">http://haoyindangdeblog.blog.163.com/blog/static/704678092008621059460</guid>
    <pubDate>Mon, 21 Jul 2008 12:59:04 +0800</pubDate>
    <dcterms:modified>2008-07-21T12:59:04+08:00</dcterms:modified>
  </item>    
  <item>
  	<title><![CDATA[ SQL基础2]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/70467809200861612711947</link>
    <description><![CDATA[<div><P>成都游戏学院 <A href="http://www.cdgamecollege.org">http://www.cdgamecollege.org</A> 电话：028-85586115 </P>
<P>&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;如果一切正常，在你单击连接按钮后会出现一个查询窗口，如图10.2所示。（如果有异常，请参考第三章） </P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 图10.2</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在执行查询之前，你需要选择数据库。安装 SQL Sever时你已为自己创建了一个数据库，SQL Sever还有许多系统数据库，如master，model，msdb，和tempdb。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 方便的是，SQL Sever带有一个特殊的名为pubs的例子数据库。库 pubs中包含供一个虚拟的出版商使用的各个表。文档中所有的例子程序都是针对这个库来设计的。本书中的许多例子也使用这个数据库。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在查询窗口顶部的DB下拉框中选择数据库pubs，这样你就选择了数据库。你所有的查询都将针对这个库中的各个表来执行。现在你可以执行你的第一个查询了。这真让人兴奋！</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 你的第一个查询将针对一个名为autrors的表，表中包含所有为某个虚拟出版商工作的作者的相关数据。单击查询窗口并输入以下的语句：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SELECT&nbsp; phone&nbsp; FROM authors WHERE au_name="Ringer"</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 输入完成后，单击执行查询按钮（一个绿色三角形，看起来像VCR播放键）。单击此按钮后，任何出现在查询窗口中的语句均会被执行。查询窗口会自动变成结果显示窗口，你可以看到查询的结果（见图10.3）。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 你看到的查询结果也许与图10.3所示的不同。在SQL Sever的不同版本中，库pubs中的数据会有所不同。对SQL Sever 6.5来说，将会找到两条记录。结果显示窗口中应显示如下内容：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; phone</P>
<P style="TEXT-INDENT: 2em">……………….</P>
<P style="TEXT-INDENT: 2em">801 826_0752</P>
<P style="TEXT-INDENT: 2em">801 826_0752</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; (2 row(s)&nbsp; affected)</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp; 图10.3</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 你所执行的SELECT语句从表authors中取出所有名字为Ringer的作者的电话号码。你通过在WHERE子句中使用特殊的选择条件来限制查询的结果。你也可以忽略选择条件，从表中取出所有作者的电话号码。要做到这一点，单击Query标签，返回到查询窗口，输入以下的SELECT语句：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">SELECT Phone&nbsp; FROM authors</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 这个查询执行后，会取出表authors中的所有电话号码（没有特定的顺序）。如果表authors中包含一百个电话号码，会有一百个记录被取出，如果表中有十亿个电话号码，这十亿条记录都会被取出（这也许需要一些时间）。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 表authrs的字段包括姓，名字，电话号码，地址，城市，州和邮政编码。通过在SELECT语句的第一部份指定它们，你可以从表中取出任何一个字段。你可以在一个SELECT语句中一次取出多个字段，比如：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SELECT au_fname ,au_lname, phone FROM authors</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 这个SELECT语句执行后，将取出这三个列的所有值。下面是这个查询的结果的一个示例（为了节省纸张，只显示查询结果的一部分，其余记录用省略号代替）：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; au_fname&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; au_lname&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; phone </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; ………………………………………………………………………….</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Johnson&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; White&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 408&nbsp; 496_7223</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Marjorie&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Green&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 415&nbsp; 986_7020</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Cheryl&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Carson&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 415&nbsp; 548_7723</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Michael&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; O’Leary&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 408&nbsp; 286_2428</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; … </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; (23 row(s)&nbsp; affected)</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在SELECT语句中，你需要列出多少个字段，你就可以列出多少。不要忘了把字段名用逗号隔开。你也可以用星号（*）从一个表中取出所有的字段。这里有一个使用星号的例子：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp; &nbsp;&nbsp;SELECT &nbsp;* &nbsp;FROM&nbsp; authors</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 这个SELECT语句执行后，表中的所有字段的值都被取出。你会发现你将在SQL查询中频繁使用星号。</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 技巧：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 你可以使用星号来查看一个表的所有列的名字。要做到这一点，只需要在执行完SELECT语句后看一下查询结果的列标题。</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">操作多个表</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 到现在为止，你只尝试了用一句SQL查询从一个表中取出数据。你也可以用一个SELECT语句同时从多个表中取出数据，只需在SELECT语句的FROM从句中列出要从中取出数据的表名称即可：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SELECT au_lname ,title&nbsp; FROM&nbsp; authors, titles&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 这个SELECT语句执行时，同时从表authors和表titles中取出数据。从表authors中取出所有的作者名字，从表titles中取出所有的书名。在ISQL/w程序中执行这个查询，看一下查询结果。你会发现一些奇怪的出乎意料的情况：作者的名字并没有和它们所著的书相匹配，而是出现了作者名字和书名的所有可能的组合，这也许不是你所希望见到的。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 出了什么差错？问题在于你没有指明这两个表之间的关系。你没有通过任何方式告诉SQL如何把表和表关联在一起。由于不知道如何关联两个表，服务器只能简单地返回取自两个表中的记录的所有可能组合。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 要从两个表中选出有意义的记录组合，你需要通过建立两表中字段的关系来关联两个表。要做到这一点的途径之一是创建第三个表，专门用来描述另外两个表的字段之间的关系。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 表authors有一个名为au_id的字段，包含有每个作者的唯一标识。表titles有一个名为title_id的字段，包含每个书名的唯一标识。如果你能在字段au_id和字段title_id 之间建立一个关系，你就可以关联这两个表。数据库pubs中有一个名为titleauthor的表，正是用来完成这个工作。表中的每个记录包括两个字段，用来把表titles和表authors关联在一起。下面的SELECT语句使用了这三个表以得到正确的结果：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SELECT&nbsp; au_name,title FROM authors,titles,titleauthor&nbsp; </P>
<P style="TEXT-INDENT: 2em">&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; WHERE&nbsp; authors.au_id=titleauthor.au_id</P>
<P style="TEXT-INDENT: 2em">&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;AND&nbsp;&nbsp;&nbsp; titles.title_id=titleauthor.title_id&nbsp;&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 当这个SELECT语句执行时，每个作者都将与正确的书名相匹配。表titleauthor指明了表authors和表titles的关系，它通过包含分别来自两个表的各一个字段实现这一点。第三个表的唯一目的是在另外两个表的字段之间建立关系。它本身不包含任何附加数据。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 注意在这个例子中字段名是如何书写的。为了区别表authors和表titles中相同的字段名au_id，每个字段名前面都加上了表名前缀和一个句号。名为author.au_id 的字段属于表authors，名为titleauthor.au_id的字段属于表titleauthor，两者不会混淆。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 通过使用第三个表，你可以在两个表的字段之间建立各种类型的关系。例如，一个作者也许写了许多不同的书，或者一本书也许由许多不同的作者共同完成。当两个表的字段之间有这种“多对多”的关系时，你需要使用第三个表来指明这种关系。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 但是，在许多情况下，两个表之间的关系并不复杂。比如你需要指明表titles和表publishers之间的关系。因为一个书名不可能与多个出版商相匹配，你不需要通过第三个表来指明这两个表之间的关系。要指明表titles和表publishers之间的关系，你只要让这两个表有一个公共的字段就可以了。在数据库pubs中，表titles和表publishers都有一个名为pub_id的字段。如果你想得到书名及其出版商的一个列表，你可以使用如下的语句：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SELECT&nbsp; title,pub_name&nbsp; FROM&nbsp; titles,publishers</P>
<P style="TEXT-INDENT: 2em">&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; WHERE titles.pub_id=publishers.pub_id</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 当然，如果一本书是由两个出版商联合出版的，那么你需要第三个表来代表这种关系。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 通常，当你予先知道两个表的字段间存在“多对多”关系时，就使用第三个表来关联这两个表。反之，如果两个表的字段间只有“一对一”或“一对多”关系，你可以使用公共字段来关联它门。</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">操作字段</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 通常，当你从一个表中取出字段值时，该值与创建该表时所定义的字段名联系在一起。如果你从表authors中选择所有的作者名字，所有的值将会与字段名au_lname相联系。但是在某些情况下，你需要对字段名进行操作。在SELECT语句中，你可以在缺省字段名后面仅跟一个新名字来取代它。例如，可以用一个更直观易读的名字Author Last Name来代替字段名au_lname：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SELECT au_lname "Author Last Name" FROM authors</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 当这个SELECT语句执行时，来自字段au_lname的值会与“Author Last Name”相联系。查询结果可能是这样：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Author Last Name </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ……………………………………………………………………..&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; White</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Green</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Carson</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; O’Leary</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Straight </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; …</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (23 row(s) affected)</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">注意字段标题不再是au_lname，而是被Author Last Name所取代。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 你也可以通过执行运算，来操作从一个表返回的字段值。例如，如果你想把表titles中的所有书的价格加倍，你可以使用下面的SELECT语句：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SELECT&nbsp; price*2 FROM titles&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 当这个查询执行时，每本书的价格从表中取出时都会加倍。但是，通过这种途径操作字段不会改变存储在表中的书价。对字段的运算只会影响SELECT语句的输出，而不会影响表中的数据。为了同时显示书的原始价格和涨价后的新价格，你可以使用下面的查询：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">SELECT&nbsp; price&nbsp; "Original&nbsp; price", price*2&nbsp; "New price" FROM&nbsp; titles</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 当数据从表titles中取出时，原始价格显示在标题Original price下面，加倍后的价格显示在标题New price下面。结果可能是这样：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; original&nbsp; price&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; new&nbsp; price</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ……………………………………………………………….</P>
<P style="TEXT-INDENT: 2em">19.99&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;&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; 39.98</P>
<P style="TEXT-INDENT: 2em">11.95&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 23.90 </P>
<P style="TEXT-INDENT: 2em">2.99&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;&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; 5.98</P>
<P style="TEXT-INDENT: 2em">19.99&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;&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; 39.98</P>
<P style="TEXT-INDENT: 2em">…</P>
<P style="TEXT-INDENT: 2em">(18 row(s)&nbsp; affected)</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 你可以使用大多数标准的数学运算符来操作字段值，如加（+），减（-），乘（*）和除（/）。你也可以一次对多个字段进行运算，例如：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp; SELECT&nbsp; price*ytd_sales "total revenue" FROM titles</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在这个例子中，通过把价格与销售量相乘，计算出了每种书的总销售额。这个SELECT语句的结果将是这样的：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; total&nbsp; revenue</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ……………………………………………..</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 81,859,05</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 46,318,20</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 55,978,78</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;81,859,05</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 40,619,68</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; …</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (18 row(s)&nbsp; affected)</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 最后，你还可以使用连接运算符（它看起来像个加号）来连接两个字符型字段：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;SELECT&nbsp; au_fname+" "+au_lname&nbsp;&nbsp; "author name" FROM authors</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在这个例子中，你把字段au_fname和字段au_lname粘贴在一起，中间用一个逗号 隔开，并把查询结果的标题指定为author name。这个语句的执行结果将是这样的：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; author&nbsp; names</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; …………………………………………………………</P></div>]]></description>
	    <author><![CDATA[游戏学院成都校区程序老师]]></author>
	    <comments>http://haoyindangdeblog.blog.163.com/blog/static/70467809200861612711947</comments>
    <slash:comments>0</slash:comments>
    <guid isPermaLink="true">http://haoyindangdeblog.blog.163.com/blog/static/70467809200861612711947</guid>
    <pubDate>Wed, 16 Jul 2008 13:27:11 +0800</pubDate>
    <dcterms:modified>2008-07-16T13:27:11+08:00</dcterms:modified>
  </item>    
  <item>
  	<title><![CDATA[SQL基础1]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/70467809200861612669</link>
    <description><![CDATA[<div><P>成都游戏学院 <A href="http://www.cdgamecollege.org">http://www.cdgamecollege.org</A> 电话：028-85586115 </P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">为了建立交互站点，你需要使用数据库来存储来自访问者的信息。例如，你要建立一个职业介绍服务的站点，你就需要存储诸如个人简历，所感兴趣的工作等等这样的信息。创建动态网叶也需要使用数据库，如果你想显示符合来访者要求的最好的工作，你就需要从数据库中取出这份工作的信息。你将会发现，在许多情况下需要使用数据库。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在这一章里，你将学会怎样使用“结构化查询语言”（SQL〕来操作数据库。SQL语言是数据库的标准语言。在Active Sever Pages 中，无论何时你要访问一个数据库，你就要使用SQL语言。因此，掌握好SQL对ASP编程是非常重要的。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 注意：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 你可以把“SQL”读作“sequel”，也可以按单个字母的读音读作S－Q－L。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 两种发音都是正确的，每种发音各有大量的支持者。在本书里，认为“SQL”读作“sequel”。</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 通过这一章的学习，你将理解怎样用SQL实现数据库查询，你将学会怎样使用这种查询从数据表中取出信息，最后，你将学会怎样设计和建立自己的数据库。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 注意：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 通过下面几章对SQL的介绍，你将对SQL有足够的了解，从而可以有效地使用Active Sever Pages。但是，SQL是一种复杂的语言，本书不可能包括它的全部细节。要全面掌握SQL语言，你需要学习在Microsoft SQL Sever 中使用SQL。你可以到附近的书店去买一本Microsoft SQL Sever 6.5。</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">SQL介绍：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 本书假设你是在SQL操作Microsoft SQL Sever 的数据库。你也可以用SQL操作许多其它类型的数据库。SQL是操作数据库的标准语言。（事实上，关于SQL语言有一个专门的ANSI标准〕</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 注意：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 不要在你的站点上试图用Microsoft Access代替Microsoft SQL Sever。SQL Sever可以同时服务于许多用户，如果你希望你的站点有较高的访问率，MS Access是不能胜任的。</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在学习SQL的细节之前，你需要理解它的两大特点。一个特点容易掌握，另一个掌握起来有点困难。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 第一个特点是所有SQL数据库中的数据都存储在表中。一个表由行和列组成。例如，下面这个简单的表包括name 和e-mail address：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Name&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Email Address</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; ................................................................</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Bill Gates&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; billg@microsoft.com</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; president Clinton&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; president@whitehouse.com</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Stephen Walther&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; swalther@somewhere.com</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 这个表有两列（列也称为字段，域〕：Name和Email Address。有三行，每一行包含一组数据。一行中的数据组合在一起称为一条记录。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 无论何时你向表中添加新数据，你就添加了一条新记录。一个数据表可以有几十个记录，也可以有几千甚至几十亿个记录。虽然你也许永远不需要存储十亿个Email地址，但知道你能这样做总是好的，也许有一天你会有这样的需要。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 你的数据库很有可能包含几十个表，所有存储在你数据库中的信息都被存储在这些表中。当你考虑怎样把信息存储在数据库中时，你应该考虑怎样把它们存储在表中。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SQL的第二个特点有些难于掌握。这种语言被设计为不允许你按照某种特定的顺序来取出记录，因为这样做会降低SQL Sever取记录的效率。使用SQL，你只能按查询条件来读取记录。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 当考虑如何从表中取出记录时，自然会想到按记录的位置读取它们。例如，也许你会尝试通过一个循环，逐个记录地扫描，来选出特定的记录。在使用SQL时，你必须训练自己，不要有这种思路。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 假如你想选出所有的名字是“Bill Gates”的记录，如果使用传统的编程语言，你也许会构造一个循环，逐个查看表中的记录，看名字域是否是“Bill Gates”。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 这种选择记录的方法是可行的，但是效率不高。使用SQL，你只要说，“选择所有名字域等于Bill Gates的记录”，SQL就会为你选出所有符合条件的记录。SQL会确定实现查询的最佳方法。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 建设你想取出表中的前十个记录。使用传统的编程语言，你可以做一个循环，取出前十个记录后结束循环。但使用标准的SQL查询，这是不可能实现的。从SQL的角度来说，在一个表中不存在前十个记录这种概念。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 开始时，当你知道你不能用SQL实现某些你感觉应该能实现的功能，你会受到挫折。你也许会以头撞墙甚至想写恶毒的信件给SQL的设计者们。但后来你会认识到，SQL的这个特点不仅不是个限制，反而是其长处。因为SQL不根据位置来读取记录，它读取记录可以很快。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 综上所述，SQL有两个特点：所有数据存储在表中，从SQL的角度来说，表中的记录没有顺序。在下一节，你将学会怎样用SQL从表中选择特殊的记录。</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">使用SQL从表中取记录。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SQL的主要功能之一是实现数据库查询。如果你熟悉Internet 引擎，那么你已经熟悉查询了。你使用查询来取得满足特定条件的信息。例如，如果你想找到有ASP信息的全部站点，你可以连接到 Yahoo!并执行一个对Active Sever Pages的搜索。在你输入这个查询后，你会收到一个列表，表中包括所有其描述中包含搜索表达式的站点。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 多数Internet 引擎允许逻辑查询。在逻辑查询中，你可以包括特殊的运算符如AND、OR和NOT，你使用这些运算符来选择特定的记录。例如，你可以用AND来限制查询结果。如果你执行一个对Active Sever Pages&nbsp; AND&nbsp; SQL的搜索。你将得到其描述中同时包含Active Sever Pages 和SQL的记录。当你需要限制查询结果时，你可以使用AND。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 如果你需要扩展查询的结果，你可以使用逻辑操作符OR。例如，如果你执行一个搜索，搜索所有的其描述中包含Active Sever Pages&nbsp; OR&nbsp; SQL的站点，你收到的列表中将包括所有其描述中同时包含两个表达式或其中任何一个表达式的站点。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 如果你想从搜索结果中排除特定的站点，你可以使用NOT。例如，查询“Active Sever Pages&nbsp; ”AND NOT “SQL”将返回一个列表，列表中的站点包含Active&nbsp; Sever Pages，但不包含SQL。当必须排除特定的记录时，你可以使用NOT。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 用SQL执行的查询与用Internet搜索引擎执行的搜索非常相似。 当你执行一个SQL查询时，通过使用包括逻辑运算符的查询条件，你可以得到一个记录列表。此时查询结果是来自一个或多个表。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SQL查询的句法非常简单。假设有一个名为email_table 的表，包含名字和地址两个字段，要得到Bill Gates 的e_mail地址,你可以使用下面的查询：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SELECT email from email_table WHERE name="Bill Gates"</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 当这个查询执行时，就从名为email_table的表中读取Bill Gates的e_mail 地址。这个简单的语句包括三部分：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; ■&nbsp; SELECT语句的第一部分指名要选取的列。在此例中，只有email列被选取。当执行 时，只显示email列的值 billg@microsoft.com。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; ■&nbsp; SELECTT语句的第二部份指明要从哪个（些）表中查询数据。在此例中，要查询的表名为email_table 。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; ■&nbsp; 最后，SELECT语句的WHERE子句指明要选择满足什么条件的记录。在此例中，查询条件为只有name列的值为Bill Gates 的记录才被选取。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Bill Gates很有可能拥有不止一个email地址。如果表中包含Bill Gates的多个email地址。用上述的SELECT语句可以读取他所有的email地址。SELECT语句从表中取出所有name字段值为Bill Gates 的记录的email 字段的值。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 前面说过，查询可以在查询条件中包含逻辑运算符。假如你想读取Bill Gates 或Clinton总统的所有email地址，你可以使用下面的查询语句：</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SELECT&nbsp; email&nbsp; FROM&nbsp; email_table&nbsp; WHERE&nbsp; name="Bill Gates" OR </P>
<P style="TEXT-INDENT: 2em">&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name="president&nbsp; Clinton"</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 此例中的查询条件比前一个复杂了一点。这个语句从表email_table中选出所有name列为Bill Gates或president Clinton的记录。如果表中含有Bill Gates或president Clinton的多个地址，所有的地址都被读取。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; SELECT语句的结构看起来很直观。如果你请一个朋友从一个表中为你选择一组记录，你也许以非常相似的方式提出你的要求。在SQL SELECT语句中，你“SELECT特定的列FROM一个表WHERE某些列满足一个特定的条件”。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 下一节将介绍怎样执行SQL查询来选取记录。这将帮助你熟悉用SELECT语句从表中取数据的各种不同方法。</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">使用ISQL执行SELECT查询</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 当你安装SQL Sever时，你同时安装了一个叫作ISQL/w的应用程序。ISQL/w允许你执行交互的SQL查询。在把查询包括到你的ASP网页中之前，用ISQL/w对其进行测试是非常有用的。</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 注意：</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在这本书的第一部份，你学习了怎样安装和配置Microsoft SQL Sever 。如果没有安装SQL Sever或者SQL Sever不能运行，请参阅第三章“安装和使用SQL Sever”。</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 选择任务上SQL Sever程序组中的ISQL_w以启动该程序。程序启动时，首先会出现一个对话框，要求输入服务器信息和登录信息（见图10.1）。在Sever框中，输入你的SQL服务器的名字。如果服务器正运行在本地计算机上，服务器名字就是你计算机的名字。在登录信息框中，输入一个登录帐号和密码或选择使用“可信连接”，然后单击Connect按钮。 </P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 图10。1</P></div>]]></description>
	    <author><![CDATA[游戏学院成都校区程序老师]]></author>
	    <comments>http://haoyindangdeblog.blog.163.com/blog/static/70467809200861612669</comments>
    <slash:comments>0</slash:comments>
    <guid isPermaLink="true">http://haoyindangdeblog.blog.163.com/blog/static/70467809200861612669</guid>
    <pubDate>Wed, 16 Jul 2008 13:26:06 +0800</pubDate>
    <dcterms:modified>2008-07-16T13:26:06+08:00</dcterms:modified>
  </item>    
  <item>
  	<title><![CDATA[为什么会出现LNK2005&quot;符号已定义&quot;的链接错误?(下)]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/7046780920086732524133</link>
    <description><![CDATA[<div><P style="TEXT-INDENT: 2em">成都游戏学院 <A href="http://www.cdgamecollege.org/">http://www.cdgamecollege.org</A> 电话：028-85586115 
</P><P style="TEXT-INDENT: 2em">&nbsp;
</P><P><FONT face="Courier New" size=4>cl /c main.c</FONT></P>
<P><FONT face="Courier New" size=4>编译，然后用</FONT></P>
<P><FONT face="Courier New" size=4>link main.obj my.lib</FONT></P>
<P><FONT face="Courier New" size=4>进行链接。这个命令能够成功地生成main.exe而不会产生LNK2005和LNK1169链接错误，你仅仅是得到了一条警告信息:"warning LNK4098: defaultlib "LIBCD" conflicts with use of other libs; use /NODEFAULTLIB:library"。我们根据前文所述的扫描规则来分析一下链接器此时做了些啥(加一个/VERBOSE选项就可以看到详尽的链接过程，但要注意，几乎所有的C编译器都会在符号前加一个下划线后再输出，所以在目标文件和链接输出信息中看到的符号名都比在源程序中见到的多出一个'_'，此点不可不察。)。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 一开始E、U、D都是空集。链接器首先扫描main.obj，把它的默认标准库libc.lib加入到输入文件列表末尾，它自己加入E集合，同时未解析的foo加入U，main加入D。接着扫描my.lib，因为这是个库，所以会拿当前U中的所有符号(当然现在就一个foo)与my.lib中的所有目标模块(当然也只有一个mylib.obj)依次匹配，看是否有模块定义了U中的符号。结果mylib.obj确实定义了foo，于是它加入到E，foo从U转移到D，未解析的printf加入到U，指定的默认标准库libcd.lib也加到输入文件列表末尾(在libc.lib之后)。不断地在my.lib库的各模块上进行迭代以匹配U中的符号，直到U、D都不再变化。很明显，现在就已经到达了这么一个不动点，所以接着扫描下一个输入文件，就是libc.lib。链接器发现libc.lib里的printf.obj里定义有printf，于是printf从U移到D，printf.obj加入到E，它定义的所有符号加入到D，它里头的未解析符号加入到U。如果链接时没有指定/ENTRY(程序入口点选项)，那么链接器默认的入口点就是函数mainCRTStartup(GUI程序的默认入口点则是WinMainCRTStartup)，它在crt0.obj中被定义，所以crt0.obj及它直接或间接引用的模块(比如malloc.obj、free.obj等)都被加入到E中，这些目标模块指定的默认库(只crt0init.obj指定了kernel32.lib)加到输入文件列表末尾，同时更新U和D。不断匹配libc.lib中各模块直至到达不动点，然后处理libcd.lib，但是它里面的所有目标模块都没有定义U中的任何一个符号，所以链接器略过它进入到最后一个输入文件kernel32.lib。事实上，U中已有和将要加入的未解析符号都可以在其中找到定义，那么当处理完kernel32.lib时，U必然为空，于是链接器合并E中的所有模块生成可执行文件。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 上文描述了虽然各目标模块指定了不同版本的缺省标准库但仍然链接成功的例子，接下来你将目睹因为这种不严谨而导致的悲惨失败。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 修改mylib.c成这个样子:</FONT></P>
<P><FONT face="Courier New" color=#a52a2a size=4>#include &lt;crtdbg.h&gt;<CRTDBG.H></CRTDBG.H></FONT></P>
<P><FONT face="Courier New" color=#a52a2a size=4>void foo(void)<BR>{<BR>&nbsp;&nbsp; <FONT color=#006400>// just a test , don't care memory leak</FONT><BR>&nbsp;&nbsp; _malloc_dbg( 1, _NORMAL_BLOCK, __FILE__, __LINE__ );<BR>}</FONT></P>
<P><FONT face="Courier New" size=4>其中_malloc_dbg不是ANSI C的标准库函数，它是VC标准库提供的malloc的调试版，与相关函数配套能帮助开发者抓各种内存错误。使用它一定要定义_DEBUG宏，否则预处理器会把它自动转为malloc。继续用</FONT></P>
<P><FONT face="Courier New" size=4>cl /c /MLd mylib.c<BR>lib /OUT:my.lib mylib.obj</FONT></P>
<P><FONT face="Courier New" size=4>编译打包。当再次用</FONT></P>
<P><FONT face="Courier New" size=4>link main.obj my.lib</FONT></P>
<P><FONT face="Courier New" size=4>进行链接时，我们看到了什么？天哪，一堆的LNK2005加上个贵为"fatal error"的LNK1169垫底，当然还少不了那个LNK4098。链接器是不是疯了？不，你冤枉可怜的链接器了，我拍胸脯保证它可是一直在尽心尽责地照章办事。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 一开始E、U、D为空，链接器扫描main.obj，把libc.lib加到输入文件列表末尾，把main.obj加进E，把foo加进U，把main加进D。接着扫描my.lib，于是mylib.obj加入E，libcd.lib加到输入文件列表末尾，foo从U转移到D，_malloc_dbg加进U。然后扫描libc.lib，这时会发现libc.lib里任何一个目标模块都没有定义_malloc_dbg(它只在调试版的标准库中存在)，所以不会有任何一个模块因为_malloc_dbg而加入E。但因为libc.lib中的crt0.obj定义了默认入口点函数mainCRTStartup，所以crt0.obj及它直接或间接引用的模块(比如malloc.obj、free.obj等)都被加入到E中，这些目标模块指定的默认库(只crt0init.obj指定了kernel32.lib)加到输入文件列表末尾，同时更新U和D。不断匹配libc.lib中各模块直至到达不动点后再处理libcd.lib，发现dbgheap.obj定义了_malloc_dbg，于是dbgheap.obj加入到E，它的未解析符号加入U，它定义的所有其它符号加入D，这时灾难便来了。之前malloc等符号已经在D中(随着libc.lib里的malloc.obj加入E而加入的)，而dbgheap.obj及因它而引入的其它模块又定义了包括malloc在内的许多同名符号，导致了重定义冲突。所以链接器在处理完所有输入文件(是的，即使中途有重定义冲突它也会处理所有的文件以便生成一个完整的冲突列表)后只好报告: 这活儿没法儿干。<BR></FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 现在我们该知道，链接器完全没有责任，责任在我们自己的身上。是我们粗心地把缺省标准库版本不一致的目标文件(main.obj)与程序库(my.lib)链接起来，引发了大灾难。解决办法很简单，要么用/MLd选项来重编译main.c；要么用/ML选项重编译mylib.c；再或者干脆在链接时用/NODEFAULTLIB:XXX选项忽略默认库XXX，但这种方法非常不保险(想想为什么？)，所以不推荐。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 在上述例子中，我们拥有库my.lib的源代码(mylib.c)，所以可以用不同的选项重新编译这些源代码并再次打包。可如果使用的是第三方的库，它并没有提供源代码，那么我们就只有改变自己程序的编译选项来适应这些库了。但是如何知道库中目标模块指定的默认库呢？其实VC提供的一个小工具便可以完成任务，这就是dumpbin.exe。运行下面这个命令</FONT></P>
<P><FONT face="Courier New" size=4>dumpbin /DIRECTIVES my.lib</FONT></P>
<P><FONT face="Courier New" size=4>然后在输出中找那些"Linker Directives"引导的信息，你一定会发现每一处这样的信息都会包含若干个类似"-defaultlib:XXXX"这样的字符串，其中XXXX便代表目标模块指定的缺省库名(注意，如果在编译时指定了/Zl选项，那么目标模块中将不会有defaultlib信息)。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 知道了第三方库指定的默认标准库，再用合适的选项编译我们的应用程序，就可以避免LNK2005和LNK1169链接错误。喜欢IDE的朋友，你一样可以到 "Project属性" -&gt; "C/C++" -&gt; "代码生成(code generation)" -&gt; "运行时库(run-time library)" 项下设置应用程序的默认标准库版本，这与命令行选项的效果是一样的。</FONT></P></div>]]></description>
	    <author><![CDATA[游戏学院成都校区程序老师]]></author>
	    <comments>http://haoyindangdeblog.blog.163.com/blog/static/7046780920086732524133</comments>
    <slash:comments>0</slash:comments>
    <guid isPermaLink="true">http://haoyindangdeblog.blog.163.com/blog/static/7046780920086732524133</guid>
    <pubDate>Mon, 7 Jul 2008 15:25:24 +0800</pubDate>
    <dcterms:modified>2008-07-07T15:25:24+08:00</dcterms:modified>
  </item>    
  <item>
  	<title><![CDATA[为什么会出现LNK2005&quot;符号已定义&quot;的链接错误?(上)]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/7046780920086732423895</link>
    <description><![CDATA[<div><P style="TEXT-INDENT: 2em">成都游戏学院 <A href="http://www.cdgamecollege.org/">http://www.cdgamecollege.org</A> 电话：028-85586115 
</P><P style="TEXT-INDENT: 2em">&nbsp;
</P><P style="TEXT-INDENT: 2em"><FONT face="Courier New" size=4>许多Visual C++的使用者都碰到过LNK2005:symbol already defined和LNK1169:one or more multiply defined symbols found这样的链接错误，而且通常是在使用第三方库时遇到的。对于这个问题，有的朋友可能不知其然，而有的朋友可能知其然却不知其所以然，那么本文就试图为大家彻底解开关于它的种种疑惑。</FONT>
</P><P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 大家都知道，从C/C++源程序到可执行文件要经历两个阶段:(1)编译器将源文件编译成汇编代码，然后由汇编器(assembler)翻译成机器指令(再加上其它相关信息)后输出到一个个目标文件(object file,VC的编译器编译出的目标文件默认的后缀名是.obj)中；(2)链接器(linker)将一个个的目标文件(或许还会有若干程序库)链接在一起生成一个完整的可执行文件。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 编译器编译源文件时会把源文件的全局符号(global symbol)分成强(strong)和弱(weak)两类传给汇编器，而随后汇编器则将强弱信息编码并保存在目标文件的符号表中。那么何谓强弱呢？编译器认为函数与初始化了的全局变量都是强符号，而未初始化的全局变量则成了弱符号。比如有这么个源文件:</FONT></P>
<P><FONT face="Courier New" color=#a52a2a size=4>extern int errorno;<BR>int buf[2] = {1,2};<BR>int *p;</FONT></P>
<P><FONT face="Courier New" color=#a52a2a size=4>int main()<BR>{<BR>&nbsp;&nbsp; return 0;<BR>}</FONT></P>
<P><FONT face="Courier New" size=4>其中main、buf是强符号，p是弱符号，而errorno则非强非弱，因为它只是个外部变量的使用声明。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 有了强弱符号的概念，链接器(Unix平台)就会按如下规则(参考[1]，p549~p550)处理与选择被多次定义的全局符号:</FONT></P>
<P><FONT face="Courier New" size=4><STRONG>规则1</STRONG>: 不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号)；</FONT></P><FONT face="Courier New" size=4>
<P><BR><STRONG>规则2</STRONG>: 如果一个符号在某个目标文件中是强符号，在其它文件中都是弱符号，那么选择强符号；</P>
<P><BR><STRONG>规则3</STRONG>: 如果一个符号在所有目标文件中都是弱符号，那么选择其中任意一个；</P></FONT>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 虽然上述3条针对的是Unix平台的链接器，但据作者试验，至少VC6.0的linker也遵守这些规则。由此可知多个目标文件不能重复定义同名的函数与初始化了的全局变量，否则必然导致LNK2005和LNK1169两种链接错误。可是，有的时候我们并没有在自己的程序中发现这样的重定义现象，却也遇到了此种链接错误，这又是何解？嗯，问题稍微有点儿复杂，容我慢慢道来。<BR></FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 众所周知，ANSI C/C++ 定义了相当多的标准函数，而它们又分布在许多不同的目标文件中，如果直接以目标文件的形式提供给程序员使用的话，就需要他们确切地知道哪个函数存在于哪个目标文件中，并且在链接时显式地指定目标文件名才能成功地生成可执行文件，显然这是一个巨大的负担。所以C语言提供了一种将多个目标文件打包成一个文件的机制，这就是静态程序库(static library)。开发者在链接时只需指定程序库的文件名，链接器就会自动到程序库中寻找那些应用程序确实用到的目标模块，并把(且只把)它们从库中拷贝出来参与构建可执行文件。几乎所有的C/C++开发系统都会把标准函数打包成标准库提供给开发者使用(有不这么做的吗？)。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 程序库为开发者带来了方便，但同时也是某些混乱的根源。我们来看看链接器(Unix平台)是如何解析(resolve)对程序库的引用的(参考[1]，p556)。<BR>&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp; 在符号解析(symbol resolution)阶段，链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们，在此期间它要维护若干个集合:(1)集合E是将被合并到一起组成可执行文件的所有目标文件集合；(2)集合D是所有之前已被加入E的目标文件定义的符号集合；(3)集合U是未解析符号(unresolved symbols，即那些被E中目标文件引用过但在D中还不存在的符号)的集合。一开始，E、D、U都是空的。</FONT></P>
<P><FONT face="Courier New" size=4><STRONG>(1):</STRONG> 对命令行中的每一个输入文件f，链接器确定它是目标文件还是库文件，如果它是目标文件，就把f加入到E，并把f中未解析的符号和已定义的符号分别加入到U、D集合中，然后处理下一个输入文件。</FONT></P>
<P><FONT face="Courier New" size=4><STRONG>(2):</STRONG> 如果f是一个库文件，链接器会尝试把U中的所有未解析符号与f中各目标模块定义的符号进行匹配。如果某个目标模块m定义了一个U中的未解析符号，那么就把m加入到E中，并把m中未解析的符号和已定义的符号分别加入到U、D集合中。不断地对f中的所有目标模块重复这个过程直至到达一个不动点(fixed point)，此时U和D不再变化。而那些未加入到E中的f里的目标模块就被简单地丢弃，链接器继续处理下一输入文件。</FONT></P>
<P><FONT face="Courier New" size=4><STRONG>(3):</STRONG> 当扫描完所有输入文件时如果U非空或者有同名的符号被多次加入D，链接器报告错误信息并退出。否则，它把E中的所有目标文件合并在一起生成可执行文件。</FONT></P>
<P><FONT face="Courier New"></FONT>&nbsp;</P>
<P><FONT face="Courier New">&nbsp;&nbsp;&nbsp; 上述规则针对的是Unix平台链接器，而VC(至少VC6.0)linker则有相当的不同: 它首先依次处理命令行中出现的所有目标文件，然后依照顺序不停地扫描所有的库文件，直至U为空或者某遍(从头到尾依次把所有的库文件扫描完称为一遍)扫描过程中U、D无任何变化时结束扫描，此刻再根据U是否为空以及是否有同名符号重复加入D来决定是出错退出还是生成可执行文件。很明显Unix链接器对输入文件在命令行中出现的顺序十分敏感，而VC的算法则可最大限度地减少文件顺序对链接的影响。作者不清楚Unix下新的开发工具是否已经改进了相应的做法，欢迎有实践经验的朋友补充这方面的信息(补充于2005年10月10日: 经试验，使用gcc 3.2.3的MinGW 3.1.0的链接器表现与参考[1]描述的一致)。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; VC带的编译器是cl.exe，它有这么几个与标准程序库有关的选项: /ML、/MLd、/MT、/MTd、/MD、/MDd。这些选项告诉编译器应用程序想使用什么版本的C标准程序库。/ML(缺省选项)对应单线程静态版的标准程序库(libc.lib)；/MT对应多线程静态版标准库(libcmt.lib)，此时编译器会自动定义_MT宏；/MD对应多线程DLL版(导入库msvcrt.lib，DLL是msvcrt.dll)，编译器自动定义_MT和_DLL两个宏。后面加d的选项都会让编译器自动多定义一个_DEBUG宏，表示要使用对应标准库的调试版，因此/MLd对应调试版单线程静态标准库(libcd.lib)，/MTd对应调试版多线程静态标准库(libcmtd.lib)，/MDd对应调试版多线程DLL标准库(导入库msvcrtd.lib，DLL是msvcrtd.dll)。虽然我们的确在编译时明白无误地告诉了编译器应用程序希望使用什么版本的标准库，可是当编译器干完了活，轮到链接器开工时它又如何得知一个个目标文件到底在思念谁？为了传递相思，我们的编译器就干了点秘密的勾当。在cl编译出的目标文件中会有一个专门的区域(关心这个区域到底在文件中什么地方的朋友可以参考COFF和PE文件格式)存放一些指导链接器如何工作的信息，其中有一项就叫缺省库(default library)，它指定了若干个库文件名，当链接器扫描该目标文件时将按照它们在目标模块中出现的顺序处理这些库名: 如果该库在当前输入文件列表中还不存在，那么便把它加入到输入文件列表末尾，否则略过。说到这里，我们先来做个小实验。写个顶顶简单的程序，然后保存为main.c :</FONT></P>
<P><FONT face="Courier New" color=#a52a2a size=4><FONT color=#006400>/* main.c */<BR></FONT>int main() { return 0; }</FONT></P>
<P><FONT face="Courier New" size=4>用下面这个命令编译main.c(什么？你从不用命令行来编译程序？这个......) :</FONT></P>
<P><FONT face="Courier New" size=4>cl /c main.c</FONT></P>
<P><FONT face="Courier New" size=4>/c是告诉cl只编译源文件，不用链接。因为/ML是缺省选项，所以上述命令也相当于: cl /c /ML main.c 。如果没什么问题的话(要出了问题才是活见鬼！当然除非你的环境变量没有设置好，这时你应该去VC的bin目录下找到vcvars32.bat文件然后运行它。)，当前目录下会出现一个main.obj文件，这就是我们可爱的目标文件。随便用一个文本编辑器打开它(是的，文本编辑器，大胆地去做别害怕)，搜索"defaultlib"字符串，通常你就会看到这样的东西: "-defaultlib:LIBC -defaultlib:OLDNAMES"。啊哈，没错，这就<BR>是保存在目标文件中的缺省库信息。我们的目标文件显然指定了两个缺省库，一个是单线程静态版标准库libc.lib(这与/ML选项相符)；一个是oldnames.lib(它是为了兼容微软以前的C/C++开发系统，基本不用了，为了简化讨论可以忽略它)。另外，如果在源程序中用了</FONT></P>
<P><FONT face="Courier New" size=4><FONT color=#006400>/* xxxx代表实际的库文件名 */<BR></FONT><FONT color=#a52a2a>#pragma comment(lib,"xxxx")</FONT></FONT></P>
<P><FONT face="Courier New" size=4>编译指示命令(compiler directive)指定要链接的库，那么这个信息也会被保存到目标文件的缺省库信息项中，且位于缺省标准库之前。如果有多个这样的命令，那么对应库名在目标文件中出现的顺序与它们在源程序中出现的顺序完全一致(且都在缺省标准库之前)。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; VC的链接器是link.exe，因为main.obj保存了缺省库信息，所以可以用</FONT></P>
<P><FONT face="Courier New" size=4>link main.obj libc.lib</FONT></P>
<P><FONT face="Courier New" size=4>或者</FONT></P>
<P><FONT face="Courier New" size=4>link main.obj</FONT></P>
<P><FONT face="Courier New" size=4>来生成可执行文件main.exe，这两个命令是等价的。但是如果你用</FONT></P>
<P><FONT face="Courier New" size=4>link main.obj libcd.lib</FONT></P>
<P><FONT face="Courier New" size=4>的话，链接器会给出一个警告: "warning LNK4098: defaultlib "LIBC" conflicts with use of other libs; use /NODEFAULTLIB:library"，因为你显式指定的标准库版本与目标文件的缺省值不一致。通常来说，应该保证链接器合并的所有目标文件指定的缺省标准库版本一致，否则编译器一定会给出上面的警告，而LNK2005和LNK1169链接错误则有时会出现有时不会。那么这个有时到底是什么时候？呵呵，别着急，下面的一切正是为喜欢追根究底的你准备的。</FONT></P>
<P><FONT face="Courier New" size=4>&nbsp;&nbsp;&nbsp; 建一个源文件，就叫mylib.c，内容如下:</FONT></P>
<P><FONT face="Courier New" color=#a52a2a size=4><FONT color=#006400>/* mylib.c */</FONT><BR>#include &lt;stdio.h&gt;<STDIO.H></STDIO.H></FONT></P>
<P><FONT face="Courier New" color=#a52a2a size=4>void foo(void)<BR>{<BR>&nbsp;&nbsp; printf("%s","I am from mylib!\n");<BR>}</FONT></P>
<P><FONT face="Courier New" size=4>用</FONT></P>
<P><FONT face="Courier New" size=4>cl /c /MLd mylib.c</FONT></P>
<P><FONT face="Courier New" size=4>命令编译，注意/MLd选项是指定libcd.lib为默认标准库。lib.exe是VC自带的用于将目标文件打包成程序库的命令，所以我们可以用</FONT></P>
<P><FONT face="Courier New" size=4>lib /OUT:my.lib mylib.obj</FONT></P>
<P><FONT face="Courier New" size=4>将mylib.obj打包成库，输出的库文件名是my.lib。接下来把main.c改成:</FONT></P>
<P><FONT face="Courier New" size=4><FONT color=#006400>/* main.c */<BR></FONT><FONT color=#a52a2a>void foo(void);</FONT></FONT></P>
<P><FONT face="Courier New" color=#a52a2a size=4>int main()<BR>{<BR>&nbsp;&nbsp; foo();<BR>&nbsp;&nbsp; return 0;<BR>}</FONT></P>
<P><FONT face="Courier New" size=4>用</FONT></P></div>]]></description>
	    <author><![CDATA[游戏学院成都校区程序老师]]></author>
	    <comments>http://haoyindangdeblog.blog.163.com/blog/static/7046780920086732423895</comments>
    <slash:comments>0</slash:comments>
    <guid isPermaLink="true">http://haoyindangdeblog.blog.163.com/blog/static/7046780920086732423895</guid>
    <pubDate>Mon, 7 Jul 2008 15:24:23 +0800</pubDate>
    <dcterms:modified>2008-07-07T15:24:23+08:00</dcterms:modified>
  </item>    
  <item>
  	<title><![CDATA[C++中的虚函数]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/7046780920086105918744</link>
    <description><![CDATA[<div><P>成都游戏学院 <A href="http://www.cdgamecollege.org">http://www.cdgamecollege.org</A> 电话：028-85586115 </P>
<P>&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">1.简介 </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。假设我们有下面的类层次：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void foo() { cout &lt;&lt; "A::foo() is called" &lt;&lt; endl;}</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class B: public A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void foo() { cout &lt;&lt; "B::foo() is called" &lt;&lt; endl;}</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">那么，在使用的时候，我们可以：</P>
<P style="TEXT-INDENT: 2em">A * a = new B();</P>
<P style="TEXT-INDENT: 2em">a-&gt;foo();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 在这里，a虽然是指向A的指针，但是被调用的函数(foo)却是B的!</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 这个例子是虚函数的一个典型应用，通过这个例子，也许你就对虚函数有了一些概念。它虚就虚在所谓“推迟联编”或者“动态联编”上，一个类函数的调用并不是在编译时刻被确定的，而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数，所以被成为“虚”函数。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 虚函数只能借助于指针或者引用来达到多态的效果，如果是下面这样的代码，则虽然是虚函数，但它不是多态的：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void foo();</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class B: public A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void foo();</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">void bar()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; A a;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; a.foo();&nbsp;&nbsp; // A::foo()被调用</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">1.1 多态 </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在了解了虚函数的意思之后，再考虑什么是多态就很容易了。仍然针对上面的类层次，但是使用的方法变的复杂了一些：</P>
<P style="TEXT-INDENT: 2em">void bar(A * a)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; a-&gt;foo();&nbsp; // 被调用的是A::foo() 还是B::foo()？</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">因为foo()是个虚函数，所以在bar这个函数中，只根据这段代码，无从确定这里被调用的是A::foo()还是B::foo()，但是可以肯定的说：如果a指向的是A类的实例，则A::foo()被调用，如果a指向的是B类的实例，则B::foo()被调用。</P>
<P style="TEXT-INDENT: 2em">这种同一代码可以产生不同效果的特点，被称为“多态”。</P>
<P style="TEXT-INDENT: 2em">1.2 多态有什么用？ </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 多态这么神奇，但是能用来做什么呢？这个命题我难以用一两句话概括，一般的C++教程（或者其它面向对象语言的教程）都用一个画图的例子来展示多态的用途，我就不再重复这个例子了，如果你不知道这个例子，随便找本书应该都有介绍。我试图从一个抽象的角度描述一下，回头再结合那个画图的例子，也许你就更容易理解。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在面向对象的编程中，首先会针对数据进行抽象（确定基类）和继承（确定派生类），构成类层次。这个类层次的使用者在使用它们的时候，如果仍然在需要基类的时候写针对基类的代码，在需要派生类的时候写针对派生类的代码，就等于类层次完全暴露在使用者面前。如果这个类层次有任何的改变（增加了新类），都需要使用者“知道”（针对新类写代码）。这样就增加了类层次与其使用者之间的耦合，有人把这种情况列为程序中的“bad smell”之一。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 多态可以使程序员脱离这种窘境。再回头看看1.1中的例子，bar()作为A-B这个类层次的使用者，它并不知道这个类层次中有多少个类，每个类都叫什么，但是一样可以很好的工作，当有一个C类从A类派生出来后，bar()也不需要“知道”（修改）。这完全归功于多态--编译器针对虚函数产生了可以在运行时刻确定被调用函数的代码。</P>
<P style="TEXT-INDENT: 2em">1.3 如何“动态联编” </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 编译器是如何针对虚函数产生可以再运行时刻确定被调用函数的代码呢？也就是说，虚函数实际上是如何被编译器处理的呢？Lippman在深度探索C++对象模型[1]中的不同章节讲到了几种方式，这里把“标准的”方式简单介绍一下。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 我所说的“标准”方式，也就是所谓的“VTABLE”机制。编译器发现一个类中有被声明为virtual的函数，就会为其搞一个虚函数表，也就是VTABLE。VTABLE实际上是一个函数指针的数组，每个虚函数占用这个数组的一个slot。一个类只有一个VTABLE，不管它有多少个实例。派生类有自己的VTABLE，但是派生类的VTABLE与基类的VTABLE有相同的函数排列顺序，同名的虚函数被放在两个数组的相同位置上。在创建类实例的时候，编译器还会在每个实例的内存布局中增加一个vptr字段，该字段指向本类的VTABLE。通过这些手段，编译器在看到一个虚函数调用的时候，就会将这个调用改写，针对1.1中的例子：</P>
<P style="TEXT-INDENT: 2em">void bar(A * a)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; a-&gt;foo();</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">会被改写为：</P>
<P style="TEXT-INDENT: 2em">void bar(A * a)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; (a-&gt;vptr[1])();</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 因为派生类和基类的foo()函数具有相同的VTABLE索引，而他们的vptr又指向不同的VTABLE，因此通过这样的方法可以在运行时刻决定调用哪个foo()函数。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 虽然实际情况远非这么简单，但是基本原理大致如此。</P>
<P style="TEXT-INDENT: 2em">1.4 overload和override </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 虚函数总是在派生类中被改写，这种改写被称为“override”。我经常混淆“overload”和“override”这两个单词。但是随着各类C++的书越来越多，后来的程序员也许不会再犯我犯过的错误了。但是我打算澄清一下：
</P><UL type=disc>
<LI>override是指派生类重写基类的虚函数，就象我们前面B类中重写了A类中的foo()函数。重写的函数必须有一致的参数表和返回值（C++标准允许返回值不同的情况，这个我会在“语法”部分简单介绍，但是很少编译器支持这个feature）。这个单词好象一直没有什么合适的中文词汇来对应，有人译为“覆盖”，还贴切一些。 </LI>
<LI>overload约定成俗的被翻译为“重载”。是指编写一个与已有函数同名但是参数表不同的函数。例如一个函数即可以接受整型数作为参数，也可以接受浮点数作为参数。 </LI></UL>
<P></P>
<P style="TEXT-INDENT: 2em">2. 虚函数的语法 </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 虚函数的标志是“virtual”关键字。</P>
<P style="TEXT-INDENT: 2em">2.1 使用virtual关键字 </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 考虑下面的类层次：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void foo();</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class B: public A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; void foo();&nbsp;&nbsp;&nbsp; // 没有virtual关键字!</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class C: public B&nbsp; // 从B继承，不是从A继承！</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; void foo();&nbsp;&nbsp;&nbsp; // 也没有virtual关键字！</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 这种情况下，B::foo()是虚函数，C::foo()也同样是虚函数。因此，可以说，基类声明的虚函数，在派生类中也是虚函数，即使不再使用virtual关键字。</P>
<P style="TEXT-INDENT: 2em">2.2 纯虚函数 </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 如下声明表示一个函数为纯虚函数：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void foo()=0;&nbsp;&nbsp; // =0标志一个虚函数为纯虚函数</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 一个函数声明为纯虚后，纯虚函数的意思是：我是一个抽象类！不要把我实例化！纯虚函数用来规范派生类的行为，实际上就是所谓的“接口”。它告诉使用者，我的派生类都会有这个函数。</P>
<P style="TEXT-INDENT: 2em">2.3 虚析构函数 </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 析构函数也可以是虚的，甚至是纯虚的。例如：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual ~A()=0;&nbsp;&nbsp; // 纯虚析构函数</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 当一个类打算被用作其它类的基类时，它的析构函数必须是虚的。考虑下面的例子：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; A() { ptra_ = new char[10];}</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; ~A() { delete[] ptra_;}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 非虚析构函数</P>
<P style="TEXT-INDENT: 2em">private:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; char * ptra_;</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class B: public A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; B() { ptrb_ = new char[20];}</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; ~B() { delete[] ptrb_;}</P>
<P style="TEXT-INDENT: 2em">private:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; char * ptrb_;</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">void foo()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; A * a = new B;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; delete a;</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在这个例子中，程序也许不会象你想象的那样运行，在执行delete a的时候，实际上只有A::~A()被调用了，而B类的析构函数并没有被调用！这是否有点儿可怕？</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 如果将上面A::~A()改为virtual，就可以保证B::~B()也在delete a的时候被调用了。因此基类的析构函数都必须是virtual的。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 纯虚的析构函数并没有什么作用，是虚的就够了。通常只有在希望将一个类变成抽象类（不能实例化的类），而这个类又没有合适的函数可以被纯虚化的时候，可以使用纯虚的析构函数来达到目的。</P>
<P style="TEXT-INDENT: 2em">2.4 虚构造函数？ </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 构造函数不能是虚的。</P>
<P style="TEXT-INDENT: 2em">3. 虚函数使用技巧 3.1 private的虚函数 </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 考虑下面的例子：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; void foo() { bar();}</P>
<P style="TEXT-INDENT: 2em">private:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void bar() { ...}</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class B: public A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">private:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void bar() { ...}</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在这个例子中，虽然bar()在A类中是private的，但是仍然可以出现在派生类中，并仍然可以与public或者protected的虚函数一样产生多态的效果。并不会因为它是private的，就发生A::foo()不能访问B::bar()的情况，也不会发生B::bar()对A::bar()的override不起作用的情况。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 这种写法的语意是：A告诉B，你最好override我的bar()函数，但是你不要管它如何使用，也不要自己调用这个函数。</P>
<P style="TEXT-INDENT: 2em">3.2 构造函数和析构函数中的虚函数调用 </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 一个类的虚函数在它自己的构造函数和析构函数中被调用的时候，它们就变成普通函数了，不“虚”了。也就是说不能在构造函数和析构函数中让自己“多态”。例如：</P>
<P style="TEXT-INDENT: 2em">class A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; A() { foo();}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 在这里，无论如何都是A::foo()被调用！</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; ~A() { foo();}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 同上</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void foo();</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">class B: public A</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void foo();</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">void bar()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; A * a = new B;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; delete a;</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 如果你希望delete a的时候，会导致B::foo()被调用，那么你就错了。同样，在new B的时候，A的构造函数被调用，但是在A的构造函数中，被调用的是A::foo()而不是B::foo()。</P>
<P style="TEXT-INDENT: 2em">3.3 多继承中的虚函数 3.4 什么时候使用虚函数 </P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 在你设计一个基类的时候，如果发现一个函数需要在派生类里有不同的表现，那么它就应该是虚的。从设计的角度讲，出现在基类中的虚函数是接口，出现在派生类中的虚函数是接口的具体实现。通过这样的方法，就可以将对象的行为抽象化。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 以设计模式[2]中Factory Method模式为例，Creator的factoryMethod()就是虚函数，派生类override这个函数后，产生不同的Product类，被产生的Product类被基类的AnOperation()函数使用。基类的AnOperation()函数针对Product类进行操作，当然Product类一定也有多态（虚函数）。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 另外一个例子就是集合操作，假设你有一个以A类为基类的类层次，又用了一个std::vector&lt;A *&gt;来保存这个类层次中不同类的实例指针，那么你一定希望在对这个集合中的类进行操作的时候，不要把每个指针再cast回到它原来的类型（派生类），而是希望对他们进行同样的操作。那么就应该将这个“一样的操作”声明为virtual。</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; 现实中，远不只我举的这两个例子，但是大的原则都是我前面说到的“如果发现一个函数需要在派生类里有不同的表现，那么它就应该是虚的”。这句话也可以反过来说：“如果你发现基类提供了虚函数，那么你最好override它”。</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P></div>]]></description>
	    <author><![CDATA[游戏学院成都校区程序老师]]></author>
	    <comments>http://haoyindangdeblog.blog.163.com/blog/static/7046780920086105918744</comments>
    <slash:comments>0</slash:comments>
    <guid isPermaLink="true">http://haoyindangdeblog.blog.163.com/blog/static/7046780920086105918744</guid>
    <pubDate>Tue, 1 Jul 2008 00:59:18 +0800</pubDate>
    <dcterms:modified>2008-07-01T00:59:18+08:00</dcterms:modified>
  </item>    
  <item>
  	<title><![CDATA[C++多态技术]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/704678092008610583037</link>
    <description><![CDATA[<div><P style="TEXT-INDENT: 2em">成都游戏学院 <A href="http://www.cdgamecollege.org/">http://www.cdgamecollege.org</A> 电话：028-85586115 
</P><P style="TEXT-INDENT: 2em">&nbsp;
</P><P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">导言</P>
<P style="TEXT-INDENT: 2em">多态（polymorphism）一词最初来源于希腊语polumorphos，含义是具有多种形式或形态的情形。在程序设计领域，一个广泛认可的定义是“一种将不同的特殊行为和单个泛化记号相关联的能力”。和纯粹的面向对象程序设计语言不同，C++中的多态有着更广泛的含义。除了常见的通过类继承和虚函数机制生效于运行期的动态多态（dynamic polymorphism）外，模板也允许将不同的特殊行为和单个泛化记号相关联，由于这种关联处理于编译期而非运行期，因此被称为静态多态（static polymorphism）。&nbsp; </P>
<P style="TEXT-INDENT: 2em">事实上，带变量的宏和函数重载机制也允许将不同的特殊行为和单个泛化记号相关联。然而，习惯上我们并不将它们展现出来的行为称为多态（或静态多态）。今天，当我们谈及多态时，如果没有明确所指，默认就是动态多态，而静态多态则是指基于模板的多态。不过，在这篇以C++各种多态技术为主题的文章中，我们首先还是回顾一下C++社群争论已久的另一种“多态”：函数多态（function polymorphism），以及更不常提的“宏多态（macro polymorphism）”。&nbsp;</P>
<P style="TEXT-INDENT: 2em">函数多态</P>
<P style="TEXT-INDENT: 2em">也就是我们常说的函数重载（function overloading）。基于不同的参数列表，同一个函数名字可以指向不同的函数定义：&nbsp; </P>
<P style="TEXT-INDENT: 2em">// overload_poly.cpp</P>
<P style="TEXT-INDENT: 2em">#include &lt;iostream&gt;</P>
<P style="TEXT-INDENT: 2em">#include &lt;string&gt;</P>
<P style="TEXT-INDENT: 2em">// 定义两个重载函数</P>
<P style="TEXT-INDENT: 2em">int my_add(int a, int b)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; return a + b;</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">int my_add(int a, std::string b)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; return a + atoi(b.c_str());</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">int main()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; int i = my_add(1, 2);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 两个整数相加</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; int s = my_add(1, "2");&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 一个整数和一个字符串相加</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "i = " &lt;&lt; i &lt;&lt; "\n";</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "s = " &lt;&lt; s &lt;&lt; "\n";</P>
<P style="TEXT-INDENT: 2em">}&nbsp;</P>
<P style="TEXT-INDENT: 2em">根据参数列表的不同（类型、个数或兼而有之），my_add(1, 2)和my_add(1, "2")被分别编译为对my_add(int, int)和my_add(int, std::string)的调用。实现原理在于编译器根据不同的参数列表对同名函数进行名字重整，而后这些同名函数就变成了彼此不同的函数。比方说，也许某个编译器会将my_add()函数名字分别重整为my_add_int_int()和my_add_int_str()。&nbsp;</P>
<P style="TEXT-INDENT: 2em">宏多态</P>
<P style="TEXT-INDENT: 2em">带变量的宏可以实现一种初级形式的静态多态：&nbsp; </P>
<P style="TEXT-INDENT: 2em">// macro_poly.cpp</P>
<P style="TEXT-INDENT: 2em">#include &lt;iostream&gt;</P>
<P style="TEXT-INDENT: 2em">#include &lt;string&gt;</P>
<P style="TEXT-INDENT: 2em">// 定义泛化记号：宏ADD</P>
<P style="TEXT-INDENT: 2em">#define ADD(A, B) (A) + (B);</P>
<P style="TEXT-INDENT: 2em">int main()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; int i1(1), i2(2);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::string s1("Hello, "), s2("world!");</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; int i = ADD(i1, i2);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 两个整数相加</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::string s = ADD(s1, s2);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 两个字符串“相加”</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "i = " &lt;&lt; i &lt;&lt; "\n";</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "s = " &lt;&lt; s &lt;&lt; "\n";</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">当程序被编译时，表达式ADD(i1, i2)和ADD(s1, s2)分别被替换为两个整数相加和两个字符串相加的具体表达式。整数相加体现为求和，而字符串相加则体现为连接。程序的输出结果符合直觉：&nbsp; </P>
<P style="TEXT-INDENT: 2em">1 + 2 = 3</P>
<P style="TEXT-INDENT: 2em">Hello, + world! = Hello, world! </P>
<P style="TEXT-INDENT: 2em">动态多态</P>
<P style="TEXT-INDENT: 2em">这就是众所周知的的多态。现代面向对象语言对这个概念的定义是一致的。其技术基础在于继承机制和虚函数。例如，我们可以定义一个抽象基类Vehicle和两个派生于Vehicle的具体类Car和Airplane：&nbsp;</P>
<P style="TEXT-INDENT: 2em">// dynamic_poly.h</P>
<P style="TEXT-INDENT: 2em">#include &lt;iostream&gt;</P>
<P style="TEXT-INDENT: 2em">// 公共抽象基类Vehicle</P>
<P style="TEXT-INDENT: 2em">class Vehicle</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void run() const = 0;</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">// 派生于Vehicle的具体类Car</P>
<P style="TEXT-INDENT: 2em">class Car: public Vehicle</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void run() const</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "run a car\n";</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">// 派生于Vehicle的具体类Airplane</P>
<P style="TEXT-INDENT: 2em">class Airplane: public Vehicle</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void run() const</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "run a airplane\n";</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">};&nbsp;</P>
<P style="TEXT-INDENT: 2em">客户程序可以通过指向基类Vehicle的指针（或引用）来操纵具体对象。通过指向基类对象的指针（或引用）来调用一个虚函数，会导致对被指向的具体对象之相应成员的调用： </P>
<P style="TEXT-INDENT: 2em">// dynamic_poly_1.cpp</P>
<P style="TEXT-INDENT: 2em">#include &lt;iostream&gt;</P>
<P style="TEXT-INDENT: 2em">#include &lt;vector&gt;</P>
<P style="TEXT-INDENT: 2em">#include "dynamic_poly.h"</P>
<P style="TEXT-INDENT: 2em">// 通过指针run任何vehicle</P>
<P style="TEXT-INDENT: 2em">void run_vehicle(const Vehicle* vehicle)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; vehicle-&gt;run();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 根据vehicle的具体类型调用对应的run()</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">int main()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Car car;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Airplane airplane;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; run_vehicle(&amp;car);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 调用Car::run()</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; run_vehicle(&amp;airplane);&nbsp;&nbsp;&nbsp; // 调用Airplane::run()</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">此例中，关键的多态接口元素为虚函数run()。由于run_vehicle()的参数为指向基类Vehicle的指针，因而无法在编译期决定使用哪一个版本的run()。在运行期，为了分派函数调用，虚函数被调用的那个对象的完整动态类型将被访问。这样一来，对一个Car对象调用run_vehicle()，实际上将调用Car::run()，而对于Airplane对象而言将调用Airplane::run()。 </P>
<P style="TEXT-INDENT: 2em">或许动态多态最吸引人之处在于处理异质对象集合的能力：&nbsp; </P>
<P style="TEXT-INDENT: 2em">// dynamic_poly_2.cpp</P>
<P style="TEXT-INDENT: 2em">#include &lt;iostream&gt;</P>
<P style="TEXT-INDENT: 2em">#include &lt;vector&gt;</P>
<P style="TEXT-INDENT: 2em">#include "dynamic_poly.h"</P>
<P style="TEXT-INDENT: 2em">// run异质vehicles集合</P>
<P style="TEXT-INDENT: 2em">void run_vehicles(const std::vector&lt;Vehicle*&gt;&amp; vehicles)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; for (unsigned int i = 0; i &lt; vehicles.size(); ++i)</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vehicles[i]-&gt;run();&nbsp;&nbsp;&nbsp;&nbsp; // 根据具体vehicle的类型调用对应的run()</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">int main()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Car car;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Airplane airplane;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::vector&lt;Vehicle*&gt; v;&nbsp;&nbsp;&nbsp; // 异质vehicles集合</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; v.push_back(&amp;car);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; v.push_back(&amp;airplane);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; run_vehicles(v);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // run不同类型的vehicles</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">在run_vehicles()中，vehicles[i]-&gt;run()依据正被迭代的元素的类型而调用不同的成员函数。这从一个侧面体现了面向对象编程风格的优雅。 </P>
<P style="TEXT-INDENT: 2em">静态多态</P>
<P style="TEXT-INDENT: 2em">如果说动态多态是通过虚函数来表达共同接口的话，那么静态多态则是通过“彼此单独定义但支持共同操作的具体类”来表达共同性，换句话说，必须存在必需的同名成员函数。&nbsp; </P>
<P style="TEXT-INDENT: 2em">我们可以采用静态多态机制重写上一节的例子。这一次，我们不再定义vehicles类层次结构，相反，我们编写彼此无关的具体类Car和Airplane（它们都有一个run()成员函数）：&nbsp; </P>
<P style="TEXT-INDENT: 2em">// static_poly.h</P>
<P style="TEXT-INDENT: 2em">#include &lt;iostream&gt;</P>
<P style="TEXT-INDENT: 2em">//具体类Car</P>
<P style="TEXT-INDENT: 2em">class Car</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; void run() const</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "run a car\n";</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">//具体类Airplane</P>
<P style="TEXT-INDENT: 2em">class Airplane</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; void run() const</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "run a airplane\n";</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">run_vehicle()应用程序被改写如下：</P>
<P style="TEXT-INDENT: 2em">&nbsp; </P>
<P style="TEXT-INDENT: 2em">// static_poly_1.cpp</P>
<P style="TEXT-INDENT: 2em">#include &lt;iostream&gt;</P>
<P style="TEXT-INDENT: 2em">#include &lt;vector&gt;</P>
<P style="TEXT-INDENT: 2em">#include "static_poly.h"</P>
<P style="TEXT-INDENT: 2em">// 通过引用而run任何vehicle</P>
<P style="TEXT-INDENT: 2em">template &lt;typename Vehicle&gt;</P>
<P style="TEXT-INDENT: 2em">void run_vehicle(const Vehicle&amp; vehicle)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; vehicle.run();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 根据vehicle的具体类型调用对应的run()</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">int main()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Car car;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Airplane airplane;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; run_vehicle(car);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 调用Car::run()</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; run_vehicle(airplane);&nbsp;&nbsp;&nbsp; // 调用Airplane::run()</P>
<P style="TEXT-INDENT: 2em">}&nbsp;</P>
<P style="TEXT-INDENT: 2em">现在Vehicle用作模板参数而非公共基类对象（事实上，这里的Vehicle只是一个符合直觉的记号而已，此外别无它意）。经过编译器处理后，我们最终会得到run_vehicle&lt;Car&gt;()和 run_vehicle&lt;Airplane&gt;()两个不同的函数。这和动态多态不同，动态多态凭借虚函数分派机制在运行期只有一个run_vehicle()函数。&nbsp; </P>
<P style="TEXT-INDENT: 2em">我们无法再透明地处理异质对象集合了，因为所有类型都必须在编译期予以决定。不过，为不同的vehicles引入不同的集合只是举手之劳。由于无需再将集合元素局限于指针或引用，我们现在可以从执行性能和类型安全两方面获得好处：&nbsp;</P>
<P style="TEXT-INDENT: 2em">// static_poly_2.cpp</P>
<P style="TEXT-INDENT: 2em">#include &lt;iostream&gt;</P>
<P style="TEXT-INDENT: 2em">#include &lt;vector&gt;</P>
<P style="TEXT-INDENT: 2em">#include "static_poly.h"</P>
<P style="TEXT-INDENT: 2em">// run同质vehicles集合</P>
<P style="TEXT-INDENT: 2em">template &lt;typename Vehicle&gt;</P>
<P style="TEXT-INDENT: 2em">void run_vehicles(const std::vector&lt;Vehicle&gt;&amp; vehicles)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; for (unsigned int i = 0; i &lt; vehicles.size(); ++i)&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vehicles[i].run(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 根据vehicle的具体类型调用相应的run()</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">int main()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Car car1, car2;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Airplane airplane1, airplane2;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::vector&lt;Car&gt; vc;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 同质cars集合</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; vc.push_back(car1);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; vc.push_back(car2);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; //vc.push_back(airplane1);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 错误：类型不匹配</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; run_vehicles(vc);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // run cars</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::vector&lt;Airplane&gt; vs;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 同质airplanes集合</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; vs.push_back(airplane1);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; vs.push_back(airplane2);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; //vs.push_back(car1);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 错误：类型不匹配</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; run_vehicles(vs);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // run airplanes</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">两种多态机制的结合使用&nbsp; </P>
<P style="TEXT-INDENT: 2em">在一些高级C++应用中，我们可能需要结合使用动态多态和静态多态两种机制，以期达到对象操作的优雅、安全和高效。例如，我们既希望一致而优雅地处理vehicles的run问题，又希望“安全而高效”地完成给飞行器（飞机、飞艇等）进行“空中加油”这样的高难度动作。为此，我们首先将上面的vehicles类层次结构改写如下：&nbsp; </P>
<P style="TEXT-INDENT: 2em">// dscombine_poly.h</P>
<P style="TEXT-INDENT: 2em">#include &lt;iostream&gt;</P>
<P style="TEXT-INDENT: 2em">#include &lt;vector&gt;</P>
<P style="TEXT-INDENT: 2em">// 公共抽象基类Vehicle</P>
<P style="TEXT-INDENT: 2em">class Vehicle</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void run() const = 0;</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">// 派生于Vehicle的具体类Car</P>
<P style="TEXT-INDENT: 2em">class Car: public Vehicle</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void run() const</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "run a car\n";</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">// 派生于Vehicle的具体类Airplane</P>
<P style="TEXT-INDENT: 2em">class Airplane: public Vehicle</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void run() const</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "run a airplane\n";</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; void add_oil() const</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "add oil to airplane\n";</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">// 派生于Vehicle的具体类Airship</P>
<P style="TEXT-INDENT: 2em">class Airship: public Vehicle</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">public:</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; virtual void run() const</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "run a airship\n";</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; void add_oil() const</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "add oil to airship\n";</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">};</P>
<P style="TEXT-INDENT: 2em">我们理想中的应用程序可以编写如下：</P>
<P style="TEXT-INDENT: 2em">&nbsp; </P>
<P style="TEXT-INDENT: 2em">// dscombine_poly.cpp</P>
<P style="TEXT-INDENT: 2em">#include &lt;iostream&gt;</P>
<P style="TEXT-INDENT: 2em">#include &lt;vector&gt;</P>
<P style="TEXT-INDENT: 2em">#include "dscombine_poly.h"</P>
<P style="TEXT-INDENT: 2em">// run异质vehicles集合</P>
<P style="TEXT-INDENT: 2em">void run_vehicles(const std::vector&lt;Vehicle*&gt;&amp; vehicles)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; for (unsigned int i = 0; i &lt; vehicles.size(); ++i)</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vehicles[i]-&gt;run();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 根据具体的vehicle类型调用对应的run()</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">// 为某种特定的aircrafts同质对象集合进行“空中加油”</P>
<P style="TEXT-INDENT: 2em">template &lt;typename Aircraft&gt;</P>
<P style="TEXT-INDENT: 2em">void add_oil_to_aircrafts_in_the_sky(const std::vector&lt;Aircraft&gt;&amp; aircrafts)</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; for (unsigned int i = 0; i &lt; aircrafts.size(); ++i)</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; {</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; aircrafts[i].add_oil();</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; }</P>
<P style="TEXT-INDENT: 2em">}</P>
<P style="TEXT-INDENT: 2em">int main()</P>
<P style="TEXT-INDENT: 2em">{</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Car car1, car2;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Airplane airplane1, airplane2;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; Airship airship1, airship2;</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::vector&lt;Vehicle*&gt; v;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 异质vehicles集合</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; v.push_back(&amp;car1);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; v.push_back(&amp;airplane1);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; v.push_back(&amp;airship1);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; run_vehicles(v);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // run不同种类的vehicles</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::vector&lt;Airplane&gt; vp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 同质airplanes集合</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; vp.push_back(airplane1);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; vp.push_back(airplane2);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; add_oil_to_aircrafts_in_the_sky(vp); &nbsp;&nbsp; // 为airplanes进行“空中加油”</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; std::vector&lt;Airship&gt; vs;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 同质airships集合</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; vs.push_back(airship1);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; vs.push_back(airship2);</P>
<P style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp; add_oil_to_aircrafts_in_the_sky(vs);&nbsp;&nbsp;&nbsp; // 为airships进行“空中加油”</P>
<P style="TEXT-INDENT: 2em">}&nbsp;</P>
<P style="TEXT-INDENT: 2em">我们保留了类层次结构，目的是为了能够利用run_vehicles()一致而优雅地处理异质对象集合vehicles的run问题。同时，利用函数模板add_oil_to_aircrafts_in_the_sky&lt;Aircraft&gt;()，我们仍然可以处理特定种类的vehicles — aircrafts（包括airplanes和airships）的“空中加油”问题。其中，我们避开使用指针，从而在执行性能和类型安全两方面达到了预期目标。</P>
<P style="TEXT-INDENT: 2em">&nbsp; </P>
<P style="TEXT-INDENT: 2em">结语&nbsp;</P>
<P style="TEXT-INDENT: 2em">长期以来，C++社群对于多态的内涵和外延一直争论不休。在comp.object这样的网络论坛上，此类话题争论至今仍随处可见。曾经有人将动态多态（dynamic polymorphism）称为inclusion polymorphism，而将静态多态（static polymorphism）称为parametric polymorphism或parameterized polymorphism。&nbsp; </P>
<P style="TEXT-INDENT: 2em">我注意到2003年斯坦福大学公开的一份<I>C++ and Object-Oriented Programming</I>教案中明确提到了函数多态概念：Function overloading is also referred to as function polymorphism as it involves one function having many forms。文后的“参考文献”单元给出了这个网页链接。</P>
<P style="TEXT-INDENT: 2em">&nbsp; </P>
<P style="TEXT-INDENT: 2em">可能你是第一次看到宏多态（macro polymorphism）这个术语。不必讶异 — 也许我就是造出这个术语的“第一人”。显然，带变量的宏（或类似于函数的宏或伪函数宏）的替换机制除了免除小型函数的调用开销之外，也表现出了类似的多态性。在我们上面的例子中，字符串相加所表现出来的符合直觉的连接操作，事实上是由底部运算符重载机制（operator overloading）支持的。值得指出的是，C++社群中有人将运算符重载所表现出来的多态称为ad hoc polymorphism。&nbsp; </P>
<P style="TEXT-INDENT: 2em">David Vandevoorde和Nicolai M. Josuttis在他们的著作<I>C++ Templates: The Complete Guide</I>一书中系统地阐述了静态多态和动态多态技术。因为认为“和其他语言机制关系不大”，这本书没有提及“宏多态”（以及“函数多态”）。（需要说明的是，笔者本人是这本书的繁体中文版译者之一，本文正是基于这本书的第14章<I>The Polymorphic Power of Templates</I>编写而成）</P>
<P style="TEXT-INDENT: 2em">动态多态只需要一个多态函数，生成的可执行代码尺寸较小，静态多态必须针对不同的类型产生不同的模板实体，尺寸会大一些，但生成的代码会更快，因为无需通过指针进行间接操作。静态多态比动态多态更加类型安全，因为全部绑定都被检查于编译期。正如前面例子所示，你不可将一个错误的类型的对象插入到从一个模板实例化而来的容器之中。此外，正如你已经看到的那样，动态多态可以优雅地处理异质对象集合，而静态多态可以用来实现安全、高效的同质对象集合操作。&nbsp;</P>
<P style="TEXT-INDENT: 2em">静态多态为C++带来了泛型编程（generic programming）的概念。泛型编程可以认为是“组件功能基于框架整体而设计”的模板编程。STL就是泛型编程的一个典范。STL是一个框架，它提供了大量的算法、容器和迭代器，全部以模板技术实现。从理论上讲，STL的功能当然可以使用动态多态来实现，不过这样一来其性能必将大打折扣。&nbsp; </P>
<P style="TEXT-INDENT: 2em">静态多态还为C++社群带来了泛型模式（generic patterns）的概念。理论上，每一个需要通过虚函数和类继承而支持的设计模式都可以利用基于模板的静态多态技术（甚至可以结合使用动态多态和静态多态两种技术）而实现。正如你看到的那样，Andrei Alexandrescu的天才作品<I>Modern C++ Design: Generic Programming and Design Patterns Applied</I>（Addison-Wesley）和Loki程序库已经走在了我们的前面。</P></div>]]></description>
	    <author><![CDATA[游戏学院成都校区程序老师]]></author>
	    <comments>http://haoyindangdeblog.blog.163.com/blog/static/704678092008610583037</comments>
    <slash:comments>0</slash:comments>
    <guid isPermaLink="true">http://haoyindangdeblog.blog.163.com/blog/static/704678092008610583037</guid>
    <pubDate>Tue, 1 Jul 2008 00:58:30 +0800</pubDate>
    <dcterms:modified>2008-07-01T00:58:30+08:00</dcterms:modified>
  </item>    
  <item>
  	<title><![CDATA[C/C++ 数据类型]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/70467809200852325556495</link>
    <description><![CDATA[<div><P>成都游戏学院 <A href="http://www.cdgamecollege.org">http://www.cdgamecollege.org</A> 电话：028-85586115 </P>
<P>&nbsp;</P>
<P style="TEXT-INDENT: 2em">
<TABLE width="100%" bgColor=#eeeeff>
<TBODY>
<TR>
<TD><A href="mk:@MSITStore:D:Game%20Colleage资料C&amp;C++语言参考.chm::/cppreference.com/index.html">cppreference.com</A> -&gt; C/C++ 数据类型 </TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em">C/C++ 数据类型</P>
<P style="TEXT-INDENT: 2em">C语言包含5个基本数据类型: void, integer, float, double, 和 char. </P>
<P style="TEXT-INDENT: 2em">
<TABLE>
<TBODY>
<TR>
<TH>类型</TH>
<TH>描述</TH></TR>
<TR bgColor=#eeeeff>
<TD>void</TD>
<TD>空类型</TD></TR>
<TR>
<TD>int</TD>
<TD>整型</TD></TR>
<TR bgColor=#eeeeff>
<TD>float</TD>
<TD>浮点类型</TD></TR>
<TR>
<TD>double</TD>
<TD>双精度浮点类型</TD></TR>
<TR bgColor=#eeeeff>
<TD>char</TD>
<TD>字符类型</TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em">C++ 定义了另外两个基本数据类型: bool 和 wchar_t. </P>
<P style="TEXT-INDENT: 2em">
<TABLE>
<TBODY>
<TR>
<TH>类型</TH>
<TH>描述</TH></TR>
<TR bgColor=#eeeeff>
<TD>bool</TD>
<TD>布尔类型, 值为true 或 false</TD></TR>
<TR>
<TD>wchar_t</TD>
<TD>宽字符类型</TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em">类型修饰符</P>
<P style="TEXT-INDENT: 2em">一些基本数据类型能够被 signed, unsigned, short, 和 long 修饰. 当类型修饰符单独使用的时候, 默认的类型是 int. 下表是所有可能出现的数据类型: 
<TABLE>
<TBODY>
<TR bgColor=#eeeeff>
<TD>bool</TD></TR>
<TR>
<TD>char</TD></TR>
<TR bgColor=#eeeeff>
<TD>unsigned char</TD></TR>
<TR>
<TD>signed char</TD></TR>
<TR bgColor=#eeeeff>
<TD>int</TD></TR>
<TR>
<TD>unsigned int</TD></TR>
<TR bgColor=#eeeeff>
<TD>signed int</TD></TR>
<TR>
<TD>short int</TD></TR>
<TR bgColor=#eeeeff>
<TD>unsigned short int</TD></TR>
<TR>
<TD>signed short int</TD></TR>
<TR bgColor=#eeeeff>
<TD>long int</TD></TR>
<TR>
<TD>signed long int</TD></TR>
<TR bgColor=#eeeeff>
<TD>unsigned long int</TD></TR>
<TR>
<TD>float</TD></TR>
<TR bgColor=#eeeeff>
<TD>double</TD></TR>
<TR>
<TD>long double</TD></TR>
<TR bgColor=#eeeeff>
<TD>wchar_t</TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em">类型大小和表示范围</P>
<P style="TEXT-INDENT: 2em">基本数据类型的大小以及能够表示的数据范围是与编译器和硬件平台有关的. "cfloat" (或者 "float.h") 头文件往往定义了基本数据类型能够表示的数据的最大值和最小值. 你也可以使用 <A href="mk:@MSITStore:D:Game%20Colleage资料C&amp;C++语言参考.chm::/cppreference.com/keywords_details.html#sizeof">sizeof</A> 来获得类型的大小(字节数) . 然而, 很多平台使用了一些数据类型的标准大小，如. int 和 float 通常占用 32位, char 占用 8位, double 通常占用64位. bools 通常以 8位 来实现. </P></div>]]></description>
	    <author><![CDATA[游戏学院成都校区程序老师]]></author>
	    <comments>http://haoyindangdeblog.blog.163.com/blog/static/70467809200852325556495</comments>
    <slash:comments>0</slash:comments>
    <guid isPermaLink="true">http://haoyindangdeblog.blog.163.com/blog/static/70467809200852325556495</guid>
    <pubDate>Mon, 23 Jun 2008 14:55:56 +0800</pubDate>
    <dcterms:modified>2008-06-23T14:55:56+08:00</dcterms:modified>
  </item>    
  <item>
  	<title><![CDATA[ASCII 码表]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/70467809200852325518526</link>
    <description><![CDATA[<div><P style="TEXT-INDENT: 2em">
<TABLE width="100%" bgColor=#eeeeff>
<TBODY>
<TR>
<TD>
<P>成都游戏学院 <A href="http://www.cdgamecollege.org">http://www.cdgamecollege.org</A> 电话：028-85586115 </P>
<P>&nbsp;</P>
<P><A href="mk:@MSITStore:D:Game%20Colleage资料C&amp;C++语言参考.chm::/cppreference.com/index.html">cppreference.com</A> -&gt;&nbsp; </P></TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em">ASCII 码表</P>
<P style="TEXT-INDENT: 2em">下面的 ASCII 码表包含数值在0-127之间的字符的十进制、八进制以及十六进制表示.
<TABLE align=center>
<TBODY>
<TR>
<TH>十进制</TH>
<TH>八进制</TH>
<TH>十六进制</TH>
<TH>字符 </TH>
<TH>描述</TH></TR>
<TR bgColor=#eeeeff>
<TD>0</TD>
<TD>0</TD>
<TD>00</TD>
<TD>NUL</TD>
<TD></TD></TR>
<TR>
<TD>1</TD>
<TD>1</TD>
<TD>01</TD>
<TD>SOH</TD>
<TD>start of header</TD></TR>
<TR bgColor=#eeeeff>
<TD>2</TD>
<TD>2</TD>
<TD>02</TD>
<TD>STX</TD>
<TD>start of text</TD></TR>
<TR>
<TD>3</TD>
<TD>3</TD>
<TD>03</TD>
<TD>ETX</TD>
<TD>end of text</TD></TR>
<TR bgColor=#eeeeff>
<TD>4</TD>
<TD>4</TD>
<TD>04</TD>
<TD>EOT</TD>
<TD>end of transmission</TD></TR>
<TR>
<TD>5</TD>
<TD>5</TD>
<TD>05</TD>
<TD>ENQ</TD>
<TD>enquiry</TD></TR>
<TR bgColor=#eeeeff>
<TD>6</TD>
<TD>6</TD>
<TD>06</TD>
<TD>ACK</TD>
<TD>acknowledge</TD></TR>
<TR>
<TD>7</TD>
<TD>7</TD>
<TD>07</TD>
<TD>BEL</TD>
<TD>bell</TD></TR>
<TR bgColor=#eeeeff>
<TD>8</TD>
<TD>10</TD>
<TD>08</TD>
<TD>BS</TD>
<TD>backspace</TD></TR>
<TR>
<TD>9</TD>
<TD>11</TD>
<TD>09</TD>
<TD>HT</TD>
<TD>horizontal tab</TD></TR>
<TR bgColor=#eeeeff>
<TD>10</TD>
<TD>12</TD>
<TD>0A</TD>
<TD>LF</TD>
<TD>line feed</TD></TR>
<TR>
<TD>11</TD>
<TD>13</TD>
<TD>0B</TD>
<TD>VT</TD>
<TD>vertical tab</TD></TR>
<TR bgColor=#eeeeff>
<TD>12</TD>
<TD>14</TD>
<TD>0C</TD>
<TD>FF</TD>
<TD>form feed</TD></TR>
<TR>
<TD>13</TD>
<TD>15</TD>
<TD>0D</TD>
<TD>CR</TD>
<TD>carriage return</TD></TR>
<TR bgColor=#eeeeff>
<TD>14</TD>
<TD>16</TD>
<TD>0E</TD>
<TD>SO</TD>
<TD>shift out</TD></TR>
<TR>
<TD>15</TD>
<TD>17</TD>
<TD>0F</TD>
<TD>SI</TD>
<TD>shift in</TD></TR>
<TR bgColor=#eeeeff>
<TD>16</TD>
<TD>20</TD>
<TD>10</TD>
<TD>DLE</TD>
<TD>data link escape</TD></TR>
<TR>
<TD>17</TD>
<TD>21</TD>
<TD>11</TD>
<TD>DC1</TD>
<TD>no assignment, but usually XON</TD></TR>
<TR bgColor=#eeeeff>
<TD>18</TD>
<TD>22</TD>
<TD>12</TD>
<TD>DC2</TD>
<TD></TD></TR>
<TR>
<TD>19</TD>
<TD>23</TD>
<TD>13</TD>
<TD>DC3</TD>
<TD>no assignment, but usually XOFF</TD></TR>
<TR bgColor=#eeeeff>
<TD>20</TD>
<TD>24</TD>
<TD>14</TD>
<TD>DC4</TD>
<TD></TD></TR>
<TR>
<TD>21</TD>
<TD>25</TD>
<TD>15</TD>
<TD>NAK</TD>
<TD>negative acknowledge</TD></TR>
<TR bgColor=#eeeeff>
<TD>22</TD>
<TD>26</TD>
<TD>16</TD>
<TD>SYN</TD>
<TD>synchronous idle</TD></TR>
<TR>
<TD>23</TD>
<TD>27</TD>
<TD>17</TD>
<TD>ETB</TD>
<TD>end of transmission block</TD></TR>
<TR bgColor=#eeeeff>
<TD>24</TD>
<TD>30</TD>
<TD>18</TD>
<TD>CAN</TD>
<TD>cancel</TD></TR>
<TR>
<TD>25</TD>
<TD>31</TD>
<TD>19</TD>
<TD>EM</TD>
<TD>end of medium</TD></TR>
<TR bgColor=#eeeeff>
<TD>26</TD>
<TD>32</TD>
<TD>1A</TD>
<TD>SUB</TD>
<TD>substitute</TD></TR>
<TR>
<TD>27</TD>
<TD>33</TD>
<TD>1B</TD>
<TD>ESC</TD>
<TD>escape</TD></TR>
<TR bgColor=#eeeeff>
<TD>28</TD>
<TD>34</TD>
<TD>1C</TD>
<TD>FS</TD>
<TD>file seperator</TD></TR>
<TR>
<TD>29</TD>
<TD>35</TD>
<TD>1D</TD>
<TD>GS</TD>
<TD>group seperator</TD></TR>
<TR bgColor=#eeeeff>
<TD>30</TD>
<TD>36</TD>
<TD>1E</TD>
<TD>RS</TD>
<TD>record seperator</TD></TR>
<TR>
<TD>31</TD>
<TD>37</TD>
<TD>1F</TD>
<TD>US</TD>
<TD>unit seperator</TD></TR>
<TR bgColor=#eeeeff>
<TD>32</TD>
<TD>40</TD>
<TD>20</TD>
<TD>SPC</TD>
<TD>space</TD></TR>
<TR>
<TD>33</TD>
<TD>41</TD>
<TD>21</TD>
<TD>!</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>34</TD>
<TD>42</TD>
<TD>22</TD>
<TD>"</TD>
<TD></TD></TR>
<TR>
<TD>35</TD>
<TD>43</TD>
<TD>23</TD>
<TD>#</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>36</TD>
<TD>44</TD>
<TD>24</TD>
<TD>$</TD>
<TD></TD></TR>
<TR>
<TD>37</TD>
<TD>45</TD>
<TD>25</TD>
<TD>%</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>38</TD>
<TD>46</TD>
<TD>26</TD>
<TD>&amp;</TD>
<TD></TD></TR>
<TR>
<TD>39</TD>
<TD>47</TD>
<TD>27</TD>
<TD>'</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>40</TD>
<TD>50</TD>
<TD>28</TD>
<TD>(</TD>
<TD></TD></TR>
<TR>
<TD>41</TD>
<TD>51</TD>
<TD>29</TD>
<TD>)</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>42</TD>
<TD>52</TD>
<TD>2A</TD>
<TD>*</TD>
<TD></TD></TR>
<TR>
<TD>43</TD>
<TD>53</TD>
<TD>2B</TD>
<TD>+</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>44</TD>
<TD>54</TD>
<TD>2C</TD>
<TD>,</TD>
<TD></TD></TR>
<TR>
<TD>45</TD>
<TD>55</TD>
<TD>2D</TD>
<TD>-</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>46</TD>
<TD>56</TD>
<TD>2E</TD>
<TD>.</TD>
<TD></TD></TR>
<TR>
<TD>47</TD>
<TD>57</TD>
<TD>2F</TD>
<TD>/</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>48</TD>
<TD>60</TD>
<TD>30</TD>
<TD>0</TD>
<TD></TD></TR>
<TR>
<TD>49</TD>
<TD>61</TD>
<TD>31</TD>
<TD>1</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>50</TD>
<TD>62</TD>
<TD>32</TD>
<TD>2</TD>
<TD></TD></TR>
<TR>
<TD>51</TD>
<TD>63</TD>
<TD>33</TD>
<TD>3</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>52</TD>
<TD>64</TD>
<TD>34</TD>
<TD>4</TD>
<TD></TD></TR>
<TR>
<TD>53</TD>
<TD>65</TD>
<TD>35</TD>
<TD>5</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>54</TD>
<TD>66</TD>
<TD>36</TD>
<TD>6</TD>
<TD></TD></TR>
<TR>
<TD>55</TD>
<TD>67</TD>
<TD>37</TD>
<TD>7</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>56</TD>
<TD>70</TD>
<TD>38</TD>
<TD>8</TD>
<TD></TD></TR>
<TR>
<TD>57</TD>
<TD>71</TD>
<TD>39</TD>
<TD>9</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>58</TD>
<TD>72</TD>
<TD>3A</TD>
<TD>:</TD>
<TD></TD></TR>
<TR>
<TD>59</TD>
<TD>73</TD>
<TD>3B</TD>
<TD>;</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>60</TD>
<TD>74</TD>
<TD>3C</TD>
<TD>&lt;</TD>
<TD></TD></TR>
<TR>
<TD>61</TD>
<TD>75</TD>
<TD>3D</TD>
<TD>=</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>62</TD>
<TD>76</TD>
<TD>3E</TD>
<TD>&gt;</TD>
<TD></TD></TR>
<TR>
<TD>63</TD>
<TD>77</TD>
<TD>3F</TD>
<TD>?</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>64</TD>
<TD>100</TD>
<TD>40</TD>
<TD>@</TD>
<TD></TD></TR>
<TR>
<TD>65</TD>
<TD>101</TD>
<TD>41</TD>
<TD>A</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>66</TD>
<TD>102</TD>
<TD>42</TD>
<TD>B</TD>
<TD></TD></TR>
<TR>
<TD>67</TD>
<TD>103</TD>
<TD>43</TD>
<TD>C</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>68</TD>
<TD>104</TD>
<TD>44</TD>
<TD>D</TD>
<TD></TD></TR>
<TR>
<TD>69</TD>
<TD>105</TD>
<TD>45</TD>
<TD>E</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>70</TD>
<TD>106</TD>
<TD>46</TD>
<TD>F</TD>
<TD></TD></TR>
<TR>
<TD>71</TD>
<TD>107</TD>
<TD>47</TD>
<TD>G</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>72</TD>
<TD>110</TD>
<TD>48</TD>
<TD>H</TD>
<TD></TD></TR>
<TR>
<TD>73</TD>
<TD>111</TD>
<TD>49</TD>
<TD>I</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>74</TD>
<TD>112</TD>
<TD>4A</TD>
<TD>J</TD>
<TD></TD></TR>
<TR>
<TD>75</TD>
<TD>113</TD>
<TD>4B</TD>
<TD>K</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>76</TD>
<TD>114</TD>
<TD>4C</TD>
<TD>L</TD>
<TD></TD></TR>
<TR>
<TD>77</TD>
<TD>115</TD>
<TD>4D</TD>
<TD>M</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>78</TD>
<TD>116</TD>
<TD>4E</TD>
<TD>N</TD>
<TD></TD></TR>
<TR>
<TD>79</TD>
<TD>117</TD>
<TD>4F</TD>
<TD>O</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>80</TD>
<TD>120</TD>
<TD>50</TD>
<TD>P</TD>
<TD></TD></TR>
<TR>
<TD>81</TD>
<TD>121</TD>
<TD>51</TD>
<TD>Q</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>82</TD>
<TD>122</TD>
<TD>52</TD>
<TD>R</TD>
<TD></TD></TR>
<TR>
<TD>83</TD>
<TD>123</TD>
<TD>53</TD>
<TD>S</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>84</TD>
<TD>124</TD>
<TD>54</TD>
<TD>T</TD>
<TD></TD></TR>
<TR>
<TD>85</TD>
<TD>125</TD>
<TD>55</TD>
<TD>U</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>86</TD>
<TD>126</TD>
<TD>56</TD>
<TD>V</TD>
<TD></TD></TR>
<TR>
<TD>87</TD>
<TD>127</TD>
<TD>57</TD>
<TD>W</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>88</TD>
<TD>130</TD>
<TD>58</TD>
<TD>X</TD>
<TD></TD></TR>
<TR>
<TD>89</TD>
<TD>131</TD>
<TD>59</TD>
<TD>Y</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>90</TD>
<TD>132</TD>
<TD>5A</TD>
<TD>Z</TD>
<TD></TD></TR>
<TR>
<TD>91</TD>
<TD>133</TD>
<TD>5B</TD>
<TD>[</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>92</TD>
<TD>134</TD>
<TD>5C</TD>
<TD>\</TD>
<TD></TD></TR>
<TR>
<TD>93</TD>
<TD>135</TD>
<TD>5D</TD>
<TD>]</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>94</TD>
<TD>136</TD>
<TD>5E</TD>
<TD>^</TD>
<TD></TD></TR>
<TR>
<TD>95</TD>
<TD>137</TD>
<TD>5F</TD>
<TD>_</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>96</TD>
<TD>140</TD>
<TD>60</TD>
<TD>`</TD>
<TD></TD></TR>
<TR>
<TD>97</TD>
<TD>141</TD>
<TD>61</TD>
<TD>a</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>98</TD>
<TD>142</TD>
<TD>62</TD>
<TD>b</TD>
<TD></TD></TR>
<TR>
<TD>99</TD>
<TD>143</TD>
<TD>63</TD>
<TD>c</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>100</TD>
<TD>144</TD>
<TD>64</TD>
<TD>d</TD>
<TD></TD></TR>
<TR>
<TD>101</TD>
<TD>145</TD>
<TD>65</TD>
<TD>e</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>102</TD>
<TD>146</TD>
<TD>66</TD>
<TD>f</TD>
<TD></TD></TR>
<TR>
<TD>103</TD>
<TD>147</TD>
<TD>67</TD>
<TD>g</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>104</TD>
<TD>150</TD>
<TD>68</TD>
<TD>h</TD>
<TD></TD></TR>
<TR>
<TD>105</TD>
<TD>151</TD>
<TD>69</TD>
<TD>i</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>106</TD>
<TD>152</TD>
<TD>6A</TD>
<TD>j</TD>
<TD></TD></TR>
<TR>
<TD>107</TD>
<TD>153</TD>
<TD>6B</TD>
<TD>k</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>108</TD>
<TD>154</TD>
<TD>6C</TD>
<TD>l</TD>
<TD></TD></TR>
<TR>
<TD>109</TD>
<TD>155</TD>
<TD>6D</TD>
<TD>m</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>110</TD>
<TD>156</TD>
<TD>6E</TD>
<TD>n</TD>
<TD></TD></TR>
<TR>
<TD>111</TD>
<TD>157</TD>
<TD>6F</TD>
<TD>o</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>112</TD>
<TD>160</TD>
<TD>70</TD>
<TD>p</TD>
<TD></TD></TR>
<TR>
<TD>113</TD>
<TD>161</TD>
<TD>71</TD>
<TD>q</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>114</TD>
<TD>162</TD>
<TD>72</TD>
<TD>r</TD>
<TD></TD></TR>
<TR>
<TD>115</TD>
<TD>163</TD>
<TD>73</TD>
<TD>s</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>116</TD>
<TD>164</TD>
<TD>74</TD>
<TD>t</TD>
<TD></TD></TR>
<TR>
<TD>117</TD>
<TD>165</TD>
<TD>75</TD>
<TD>u</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>118</TD>
<TD>166</TD>
<TD>76</TD>
<TD>v</TD>
<TD></TD></TR>
<TR>
<TD>119</TD>
<TD>167</TD>
<TD>77</TD>
<TD>w</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>120</TD>
<TD>170</TD>
<TD>78</TD>
<TD>x</TD>
<TD></TD></TR>
<TR>
<TD>121</TD>
<TD>171</TD>
<TD>79</TD>
<TD>y</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>122</TD>
<TD>172</TD>
<TD>7A</TD>
<TD>z</TD>
<TD></TD></TR>
<TR>
<TD>123</TD>
<TD>173</TD>
<TD>7B</TD>
<TD>{</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>124</TD>
<TD>174</TD>
<TD>7C</TD>
<TD>|</TD>
<TD></TD></TR>
<TR>
<TD>125</TD>
<TD>175</TD>
<TD>7D</TD>
<TD>}</TD>
<TD></TD></TR>
<TR bgColor=#eeeeff>
<TD>126</TD>
<TD>176</TD>
<TD>7E</TD>
<TD>~</TD>
<TD></TD></TR>
<TR>
<TD>127</TD>
<TD>177</TD>
<TD>7F</TD>
<TD>DEL</TD>
<TD>delete</TD></TR></TBODY></TABLE></P></div>]]></description>
	    <author><![CDATA[游戏学院成都校区程序老师]]></author>
	    <comments>http://haoyindangdeblog.blog.163.com/blog/static/70467809200852325518526</comments>
    <slash:comments>0</slash:comments>
    <guid isPermaLink="true">http://haoyindangdeblog.blog.163.com/blog/static/70467809200852325518526</guid>
    <pubDate>Mon, 23 Jun 2008 14:55:18 +0800</pubDate>
    <dcterms:modified>2008-06-23T14:55:18+08:00</dcterms:modified>
  </item>    
  <item>
  	<title><![CDATA[第一章 简介]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/7046780920085163251799</link>
    <description><![CDATA[<div><P>成都游戏学院 <A href="http://www.cdgamecollege.org">http://www.cdgamecollege.org</A> 电话：028-85586115 </P>
<P>&nbsp;</P>
<P style="TEXT-INDENT: 2em">
<TABLE width="100%" border=0>
<TBODY>
<TR>
<TD>1.1 什么是Windows Sockets规范?</TD></TR>
<TR>
<TD>
<P></P>
<P style="TEXT-INDENT: 2em">Windows Sockets规范以U.C. Berkeley大学BSD UNIX中流行的Socket接口为范例定义了一套Micosoft Windows下网络编程接口。它不仅包含了人们所熟悉的Berkeley Socket风格的库函数；也包含了一组针对Windows的扩展库函数，以使程序员能充分地利用Windows消息驱动机制进行编程。 </P>
<P style="TEXT-INDENT: 2em"></P></TD></TR>
<TR>
<TD>Windows Sockets规范本意在于提供给应用程序开发者一套简单的API，并让各家网络软件供应商共同遵守。此外，在一个特定版本Windows的基础上，Windows Sockets也定义了一个二进制接口（ABI），以此来保证应用Windows Sockets API的应用程序能够在任何网络软件供应商的符合Windows Sockets协议的实现上工作。因此这份规范定义了应用程序开发者能够使用，并且网络软件供应商能够实现的一套库函数调用和相关语义。</TD></TR>
<TR>
<TD>遵守这套Windows Sockets规范的网络软件，我们称之为Windows Sockets兼容的，而Windows Sockets兼容实现的提供者，我们称之为Windows Sockets提供者。一个网络软件供应商必须百分之百地实现Windows Sockets规范才能做到现Windows Sockets兼容。 </TD></TR>
<TR>
<TD>任何能够与Windows Sockets兼容实现协同工作的应用程序就被认为是具有Windows Sockets接口。我们称这种应用程序为Windows Sockets应用程序。</TD></TR>
<TR>
<TD>Windows Sockets规范定义并记录了如何使用API与Internet协议族（IPS，通常我们指的是TCP/IP）连接，尤其要指出的是所有的Windows Sockets实现都支持流套接口和数据报套接口. 应用程序调用Windows Sockets的API实现相互之间的通讯。</TD></TR>
<TR>
<TD height=37>Windows Sockets又利用下层的网络通讯协议功能和操作系统调用实现实际的通讯工作。它们之间的关系如图1-1。 </TD></TR>
<TR>
<TD height=445>
<P></P>
<P style="TEXT-INDENT: 2em"><IMG src="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/Image2.gif" border=0></P>
<P style="TEXT-INDENT: 2em"></P></TD></TR>
<TR>
<TD>虽然我们并不反对使用这一套API来实现另一通讯协议栈（而且我们期望在将来规范的修改中能够讨论这个问题），但这种用法已经超出了我们这一份规范所规定的范围，我们在此将不作讨论。 </TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em">&nbsp;</P></div>]]></description>
	    <author><![CDATA[游戏学院成都校区程序老师]]></author>
	    <comments>http://haoyindangdeblog.blog.163.com/blog/static/7046780920085163251799</comments>
    <slash:comments>0</slash:comments>
    <guid isPermaLink="true">http://haoyindangdeblog.blog.163.com/blog/static/7046780920085163251799</guid>
    <pubDate>Mon, 16 Jun 2008 15:25:01 +0800</pubDate>
    <dcterms:modified>2008-06-16T15:25:01+08:00</dcterms:modified>
  </item>    
  <item>
  	<title><![CDATA[Windows Sockets 规范及应用]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/704678092008516323430</link>
    <description><![CDATA[<div><P style="TEXT-INDENT: 2em">成都游戏学院 <A href="http://www.cdgamecollege.org/">http://www.cdgamecollege.org</A> 电话：028-85586115 
</P><P style="TEXT-INDENT: 2em">&nbsp;
</P><P style="TEXT-INDENT: 2em">
<TABLE width="90%" border=0>
<TBODY>
<TR>
<TD colSpan=2>
<P></P>
<P style="TEXT-INDENT: 2em">－Windows网络编程接口 </P>
<P style="TEXT-INDENT: 2em"></P></TD></TR>
<TR>
<TD width="6%">内容提要</TD>
<TD width="94%">本书适应了Windows、Internet及计算机网络普及的潮流，介绍了一套在Windows下网络编程的规范－Windows Sockets。这套规范是Windows下得到广泛应用的、开放的、支持多种协议的网络编程接口。从1991年的1.0版到1995年的2.0.8版，经过不断完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下，已成为Windows网络编程的事实上的标准。为使读者能够充分理解和应用这套规范，本书不但对Windows Sockets 1.1及2.0规范作了较为详尽的介绍，还结合了作者的实际工作，给出了具有实际应用价值的程序实例。书中的内容包括：Windows Sockets规范1.1版及2.0.8版介绍；Windows Sockets网络编程指导和具体应用实例；Windows Sockets规范1.1版及2.0.8版库函数参考等。 本书体系完整，文字流畅，可供从事网络应用开发的工程技术人员和大专院校师生参考。</TD></TR></TBODY></TABLE></P>
<P style="TEXT-INDENT: 2em">&nbsp;
<TABLE width="90%" border=0>
<TBODY>
<TR>
<TD>目录</TD></TR>
<TR>
<TD>第一章 简介</TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/1-1.htm">1.1 什么是WINDOWS SOCKETS规范?</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/1-2.htm">1.2 BEKELEY套接口</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/1-3.htm">1.3 MICROSOFT WINDOWS和针对WINDOWS的扩展</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/1-4.htm">1.4 这份规范的地位</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/1-5.htm">1.5 曾经作过的修改</A></TD></TR>
<TR>
<TD>第二章 使用WINDOWS SOCKETS 1.1编程</TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/2-1.htm">2.1 WINDOWS SOCKETS协议栈安装检查</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/2-2.htm">2.2 套接口</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/2-3.htm">2.3 字节顺序</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/2-4.htm">2.4 套接口属性选项</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/2-5.htm">2.5 数据库文件</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/2-6.htm">2.6 与BERKELEY套接口的不同</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/2-7.htm">2.7 在多线程WINDOWS版本中的WINDOWS SOCKETS</A></TD></TR>
<TR>
<TD>第三章 WINDOWS SOCKETS 1.1应用实例</TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/3-1.htm">3.1 套接口网络编程原理</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/3-2.htm">3.2 WINDOWS SOCKETS编程原理</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/3-3.htm">3.3 WINDOWS SOCKETS与UNIX套接口编程实例</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/3-4.html">3.4 另一个精巧的应用程序实例－WSHOUT</A></TD></TR>
<TR>
<TD>第四章 WINDOWS SOCKET 1.1库函数概览</TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/4-1.html">4.1 套接口函数</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/4-2.html">4.2 数据库函数</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/4-3.html">4.3 针对MICROSOFT WINDOWS的扩展函数</A></TD></TR>
<TR>
<TD>第五章 套接口库函数参考</TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/5-1.html">5.1 WINDOWS SOCKET 1.1库函数参考</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/5-2.html">5.2 数据库函数</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/5-3.html">5.3 WINDOWS扩展函数</A></TD></TR>
<TR>
<TD>第六章 WINDOWS SOCKET 2的扩展特性</TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-1.html">6.1 同时使用多个传输协议</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-2.html">6.2 与WINDOWS SOCKET 1.1应用程序的向后兼容性</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-3.html">6.3 在WINDOWS SOCKETS中注册传输协议</A> </TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-4.html">6.4 协议无关的名字解析</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-5.html">6.5 重叠I/O和事件对象 </A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-6.html">6.6 使用事件对象异步通知</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-7.html">6.7 服务的质量（QOS）</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-8.html">6.8 套接口组</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-9.html">6.9 共享套接口</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-10.html">6.10 连接建立和拆除的高级函数</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-11.html">6.11 扩展的字节顺序转换例程</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-12.html">6.12 分散/聚集方式I/O</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-13.html">6.13 协议无关的多点通讯</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-14.html">6.14 新增套接口选项一览</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-15.html">6.15 新增套接口IOCTL操作代码</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/6-16.html">6.16 新增函数一览 </A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/7.html">第七章 WINDOWS SOCKETS 2扩展库函数简要参考</A></TD></TR>
<TR>
<TD>7.1 WSAACCEPT()</TD></TR>
<TR>
<TD>7.2 WSACLOSEEVENT()</TD></TR>
<TR>
<TD>7.3 WSACONNECT()</TD></TR>
<TR>
<TD>7.4 WSACREATEEVENT(</TD></TR>
<TR>
<TD>7.5 WSADUPLICATESOCKET()</TD></TR>
<TR>
<TD>7.6 WSAENUMNETWORKEVENTS()</TD></TR>
<TR>
<TD>7.7 WSAENUMPROTOCOLS()</TD></TR>
<TR>
<TD>7.8 WSAEVENTSELECT()</TD></TR>
<TR>
<TD>7.9 WSAGETOVERLAPPEDRESULT()</TD></TR>
<TR>
<TD>7.10 WSAGETQOSBYNAME(</TD></TR>
<TR>
<TD>7.11 WSAHTONL()</TD></TR>
<TR>
<TD>7.12 WSAHTONS()</TD></TR>
<TR>
<TD>7.13 WSAIOCTL()</TD></TR>
<TR>
<TD>7.14 WSAJOINLEAF()</TD></TR>
<TR>
<TD>7.15 WSANTOHL()</TD></TR>
<TR>
<TD>7.16 WSANTOHS()</TD></TR>
<TR>
<TD>7.17 WSARECV()</TD></TR>
<TR>
<TD>7.18 WSARECVDISCONNECT()</TD></TR>
<TR>
<TD>7.19 WSARECVFROM()</TD></TR>
<TR>
<TD>7.20 WSARESETEVENT()</TD></TR>
<TR>
<TD>7.21 WSASEND(</TD></TR>
<TR>
<TD>7.22 WSASENDDISCONNECT()</TD></TR>
<TR>
<TD>7.23 WSASENDTO()</TD></TR>
<TR>
<TD>7.24 WSASETEVENT()</TD></TR>
<TR>
<TD>7.25 WSASOCKET()</TD></TR>
<TR>
<TD>7.26 WSAWAITFORMULTIPLEEVENTS()</TD></TR>
<TR>
<TD height=21><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/f-a.html">附录A 错误代码</A></TD></TR>
<TR>
<TD>附录B WINDOWS SOCKETS头文件</TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/f-b1.html">附录B.1 WINDOWS SOCKETS 1.1头文件</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/f-b2.html">附录B.2 WINDOWS SOCKETS 2头文件</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/f-b3.html">附录B.3 WINSOCK.DEF文件</A></TD></TR>
<TR>
<TD><A href="mk:@MSITStore:F:资料SocketWinSock规范及应用.chm::/f-c.html">附录C 参考文献</A></TD></TR></TBODY></TABLE></P></div>]]></description>
	    <author><![CDATA[游戏学院成都校区程序老师]]></author>
	    <comments>http://haoyindangdeblog.blog.163.com/blog/static/704678092008516323430</comments>
    <slash:comments>0</slash:comments>
    <guid isPermaLink="true">http://haoyindangdeblog.blog.163.com/blog/static/704678092008516323430</guid>
    <pubDate>Mon, 16 Jun 2008 15:23:04 +0800</pubDate>
    <dcterms:modified>2008-06-16T15:23:04+08:00</dcterms:modified>
  </item>    
  <item>
  	<title><![CDATA[矢量运算简介]]></title>	
    <link>http://haoyindangdeblog.blog.163.com/blog/static/7046780920085109730631</link>
    <description><![CDATA[<div><P>成都游戏学院 <A href="http://www.cdgamecollege.org">http://www.cdgamecollege.org</A> 电话：028-85586115 </P>
<P>质量保证:<BR>只讨论可以任意平移自由矢量<BR>以下原理适于你个人开发的3D系统,D3D系统,OpenGL系统</P>
<P>一.两点距离<BR>2D系统:<BR>Point1(x1,y1),Point2(x2,y2)<BR>距离D=sqr( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) )<BR>3D系统:<BR>Point 1 (x1, y1, z1) Point 2 at (x2, y2, z2).</P>
<P>xd = x2-x1<BR>yd = y2-y1<BR>zd = z2-z1<BR>距离Distance = SquareRoot(xd*xd + yd*yd + zd*zd)</P>
<P>做游戏和demo永远不要去做开方:<BR>1.用LUT查表技术(Look up Table )<BR>2.在做碰撞检测时,误差Distance*Distance&lt;a certain number就可以认为点相撞了</P>
<P>二规格化,单位化(Normalize)<BR>先要说矢量的长度:<BR>矢量Vector(x,y,z)<BR>矢量长度Length(Vector)= |Vector|=sqr(x*x+y*y+z*z)<BR>Normalize后:<BR>(x/Length(Vector),y/Length(Vector),z/Length(Vector))<BR>方向不变,长度为1个单位</P>
<P><BR>三.点乘 点积 数量积(Dot Product)<BR>是一回事儿.首先明确两个矢量的点积是个标量.<BR>中学物理的力做功就是矢量点积的例子:W=|F|.|S|.cos(theta)</P>
<P>二矢量点积:<BR>Vector1:(x1,y1,z1) Vector2(x2,y2,z2)<BR>DotProduct=x1*x2+y1*y2+z1*z2</P>
<P>很重要的应用:<BR>1.求二矢量余弦:<BR>由我们最熟悉的力做功:<BR>cos(theta)=F.S/(|F|.|S|)<BR>可以判断二矢量的方向情况: cos=1同向,cos=-1相反,cos=0直角<BR>曲面消隐(Cull fa