﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-hainan-随笔分类-C++</title><link>http://www.cppblog.com/hainan/category/3414.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 22 May 2008 22:11:19 GMT</lastBuildDate><pubDate>Thu, 22 May 2008 22:11:19 GMT</pubDate><ttl>60</ttl><item><title>多态</title><link>http://www.cppblog.com/hainan/archive/2007/07/18/28316.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Wed, 18 Jul 2007 14:59:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/07/18/28316.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/28316.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/07/18/28316.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/28316.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/28316.html</trackback:ping><description><![CDATA[<p>&nbsp;</p>
<div><strong><font size=6><a name=_Toc161466886><span>1.<span style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp; </span></span>什么是多态</a></font></strong></div>
<div>多态是C++中的一个重要的基础，可以这样说，不掌握多态就是C++的门外汉。然而长期以来，C++社群对于多态的内涵和外延一直争论不休。大有只见树木不见森林之势。多态到底是怎么回事呢？说实在的，我觉的多态这个名字起的不怎么好（或是译的不怎么好）。要是我给起名的话，我就给它定一个这样的名字--&#8220;调用&#8217;同名函数&#8217;却会因上下文不同会有不同的实现的一种机制&#8221;。这个名字长是长了点儿，可是比&#8220;多态&#8221;清楚多了。看这个长的定义，我们可以从中找出多态的三个重要的部分。一是&#8220;<span style="COLOR: red">相同函数名&#8221;，二是&#8220;<span style="COLOR: red">依据上下文</span>&#8221;，三是&#8220;<span style="COLOR: red">实现却不同</span>&#8221;。嘿，还是个顺口溜呢。我们且把它们叫做多态三要素吧。</span></div>
<div><strong><font size=6><a name=_Toc161466887><span>2.<span style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp; </span></span>多态带来的好处</a></font></strong></div>
<div>多态带来两个明显的好处：一是不用记大量的函数名了，二是它会依据调用时的上下文来确定实现。确定实现的过程由C++本身完成另外还有一个不明显但却很重要的好处是：带来了面向对象的编程。</div>
<div><strong><font size=6><a name=_Toc161466888><span>3.<span style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp; </span></span>C++</a><span>中实现多态的方式</span></font></strong></div>
<div>C++中共有三种实现多态的方式。由&#8220;容易说明白&#8221;到&#8220;不容易说明白&#8221;排序分别为。第一种是函数重载；第二种是模板函数；第三种是虚函数。</div>
<div><strong><font size=6><a name=_Toc161466889><span>4.<span style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp; </span></span>细说用<span style="COLOR: red">函数重载实现的多态</span></a></font></strong></div>
<div>函数重载是这样一种机制：允许有不同参数的函数有相同的名字。</div>
<div>具体一点讲就是：假如有如下三个函数：</div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">void</span><span style="FONT-SIZE: 12pt"> test(</span><span style="FONT-SIZE: 9pt; COLOR: blue">int</span><span style="FONT-SIZE: 12pt"> arg){}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">函数1</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">void</span><span style="FONT-SIZE: 12pt"> test(</span><span style="FONT-SIZE: 9pt; COLOR: blue">char</span><span style="FONT-SIZE: 12pt"> arg){}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">函数2</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">void</span><span style="FONT-SIZE: 12pt"> test(</span><span style="FONT-SIZE: 9pt; COLOR: blue">int</span><span style="FONT-SIZE: 12pt"> arg1,</span><span style="FONT-SIZE: 9pt; COLOR: blue">int</span><span style="FONT-SIZE: 12pt"> arg2){}<span>&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">函数3</span></div>
<div>如果在C中编译，将会得到一个名字冲突的错误而不能编译通过。在C++中这样做是合法的。可是当我们调用test的时候到底是会调用上面三个函数中的哪一个呢？这要依据你在调用时给的出的参数来决定。如下：</div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; test(5);&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">调用函数1</span></div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; test(</span></span><span style="FONT-SIZE: 9pt; COLOR: maroon">'c'</span><span style="FONT-SIZE: 12pt">);</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">调用函数2</span></div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; test(4,5); </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">调用函数3</span></div>
<div>C++是如何做到这一点的呢？原来聪明的C++编译器在编译的时候悄悄的在我们的函数名上根据函数的参数的不同做了一些不同的记号。具体说如下：</div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">void</span><span style="FONT-SIZE: 12pt"> test(</span><span style="FONT-SIZE: 9pt; COLOR: blue">int</span><span style="FONT-SIZE: 12pt"> arg)<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">被标记为　&#8216;test有一个int型参数&#8217;</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">void</span><span style="FONT-SIZE: 12pt"> test(</span><span style="FONT-SIZE: 9pt; COLOR: blue">char</span><span style="FONT-SIZE: 12pt"> arg)<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">被标记为　&#8216;test有一个char型的参数&#8217;</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">void</span><span style="FONT-SIZE: 12pt"> test(</span><span style="FONT-SIZE: 9pt; COLOR: blue">int</span><span style="FONT-SIZE: 12pt"> arg1,</span><span style="FONT-SIZE: 9pt; COLOR: blue">int</span><span style="FONT-SIZE: 12pt"> arg2)&nbsp;</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">被标记为　&#8216;test第一个参数是int型，第二个参数为int型&#8217;</span></div>
<div>这样一来当我们进行对test的调用时，C++就可以根据调用时的参数来确定到底该用哪一个test函数了。噢，聪明的C++编译器。其实C++做标记做的比我上面所做的更聪明。我上面哪样的标记太长了。C++编译器用的标记要比我的短小的多。看看这个真正的C++的对这三个函数的标记：</div>
<div>?test@@YAXD@Z</div>
<div>?test@@YAXH@Z</div>
<div>?test@@YAXHH@Z</div>
<div>是不是短多了。但却不好看明白了。好在这是给计算机看的，人看不大明白是可以理解的。</div>
<div>还记得cout吧。我们用&lt;&lt;可以让它把任意类型的数据输出。比如可以象下面那样：</div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; cout &lt;&lt; 1;&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">输出int型</span></div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; cout &lt;&lt; 8.9;&nbsp;</span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">输出double型</span></div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; cout &lt;&lt; </span></span><span style="FONT-SIZE: 9pt; COLOR: maroon">'a'</span><span style="FONT-SIZE: 12pt">;<span>&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">输出char型</span></div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; cout &lt;&lt; </span></span><span style="FONT-SIZE: 9pt; COLOR: maroon">"abc"</span><span style="FONT-SIZE: 12pt">;</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">输出char数组型</span></div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; cout &lt;&lt; endl; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">输出一个函数</span></div>
<div>cout之所以能够用一个函数名&lt;&lt;（&lt;&lt;是一个函数名）就能做到这些全是函数重载的功能。要是没有函数重载，我们也许会这样使用cout，如下：</div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; cout int&lt;&lt; 1;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">输出int型</span></div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; cout double&lt;&lt; 8.9;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">输出double型</span></div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; cout char&lt;&lt; </span></span><span style="FONT-SIZE: 9pt; COLOR: maroon">'a'</span><span style="FONT-SIZE: 12pt">;&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">输出char型</span></div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; cout charArray&lt;&lt; </span></span><span style="FONT-SIZE: 9pt; COLOR: maroon">"abc"</span><span style="FONT-SIZE: 12pt">;</span><span style="FONT-SIZE: 12pt">　<span><span>&nbsp;&nbsp;&nbsp; </span></span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">输出char数组型</span></div>
<div align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; cout function(&#8230;)&lt;&lt; endl;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">输出函数</span></div>
<div>为每一种要输出的类型起一个函数名，这岂不是很麻烦呀。</div>
<div>不过函数重载有一个美中不足之处就是不能为返回值不同的函数进行重载。那是因为人们常常不为函数调用指出返回值。并不是技术上不能通过返回值来进行重载。</div>
<div><strong><font size=6><a name=_Toc161466890><span>5.<span style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp; </span></span>细说用<span style="COLOR: red">模板函数实现的多态</span></a></font></strong></div>
<div>所谓模板函数（也有人叫函数模板）是这样一个概念：函数的内容有了，但函数的参数类型却是待定的（注意：参数个数不是待定的）。比如说一个(准确的说是一类或一群)函数带有两个参数，它的功能是返回其中的大值。这样的函数用模板函数来实现是适合不过的了。如下。</div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">template</span><span style="FONT-SIZE: 12pt"> &lt; </span><span style="FONT-SIZE: 9pt; COLOR: blue">typename</span><span style="FONT-SIZE: 12pt"> T&gt;</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">T getMax(T arg1, T arg2)</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">{</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: blue">return</span><span style="FONT-SIZE: 12pt"> arg1 &gt; arg2 ? arg1:arg2;&nbsp;</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">代码段1</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">}</span></div>
<div>这就是基于模板的多态吗？不是。因为现在我们不论是调用getMax(1, 2)还是调用getMax(3.0, 5.0)都是走的上面的函数定义。它没有根据调用时的上下文不同而执行不同的实现。所以这充其量也就是用了一个模板函数，和多态不沾边。怎样才能和多态沾上边呢？用模板特化呀！象这样：</div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">template</span><span style="FONT-SIZE: 12pt">&lt;&gt;</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">char</span><span style="FONT-SIZE: 12pt">* getMax(</span><span style="FONT-SIZE: 9pt; COLOR: blue">char</span><span style="FONT-SIZE: 12pt">* arg1, </span><span style="FONT-SIZE: 9pt; COLOR: blue">char</span><span style="FONT-SIZE: 12pt">* arg2)</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">{</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: blue">return</span><span style="FONT-SIZE: 12pt"> (strcmp(arg1, arg2) &gt; 0)?arg1:arg2;</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">代码段2</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">}</span></div>
<div>这样一来当我们调用getMax(&#8220;abc&#8221;, &#8220;efg&#8221;)的时候，就会执行代码段2，而不是代码段1。这样就是多态了。</div>
<div>更有意思的是如果我们再写这样一个函数：</div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">char</span><span style="FONT-SIZE: 12pt"> getMax(</span><span style="FONT-SIZE: 9pt; COLOR: blue">char</span><span style="FONT-SIZE: 12pt"> arg1, </span><span style="FONT-SIZE: 9pt; COLOR: blue">char</span><span style="FONT-SIZE: 12pt"> arg2)</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">{</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: blue">return</span><span style="FONT-SIZE: 12pt"> arg1&gt;arg2?arg1:arg2;&nbsp;</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">代码段3</span></div>
<div style="MARGIN-LEFT: 10.5pt; TEXT-INDENT: 10.5pt" align=left><span style="FONT-SIZE: 9pt">}</span></div>
<div>当我们调用getMax(&#8216;a&#8217;, &#8216;b&#8217;)的时候，执行的会是代码段3，而不是代码段1或代码段2。C++允许对模板函数进行函数重载，就象这个模板函数是一个普通的函数一样。于是我们马上能想到写下面这样一个函数来做三个数中取大值的处理：</div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">int</span><span style="FONT-SIZE: 12pt"> getMax( </span><span style="FONT-SIZE: 9pt; COLOR: blue">int</span><span style="FONT-SIZE: 12pt"> arg1, </span><span style="FONT-SIZE: 9pt; COLOR: blue">int</span><span style="FONT-SIZE: 12pt"> arg2, </span><span style="FONT-SIZE: 9pt; COLOR: blue">int</span><span style="FONT-SIZE: 12pt"> arg3)</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">{</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: blue">return</span><span style="FONT-SIZE: 12pt"> getMax(arg1, max(arg2, arg3) );</span><span style="FONT-SIZE: 12pt">　</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">代码段4</span></div>
<div style="MARGIN-LEFT: 21pt"><span style="FONT-SIZE: 9pt">}</span></div>
<div>同样我们还可以这样写：</div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">template</span><span style="FONT-SIZE: 12pt"> &lt;</span><span style="FONT-SIZE: 9pt; COLOR: blue">typename</span><span style="FONT-SIZE: 12pt"> T&gt;</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">T getMax(T arg1, T arg2, T arg3)</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">{</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: blue">return</span><span style="FONT-SIZE: 12pt"> getMax(arg1, getMax(arg2, arg3) ); </span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">代码段5</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">}</span></div>
<div>现在看到结合了模板的多态的威力了吧。比只用函数重载厉害多了。</div>
<div><strong><font size=6><a name=_Toc161466891><span>6.<span style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp; </span></span>小结</a></font></strong></div>
<div>上面的两种多态在C++中有一个总称：静态多态。之所以叫它们静态多态是因为它们的多态是在编译期间就确定了。也就是说前面所说的函数1，2，3代码段1，2，3，4，5这些，在编译完成后，应该在什么样的上下文的调用中执行哪一些就确定了。比如：如果调用getMax(0.1, 0.2, 0.3)就会执行代码段5。如果调用test(5)就执行函数1。这些是在编译期间就能确定下来的。</div>
<div>静态多态还有一个特点，就是：&#8220;总和参数较劲儿&#8221;。</div>
<div>下面所要讲的一种多态就是必需是在程序的执行过程中才能确定要真正执行的函数。所以这种多态在C++中也被叫做动态多态。</div>
<div><strong><font size=6><a name=_Toc161466892><span>7.<span style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp; </span></span>细说用<span style="COLOR: red">虚函数实现的多态</span></a></font></strong></div>
<div><strong><font size=5><a name=_Toc161466893><span>7.1.</span>虚函数是怎么回事</a></font></strong></div>
<div>首先来说一说虚函数，所谓虚函数是这样一个概念：基类中有这么一些函数，这些函数允许在派生类中其实现可以和基类的不一样。在C++中用关键字virtual来表示一个函数是虚函数。</div>
<div>C++中还有一个术语 &#8220;覆盖&#8221;与虚函数关系密切。所谓覆盖就是说，派生类中的一个函数的声明，与基类中某一个函数的声明一模一样,包括返回值，函数名，参数个数，参数类型，参数次序都不能有差异。<sup>（注</sup><sup>1</sup><sup>）</sup>说覆盖和虚函数关系密切的原因有两个：一个原因是，只有覆盖基类的虚函数才是安全的。第二个原因是，要想实现基于虚函数的多态就必须在派生类中覆盖基类的虚函数。</div>
<div>接下来让我们说一说为什么要有虚函数，分析一下为什么派生类非要在某些情况下覆盖基类的虚函数。就以那个非常著名的图形绘制的例子来说吧。假设我们在为一个图形系统编程。我们可能有如下的一个类结构。</div>
<div style="TEXT-INDENT: 21pt" align=center><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/guanwl/fb843c969c534e7ab0437e5f04f9c53b.jpg"></div>
<div>形状对外公开一个函数来把自己绘制出来。这是合理的，形状就应该能绘制出来，对吧？由于继承的原因，多边形和圆形也有了绘制自己这个函数。</div>
<div>现在我们来讨论在这三个类中的绘制自己的函数都应该怎么实现。在形状中嘛，什么也不做就行了。在多边形中嘛,只要把它所有的顶点首尾相连起来就行了。在圆形中嘛，依据它的圆心和它的半径画一个360度的圆弧就行了。</div>
<div>可是现在的问题是：多边形和圆形的绘制自己的函数是从形状继承而来的，并不能做连接顶点和画圆弧的工作。</div>
<div>怎么办呢？覆盖它，覆盖形状中的绘制自己这个函数。于是我们在多边形和圆形中各做一个绘制自己的函数，覆盖形状中的绘制自己的函数。为了实现覆盖，我们需要把形状中的绘制自己这个函数用virtual修饰。而且形状中的绘制自己这个函数什么也不干，我们就把它做成一个纯虚函数。纯虚函数还有一个作用，就是让它所在的类成为抽象类。形状理应是一个抽象类，不是吗？于是我们很快写出这三个类的代码如下：</div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">class</span><span style="FONT-SIZE: 12pt"> Shape</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">形状</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">{</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">public</span><span style="FONT-SIZE: 12pt">:</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: blue">virtual</span><span style="FONT-SIZE: 9pt; COLOR: blue">void</span><span style="FONT-SIZE: 12pt"> DrawSelf()</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">绘制自己</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; {</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; </span></span><span style="FONT-SIZE: 9pt; COLOR: maroon">"</span><span style="FONT-SIZE: 9pt; COLOR: maroon">我是一个什么也绘不出的图形"</span><span style="FONT-SIZE: 12pt"> &lt;&lt; endl;</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; }</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">};</span></div>
<div style="MARGIN-LEFT: 21pt" align=left>&nbsp;</div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">class</span><span style="FONT-SIZE: 12pt"> Polygo:</span><span style="FONT-SIZE: 9pt; COLOR: blue">public</span><span style="FONT-SIZE: 12pt"> Shape</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">多边形</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">{</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">public</span><span style="FONT-SIZE: 12pt">:</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: blue">void</span><span style="FONT-SIZE: 12pt"> DrawSelf()<span>&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">绘制自己</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; {</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; </span></span><span style="FONT-SIZE: 9pt; COLOR: maroon">"</span><span style="FONT-SIZE: 9pt; COLOR: maroon">连接各顶点"</span><span style="FONT-SIZE: 12pt"> &lt;&lt; endl;</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; }</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">};</span></div>
<div style="MARGIN-LEFT: 21pt" align=left>&nbsp;</div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">class</span><span style="FONT-SIZE: 12pt"> Circ:</span><span style="FONT-SIZE: 9pt; COLOR: blue">public</span><span style="FONT-SIZE: 12pt"> Shape</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">圆</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">{</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">public</span><span style="FONT-SIZE: 12pt">:</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: blue">void</span><span style="FONT-SIZE: 12pt"> DrawSelf()<span>&nbsp;&nbsp; </span></span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">绘制自己</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; {</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 12pt"><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; </span></span><span style="FONT-SIZE: 9pt; COLOR: maroon">"</span><span style="FONT-SIZE: 9pt; COLOR: maroon">以圆心和半径为依据画弧"</span><span style="FONT-SIZE: 12pt"> &lt;&lt; endl;</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; }</span></div>
<div style="TEXT-INDENT: 21pt"><span style="FONT-SIZE: 9pt">};</span></div>
<div>下面，我们将以上面的这三个类为基础来说明动态多态。在进行更进一步的说明之前，我们先来说一个不得不说的两个概念：&#8220;子类型&#8221;和&#8220;向上转型&#8221;。</div>
<div><strong><font size=5><a name=_Toc161466894><span>7.2.</span>向上转型</a></font></strong></div>
<div>子类型很好理解，比如上面的多边形和圆形就是形状的子类型。关于子类型还有一个确切的定义为：<span>如果类型</span><span>X</span><span>扩充或实现了类型Y</span><span>，那么就说X</span><span>是Y</span><span>的子类型。</span></div>
<div><span>向上转型的意思是说把一个子类型转的对象换为父类型的对象。就好比把一个多边形转为一个形状。向上转型的意思就这么简单，但它的意义却很深远。向上转型中有三点需要我们特别注意。第一，向上转型是安全的。第二，向上转型可以自动完成。第三，向上转型的过程中会丢失子类型信息。这三点在整个动态多态中发挥着重要的作用。</span></div>
<div><span>假如我们有如下的一个函数：</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">void</span><span style="FONT-SIZE: 12pt"> OutputShape( Shape arg)</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">专门负责调用形状的绘制自己的函数</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">{</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; arg.DrawSelf();</span></div>
<div style="TEXT-INDENT: 21pt"><span style="FONT-SIZE: 9pt">}</span></div>
<div>那么现在我们可以这样使用<span>OutputShape</span>这个函数：</div>
<div align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; Polygon shape1;</span></div>
<div align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; Circ shape2;</span></div>
<div align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; OutputShape(shape1);</span></div>
<div align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; OutputShape(shape2);</span></div>
<div>我们之所以可以这样使用OutputShape函数，正是由于向上转型是安全的（不会有任何的编译警告），是由于向上转弄是自动的（我们没有自己把shape1和shape2转为Shape类型再传给<span style="FONT-SIZE: 9pt">OutputShape</span><span style="FONT-SIZE: 9pt">函数</span>）。可是上面这段程序运行后的输出结果是这样的：</div>
<div style="TEXT-INDENT: 21pt"><span style="FONT-SIZE: 9pt; COLOR: maroon">我是一个什么也绘不出的图形</span></div>
<div style="TEXT-INDENT: 21pt"><span style="FONT-SIZE: 9pt; COLOR: maroon">我是一个什么也绘不出的图形</span></div>
<div>明明是一个多边形和一个圆呀，应该是输出这下面这个样子才合理呀！</div>
<div style="TEXT-INDENT: 21pt"><span style="FONT-SIZE: 9pt; COLOR: maroon">连接各顶点</span></div>
<div style="TEXT-INDENT: 21pt"><span style="FONT-SIZE: 9pt; COLOR: maroon">以圆心和半径为依据画弧</span></div>
<div>造成前面的不合理的输出的罪魁祸首正是&#8216;向上转型中的子类型信息丢失&#8217;。为了得到一个合理的输出，得想个办法来找回那些丢失的子类型信息。C++中用一种比较巧妙的办法来找回那些丢失的子类型信息。这个办法就是采用指针或引用。</div>
<div><strong><font size=5><a name=_Toc161466895><span>7.3.</span>为什么要用指针或引用来实现动态多态</a></font></strong></div>
<div><span>对于一个对象来说无论有多少个指针指向它，这些个指针所指的都是同一个对象。(</span><span>即使你用一个void</span><span>的指针指向一个对象也是这样的，不是吗？)</span><span>同理对于引用也一样。</span></div>
<div><span>这究竟有多少深层次的意义呢？这里的深层的意义是这样的：<span style="font-emphasize: dot">子类型的信息本来就在它本身中存在，所以我们用一个基类的指针来指出它，这个子类型的信息也会被找到，同理引用也是一样的。</span>C++</span><span>正是利用了指针的这一特性。来做到动态多态的。<sup>注</sup><sup>2</sup></span><span>现在让我们来改写OutputShape</span><span>函数为这样：</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt; COLOR: blue">void</span><span style="FONT-SIZE: 12pt"> OutputShape( Shape&amp; arg)</span><span style="FONT-SIZE: 9pt; COLOR: green">//</span><span style="FONT-SIZE: 9pt; COLOR: green">专门负责调用形状的绘制自己的函数</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">{</span></div>
<div style="MARGIN-LEFT: 21pt" align=left><span style="FONT-SIZE: 9pt">&nbsp;&nbsp;&nbsp; arg.DrawSelf();</span></div>
<div style="TEXT-INDENT: 21pt"><span style="FONT-SIZE: 9pt">}</span></div>
<div><span>现在我们的程序的输出为：</span></div>
<div style="TEXT-INDENT: 21pt"><span style="FONT-SIZE: 9pt; COLOR: maroon">连接各顶点</span></div>
<div style="TEXT-INDENT: 21pt"><span style="FONT-SIZE: 9pt; COLOR: maroon">以圆心和半径为依据画弧</span></div>
<div><span>这样的输出才是我们真正的想要的。我们实现的这种真正想要的输出就是动态多态的实质。</span></div>
<div><strong><font size=5><a name=_Toc161466896><span><span>7.4.</span></span><span>为什么动态多态要用public</span></a><span><span>继承</span></span></font></strong></div>
<div><span>在我们上面的代码中，圆和多边形都是从形状公有继承而来的。要是我们把圆的继承改为私有或保护会怎么样呢？我们来试一试。哇，我们得到一个编译错误。这个错误的大致意思是说：&#8220;请不要用一个私有的方法&#8221;。怎么回事呢？</span></div>
<div style="MARGIN-LEFT: 21pt">是这么回事。它的意思是说下面这样说不合理。</div>
<div style="MARGIN-LEFT: 21pt">所有的形状都可以画出来，圆这种形状是不能画出来的。</div>
<div style="MARGIN-LEFT: 21pt">这样合理吗？不合理。所以请在多态中使用公有继承吧。</div>
<div><strong><font size=6><a name=_Toc161466897><span>8.<span style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp; </span></span>总结</a></font></strong></div>
<div><span>多态的思想其实早在面向对象的编程出现之前就有了。比如C</span><span>语言中的+</span><span>运算符。这个运算符可以对两个int</span><span>型的变量求和，也可以对两个char</span><span>的变量求和，也可以对一个int</span><span>型一个char</span><span>型的两个变量求和。加法运算的这种特性就是典型的多态。所以说多态的本质是同样的用法在实现上却是不同的。</span></div>
<div><strong><font size=6><a name=_Toc161466898><span>9.<span style="FONT: 7pt 'Times New Roman'">&nbsp;&nbsp; </span></span>附录：</a></font></strong></div>
<div>注1：严格地讲返回值可以不同，但这种不同是有限制的。详细情况请看有关协变的内容。</div>
<div>注2：C++会悄悄地在含有虚函数的类里面加一个指针。用这个指针来指向一个表格。这个表格会包含每一个虚函数的索引。用这个索引来找出相应的虚函数的入口地址。对于我们所举的形状的例子来说，C++会悄悄的做三个表，Shape一个，Polygon一个，Circ一个。它们分别记录一个DrawSelf函数的入口地址。在程序运行的过程中，C++会先通过类中的那个指针来找到这个表格。再从这个表格中查出DrawSelf的入口地址。然后现通过这个入口地址来调用正直的DrawSelf。正是由于这个查找的过程，是在运行时完成的。所以这样的多态才会被叫做动态多态（运行时多态）</div>
<img src ="http://www.cppblog.com/hainan/aggbug/28316.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-07-18 22:59 <a href="http://www.cppblog.com/hainan/archive/2007/07/18/28316.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>函数指针</title><link>http://www.cppblog.com/hainan/archive/2007/04/12/21710.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Thu, 12 Apr 2007 04:07:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/04/12/21710.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/21710.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/04/12/21710.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/21710.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/21710.html</trackback:ping><description><![CDATA[<p>函数指针 <br>函数存放在内存的代码区域内，它们同样有地址，我们如何能获得函数的地址呢？ </p>
<p>　　如果我们有一个int test(int a)的函数，那么，它的地址就是函数的名字，这一点如同数组一样，数组的名字就是数组的起始地址。 </p>
<p>　　定义一个指向函数的指针用如下的形式，以上面的test()为例： </p>
<p>int (*fp)(int a);//这里就定义了一个指向函数的指针 </p>
<p>　　函数指针不能绝对不能指向不同类型，或者是带不同形参的函数，在定义函数指针的时候我们很容易犯如下的错误。</p>
<p>int *fp(int a);//这里是错误的，因为按照结合性和优先级来看就是先和()结合，然后变成了一个返回整形指针的函数了，而不是函数指针，这一点尤其需要注意！ </p>
<p>　　下面我们来看一个具体的例子：</p>
<p><br>#include &lt;iostream&gt;&nbsp; <br>#include &lt;string&gt;&nbsp; <br>using namespace std;&nbsp; <br>&nbsp; <br>int test(int a);&nbsp; <br>&nbsp; <br>void main(int argc,char* argv[])&nbsp;&nbsp;&nbsp; <br>{&nbsp; <br>&nbsp;&nbsp;&nbsp; cout&lt;&lt;test&lt;&lt;endl;//显示函数地址&nbsp; <br>&nbsp;&nbsp;&nbsp; int (*fp)(int a);&nbsp; <br>&nbsp;&nbsp;&nbsp; fp=test;//将函数test的地址赋给函数学指针fp&nbsp; <br>&nbsp;&nbsp;&nbsp; cout&lt;&lt;fp(5)&lt;&lt;"|"&lt;&lt;(*fp)(10)&lt;&lt;endl;&nbsp; <br>//上面的输出fp(5),这是标准c++的写法,(*fp)(10)这是兼容c语言的标准写法,两种同意,但注意区分,避免写的程序产生移植性问题!&nbsp; <br>&nbsp;&nbsp;&nbsp; cin.get();&nbsp; <br>}&nbsp; <br>&nbsp; <br>int test(int a)&nbsp; <br>{&nbsp; <br>&nbsp;&nbsp;&nbsp; return a;&nbsp; <br>}</p>
<p><br>typedef定义可以简化函数指针的定义，在定义一个的时候感觉不出来，但定义多了就知道方便了，上面的代码改写成如下的形式：</p>
<p>#include &lt;iostream&gt;&nbsp; <br>#include &lt;string&gt;&nbsp; <br>using namespace std;&nbsp; <br>&nbsp; <br>int test(int a);&nbsp; <br>&nbsp; <br>void main(int argc,char* argv[])&nbsp;&nbsp;&nbsp; <br>{&nbsp; <br>&nbsp;&nbsp;&nbsp; cout&lt;&lt;test&lt;&lt;endl;&nbsp; <br>&nbsp;&nbsp;&nbsp; typedef int (*fp)(int a);//注意,这里不是生命函数指针,而是定义一个函数指针的类型,这个类型是自己定义的,类型名为fp&nbsp; <br>&nbsp;&nbsp;&nbsp; fp fpi;//这里利用自己定义的类型名fp定义了一个fpi的函数指针!&nbsp; <br>&nbsp;&nbsp;&nbsp; fpi=test;&nbsp; <br>&nbsp;&nbsp;&nbsp; cout&lt;&lt;fpi(5)&lt;&lt;"|"&lt;&lt;(*fpi)(10)&lt;&lt;endl;&nbsp; <br>&nbsp;&nbsp;&nbsp; cin.get();&nbsp; <br>}&nbsp; <br>&nbsp; <br>int test(int a)&nbsp; <br>{&nbsp; <br>&nbsp;&nbsp;&nbsp; return a;&nbsp; <br>} </p>
<p>&nbsp;</p>
<img src ="http://www.cppblog.com/hainan/aggbug/21710.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-04-12 12:07 <a href="http://www.cppblog.com/hainan/archive/2007/04/12/21710.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>字节对齐</title><link>http://www.cppblog.com/hainan/archive/2007/04/12/21708.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Thu, 12 Apr 2007 03:59:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/04/12/21708.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/21708.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/04/12/21708.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/21708.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/21708.html</trackback:ping><description><![CDATA[<p>一.什么是字节对齐,为什么要对齐?<br>&nbsp;&nbsp;&nbsp; 现代计算机中内存空间都是按照byte划分的，从理论上讲似乎对任何类型的变量的访问可以从任何地址开始，但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问，这就需要各种类型数据按照一定的规则在空间上排列，而不是顺序的一个接一个的排放，这就是对齐。<br>&nbsp;&nbsp;&nbsp; 对齐的作用和原因：各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况，但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐，会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始，如果一个int型（假设为32位系统）如果存放在偶地址开始的地方，那 么一个读周期就可以读出这32bit，而如果存放在奇地址开始的地方，就需要2个读周期，并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。</p>
<p>二.字节对齐对程序的影响:<br>&nbsp;&nbsp;&nbsp; 先让我们看几个例子吧(32bit,x86环境,gcc编译器):<br>设结构体如下定义：<br>struct A<br>{<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>struct B<br>{<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>现在已知32位机器上各种数据类型的长度如下:<br>char:1(有符号无符号同)&nbsp;&nbsp;&nbsp; <br>short:2(有符号无符号同)&nbsp;&nbsp;&nbsp; <br>int:4(有符号无符号同)&nbsp;&nbsp;&nbsp; <br>long:4(有符号无符号同)&nbsp;&nbsp;&nbsp; <br>float:4&nbsp;&nbsp;&nbsp; double:8<br>那么上面两个结构大小如何呢?<br>结果是:<br>sizeof(strcut A)值为8<br>sizeof(struct B)的值却是12</p>
<p>结构体A中包含了4字节长度的int一个，1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。<br>之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:<br>#pragma pack (2) /*指定按2字节对齐*/<br>struct C<br>{<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>#pragma pack () /*取消指定对齐，恢复缺省对齐*/<br>sizeof(struct C)值是8。<br>修改对齐值为1：<br>#pragma pack (1) /*指定按1字节对齐*/<br>struct D<br>{<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>#pragma pack () /*取消指定对齐，恢复缺省对齐*/<br>sizeof(struct D)值为7。<br>后面我们再讲解#pragma pack()的作用.</p>
<p>三.编译器是按照什么样的原则进行对齐的?<br>&nbsp;&nbsp;&nbsp; 先让我们看四个重要的基本概念：<br>1.数据类型自身的对齐值：<br>&nbsp; 对于char型数据，其自身对齐值为1，对于short型为2，对于int,float,double类型，其自身对齐值为4，单位字节。<br>2.结构体或者类的自身对齐值：其成员中自身对齐值最大的那个值。<br>3.指定对齐值：#pragma pack (value)时的指定对齐值value。<br>4.数据成员、结构体和类的有效对齐值：自身对齐值和指定对齐值中小的那个值。<br>有 了这些值，我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值，最重要。有效对齐N，就是 表示&#8220;对齐在N上&#8221;，也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数 据结构的起始地址。结构体的成员变量要对齐排放，结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数 倍，结合下面例子理解)。这样就不能理解上面的几个例子的值了。<br>例子分析：<br>分析例子B；<br>struct B<br>{<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>假 设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值，在笔者环境下，该值默认为4。第一个成员变量b的自身对齐值是1，比指定或者默认指定 对齐值4小，所以其有效对齐值为1，所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a，其自身对齐值为4，所以有效对齐值也为4， 所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中，复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为 2，所以有效对齐值也是2，可以存放在0x0008到0x0009这两个字节空间中，符合0x0008%2=0。所以从0x0000到0x0009存放的 都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是a）所以就是4，所以结构体的有效对齐值也是4。根据结构体圆整的要求， 0x0009到0x0000=10字节，（10＋2）％4＝0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B 共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了, 因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那 么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一 个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据，其 自身对齐值为1，对于short型为2，对于int,float,double类型，其自身对齐值为4，这些已有类型的自身对齐值也是基于数组考虑的,只 是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.<br>同理,分析上面例子C：<br>#pragma pack (2) /*指定按2字节对齐*/<br>struct C<br>{<br>&nbsp;&nbsp;&nbsp; char b;<br>&nbsp;&nbsp;&nbsp; int a;<br>&nbsp;&nbsp;&nbsp; short c;<br>};<br>#pragma pack () /*取消指定对齐，恢复缺省对齐*/<br>第 一个变量b的自身对齐值为1，指定对齐值为2，所以，其有效对齐值为1，假设C从0x0000开始，那么b存放在0x0000，符合0x0000%1= 0;第二个变量，自身对齐值为4，指定对齐值为2，所以有效对齐值为2，所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续 字节中，符合0x0002%2=0。第三个变量c的自身对齐值为2，所以有效对齐值为2，顺序存放<br>在0x0006、0x0007中，符合 0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4，所以C的有效对齐值为2。又8%2=0,C 只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.</p>
<p>四.如何修改编译器的默认对齐值?<br>1.在VC IDE中，可以这样修改：[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改，默认是8字节。<br>2.在编码时，可以这样动态修改：#pragma pack .注意:是pragma而不是progma.</p>
<p>五.针对字节对齐,我们在编程中如何考虑?</p>
<p>&nbsp;&nbsp;&nbsp; 如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照 类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做 法是显式的插入reserved成员：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct A{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char a;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char reserved[3];//使用空间换时间<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int b;<br>}</p>
<p>reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.</p>
<p>六.字节对齐可能带来的隐患:<br>&nbsp;&nbsp;&nbsp; 代码中关于对齐的隐患，很多是隐式的。比如在强制类型转换的时候。例如：<br>unsigned int i = 0x12345678;<br>unsigned char *p=NULL;<br>unsigned short *p1=NULL;</p>
<p>p=&amp;i;<br>*p=0x00;<br>p1=(unsigned short *)(p+1);<br>*p1=0x0000;<br>最后两句代码，从奇数边界去访问unsignedshort型变量，显然不符合对齐的规定。<br>在x86上，类似的操作只会影响效率，但是在MIPS或者sparc上，可能就是一个error,因为它们要求必须字节对齐.</p>
<p>七.如何查找与字节对齐方面的问题:<br>如果出现对齐或者赋值问题首先查看<br>1. 编译器的big little端设置<br>2. 看这种体系本身是否支持非对齐访问<br>3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。</p>
<p>八.相关文章:转自<a href="http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx">http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx</a></p>
<p>&nbsp;ARM下的对齐处理 <br>from DUI0067D_ADS1_2_CompLib </p>
<p>3.13 type&nbsp; qulifiers </p>
<p>有部分摘自ARM编译器文档对齐部分</p>
<p>对齐的使用:<br>1.__align(num)<br>&nbsp;&nbsp; 这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时<br>&nbsp;&nbsp; 就要用到此命令__align(8)进行修饰限制。来保证数据对象是相应对齐。<br>&nbsp;&nbsp; 这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节<br>&nbsp;&nbsp; 对齐,但是不能让4字节的对象2字节对齐。<br>&nbsp;&nbsp; __align是存储类修改,他只修饰最高级类型对象不能用于结构或者函数对象。<br>&nbsp;&nbsp; <br>2.__packed <br>&nbsp; __packed是进行一字节对齐<br>&nbsp; 1.不能对packed的对象进行对齐<br>&nbsp; 2.所有对象的读写访问都进行非对齐访问<br>&nbsp; 3.float及包含float的结构联合及未用__packed的对象将不能字节对齐<br>&nbsp; 4.__packed对局部整形变量无影响<br>&nbsp; 5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定<br>&nbsp; 义为packed。<br>&nbsp;&nbsp;&nbsp;&nbsp; __packed int* p;&nbsp; //__packed int 则没有意义<br>&nbsp; 6.对齐或非对齐读写访问带来问题<br>&nbsp; __packed struct STRUCT_TEST<br>&nbsp;{<br>&nbsp; char a;<br>&nbsp; int b;<br>&nbsp; char c;<br>&nbsp;}&nbsp; ;&nbsp;&nbsp;&nbsp; //定义如下结构此时b的起始地址一定是不对齐的<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL]<br>//将下面变量定义成全局静态不在栈上 <br>static char* p;<br>static struct STRUCT_TEST a;<br>void Main()<br>{<br>&nbsp;__packed int* q;&nbsp; //此时定义成__packed来修饰当前q指向为非对齐的数据地址下面的访问则可以</p>
<p>&nbsp;p = (char*)&amp;a;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;q = (int*)(p+1);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;<br>&nbsp;*q = 0x87654321; <br>/*&nbsp;&nbsp; <br>得到赋值的汇编指令很清楚<br>ldr&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r5,0x20001590 ; = #0x12345678<br>[0xe1a00005]&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r0,r5<br>[0xeb0000b0]&nbsp;&nbsp; bl&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; __rt_uwrite4&nbsp; //在此处调用一个写4byte的操作函数 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>[0xe5c10000]&nbsp;&nbsp; strb&nbsp;&nbsp;&nbsp;&nbsp; r0,[r1,#0]&nbsp;&nbsp; //函数进行4次strb操作然后返回保证了数据正确的访问<br>[0xe1a02420]&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r2,r0,lsr #8<br>[0xe5c12001]&nbsp;&nbsp; strb&nbsp;&nbsp;&nbsp;&nbsp; r2,[r1,#1]<br>[0xe1a02820]&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r2,r0,lsr #16<br>[0xe5c12002]&nbsp;&nbsp; strb&nbsp;&nbsp;&nbsp;&nbsp; r2,[r1,#2]<br>[0xe1a02c20]&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r2,r0,lsr #24<br>[0xe5c12003]&nbsp;&nbsp; strb&nbsp;&nbsp;&nbsp;&nbsp; r2,[r1,#3]<br>[0xe1a0f00e]&nbsp;&nbsp; mov&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pc,r14<br>*/</p>
<p>/*<br>如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败<br>[0xe59f2018]&nbsp;&nbsp; ldr&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r2,0x20001594 ; = #0x87654321<br>[0xe5812000]&nbsp;&nbsp; str&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r2,[r1,#0]<br>*/</p>
<p>//这样可以很清楚的看到非对齐访问是如何产生错误的<br>//以及如何消除非对齐访问带来问题<br>//也可以看到非对齐访问和对齐访问的指令差异导致效率问题<br>} <br></p>
<img src ="http://www.cppblog.com/hainan/aggbug/21708.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-04-12 11:59 <a href="http://www.cppblog.com/hainan/archive/2007/04/12/21708.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>构造函数、析构函数与赋值函数</title><link>http://www.cppblog.com/hainan/archive/2007/04/12/21699.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Thu, 12 Apr 2007 03:42:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/04/12/21699.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/21699.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/04/12/21699.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/21699.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/21699.html</trackback:ping><description><![CDATA[<p>构造函数、析构函数与赋值函数是每个类最基本的函数。它们太普通以致让人容易麻痹大意，其实这些貌似简单的函数就象没有顶盖的下水道那样危险。 <br>每个类只有一个析构函数和一个赋值函数，但可以有多个构造函数（包含一个拷贝构造函数，其它的称为普通构造函数）。对于任意一个类A，如果不想编写上述函数，C++编译器将自动为A产生四个缺省的函数，如</p>
<p>A(void); // 缺省的无参数构造函数</p>
<p>A(const A &amp;a); // 缺省的拷贝构造函数</p>
<p>~A(void); // 缺省的析构函数</p>
<p>A &amp; operate =(const A &amp;a); // 缺省的赋值函数</p>
<p><br>这不禁让人疑惑，既然能自动生成函数，为什么还要程序员编写？</p>
<p>原因如下：</p>
<p>（1）如果使用&#8220;缺省的无参数构造函数&#8221;和&#8220;缺省的析构函数&#8221;，等于放弃了自主&#8220;初始化&#8221;和&#8220;清除&#8221;的机会，C++发明人Stroustrup的好心好意白费了。</p>
<p>（2）&#8220;缺省的拷贝构造函数&#8221;和&#8220;缺省的赋值函数&#8221;均采用&#8220;位拷贝&#8221;而非&#8220;值拷贝&#8221;的方式来实现，倘若类中含有指针变量，这两个函数注定将出错。</p>
<p><br>对于那些没有吃够苦头的C++程序员，如果他说编写构造函数、析构函数与赋值函数很容易，可以不用动脑筋，表明他的认识还比较肤浅，水平有待于提高。</p>
<p>本章以类String的设计与实现为例，深入阐述被很多教科书忽视了的道理。String的结构如下：</p>
<p>class String</p>
<p>{</p>
<p>public:</p>
<p>String(const char *str = NULL); // 普通构造函数</p>
<p>String(const String &amp;other); // 拷贝构造函数</p>
<p>~ String(void); // 析构函数</p>
<p>String &amp; operate =(const String &amp;other); // 赋值函数</p>
<p>private:</p>
<p>char *m_data; // 用于保存字符串</p>
<p>};</p>
<p>&nbsp;</p>
<p>9.1 构造函数与析构函数的起源<br>作为比C更先进的语言，C++提供了更好的机制来增强程序的安全性。C++编译器具有严格的类型安全检查功能，它几乎能找出程序中所有的语法问题，这的确帮了程序员的大忙。但是程序通过了编译检查并不表示错误已经不存在了，在&#8220;错误&#8221;的大家庭里，&#8220;语法错误&#8221;的地位只能算是小弟弟。级别高的错误通常隐藏得很深，就象狡猾的罪犯，想逮住他可不容易。</p>
<p>根据经验，不少难以察觉的程序错误是由于变量没有被正确初始化或清除造成的，而初始化和清除工作很容易被人遗忘。Stroustrup在设计C++语言时充分考虑了这个问题并很好地予以解决：把对象的初始化工作放在构造函数中，把清除工作放在析构函数中。当对象被创建时，构造函数被自动执行。当对象消亡时，析构函数被自动执行。这下就不用担心忘了对象的初始化和清除工作。</p>
<p>构造函数与析构函数的名字不能随便起，必须让编译器认得出才可以被自动执行。Stroustrup的命名方法既简单又合理：让构造函数、析构函数与类同名，由于析构函数的目的与构造函数的相反，就加前缀&#8216;~&#8217;以示区别。</p>
<p>除了名字外，构造函数与析构函数的另一个特别之处是没有返回值类型，这与返回值类型为void的函数不同。构造函数与析构函数的使命非常明确，就象出生与死亡，光溜溜地来光溜溜地去。如果它们有返回值类型，那么编译器将不知所措。为了防止节外生枝，干脆规定没有返回值类型。</p>
<p>&nbsp;</p>
<p>9.2 构造函数的初始化表<br>构造函数有个特殊的初始化方式叫&#8220;初始化表达式表&#8221;（简称初始化表）。初始化表位于函数参数表之后，却在函数体 {} 之前。这说明该表里的初始化工作发生在函数体内的任何代码被执行之前。</p>
<p>构造函数初始化表的使用规则：</p>
<p>u 如果类存在继承关系，派生类必须在其初始化表里调用基类的构造函数。</p>
<p>例如</p>
<p>class A</p>
<p>{&#8230;</p>
<p>A(int x); // A的构造函数</p>
<p>}; </p>
<p>class B : public A</p>
<p>{&#8230;</p>
<p>B(int x, int y);// B的构造函数</p>
<p>};</p>
<p>B::B(int x, int y)</p>
<p>: A(x) // 在初始化表里调用A的构造函数</p>
<p>{</p>
<p>&#8230;</p>
<p>} </p>
<p>u 类的const常量只能在初始化表里被初始化，因为它不能在函数体内用赋值的方式来初始化（参见5.4节）。</p>
<p>u 类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式，这两种方式的效率不完全相同。</p>
<p>非内部数据类型的成员对象应当采用第一种方式初始化，以获取更高的效率。例如</p>
<p>class A</p>
<p>{&#8230;</p>
<p>A(void); // 无参数构造函数</p>
<p>A(const A &amp;other); // 拷贝构造函数</p>
<p>A &amp; operate =( const A &amp;other); // 赋值函数</p>
<p>}；</p>
<p><br>class B</p>
<p>{</p>
<p>public:</p>
<p>B(const A &amp;a); // B的构造函数</p>
<p>private: </p>
<p>A m_a; // 成员对象</p>
<p>};</p>
<p><br>示例9-2(a)中，类B的构造函数在其初始化表里调用了类A的拷贝构造函数，从而将成员对象m_a初始化。</p>
<p>示例9-2 (b)中，类B的构造函数在函数体内用赋值的方式将成员对象m_a初始化。我们看到的只是一条赋值语句，但实际上B的构造函数干了两件事：先暗地里创建m_a对象（调用了A的无参数构造函数），再调用类A的赋值函数，将参数a赋给m_a。</p>
<p><br>B::B(const A &amp;a)</p>
<p>: m_a(a) </p>
<p>{ </p>
<p>&#8230; </p>
<p>}<br>B::B(const A &amp;a)</p>
<p>{</p>
<p>m_a = a;</p>
<p>&#8230;</p>
<p>}</p>
<p><br>示例9-2(a) 成员对象在初始化表中被初始化 示例9-2(b) 成员对象在函数体内被初始化</p>
<p><br>对于内部数据类型的数据成员而言，两种初始化方式的效率几乎没有区别，但后者的程序版式似乎更清晰些。若类F的声明如下：</p>
<p>class F</p>
<p>{</p>
<p>public:</p>
<p>F(int x, int y); // 构造函数</p>
<p>private:</p>
<p>int m_x, m_y;</p>
<p>int m_i, m_j;</p>
<p>}</p>
<p>示例9-2(c)中F的构造函数采用了第一种初始化方式，示例9-2(d)中F的构造函数采用了第二种初始化方式。</p>
<p><br>F::F(int x, int y)</p>
<p>: m_x(x), m_y(y) </p>
<p>{ </p>
<p>m_i = 0; </p>
<p>m_j = 0;</p>
<p>}<br>F::F(int x, int y)</p>
<p>{ </p>
<p>m_x = x;</p>
<p>m_y = y;</p>
<p>m_i = 0; </p>
<p>m_j = 0;</p>
<p>}</p>
<p><br>示例9-2(c) 数据成员在初始化表中被初始化 示例9-2(d) 数据成员在函数体内被初始化</p>
<p>&nbsp;</p>
<p><br>9.3 构造和析构的次序<br>构造从类层次的最根处开始，在每一层中，首先调用基类的构造函数，然后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行，该次序是唯一的，否则编译器将无法自动执行析构过程。</p>
<p>一个有趣的现象是，成员对象初始化的次序完全不受它们在初始化表中次序的影响，只由成员对象在类中声明的次序决定。这是因为类的声明是唯一的，而类的构造函数可以有多个，因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造，这将导致析构函数无法得到唯一的逆序。[Eckel, p260-261]</p>
<p>9.4 示例：类String的构造函数与析构函数<br>// String的普通构造函数</p>
<p>String::String(const char *str)</p>
<p>{</p>
<p>if(str==NULL)</p>
<p>{</p>
<p>m_data = new char[1];</p>
<p>*m_data = &#8216;\0&#8217;;</p>
<p>} </p>
<p>else</p>
<p>{</p>
<p>int length = strlen(str);</p>
<p>m_data = new char[length+1];</p>
<p>strcpy(m_data, str);</p>
<p>}</p>
<p>} </p>
<p><br>// String的析构函数</p>
<p>String::~String(void)</p>
<p>{</p>
<p>delete [] m_data; </p>
<p>// 由于m_data是内部数据类型，也可以写成 delete m_data;</p>
<p>}</p>
<p>9.5 不要轻视拷贝构造函数与赋值函数<br>由于并非所有的对象都会使用拷贝构造函数和赋值函数，程序员可能对这两个函数有些轻视。请先记住以下的警告，在阅读正文时就会多心：</p>
<p>u 本章开头讲过，如果不主动编写拷贝构造函数和赋值函数，编译器将以&#8220;位拷贝&#8221;的方式自动生成缺省的函数。倘若类中含有指针变量，那么这两个缺省的函数就隐含了错误。以类String的两个对象a,b为例，假设a.m_data的内容为&#8220;hello&#8221;，b.m_data的内容为&#8220;world&#8221;。</p>
<p>现将a赋给b，缺省赋值函数的&#8220;位拷贝&#8221;意味着执行b.m_data = a.m_data。这将造成三个错误：一是b.m_data原有的内存没被释放，造成内存泄露；二是b.m_data和a.m_data指向同一块内存，a或b任何一方变动都会影响另一方；三是在对象被析构时，m_data被释放了两次。</p>
<p><br>u 拷贝构造函数和赋值函数非常容易混淆，常导致错写、错用。拷贝构造函数是在对象被创建时调用的，而赋值函数只能被已经存在了的对象调用。以下程序中，第三个语句和第四个语句很相似，你分得清楚哪个调用了拷贝构造函数，哪个调用了赋值函数吗？</p>
<p>String a(&#8220;hello&#8221;);</p>
<p>String b(&#8220;world&#8221;);</p>
<p>String c = a; // 调用了拷贝构造函数，最好写成 c(a);</p>
<p>c = b; // 调用了赋值函数</p>
<p>本例中第三个语句的风格较差，宜改写成String c(a) 以区别于第四个语句。</p>
<p>9.6 示例：类String的拷贝构造函数与赋值函数<br>// 拷贝构造函数</p>
<p>String::String(const String &amp;other)</p>
<p>{ </p>
<p>// 允许操作other的私有成员m_data</p>
<p>int length = strlen(other.m_data); </p>
<p>m_data = new char[length+1];</p>
<p>strcpy(m_data, other.m_data);</p>
<p>}</p>
<p><br>// 赋值函数</p>
<p>String &amp; String::operate =(const String &amp;other)</p>
<p>{ </p>
<p>// (1) 检查自赋值</p>
<p>if(this == &amp;other)</p>
<p>return *this;</p>
<p><br>// (2) 释放原有的内存资源</p>
<p>delete [] m_data;</p>
<p><br>// （3）分配新的内存资源，并复制内容</p>
<p>int length = strlen(other.m_data); </p>
<p>m_data = new char[length+1];</p>
<p>strcpy(m_data, other.m_data);</p>
<p><br>// （4）返回本对象的引用</p>
<p>return *this;</p>
<p>} </p>
<p><br>类String拷贝构造函数与普通构造函数（参见9.4节）的区别是：在函数入口处无需与NULL进行比较，这是因为&#8220;引用&#8221;不可能是NULL，而&#8220;指针&#8221;可以为NULL。</p>
<p>&nbsp;</p>
<p>类String的赋值函数比构造函数复杂得多，分四步实现：</p>
<p>（1）第一步，检查自赋值。你可能会认为多此一举，难道有人会愚蠢到写出 a = a 这样的自赋值语句！的确不会。但是间接的自赋值仍有可能出现，例如</p>
<p><br>// 内容自赋值</p>
<p>b = a;</p>
<p>&#8230;</p>
<p>c = b;</p>
<p>&#8230;</p>
<p>a = c; <br>// 地址自赋值</p>
<p>b = &amp;a;</p>
<p>&#8230;</p>
<p>a = *b;</p>
<p>&nbsp;</p>
<p>也许有人会说：&#8220;即使出现自赋值，我也可以不理睬，大不了化点时间让对象复制自己而已，反正不会出错！&#8221;</p>
<p>他真的说错了。看看第二步的delete，自杀后还能复制自己吗？所以，如果发现自赋值，应该马上终止函数。注意不要将检查自赋值的if语句</p>
<p>if(this == &amp;other)</p>
<p>错写成为</p>
<p>if( *this == other)</p>
<p>（2）第二步，用delete释放原有的内存资源。如果现在不释放，以后就没机会了，将造成内存泄露。</p>
<p>（3）第三步，分配新的内存资源，并复制字符串。注意函数strlen返回的是有效字符串长度，不包含结束符&#8216;\0&#8217;。函数strcpy则连&#8216;\0&#8217;一起复制。</p>
<p>（4）第四步，返回本对象的引用，目的是为了实现象 a = b = c 这样的链式表达。注意不要将 return *this 错写成 return this 。那么能否写成return other 呢？效果不是一样吗？</p>
<p>不可以！因为我们不知道参数other的生命期。有可能other是个临时对象，在赋值结束后它马上消失，那么return other返回的将是垃圾。</p>
<p>9.7 偷懒的办法处理拷贝构造函数与赋值函数<br>如果我们实在不想编写拷贝构造函数和赋值函数，又不允许别人使用编译器生成的缺省函数，怎么办？</p>
<p>偷懒的办法是：只需将拷贝构造函数和赋值函数声明为私有函数，不用编写代码。</p>
<p>例如：</p>
<p>class A</p>
<p>{ &#8230;</p>
<p>private:</p>
<p>A(const A &amp;a); // 私有的拷贝构造函数</p>
<p>A &amp; operate =(const A &amp;a); // 私有的赋值函数</p>
<p>};</p>
<p><br>如果有人试图编写如下程序：</p>
<p>A b(a); // 调用了私有的拷贝构造函数</p>
<p>b = a; // 调用了私有的赋值函数</p>
<p>编译器将指出错误，因为外界不可以操作A的私有函数。</p>
<p>&nbsp;</p>
<img src ="http://www.cppblog.com/hainan/aggbug/21699.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-04-12 11:42 <a href="http://www.cppblog.com/hainan/archive/2007/04/12/21699.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>volatile关键字</title><link>http://www.cppblog.com/hainan/archive/2007/04/12/21697.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Thu, 12 Apr 2007 03:37:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/04/12/21697.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/21697.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/04/12/21697.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/21697.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/21697.html</trackback:ping><description><![CDATA[<h2><u><font color=#810081></font></u>&nbsp;</h2>
<div class=postbody>
<div class=postsub>
<p>　　volatile关键字是一种类型修饰符，用它声明的类型变量表示可以被某些编译器未知的因素更改，比如：操作系统、硬件或者其它线程等。遇到这个关键字声明的变量，编译器对访问该变量的代码就不再进行优化，从而可以提供对特殊地址的稳定访问。<br><br>使用该关键字的例子如下：</p>
<pre>int volatile nVint;</pre>
<p>　　当要求使用volatile 声明的变量的值的时候，系统总是重新从它所在的内存读取数据，即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。<br><br>例如：</p>
<pre>volatile int i=10;
int a = i;
...
//其他代码，并未明确告诉编译器，对i进行过操作
int b = i;</pre>
<p>　　volatile 指出 i是随时可能发生变化的，每次使用它的时候必须从i的地址中读取，因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是，由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作，它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来，如果i是一个寄存器变量或者表示一个端口数据就容易出错，所以说volatile可以保证对特殊地址的稳定访问。<br>　　注意，在vc6中，一般调试模式没有进行代码优化，所以这个关键字的作用看不出来。下面通过插入汇编代码，测试有无volatile关键字，对程序最终代码的影响：<br>　　首先，用classwizard建一个win32 console工程，插入一个voltest.cpp文件，输入下面的代码：<br>　</p>
<pre>#include &lt;stdio.h&gt;
void main()
{
int i=10;
int a = i;
printf("i= %d\n",a);
//下面汇编语句的作用就是改变内存中i的值，但是又不让编译器知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d\n",b);
}      </pre>
<p>然后，在调试版本模式运行程序，输出结果如下：</p>
<pre>i = 10
i = 32</pre>
<p>然后，在release版本模式运行程序，输出结果如下：</p>
<pre>i = 10
i = 10</pre>
<p>输出的结果明显表明，release模式下，编译器对代码进行了优化，第二次没有输出正确的i值。下面，我们把 i的声明加上volatile关键字，看看有什么变化：</p>
<pre>#include &lt;stdio.h&gt;
void main()
{
volatile int i=10;
int a = i;
printf("i= %d\n",a);
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d\n",b);
}      </pre>
<p>分别在调试版本和release版本运行程序，输出都是：</p>
<pre>i = 10
i = 32</pre>
<p>这说明这个关键字发挥了它的作用！<br></p>
</div>
</div>
<img src ="http://www.cppblog.com/hainan/aggbug/21697.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-04-12 11:37 <a href="http://www.cppblog.com/hainan/archive/2007/04/12/21697.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>链表类</title><link>http://www.cppblog.com/hainan/archive/2007/04/12/21694.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Thu, 12 Apr 2007 03:32:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/04/12/21694.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/21694.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/04/12/21694.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/21694.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/21694.html</trackback:ping><description><![CDATA[<p>template&lt;class type&gt;<br>class ListNode<br>{<br>&nbsp;public:<br>&nbsp;&nbsp;ListNode()<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;nest=NULL;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;ListNode(const type &amp;item,ListNode&lt;type&gt; *next1=NULL)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;data=item;<br>&nbsp;&nbsp;&nbsp;next=next1;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;type data;<br>&nbsp;&nbsp;ListNode&lt;type&gt; *next;<br>};</p>
<p><br>template&lt;class type&gt;<br>class ablist<br>{<br>&nbsp;public:<br>&nbsp;&nbsp;ListNode&lt;type&gt; *GetHead()<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;return head;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;ListNode&lt;type&gt; *GetNext(ListNode&lt;type&gt; &amp;n)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;return n.next==head?n.next-&gt;next:n.next;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;type Get(int i);<br>&nbsp;&nbsp;bool Set(type x,int i);<br>&nbsp;&nbsp;ListNode&lt;type&gt; *Find1(type value);<br>&nbsp;&nbsp;ListNode&lt;type&gt; *Find(int i);<br>&nbsp;&nbsp;void MakeEmpty();<br>&nbsp;&nbsp;virtual bool Insert(type value,int i)=0;<br>&nbsp;&nbsp;virtual type Remove(int i)=0;<br>&nbsp;&nbsp;virtual type Remove1(type value)=0;<br>&nbsp;protected:<br>&nbsp;&nbsp;ListNode&lt;type&gt; *head;<br>&nbsp;&nbsp;int length;<br>};<br><br><br>template&lt;class type&gt;<br>bool ablist&lt;type&gt;::Set(type x,int i)<br>{<br>&nbsp;ListNode&lt;type&gt; *p=Find(i);<br>&nbsp;if(p==NULL||p==head)<br>&nbsp;{<br>&nbsp;&nbsp;return false;<br>&nbsp;}<br>&nbsp;else<br>&nbsp;{<br>&nbsp;&nbsp;p-&gt;data=x;<br>&nbsp;}<br>&nbsp;return true;<br>}</p>
<p>template&lt;class type&gt;<br>type ablist&lt;type&gt;::Get(int i)<br>{<br>&nbsp;ListNode&lt;type&gt; *p=Find(i);<br>&nbsp;assert(p&amp;&amp;p!=head);<br>&nbsp;return p-&gt;data;<br>}<br></p>
<p><br><br>template&lt;class type&gt;<br>void ablist&lt;type&gt;::MakeEmpty()<br>{<br>&nbsp;ListNode&lt;type&gt; *q=head-&gt;next;<br>&nbsp;int i=1;<br>&nbsp;while(i++&lt;=length)<br>&nbsp;{<br>&nbsp;&nbsp;head-&gt;next=q-&gt;next;<br>&nbsp;&nbsp;delete q;<br>&nbsp;&nbsp;q=head-&gt;next;<br>&nbsp;}<br>&nbsp;length=0;<br>}<br></p>
<p><br><br>template&lt;class type&gt;<br>ListNode&lt;type&gt; *ablist&lt;type&gt;::Find1(type value)<br>{<br>&nbsp;ListNode&lt;type&gt; *p=head-&gt;next;<br>&nbsp;int i=1;<br>&nbsp;while(i++&lt;=length&amp;&amp;p-&gt;data!=value)<br>&nbsp;{<br>&nbsp;&nbsp;p=p-&gt;next;<br>&nbsp;}<br>&nbsp;return p;<br>}<br></p>
<p><br><br>template&lt;class type&gt;<br>ListNode&lt;type&gt; *ablist&lt;type&gt;::Find(int i)<br>{<br>&nbsp;if(i&lt;0||i&gt;length)<br>&nbsp;{<br>&nbsp;&nbsp;return NULL;<br>&nbsp;}<br>&nbsp;if(i==0)<br>&nbsp;{<br>&nbsp;&nbsp;return head;<br>&nbsp;}<br>&nbsp;ListNode&lt;type&gt; *p=head-&gt;next;<br>&nbsp;int j=1;<br>&nbsp;while(p!=Null&amp;&amp;j&lt;i)<br>&nbsp;{<br>&nbsp;&nbsp;p=p-&gt;next;<br>&nbsp;&nbsp;j++;<br>&nbsp;}<br>&nbsp;return p;<br>}</p>
<p>&nbsp;</p>
<p>class List:public ablist&lt;type&gt;<br>{<br>public:<br>&nbsp;List()<br>&nbsp;{<br>&nbsp;&nbsp;head=new ListNode&lt;type&gt;;<br>&nbsp;&nbsp;length=0;<br>&nbsp;}<br>&nbsp;List(List&lt;type&gt; &amp;l)<br>&nbsp;{<br>&nbsp;&nbsp;Copy(l);<br>&nbsp;}<br>&nbsp;~List()<br>&nbsp;{<br>&nbsp;&nbsp;MakeEmpty();<br>&nbsp;&nbsp;delete head;<br>&nbsp;}<br>&nbsp;bool Insert(type value,int i);<br>&nbsp;type Remove(int i);<br>&nbsp;type Remove1(type value);<br>&nbsp;List&lt;type&gt; &amp;Copy(List&lt;type&gt; &amp;l);<br>&nbsp;List&lt;type&gt; &amp;operator=(List&lt;type&gt; &amp;l);<br>&nbsp;friend ostream &amp;operator&lt;&lt;(ostream &amp;,List&lt;type&gt; &amp;);<br>};<br></p>
<p><br><br>template&lt;class type&gt;<br>bool List&lt;type&gt;::Insert(type value,int i)<br>{<br>&nbsp;ListNode&lt;type&gt; *p=Find(i-1);<br>&nbsp;if(p=NULL)<br>&nbsp;{<br>&nbsp;&nbsp;return false;<br>&nbsp;}<br>&nbsp;ListNode&lt;type&gt; *newnode=new ListNode&lt;type&gt;(value,p-&gt;next);<br>&nbsp;assert(newnode);<br>&nbsp;p-&gt;next=newnode;<br>&nbsp;length++;<br>&nbsp;return true;<br>}<br><br><br>template&lt;class type&gt;<br>type List&lt;type&gt;::Remove(int i)<br>{<br>&nbsp;ListNode&lt;type&gt; *p=Find(i-1),*q;<br>&nbsp;assert(p&amp;&amp;p-&gt;next);<br>&nbsp;q=p-&gt;next;<br>&nbsp;p-&gt;next=q-&gt;next;<br>&nbsp;type value=q-&gt;data;<br>&nbsp;delete q;<br>&nbsp;length--;<br>&nbsp;return value;<br>}<br></p>
<p><br><br>template&lt;class type&gt;<br>type List&lt;type&gt;::Remove1(type value)<br>{<br>&nbsp;ListNode&gt;type&gt; *q,p=head;<br>&nbsp;while(p-&gt;next!=NULL&amp;&amp;p-&gt;next-&gt;data!=value)<br>&nbsp;{<br>&nbsp;&nbsp;p=p-&gt;next;<br>&nbsp;}<br>&nbsp;assert(p-&gt;next);<br>&nbsp;p-&gt;next=q-&gt;next;<br>&nbsp;delete q;<br>&nbsp;length--;<br>&nbsp;return value;<br>}<br></p>
<p><br><br>template&lt;class type&gt;<br>List&lt;type&gt;&amp; List&lt;type&gt;::Copy(List&lt;type&gt; &amp;l)<br>{<br>&nbsp;ListNode&lt;type&gt; *p,*q,*r;<br>&nbsp;Length=l.length;<br>&nbsp;Head=NULL;<br>&nbsp;if(!l.head)<br>&nbsp;{<br>&nbsp;&nbsp;return *this;<br>&nbsp;}<br>&nbsp;head=new ListNode&lt;type&gt;;<br>&nbsp;if(!head)<br>&nbsp;{<br>&nbsp;&nbsp;return *this;<br>&nbsp;}<br>&nbsp;head-&gt;data=(l.head)-&gt;data;<br>&nbsp;head-&gt;next=NULL;<br>&nbsp;r=NULL;<br>&nbsp;p=head;<br>&nbsp;q=l.head-&gt;next;<br>&nbsp;while(q)<br>&nbsp;{<br>&nbsp;&nbsp;r=new ListNode&lt;type&gt;;<br>&nbsp;&nbsp;if(!r)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;return *this;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;r-&gt;data=q-&gt;data;<br>&nbsp;&nbsp;r-next=NULL;<br>&nbsp;&nbsp;p-&gt;next=r;<br>&nbsp;&nbsp;p=p-&gt;next;<br>&nbsp;&nbsp;q=q-&gt;next;<br>&nbsp;}<br>&nbsp;return *this;<br>}<br></p>
<p><br><br>template&lt;class type&gt;<br>List&lt;type&gt;&amp; List&lt;type&gt;::operator=(List&lt;type&gt;&amp; l)<br>{<br>&nbsp;if(head)<br>&nbsp;{<br>&nbsp;&nbsp;MakeEmpty();<br>&nbsp;}<br>&nbsp;Copy(l);<br>&nbsp;return *this;<br>}</p>
<p>template&lt;class type&gt;<br>ostream&amp; operator&lt;&lt;(ostream&amp; out,List&lt;type&gt; &amp;l)<br>{<br>&nbsp;ListNode&lt;type&gt; *p=l.head-&gt;next;<br>&nbsp;out&lt;&lt;"length:"&lt;&lt;l.length&lt;&lt;"\ndata";<br>&nbsp;while(p)<br>&nbsp;{<br>&nbsp;&nbsp;out&lt;&lt;p-&gt;data&lt;&lt;"\n";<br>&nbsp;&nbsp;p=p-&gt;next;<br>&nbsp;}<br>&nbsp;out&lt;&lt;"输出完毕";<br>&nbsp;return out;<br>}</p>
<p><br>template&lt;class type&gt;<br>class CirList:public ablist&lt;type&gt;<br>{<br>&nbsp;public:<br>&nbsp;&nbsp;CirList();<br>&nbsp;&nbsp;CirList(CirList&lt;type&gt; &amp;l)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;Copy(l);<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;~CirList()<br>&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;MakeEmpty();<br>&nbsp;&nbsp;&nbsp;&nbsp;delete head;<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;bool Insert(type value,int i);<br>&nbsp;&nbsp;&nbsp;type Remove(int i);<br>&nbsp;&nbsp;&nbsp;type Remove1(type value);<br>&nbsp;&nbsp;&nbsp;CirList&lt;type&gt;&amp; Copy(CirList&amp; l);<br>&nbsp;&nbsp;&nbsp;CirList&lt;type&gt;&amp; operator=(CirList&lt;type&gt; &amp;l);<br>&nbsp;&nbsp;&nbsp;friend ostream&amp; operator&lt;&lt;(ostream&amp;,CirList&lt;type&gt;&amp;);<br>&nbsp;private:<br>&nbsp;&nbsp;ListNode&lt;type&gt;* tail;<br>};<br><br><br><br>template&lt;class type&gt;<br>CirList&lt;type&gt;::CirList()<br>{<br>&nbsp;tail=head=new ListNode&lt;type&gt;;<br>&nbsp;tail-&gt;next=head;<br>&nbsp;length=0;<br>}<br></p>
<p><br><br>template&lt;class type&gt;<br>CirList&lt;type&gt;&amp; CirList&lt;type&gt;::Copy(CirList&lt;type&gt; &amp;l)<br>{<br>&nbsp;ListNode&lt;type&gt; *p.*q,*r;<br>&nbsp;Length=l.length;<br>&nbsp;head=tail=NULL;<br>&nbsp;if(!l.head)<br>&nbsp;{<br>&nbsp;&nbsp;return *this;<br>&nbsp;}<br>&nbsp;head-&gt;data=(l.head)-&gt;data;<br>&nbsp;tail=head;<br>&nbsp;tail-&gt;next=head;<br>&nbsp;r=head;<br>&nbsp;p=head;<br>&nbsp;q=l.head-&gt;next;<br>&nbsp;while(q!=l.head)<br>&nbsp;{<br>&nbsp;&nbsp;r=new ListNode&lt;type&gt;;<br>&nbsp;&nbsp;if(!r)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;return *this;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;r-&gt;data=q-&gt;data;<br>&nbsp;&nbsp;r-&gt;next=head;<br>&nbsp;&nbsp;p-&gt;next=r;<br>&nbsp;&nbsp;tail=r;<br>&nbsp;&nbsp;p=p-&gt;next;<br>&nbsp;&nbsp;q=q-&gt;next;<br>&nbsp;}<br>&nbsp;return *this;<br>}<br></p>
<p><br><br>template&lt;class type&gt;<br>bool CirList&lt;type&gt;::Insert(type value,int i)<br>{<br>&nbsp;ListNode&lt;type&gt;* p=Find(i-1);<br>&nbsp;if(p==NULL)<br>&nbsp;{<br>&nbsp;&nbsp;return false;<br>&nbsp;}<br>&nbsp;ListNode&lt;type&gt;* newnode=new ListNode&lt;type&gt;(value,p-&gt;next);<br>&nbsp;if(p-&gt;next==head)<br>&nbsp;{<br>&nbsp;&nbsp;tail=newnode;<br>&nbsp;&nbsp;tail-&gt;next=head;<br>&nbsp;}<br>&nbsp;p-&gt;next=newnode;<br>&nbsp;length++;<br>&nbsp;return true;<br>}<br>template&lt;class type&gt;<br>type CirList&lt;type&gt;::Remove(int i)<br>{<br>&nbsp;ListNode&lt;type&gt; *p=Find(i-1);<br>&nbsp;assert(!(p==NULL||p-&gt;next==head));<br>&nbsp;q=p-&gt;next;<br>&nbsp;p-&gt;next=q-&gt;next;<br>&nbsp;type value=q-&gt;data;<br>&nbsp;if(q==tail)<br>&nbsp;{<br>&nbsp;&nbsp;tail=p;<br>&nbsp;&nbsp;tail-&gt;next=head;<br>&nbsp;}<br>&nbsp;delete q;<br>&nbsp;length--;<br>&nbsp;return value;<br>}<br></p>
<p><br><br>template&lt;class type&gt;<br>type CirList&lt;type&gt;::Remove1(type value)<br>{<br>&nbsp;ListNode&lt;type&gt; *q,*p=head;<br>&nbsp;while(p-&gt;next!=head&amp;&amp;p-&gt;next-&gt;data!=value)<br>&nbsp;{<br>&nbsp;&nbsp;p=p-&gt;next;<br>&nbsp;&nbsp;assert(!(p-&gt;next==head&amp;&amp;p-&gt;data!=value);<br>&nbsp;}<br>&nbsp;q=p-&gt;next;<br>&nbsp;p-&gt;next=q-&gt;next;<br>&nbsp;if(q==tail)<br>&nbsp;{<br>&nbsp;&nbsp;tail=p;<br>&nbsp;&nbsp;tail-&gt;next=head;<br>&nbsp;}<br>&nbsp;delete q;<br>&nbsp;length--;<br>&nbsp;return value;<br>}</p>
<p><br>template&lt;class type&gt;<br>CirList&lt;type&gt;&amp; CirList&lt;type&gt;::operator=(CirList&lt;type&gt; &amp;l)<br>{<br>&nbsp;if(head)<br>&nbsp;{<br>&nbsp;&nbsp;MakeEmpty();<br>&nbsp;&nbsp;Copy(l);<br>&nbsp;}<br>&nbsp;return *this;<br>}<br></p>
<p><br><br>template&lt;class type&gt;<br>ostream&amp; operator&lt;&lt;(ostream&amp; out,CirList&lt;type&gt; &amp;l)<br>{<br>&nbsp;ListNode&lt;type&gt; *p=l.head-&gt;next;<br>&nbsp;out&lt;&lt;"\nlength:"&lt;&lt;l.length&lt;&lt;"\ndata:";<br>&nbsp;while(p!=head);<br>&nbsp;{<br>&nbsp;&nbsp;out&lt;&lt;p-&gt;data&lt;&lt;"\n";<br>&nbsp;&nbsp;p=p-&gt;next;<br>&nbsp;}<br>&nbsp;out&lt;&lt;"输出完毕\n";<br>&nbsp;return out;<br>}</p>
<p><br>&nbsp;</p>
<img src ="http://www.cppblog.com/hainan/aggbug/21694.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-04-12 11:32 <a href="http://www.cppblog.com/hainan/archive/2007/04/12/21694.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>static</title><link>http://www.cppblog.com/hainan/archive/2007/03/28/20771.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Wed, 28 Mar 2007 07:12:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/03/28/20771.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/20771.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/03/28/20771.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/20771.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/20771.html</trackback:ping><description><![CDATA[C++的static有两种用法：面向过程程序设计中的static和面向对象程序设计中的static。前者应用于普通变量和函数，不涉及类；后者主要说明static在类中的作用。 
<p><b>一、面向过程设计中的static</b></p><p>1、静态全局变量</p><p>在全局变量前，加上关键字static，该变量就被定义成为一个静态全局变量。我们先举一个静态全局变量的例子，如下： </p><pre>//Example 1
#include &lt;iostream.h&gt;
void fn();
static int n; //定义静态全局变量
void main()
{
	n=20;
	cout&lt;&lt;n&lt;&lt;endl;
	fn();
}

void fn()
{
	n++;
	cout&lt;&lt;n&lt;&lt;endl;
}
</pre>静态全局变量有以下特点： 
<ul><li>该变量在全局数据区分配内存； 
</li><li>未经初始化的静态全局变量会被程序自动初始化为0（自动变量的值是随机的，除非它被显式初始化）； 
</li><li>静态全局变量在声明它的整个文件都是可见的，而在文件之外是不可见的；　 </li></ul>静态变量都在全局数据区分配内存，包括后面将要提到的静态局部变量。对于一个完整的程序，在内存中的分布情况如下：<br /><br />　 
<table id="table1" width="10%" border="1"><tbody><tr><td>代码区</td></tr><tr><td>全局数据区</td></tr><tr><td>堆区</td></tr><tr><td>栈区</td></tr></tbody></table><br />一般程序的由new产生的动态数据存放在堆区，函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间，静态数据（即使是函数内部的静态局部变量）也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。细心的读者可能会发现，Example 1中的代码中将 <pre>	static int n; //定义静态全局变量</pre>改为<pre>	int n; //定义全局变量</pre>程序照样正常运行。<br />的确，定义全局变量就可以实现变量在文件中的共享，但定义静态全局变量还有以下好处： 
<ul><li>静态全局变量不能被其它文件所用； 
</li><li>其它文件中可以定义相同名字的变量，不会发生冲突； </li></ul><p>您可以将上述示例代码改为如下：<br /></p><pre>//Example 2
//File1
#include &lt;iostream.h&gt;
void fn();
static int n; //定义静态全局变量
void main()
{
	n=20;
	cout&lt;&lt;n&lt;&lt;endl;
	fn();
}

//File2
#include &lt;iostream.h&gt;
extern int n;
void fn()
{
	n++;
	cout&lt;&lt;n&lt;&lt;endl;
}
</pre>编译并运行Example 2，您就会发现上述代码可以分别通过编译，但运行时出现错误。试着将 <pre>static int n; //定义静态全局变量
</pre>改为 <pre>int n; //定义全局变量
</pre>再次编译运行程序，细心体会全局变量和静态全局变量的区别。 
<p>2、静态局部变量</p><p>在局部变量前，加上关键字static，该变量就被定义成为一个静态局部变量。 </p><p>我们先举一个静态局部变量的例子，如下： </p><pre>//Example 3
#include &lt;iostream.h&gt;
void fn();
void main()
{
	fn();
	fn();
	fn();
}
void fn()
{
	static n=10;
	cout&lt;&lt;n&lt;&lt;endl;
	n++;
}
</pre>　　通常，在函数体内定义了一个变量，每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体，系统就会收回栈内存，局部变量也相应失效。<br />　　但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来，变量已经不再属于函数本身了，不再仅受函数的控制，给程序的维护带来不便。<br />　　静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区，而不是保存在栈中，每次的值保持到下一次调用，直到下次赋新值。 
<p>静态局部变量有以下特点：</p><ul><li>该变量在全局数据区分配内存； 
</li><li>静态局部变量在程序执行到该对象的声明处时被首次初始化，即以后的函数调用不再进行初始化； 
</li><li>静态局部变量一般在声明处初始化，如果没有显式初始化，会被程序自动初始化为0； 
</li><li>它始终驻留在全局数据区，直到程序运行结束。但其作用域为局部作用域，当定义它的函数或语句块结束时，其作用域随之结束； </li></ul><p>3、静态函数</p><p>　　在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同，它只能在声明它的文件当中可见，不能被其它文件使用。</p><p>静态函数的例子： </p><pre>//Example 4
#include &lt;iostream.h&gt;
static void fn();//声明静态函数
void main()
{
	fn();
}
void fn()//定义静态函数
{
	int n=10;
	cout&lt;&lt;n&lt;&lt;endl;
}
</pre>定义静态函数的好处： 
<ul><li>静态函数不能被其它文件所用； 
</li><li>其它文件中可以定义相同名字的函数，不会发生冲突； </li></ul><p><b>二、面向对象的static关键字（类中的static关键字）</b></p><p>1、静态数据成员</p><p>在类内数据成员的声明前加上关键字static，该数据成员就是类内的静态数据成员。先举一个静态数据成员的例子。 </p><pre>//Example 5
#include &lt;iostream.h&gt;
class Myclass
{
public:
	Myclass(int a,int b,int c);
	void GetSum();
private:
	int a,b,c;
	static int Sum;//声明静态数据成员
};
int Myclass::Sum=0;//定义并初始化静态数据成员

Myclass::Myclass(int a,int b,int c)
{
	this-&gt;a=a;
	this-&gt;b=b;
	this-&gt;c=c;
	Sum+=a+b+c;
}

void Myclass::GetSum()
{
	cout&lt;&lt;"Sum="&lt;&lt;Sum&lt;&lt;endl;
}

void main()
{
	Myclass M(1,2,3);
	M.GetSum();
	Myclass N(4,5,6);
	N.GetSum();
	M.GetSum();

}
</pre>可以看出，静态数据成员有以下特点： 
<ul><li>对于非静态数据成员，每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个，静态数据成员在程序中也只有一份拷贝，由该类型的所有对象共享访问。也就是说，静态数据成员是该类的所有对象所共有的。对该类的多个对象来说，静态数据成员只分配一次内存，供所有对象共用。所以，静态数据成员的值对每个对象都是一样的，它的值可以更新； 
</li><li>静态数据成员存储在全局数据区。静态数据成员定义时要分配空间，所以不能在类声明中定义。在Example 5中，语句int Myclass::Sum=0;是定义静态数据成员； 
</li><li>静态数据成员和普通数据成员一样遵从public,protected,private访问规则； 
</li><li>因为静态数据成员在全局数据区分配内存，属于本类的所有对象共享，所以，它不属于特定的类对象，在没有产生类对象时其作用域就可见，即在没有产生类的实例时，我们就可以操作它； 
</li><li>静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为：<br />＜数据类型＞＜类名＞::＜静态数据成员名＞=＜值＞ 
</li><li>类的静态数据成员有两种访问形式：<br />＜类对象名＞.＜静态数据成员名＞ 或 ＜类类型名＞::＜静态数据成员名＞<br />如果静态数据成员的访问权限允许的话（即public的成员），可在程序中，按上述格式来引用静态数据成员 ； 
</li><li>静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类，每个实例的利息都是相同的。所以，应该把利息设为存款类的静态数据成员。这有两个好处，第一，不管定义多少个存款类对象，利息数据成员都共享分配在全局数据区的内存，所以节省存储空间。第二，一旦利息需要改变时，只要改变一次，则所有存款类对象的利息全改变过来了； 
</li><li>同全局变量相比，使用静态数据成员有两个优势： </li></ul><ol><li>静态数据成员没有进入程序的全局名字空间，因此不存在与程序中其它全局名字冲突的可能性； 
</li><li>可以实现信息隐藏。静态数据成员可以是private成员，而全局变量不能；<br /></li></ol>2、静态成员函数 
<p>　　与静态数据成员一样，我们也可以创建一个静态成员函数，它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样，都是类的内部实现，属于类定义的一部分。普通的成员函数一般都隐含了一个this指针，this指针指向类的对象本身，因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下，this是缺省的。如函数fn()实际上是this-&gt;fn()。但是与普通函数相比，静态成员函数由于不是与任何的对象相联系，因此它不具有this指针。从这个意义上讲，它无法访问属于类对象的非静态数据成员，也无法访问非静态成员函数，它只能调用其余的静态成员函数。下面举个静态成员函数的例子。 </p><pre>//Example 6
#include &lt;iostream.h&gt;
class Myclass
{
public:
	Myclass(int a,int b,int c);
	static void GetSum();/声明静态成员函数
private:
	int a,b,c;
	static int Sum;//声明静态数据成员
};
int Myclass::Sum=0;//定义并初始化静态数据成员

Myclass::Myclass(int a,int b,int c)
{
	this-&gt;a=a;
	this-&gt;b=b;
	this-&gt;c=c;
	Sum+=a+b+c; //非静态成员函数可以访问静态数据成员
}

void Myclass::GetSum() //静态成员函数的实现
{
//	cout&lt;&lt;a&lt;&lt;endl; //错误代码，a是非静态数据成员
	cout&lt;&lt;"Sum="&lt;&lt;Sum&lt;&lt;endl;
}

void main()
{
	Myclass M(1,2,3);
	M.GetSum();
	Myclass N(4,5,6);
	N.GetSum();
	Myclass::GetSum();
}
</pre><p></p><p></p>关于静态成员函数，可以总结为以下几点： 
<p></p><ul><li>出现在类体外的函数定义不能指定关键字static； 
</li><li>静态成员之间可以相互访问，包括静态成员函数访问静态数据成员和访问静态成员函数； 
</li><li>非静态成员函数可以任意地访问静态成员函数和静态数据成员； 
</li><li>静态成员函数不能访问非静态成员函数和非静态数据成员； 
</li><li>由于没有this指针的额外开销，因此静态成员函数与类的全局函数相比速度上会有少许的增长； 
</li><li>调用静态成员函数，可以用成员访问操作符(.)和(-&gt;)为一个类的对象或指向类对象的指针调用静态成员函数，也可以直接使用如下格式：<br />＜类名＞::＜静态成员函数名＞（＜参数表＞）<br />调用类的静态成员函数。</li></ul><img src ="http://www.cppblog.com/hainan/aggbug/20771.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-03-28 15:12 <a href="http://www.cppblog.com/hainan/archive/2007/03/28/20771.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>多线程</title><link>http://www.cppblog.com/hainan/archive/2007/03/28/20770.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Wed, 28 Mar 2007 07:09:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/03/28/20770.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/20770.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/03/28/20770.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/20770.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/20770.html</trackback:ping><description><![CDATA[当前流行的Windows操作系统，它能同时运行几个程序(独立运行的程序又称之为进程)，对于同一个程序，它又可以分成若干个独立的执行流，我们称之为线程，线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法，进程和线程的概念的出现，对提高软件的并行性有着重要的意义。现在的应用软件无一不是多线程多任务处理，单线城的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本文针对多线程技术在应用中经常遇到的问题，如线程间的通信、同步等，对它们分别进行探讨。 <br /><br />　　 一、 理解线程 <br /><br />　　 要讲解线程，不得不说一下进程，进程是应用程序的执行实例，每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单，它是一个独立的执行流，是进程内部的一个独立的执行单元，相当于一个子程序，它对应Visual C++中的CwinThread类的对象。单独一个执行程序运行时，缺省的运行包含的一个主线程，主线程以函数地址的形式，如main或WinMain函数，提供程序的启动点，当主线程终止时，进程也随之终止，但根据需要，应用程序又可以分解成许多独立执行的线程，每个线程并行的运行在同一进程中。 <br /><br />　　 一个进程中的所有线程都在该进程的虚拟地址空间中，使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片，在某一个时刻，CPU只执行一个时间片内的线程，多个时间片中的相应线程在CPU内轮流执行，由于每个时间片时间很短，所以对用户来说，仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间，优先级高的线程优先运行，优先级低的线程则继续等待。 <br /><br />　　 线程被分为两种：用户界面线程和工作线程（又称为后台线程）。用户界面线程通常用来处理用户的输入并响应各种事件和消息，其实，应用程序的主执行线程CWinAPP对象就是一个用户界面线程，当应用程序启动时自动创建和启动，同样它的终止也意味着该程序的结束，进城终止。工作者线程用来执行程序的后台处理任务，比如计算、调度、对串口的读写操作等，它和用户界面线程的区别是它不用从CwinThread类派生来创建，对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本；最后需要读者明白的是，一个进程中的所有线程共享它们父进程的变量，但同时每个线程可以拥有自己的变量。 <br />　　 二、 线程的管理和操作 <br /><br />　　 1． 线程的启动 <br /><br />　　 创建一个用户界面线程，首先要从类CwinThread产生一个派生类，同时必须使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。 <br /><br />　　 第二步是根据需要重载该派生类的一些成员函数如：ExitInstance()；InitInstance()；OnIdle();PreTranslateMessage()等函数，最后启动该用户界面线程，调用AfxBeginThread()函数的一个版本：CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );其中第一个参数为指向定义的用户界面线程类指针变量，第二个参数为线程的优先级，第三个参数为线程所对应的堆栈大小，第四个参数为线程创建时的附加标志，缺省为正常状态，如为CREATE_SUSPENDED则线程启动后为挂起状态。 <br /><br />　　 对于工作线程来说，启动一个线程，首先需要编写一个希望与应用程序的其余部分并行运行的函数如Fun1()，接着定义一个指向CwinThread对象的指针变量*pThread,调用AfxBeginThread(Fun1,param,priority)函数，返回值付给pThread变量的同时一并启动该线程来执行上面的Fun1()函数，其中Fun1是线程要运行的函数的名字，也既是上面所说的控制函数的名字，param是准备传送给线程函数Fun1的任意32位值，priority则是定义该线程的优先级别，它是预定义的常数，读者可参考MSDN。 <br /><br />　　 2．线程的优先级 <br /><br />　　 以下的CwinThread类的成员函数用于线程优先级的操作： <br /><br />int GetThreadPriority(); <br />BOOL SetThradPriority()(int nPriority); <br /><br />上述的二个函数分别用来获取和设置线程的优先级，这里的优先级，是相对于该线程所处的优先权层次而言的，处于同一优先权层次的线程，优先级高的线程先运行；处于不同优先权层次上的线程，谁的优先权层次高，谁先运行。至于优先级设置所需的常数，自己参考MSDN就可以了，要注意的是要想设置线程的优先级，这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置，CwinThread类没有提供相应的函数，但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityClass()来实现。 <br /><br />　　 3．线程的悬挂、恢复 <br /><br />　　 CwinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数，其中SuspendThread()用来悬挂线程，暂停线程的执行；ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread()，则需要连续执行相应次的ResumeThread()来恢复线程的运行。 <br /><br />　　 4．结束线程 <br /><br />　　 终止线程有三种途径，线程可以在自身内部调用AfxEndThread()来终止自身的运行；可以在线程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )来强行终止一个线程的运行，然后调用CloseHandle（）函数释放线程所占用的堆栈；第三种方法是改变全局变量，使线程的执行函数返回，则该线程终止。下面以第三种方法为例，给出部分代码： <br /><br />//////////////////////////////////////////////////////////////// <br />//////CtestView message handlers <br />/////Set to True to end thread <br />Bool bend=FALSE;//定义的全局变量，用于控制线程的运行 <br />//The Thread Function <br />UINT ThreadFunction(LPVOID pParam)//线程函数 <br />{ <br />while(!bend) <br />{Beep(100,100); <br />Sleep(1000); <br />} <br />return 0; <br />} <br />CwinThread *pThread; <br />HWND hWnd; <br />///////////////////////////////////////////////////////////// <br />Void CtestView::OninitialUpdate() <br />{ <br />hWnd=GetSafeHwnd(); <br />pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程 <br />pThread-&gt;m_bAutoDelete=FALSE;//线程为手动删除 <br />Cview::OnInitialUpdate(); <br />} <br />//////////////////////////////////////////////////////////////// <br />Void CtestView::OnDestroy() <br />{ bend=TRUE;//改变变量，线程结束 <br />WaitForSingleObject(pThread-&gt;m_hThread,INFINITE);//等待线程结束 <br />delete pThread;//删除线程 <br />Cview::OnDestroy(); <br />} <br />　　 三、 线程之间的通信 <br /><br />　　 通常情况下，一个次级线程要为主线程完成某种特定类型的任务，这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下，有下面的几种方法实现这种通信任务：使用全局变量（上一节的例子其实使用的就是这种方法）、使用事件对象、使用消息。这里我们主要介绍后两种方法。 <br /><br />　　 1． 利用用户定义的消息通信 <br /><br />　　 在Windows程序设计中，应用程序的每一个线程都拥有自己的消息队列，甚至工作线程也不例外，这样一来，就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息，如下所示：#define WM_USERMSG WMUSER+100；在需要的时候，在一个线程中调用 <br /><br />：：PostMessage((HWND)param,WM_USERMSG,0,0) <br />或 <br />CwinThread::PostThradMessage() <br /><br />来向另外一个线程发送这个消息，上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改，修改后的结果是在线程结束时显示一个对话框，提示线程结束： <br /><br />UINT ThreadFunction(LPVOID pParam) <br />{ <br />while(!bend) <br />{ <br />Beep(100,100); <br />Sleep(1000); <br />} <br />：：PostMessage(hWnd,WM_USERMSG,0,0)； <br />return 0; <br />} <br />////////WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam,LPARAM lParam) <br />LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam) <br />{ <br />AfxMessageBox("Thread ended."); <br />Retrun 0; <br />} <br /><br />上面的例子是工作者线程向用户界面线程发送消息，对于工作者线程，如果它的设计模式也是消息驱动的，那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息，让它在后台完成。在控制函数中可以直接使用：：GetMessage()这个SDK函数进行消息分检和处理，自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时，线程将系统分配给它的时间片让给其它线程，不无效的占用CPU的时间，如果消息队列不为空，就获取这个消息，判断这个消息的内容并进行相应的处理。 <br /><br />　　 2．用事件对象实现通信 <br /><br />　　 在线程之间传递信号进行通信比较复杂的方法是使用事件对象，用MFC的Cevent类的对象来表示。事件对象处于两种状态之一：有信号和无信号，线程可以监视处于有信号状态的事件，以便在适当的时候执行对事件的操作。上述例子代码修改如下： <br /><br />//////////////////////////////////////////////////////////////////// <br />Cevent threadStart,threadEnd; <br />//////////////////////////////////////////////////////////////////// <br />UINT ThreadFunction(LPVOID pParam) <br />{ <br />：：WaitForSingleObject(threadStart.m_hObject,INFINITE); <br />AfxMessageBox("Thread start."); <br />while(!bend) <br />{ <br />Beep(100,100); <br />Sleep(1000); <br />Int result=::WaitforSingleObject(threadEnd.m_hObject,0); <br />//等待threadEnd事件有信号，无信号时线程在这里悬停 <br />If(result==Wait_OBJECT_0) <br />Bend=TRUE; <br />} <br />：：PostMessage(hWnd,WM_USERMSG,0,0)； <br />return 0; <br />} <br />///////////////////////////////////////////////////////////// <br />Void CtestView::OninitialUpdate() <br />{ <br />hWnd=GetSafeHwnd(); <br />threadStart.SetEvent();//threadStart事件有信号 <br />pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程 <br />pThread-&gt;m_bAutoDelete=FALSE; <br />Cview::OnInitialUpdate(); <br />} <br />//////////////////////////////////////////////////////////////// <br />Void CtestView::OnDestroy() <br />{ threadEnd.SetEvent(); <br />WaitForSingleObject(pThread-&gt;m_hThread,INFINITE); <br />delete pThread; <br />Cview::OnDestroy(); <br />} <br /><br />运行这个程序，当关闭程序时，才显示提示框，显示"Thread ended" <br />　　 四、 线程之间的同步 <br /><br />　　 前面我们讲过，各个线程可以访问进程中的公共变量，所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据，以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题，MFC支持的七个多线程的同步类可以分成两大类：同步对象（CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent）和同步访问对象（CmultiLock和CsingleLock）。本节主要介绍临界区（critical section）、互斥（mutexe）、信号量（semaphore），这些同步对象使各个线程协调工作，程序运行起来更安全。 <br /><br />　　 1． 临界区 <br /><br />　　 临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中，需要给各个线程提供一个共享的临界区对象，无论哪个线程占有临界区对象，都可以访问受到保护的数据，这时候其它的线程需要等待，直到该线程释放临界区对象为止，临界区被释放后，另外的线程可以强占这个临界区，以便访问共享的数据。临界区对应着一个CcriticalSection对象，当线程需要访问保护数据时，调用临界区对象的Lock()成员函数；当对保护数据的操作完成之后，调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权，以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程，它们对应的函数分别为WriteThread()和ReadThread()，用以对公共数组组array[]操作，下面的代码说明了如何使用临界区对象： <br /><br />#include "afxmt.h" <br />int array[10],destarray[10]; <br />CCriticalSection Section; <br />//////////////////////////////////////////////////////////////////////// <br />UINT WriteThread(LPVOID param) <br />{Section.Lock(); <br />for(int x=0;x&lt;10;x++) <br />array[x]=x; <br />Section.Unlock(); <br />} <br />UINT ReadThread(LPVOID param) <br />{ <br />Section.Lock(); <br />For(int x=0;x&lt;10;x++) <br />Destarray[x]=array[x]; <br />Section.Unlock(); <br />} <br /><br />上述代码运行的结果应该是Destarray数组中的元素分别为1-9，而不是杂乱无章的数，如果不使用同步，则不是这个结果，有兴趣的读者可以实验一下。 <br />　　 2． 互斥 <br /><br />　　 互斥与临界区很相似，但是使用时相对复杂一些，它不仅可以在同一应用程序的线程间实现同步，还可以在不同的进程间实现同步，从而实现资源的安全共享。互斥与Cmutex类的对象相对应，使用互斥对象时，必须创建一个CSingleLock或CMultiLock对象，用于实际的访问控制，因为这里的例子只处理单个互斥，所以我们可以使用CSingleLock对象，该对象的Lock()函数用于占有互斥，Unlock()用于释放互斥。实现代码如下： <br /><br />#include "afxmt.h" <br />int array[10],destarray[10]; <br />CMutex Section; <br /><br />///////////////////////////////////////////////////////////// <br />UINT WriteThread(LPVOID param) <br />{ CsingleLock singlelock; <br />singlelock (&amp;Section); <br />singlelock.Lock(); <br />for(int x=0;x&lt;10;x++) <br />array[x]=x; <br />singlelock.Unlock(); <br />} <br />UINT ReadThread(LPVOID param) <br />{ CsingleLock singlelock; <br />singlelock (&amp;Section); <br />singlelock.Lock(); <br /><br />For(int x=0;x&lt;10;x++) <br />Destarray[x]=array[x]; <br />singlelock.Unlock(); <br /><br />} <br /><br />　　 3． 信号量 <br /><br />　　 信号量的用法和互斥的用法很相似，不同的是它可以同一时刻允许多个线程访问同一个资源，创建一个信号量需要用Csemaphore类声明一个对象，一旦创建了一个信号量对象，就可以用它来对资源的访问技术。要实现计数处理，先创建一个CsingleLock或CmltiLock对象，然后用该对象的Lock()函数减少这个信号量的计数值，Unlock()反之。下面的代码分别启动三个线程，执行时同时显示二个消息框，然后10秒后第三个消息框才得以显示。 <br /><br />///////////////////////////////////////////////////////////////// <br />Csemaphore *semaphore; <br />Semaphore=new Csemaphore(2,2); <br />HWND hWnd=GetSafeHwnd(); <br />AfxBeginThread(threadProc1,hWnd); <br />AfxBeginThread(threadProc2,hWnd); <br />AfxBeginThread(threadProc3,hWnd); <br />////////////////////////////////////////////////////////////////////// <br />UINT ThreadProc1(LPVOID param) <br />{CsingleLock singelLock(semaphore); <br />singleLock.Lock(); <br />Sleep(10000); <br />::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK); <br />return 0; <br />} <br />UINT ThreadProc2(LPVOID param) <br />{CSingleLock singelLock(semaphore); <br />singleLock.Lock(); <br />Sleep(10000); <br />::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK); <br />return 0; <br />} <br />UINT ThreadProc3(LPVOID param) <br />{CsingleLock singelLock(semaphore); <br />singleLock.Lock(); <br />Sleep(10000); <br />::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK); <br />return 0; <br />} <br /><br /><br />　　 对复杂的应用程序来说，线程的应用给应用程序提供了高效、快速、安全的数据处理能力。<img src ="http://www.cppblog.com/hainan/aggbug/20770.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-03-28 15:09 <a href="http://www.cppblog.com/hainan/archive/2007/03/28/20770.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>   如何重载增量运算符 ++ 和 --</title><link>http://www.cppblog.com/hainan/archive/2007/01/07/17395.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Sun, 07 Jan 2007 09:00:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/01/07/17395.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/17395.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/01/07/17395.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/17395.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/17395.html</trackback:ping><description><![CDATA[
		<p>   如何重载增量运算符 ++ 和 --<br />    运算符++和—有前置和后置两种形式，如果不区分前置和后置，则使用operator++( )或operator--( )即可；否则，要使用operator++( )或operator--( )来重载前置运算符，使用operator++(int)或operator--(int)来重载后置运算符，调用时，参数int被传递给值0。如下列程序段：</p>
		<p>#include &lt;iostream.h&gt;<br />class Clock<br />{<br />public:<br />Clock(int newHour = 0,int newMinute = 0,int newsSecond = 0);<br />void showTime( );<br />Clock operator ++( );<br />Clock operator ++( int );<br />private:<br />void incOneSecond( );<br />int hour,minute,second;<br />};<br />void Clock::incOneSecond( )<br />{<br />second++;<br />if(second&gt;=60)<br />{<br />second=second-60;<br />minute++;<br />if(minute&gt;=60)<br />{<br />minute = minute - 60;<br />hour++;<br />hour=hour%24;<br />}<br />}<br />}<br />Clock::Clock(int newHour , int newMinute, int newSecond)<br />{<br />if(0&lt;=newHour&amp;&amp;newHour&lt;24&amp;&amp;0&lt;=newMinute&amp;&amp;newMinute&lt;60&amp;&amp;0&lt;=newSecond&amp;&amp;newSecond&lt;60)<br />{<br />hour  = newHour;<br />minute = newMinute;<br />second = newSecond;<br />}<br />else  cout&lt;&lt;”time error!”;<br />}<br />void Clock::showTime( )<br />{<br />cout&lt;&lt;hour&lt;&lt;”:”&lt;&lt;minute&lt;&lt;”:”&lt;&lt;second&lt;&lt;endl;<br />}<br />Clock Clock::operator++( ) //前置<br />{<br />incOneSecond( ); <br />cout&lt;&lt;”++Clock:”;<br />return *this;<br />}<br />Clock Clock::operator++( int ) //后置<br />{  Clock temp(*this);<br />incOneSecond( );<br />cout&lt;&lt;”Clock++:”;<br />return temp;<br />}<br />void main( )<br />{<br />Clock myClock(23,59,59);<br />cout&lt;&lt;”first time output:”;<br />myClock.showTime();<br />myClock++.showTime();<br />(++myClock).showtime();<br />}</p>
		<p>对于“++” 和“--”这两个一元运算符，存在前置和后置的问题，在定义时必须有所区分。<br />（1） 用成员函数的形式来进行重载<br />如果++为前增量运算符时，重载函数的一般格式为：<br />&lt; type &gt; ClassName :: operator ++ ( )<br />{<br /> //…<br />}</p>
		<p>如果++为后增量运算符时，重载函数的一般格式为：<br />&lt; type &gt; ClassName :: operator ++ ( int  )<br />{<br /> //…<br />}</p>
		<p>(2) 非成员函数形式的重载<br />以++ 为例说明<br />用友元函数来实现“++”运算符的重载时，前置++运算符的重载的一般格式：<br />friend &lt;type&gt; operator ++ (ClassName &amp; );<br />其中，第一个参数是要实现++运算的对象。</p>
		<p>后置++运算符的重载的一般格式：<br />friend &lt;type&gt; operator ++(ClassName &amp;,int);<br />其中，第一个参数是要实现++运算的对象；而第二个参数除了用于区分是后置运算外，并没有其他意义，故起参数可有可无。</p>
		<p> </p>
<img src ="http://www.cppblog.com/hainan/aggbug/17395.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-01-07 17:00 <a href="http://www.cppblog.com/hainan/archive/2007/01/07/17395.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>两种多态性</title><link>http://www.cppblog.com/hainan/archive/2007/01/07/17394.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Sun, 07 Jan 2007 08:36:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/01/07/17394.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/17394.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/01/07/17394.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/17394.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/17394.html</trackback:ping><description><![CDATA[
		<h1 style="MARGIN: 0cm 35.9pt 0pt 18pt; LAYOUT-GRID-MODE: char; TEXT-INDENT: 21.25pt; TEXT-ALIGN: left; mso-para-margin-top: 0cm; mso-para-margin-right: 3.42gd; mso-para-margin-bottom: .0001pt; mso-para-margin-left: 18.0pt; mso-line-height-alt: 0pt" align="left">
				<span>
						<font face="宋体">两种多态性</font>
				</span>
		</h1>
		<span>
				<font face="宋体">
						<h1 style="MARGIN: 0cm 35.9pt 0pt 18pt; LAYOUT-GRID-MODE: char; TEXT-INDENT: 21.25pt; TEXT-ALIGN: left; mso-para-margin-top: 0cm; mso-para-margin-right: 3.42gd; mso-para-margin-bottom: .0001pt; mso-para-margin-left: 18.0pt; mso-line-height-alt: 0pt" align="left">
								<br />1．编译时的多态性：亦称静态联编（束定）(static binding)或早期联编(early binding)。即本章中前半部分将要讲述的函数和运算符[函数]的重载。编译系统进行编译时在函数地址表中找到多个重载的函数中相对应的一个函数（即形参的数量和类型完全相同的那个函数），将它们与主程序中调用它们的代码联编(binding)，以备主程序运行时正确地调用。这在C＋＋中这表现为“行为共享”（即同名函数实现不同功能），或称“同一接口，多种方法”。</h1>
						<h1 style="MARGIN: 0cm 35.9pt 0pt 18pt; LAYOUT-GRID-MODE: char; TEXT-INDENT: 21.25pt; TEXT-ALIGN: left; mso-para-margin-top: 0cm; mso-para-margin-right: 3.42gd; mso-para-margin-bottom: .0001pt; mso-para-margin-left: 18.0pt; mso-line-height-alt: 0pt" align="left">
								<br />2．运行时的多态性：亦称动态联编（束定）(dynamic binding)或滞后联编（late binding）。编译系统进行编译时，根据程序代码内容、按照当时动态地确定的this指针、找到相应的虚函数，将它与主程序中调用它的代码联编(binding)，以供主程序在运行中调用它（本章后半部分将详细讲解）。<br /></h1>
				</font>
		</span>
<img src ="http://www.cppblog.com/hainan/aggbug/17394.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-01-07 16:36 <a href="http://www.cppblog.com/hainan/archive/2007/01/07/17394.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于while(cin&gt;&gt;str)无法结束的问题.</title><link>http://www.cppblog.com/hainan/archive/2007/01/03/17187.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Wed, 03 Jan 2007 11:45:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/01/03/17187.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/17187.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/01/03/17187.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/17187.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/17187.html</trackback:ping><description><![CDATA[
		<p>输入缓冲是行缓冲。当从键盘上输入一串字符并按回车后，这些字符会首先被送到输入缓冲区中存储。每当按下回车键后，cin.get() 就会检测输入缓冲区中是否有了可读的数据。cin.get() 还会对键盘上是否有作为流结束标志的 Ctrl+Z 或者 Ctrl+D 键按下作出检查，其检查的方式有两种：阻塞式以及非阻塞式。</p>
		<p>阻塞式检查方式指的是只有在回车键按下之后才对此前是否有 Ctrl+Z 组合键按下进行检查，非阻塞式样指的是按下 Ctrl+D 之后立即响应的方式。如果在按 Ctrl+D 之前已经从键盘输入了字符，则 Ctrl+D的作用就相当于回车，即把这些字符送到输入缓冲区供读取使用，此时Ctrl+D不再起流结束符的作用。如果按 Ctrl+D 之前没有任何键盘输入，则 Ctrl+D 就是流结束的信号。</p>
		<p>Windows系统中一般采用阻塞式检查 Ctrl+Z、Unix/Linux系统下一般采用非阻塞式的检查 Ctrl+D。楼主是在Windows系统下，因此使用阻塞式的 Ctrl+Z 来标识流的结束。</p>
		<p>这种阻塞式的方式有一个特点：只有按下回车之后才有可能检测在此之前是否有Ctrl+Z按下。还有一个特点就是：如果输入缓冲区中有可读的数据则不会检测Ctrl+Z（因为有要读的数据，还不能认为到了流的末尾）。还有一点需要知道：Ctrl+Z产生的不是一个普通的ASCII码值，也就是说它产生的不是一个字符，所以不会跟其它从键盘上输入的字符一样能够存放在输入缓冲区。明白了这几点之后就可以来解释楼主提出的问题了。</p>
		<p>从键盘上输入abcd^z 加 回车之后在Windows系统上是这样处理的：由于回车的作用，前面的 abcd 等字符被送到输入缓冲区（注意：上面说过了，^z不会产生字符，所以更不会存储到输入缓冲区，缓冲区中没有 ^z 的存在）。这时，cin.get() 检测到输入缓冲区中已经有数据存在（因此不再检查是否有 ^z 的输入），于是从缓冲中读取相应的数据。如果都读取完了，则输入缓冲区重新变为空，cin.get() 等待新的输入。可见，尽管有 ^z 按下，但是由于在此之前还有其它输入字符（abcd），所以流也不会结束。</p>
		<p>因此，输入流结束的条件就是：^z 之前不能有任何字符输入（回车除外），否则 ^z 起不到流结束的作用。 </p>
<img src ="http://www.cppblog.com/hainan/aggbug/17187.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-01-03 19:45 <a href="http://www.cppblog.com/hainan/archive/2007/01/03/17187.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[]</title><link>http://www.cppblog.com/hainan/archive/2007/01/03/17186.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Wed, 03 Jan 2007 11:35:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/01/03/17186.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/17186.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/01/03/17186.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/17186.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/17186.html</trackback:ping><description><![CDATA[找错题<br /><br />　　试题1：<br /><br /><code>void test1()<br />{<br />　char string[10];<br />　char* str1 = "0123456789";<br />　strcpy( string, str1 );<br />}</code><br />　　试题2：<br /><br /><code>void test2()<br />{<br />　char string[10], str1[10];<br />　int i;<br />　for(i=0; i&lt;10; i++)<br />　{<br />　　str1[i] = 'a';<br />　}<br />　strcpy( string, str1 );<br />}</code><br />　　试题3：<br /><br /><code>void test3(char* str1)<br />{<br />　char string[10];<br />　if( strlen( str1 ) &lt;= 10 )<br />　{<br />　　strcpy( string, str1 );<br />　}<br />}</code><br />　　解答：<br /><br />　　试题1字符串str1需要11个字节才能存放下（包括末尾的’\0’），而string只有10个字节的空间，strcpy会导致数组越界；<br /><br />　　对试题2，如果面试者指出字符数组str1不能在数组内结束可以给3分；如果面试者指出strcpy(string, str1)调用使得从str1内存起复制到string内存起所复制的字节数具有不确定性可以给7分，在此基础上指出库函数strcpy工作方式的给10分；<br /><br />　　对试题3，if(strlen(str1) &lt;= 10)应改为if(strlen(str1) &lt; 10)，因为strlen的结果未统计’\0’所占用的1个字节。<br /><br />　　剖析：<br /><br />　　考查对基本功的掌握：<br /><br />　　(1)字符串以’\0’结尾；<br /><br />　　(2)对数组越界把握的敏感度；<br /><br />　　(3)库函数strcpy的工作方式，如果编写一个标准strcpy函数的总分值为10，下面给出几个不同得分的答案：<br /><br />　　2分<br /><br /><code>void strcpy( char *strDest, char *strSrc )<br />{<br />　 while( (*strDest++ = * strSrc++) != ‘\0’ );<br />}</code><br />　　4分<br /><br /><code>void strcpy( char *strDest, const char *strSrc ) <br />//将源字符串加const，表明其为输入参数，加2分<br />{<br />　 while( (*strDest++ = * strSrc++) != ‘\0’ );<br />}</code><br />　　7分<br /><br /><code>void strcpy(char *strDest, const char *strSrc) <br />{<br />　//对源地址和目的地址加非0断言，加3分<br />　assert( (strDest != NULL) &amp;&amp; (strSrc != NULL) );<br />　while( (*strDest++ = * strSrc++) != ‘\0’ );<br />}</code><br />　　10分<br /><br /><code>//为了实现链式操作，将目的地址返回，加3分！<br /><br />char * strcpy( char *strDest, const char *strSrc ) <br />{<br />　assert( (strDest != NULL) &amp;&amp; (strSrc != NULL) );<br />　char *address = strDest; <br />　while( (*strDest++ = * strSrc++) != ‘\0’ ); <br />　　return address;<br />}</code><br />　　从2分到10分的几个答案我们可以清楚的看到，小小的strcpy竟然暗藏着这么多玄机，真不是盖的！需要多么扎实的基本功才能写一个完美的strcpy啊！<br /><br />　　(4)对strlen的掌握，它没有包括字符串末尾的'\0'。<br /><br />　　读者看了不同分值的strcpy版本，应该也可以写出一个10分的strlen函数了，完美的版本为： int strlen( const char *str ) //输入参数const<br /><br /><code>{<br />　assert( strt != NULL ); //断言字符串地址非0<br />　int len;<br />　while( (*str++) != '\0' ) <br />　{ <br />　　len++; <br />　} <br />　return len;<br />}</code><br />　　试题4：<br /><br /><code>void GetMemory( char *p )<br />{<br />　p = (char *) malloc( 100 );<br />}<br /><br />void Test( void ) <br />{<br />　char *str = NULL;<br />　GetMemory( str ); <br />　strcpy( str, "hello world" );<br />　printf( str );<br />}</code><br />　　试题5：<br /><br /><code>char *GetMemory( void )<br />{ <br />　char p[] = "hello world"; <br />　return p; <br />}<br /><br />void Test( void )<br />{ <br />　char *str = NULL; <br />　str = GetMemory(); <br />　printf( str ); <br />}</code><br />　　试题6：<br /><br /><code>void GetMemory( char **p, int num )<br />{<br />　*p = (char *) malloc( num );<br />}<br /><br />void Test( void )<br />{<br />　char *str = NULL;<br />　GetMemory( &amp;str, 100 );<br />　strcpy( str, "hello" ); <br />　printf( str ); <br />}</code><br />　　试题7：<br /><br /><code>void Test( void )<br />{<br />　char *str = (char *) malloc( 100 );<br />　strcpy( str, "hello" );<br />　free( str ); <br />　... //省略的其它语句<br />}</code><br />　　解答：<br /><br />　　试题4传入中GetMemory( char *p )函数的形参为字符串指针，在函数内部修改形参并不能真正的改变传入形参的值，执行完<br /><br /><code>char *str = NULL;<br />GetMemory( str ); </code><br />　　后的str仍然为NULL；<br /><br />　　试题5中<br /><br /><code>char p[] = "hello world"; <br />return p; </code><br />　　的p[]数组为函数内的局部自动变量，在函数返回后，内存已经被释放。这是许多程序员常犯的错误，其根源在于不理解变量的生存期。<br /><br />　　试题6的GetMemory避免了试题4的问题，传入GetMemory的参数为字符串指针的指针，但是在GetMemory中执行申请内存及赋值语句<br /><br /><code>*p = (char *) malloc( num );</code><br />　　后未判断内存是否申请成功，应加上：<br /><br /><code>if ( *p == NULL )<br />{<br />　...//进行申请内存失败处理<br />}</code><br />　　试题7存在与试题6同样的问题，在执行<br /><br /><code>char *str = (char *) malloc(100);</code><br />　　后未进行内存是否申请成功的判断；另外，在free(str)后未置str为空，导致可能变成一个“野”指针，应加上：<br /><br /><code>str = NULL;</code><br />　　试题6的Test函数中也未对malloc的内存进行释放。<br /><br />　　剖析：<br /><br />　　试题4～7考查面试者对内存操作的理解程度，基本功扎实的面试者一般都能正确的回答其中50~60的错误。但是要完全解答正确，却也绝非易事。<br /><br />　　对内存操作的考查主要集中在：<br /><br />　　（1）指针的理解；<br /><br />　　（2）变量的生存期及作用范围；<br /><br />　　（3）良好的动态内存申请和释放习惯。<br /><br />　　再看看下面的一段程序有什么错误：<br /><br /><code>swap( int* p1,int* p2 )<br />{<br />　int *p;<br />　*p = *p1;<br />　*p1 = *p2;<br />　*p2 = *p;<br />}</code><br />　　在swap函数中，p是一个“野”指针，有可能指向系统区，导致程序运行的崩溃。在VC++中DEBUG运行时提示错误“Access Violation”。该程序应该改为：<br /><br /><code>swap( int* p1,int* p2 )<br />{<br />　int p;<br />　p = *p1;<br />　*p1 = *p2;<br />　*p2 = p;<br />}</code><img src ="http://www.cppblog.com/hainan/aggbug/17186.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/hainan/" target="_blank">Hainan's CppBlog</a> 2007-01-03 19:35 <a href="http://www.cppblog.com/hainan/archive/2007/01/03/17186.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>