﻿<?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</title><link>http://www.cppblog.com/hainan/</link><description /><language>zh-cn</language><lastBuildDate>Thu, 09 Apr 2026 01:05:45 GMT</lastBuildDate><pubDate>Thu, 09 Apr 2026 01:05:45 GMT</pubDate><ttl>60</ttl><item><title>const</title><link>http://www.cppblog.com/hainan/archive/2007/07/18/28318.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Wed, 18 Jul 2007 15:12:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/07/18/28318.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/28318.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/07/18/28318.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/28318.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/28318.html</trackback:ping><description><![CDATA[<p>当我自己写程序需要用到const的时候，或者是读别人的代码碰到const的时候，我常常会停下来想一会儿。许多程序员从来不用const,理由是即使没用const他们也这么过来了。本文仅对const的用法稍作探讨，希望能够对提高软件的源代码质量有所帮助。</p>
<p>
<p>常变量</p>
<p>变量用const修饰，其值不得被改变。任何改变此变量的代码都会产生编译错误。Const加在数据类型前后均可。</p>
<p>例如：</p>
<p>void main(void)</p>
<p>{</p>
<p>const int i = 10; //i,j都用作常变量</p>
<p>int const j = 20;</p>
<p>i = 15; //错误，常变量不能改变</p>
<p>j = 25; //错误，常变量不能改变</p>
<p>}</p>
<p>
<p>常指针</p>
<p>Const跟指针一起使用的时候有两种方法。</p>
<p>
<p>const可用来限制指针不可变。也就是说指针指向的内存地址不可变，但可以随意改变该地址指向的内存的内容。</p>
<p>int main(void) </p>
<p>{ </p>
<p>int i = 10;</p>
<p>int *const j = &amp;i; //常指针, 指向int型变量</p>
<p>(*j)++; //可以改变变量的内容</p>
<p>j++; //错误，不能改变常指针指向的内存地址</p>
<p>}</p>
<p>
<p>const也可用来限制指针指向的内存不可变，但指针指向的内存地址可变。</p>
<p>int main(void)</p>
<p>{</p>
<p>int i = 20;</p>
<p>const int *j = &amp;i; //指针,指向int型常量</p>
<p>//也可以写成int const *j = &amp;i;</p>
<p>j++; //指针指向的内存地址可变</p>
<p>(*j)++; //错误,不能改变内存内容</p>
<p>}</p>
<p>看完上面的两个例子，是不是糊涂了？告诉你一个诀窍，在第一个例子中，const用来修饰指针j,j不可变（也就是指向int变量的常指针）；第二个例子中，const用来修饰*j,*j不可变（也就是指向int常量的指针）。</p>
<p>
<p>这两种方式可以组合起来使用，使指针和内存内容都不可变。</p>
<p>int main(void)</p>
<p>{</p>
<p>int i = 10;</p>
<p>const int *const j = &amp;i; //指向int常量的常指针</p>
<p>j++; //错误，不能改变指针指向的地址</p>
<p>(*j)++; //错误，不能改变常量的值</p>
<p>}</p>
<p>
<p>Const和引用</p>
<p>引用实际上就是变量的别名，这里有几条规则：</p>
<p>声明变量时必须初始化</p>
<p>一经初始化，引用不能在指向其它变量。</p>
<p>任何对引用的改变都将改变原变量。 </p>
<p>引用和变量本身指向同一内存地址。</p>
<p>
<p>下面的例子演示了以上的规则:</p>
<p>void main(void)</p>
<p>{</p>
<p>int i = 10; //i和j是int型变量</p>
<p>int j = 20; </p>
<p>int &amp;r = i; //r 是变量i的引用</p>
<p>int &amp;s; //错误,声明引用时必须初始化</p>
<p>i = 15; //i 和 r 都等于15</p>
<p>i++; //i 和 r都等于16</p>
<p>r = 18; //i 和r 都等于18</p>
<p>printf("Address of i=%u, Address of r=%u",&amp;i,&amp;r); //内存地址相同</p>
<p>r = j; //i 和 r都等于20,但r不是j的引用</p>
<p>r++; //i 和 r 都等于21, j 仍等于20</p>
<p>}</p>
<p>
<p>用const修饰引用,使应用不可修改，但这并不耽误引用反映任何对变量的修改。Const加在数据类型前后均可。</p>
<p>例如：</p>
<p>void main(void)</p>
<p>{</p>
<p>int i = 10;</p>
<p>int j = 100;</p>
<p>const int &amp;r = i;</p>
<p>int const &amp;s = j;</p>
<p>r = 20; //错，不能改变内容</p>
<p>s = 50; //错，不能改变内容</p>
<p>i = 15; // i和r 都等于15</p>
<p>j = 25; // j和s 都等于25</p>
<p>}</p>
<p>
<p>Const和成员函数</p>
<p>声明成员函数时，末尾加const修饰，表示在成员函数内不得改变该对象的任何数据。这种模式常被用来表示对象数据只读的访问模式。例如：</p>
<p>class MyClass</p>
<p>{</p>
<p>char *str ="Hello, World";</p>
<p>MyClass()</p>
<p>{</p>
<p>//void constructor</p>
<p>}</p>
<p>
<p>~MyClass()</p>
<p>{</p>
<p>//destructor</p>
<p>}</p>
<p>
<p>char ValueAt(int pos) const //const method is an accessor method</p>
<p>{</p>
<p>if(pos &gt;= 12)</p>
<p>return 0;</p>
<p>*str = 'M'; //错误，不得修改该对象</p>
<p>return str[pos]; //return the value at position pos</p>
<p>}</p>
<p>}</p>
<p>
<p>Const和重载</p>
<p>重载函数的时候也可以使用const,考虑下面的代码：</p>
<p>class MyClass</p>
<p>{</p>
<p>char *str ="Hello, World";</p>
<p>MyClass()</p>
<p>{</p>
<p>//void constructor</p>
<p>}</p>
<p>
<p>~MyClass()</p>
<p>{</p>
<p>//destructor</p>
<p>}</p>
<p>
<p>char ValueAt(int pos) const //const method is an accessor method</p>
<p>{</p>
<p>if(pos &gt;= 12)</p>
<p>return 0;</p>
<p>return str[pos]; //return the value at position pos</p>
<p>}</p>
<p>
<p>char&amp; ValueAt(int pos) //通过返回引用设置内存内容</p>
<p>{</p>
<p>if(pos &gt;= 12)</p>
<p>return NULL;</p>
<p>return str[pos];</p>
<p>}</p>
<p>}</p>
<p>
<p>在上面的例子中，ValueAt是被重载的。Const实际上是函数参数的一部分，在第一个成员函数中它限制这个函数不能改变对象的数据，而第二个则没有。这个例子只是用来说明const可以用来重载函数，没有什么实用意义。</p>
<p>
<p>实际上我们需要一个新版本的GetValue。如果GetValue被用在operator=的右边，它就会充当一个变量；如果GetValue被用作一元操作符，那么返回的引用可以被修改。这种用法常用来重载操作符。String类的operator[]是个很好的例子。（这一段译得很烂，原文如下：In reality due to the beauty of references just the second definition of GetValue is actually required. If the GetValue method is used on the the right side of an = operator then it will act as an accessor, while if it is used as an l-value (left hand side value) then the returned reference will be modified and the method will be used as setter. This is frequently done when overloading operators. The [] operator in String classes is a good example.）</p>
<p>
<p>class MyClass</p>
<p>{</p>
<p>char *str ="Hello, World";</p>
<p>MyClass()</p>
<p>{</p>
<p>//void constructor</p>
<p>}</p>
<p>
<p>~MyClass()</p>
<p>{</p>
<p>//destructor</p>
<p>}</p>
<p>
<p>char&amp; operator[](int pos) //通过返回引用可用来更改内存内容</p>
<p>{</p>
<p>if(pos &gt;= 12)</p>
<p>return NULL;</p>
<p>return str[pos];</p>
<p>}</p>
<p>}</p>
<p>
<p>void main(void)</p>
<p>{</p>
<p>MyClass m;</p>
<p>char ch = m[0]; //ch 等于 'H'</p>
<p>m[0] = 'M'; //m的成员str变成：Mello, World</p>
<p>}</p>
<p>
<p>Const的担心</p>
<p>C/C++中，数据传递给函数的方式默认的是值传递，也就是说当参数传递给函数时会产生一个该参数的拷贝，这样该函数内任何对该参数的改变都不会扩展到此函数以外。每次调用该函数都会产生一个拷贝，效率不高，尤其是函数调用的次数很高的时候。</p>
<p>例如：</p>
<p>
<p>class MyClass</p>
<p>{</p>
<p>public:</p>
<p>int x; </p>
<p>char ValueAt(int pos) const //const method is an accessor method</p>
<p>{</p>
<p>if(pos &gt;= 12)</p>
<p>return 0;</p>
<p>return str[pos]; //return the value at position pos</p>
<p>}</p>
<p>MyClass()</p>
<p>{</p>
<p>//void constructor</p>
<p>}</p>
<p>~MyClass()</p>
<p>{</p>
<p>//destructor</p>
<p>}</p>
<p>MyFunc(int y) //值传递</p>
<p>{</p>
<p>y = 20;</p>
<p>x = y; //x 和 y 都等于 20.</p>
<p>}</p>
<p>}</p>
<p>
<p>void main(void)</p>
<p>{</p>
<p>MyClass m;</p>
<p>int z = 10;</p>
<p>m.MyFunc(z);</p>
<p>printf("z=%d, MyClass.x=%d",z,m.x); //z 不变, x 等于20.</p>
<p>}</p>
<p>
<p>通过上面的例子可以看出，z没有发生变化，因为MyFunc()操作的是z的拷贝。为了提高效率，我们可以在传递参数的时候，不采用值传递的方式，而采用引用传递。这样传递给函数的是该参数的引用，而不再是该参数的拷贝。然而问题是如果在函数内部改变了参数，这种改变会扩展到函数的外部，有可能会导致错误。在参数前加const修饰保证该参数在函数内部不会被改变。</p>
<p>class MyClass</p>
<p>{</p>
<p>public:</p>
<p>int x;</p>
<p>MyClass()</p>
<p>{</p>
<p>//void constructor</p>
<p>}</p>
<p>~MyClass()</p>
<p>{</p>
<p>//destructor</p>
<p>}</p>
<p>int MyFunc(const int&amp; y) //引用传递, 没有任何拷贝</p>
<p>{</p>
<p>y =20; //错误，不能修改常变量</p>
<p>x = y </p>
<p>}</p>
<p>}</p>
<p>
<p>void main(void)</p>
<p>{</p>
<p>MyClass m;</p>
<p>int z = 10;</p>
<p>m.MyFunc(z);</p>
<p>printf("z=%d, MyClass.x=%d",z,m.x); //z不变, x等于10.</p>
<p>}</p>
<p>
<p>如此，const通过这种简单安全机制使你写不出那种说不定是什么时候就会掉过头来咬你一口的代码。你应该尽可能的使用const引用，通过声明你的函数参数为常变量（任何可能的地方）或者定义那种const method，你就可以非常有效确立这样一种概念：本成员函数不会改变任何函数参数，或者不会改变任何该对象的数据。别的程序员在使用你提供的成员函数的时候，不会担心他们的数据被改得一塌糊涂。</p>
<img src ="http://www.cppblog.com/hainan/aggbug/28318.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 23:12 <a href="http://www.cppblog.com/hainan/archive/2007/07/18/28318.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><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/23/22674.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Mon, 23 Apr 2007 12:39:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/04/23/22674.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/22674.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/04/23/22674.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/22674.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/22674.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如题，最近上课不用心。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;突然发现对老师的说法完全不了解，不知老师在云些什么。觉得有可能是上课不<br>用心的缘故。于是心里有些不安。<br>
<img src ="http://www.cppblog.com/hainan/aggbug/22674.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-23 20:39 <a href="http://www.cppblog.com/hainan/archive/2007/04/23/22674.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>今天心情不好</title><link>http://www.cppblog.com/hainan/archive/2007/04/11/21674.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Wed, 11 Apr 2007 14:46:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/04/11/21674.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/21674.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/04/11/21674.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/21674.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/21674.html</trackback:ping><description><![CDATA[今天心情不好<br>踢球没意思，心情不好<br>还感冒，心情更不好<br>还吵架，心情更更不好<br>不过，确实是心情不好<br><br>她停留在原地，<br>不动，<br>我转过头等了四次，<br>然后的下一次，<br>我没有了耐心，<br>转身就走，<br>怕你难过，不敢回头。<br><br>走得很慢，<br>很想你追上来跟我说话，<br>但慢慢地走了很久，<br>发现后面没有了脚步声。<br><br>回头，不知道你去了哪里，<br>一开始就猜想你偷偷地给我去买感冒药了。<br>有些莫名地为猜想欣喜。<br><br>于是慢慢地跟上你，<br>距离有点远，<br>你应该看不清楚。<br>你经过在十字路口的时候，<br>我打了三个电话给你，<br>没接，突然你回头看，<br>但并没有看到我吧，我想。<br>于是你好像很失望似的，<br>走得很快，我也加快脚步。<br><br><br>远远地，此刻却感觉如此的近。<br>不过原来，你才不是去给我买药。<br>哼。<br>不管我就算了。<br><br>我紧紧跟着，怕漏掉你。<br>很快，你绕过铁门，<br>快要上去宿舍了。<br>我还是跟在后边。<br><br>在宿舍楼梯的转角，<br>你的头刚刚转向，<br>我就踏进了你前一秒钟的视线，<br>你慢慢地上楼去了，<br>有点高。<br><br><br>我在下面一直望着你，<br>看你从一楼走到顶层，<br>有些孤独，有些难过。<br><br>可是我不知道怎么跟你讲，<br>不要看我平时的样子，<br>我也会有不知所措的时候，<br>只是，你不了解。<br><br>我继续，<br>走得很慢。
<img src ="http://www.cppblog.com/hainan/aggbug/21674.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-11 22:46 <a href="http://www.cppblog.com/hainan/archive/2007/04/11/21674.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>生活的磨砺啊(1)</title><link>http://www.cppblog.com/hainan/archive/2007/04/01/21066.html</link><dc:creator>Hainan's CppBlog</dc:creator><author>Hainan's CppBlog</author><pubDate>Sun, 01 Apr 2007 14:39:00 GMT</pubDate><guid>http://www.cppblog.com/hainan/archive/2007/04/01/21066.html</guid><wfw:comment>http://www.cppblog.com/hainan/comments/21066.html</wfw:comment><comments>http://www.cppblog.com/hainan/archive/2007/04/01/21066.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/hainan/comments/commentRss/21066.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/hainan/services/trackbacks/21066.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;今天我某某人生病了，自己当事人相当思路清晰，估计自己是肠胃炎什么之<br>类的，根据是肚子有着肠胃炎独特的剧痛。于是乎，我们在等了十分钟的士后，<br>痛下决心，奔赴人民医院。在车上，我的问题是的士怎么那么难等啊。不过其实<br>也不难，因为，在那十分钟之内我们身边飞驰过了数十部，可里面都有人在用复<br>杂的眼神爱莫能助地观看着车外的这个世界。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;人民医院给我的最大感觉是任何事情都要排队。另外一个特性就是任何事情<br>都有人插队。很实际地讲，严重点病的人在排了诸多队伍之后，下一个要排的就<br>是火葬场的了。人超乱！超杂！低素质！<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在医院确实是有不少牌子写着别插队，但是作为医院最拽的人，医生却不劝<br>阻，而且在他们的眼神中还似乎看到了鼓励。我很奇怪，为什么在中国制订规则<br>的人却往往不维护所制订的规则。没素质！<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;另一个医院的标准规则就是什么都要先去化验，巴不得连亲属都拉上去验的<br>一幅嘴脸。在化验之前我已经提醒过那个混蛋说可能是肠胃炎了，当然他不是没<br>采信我的话，只不过是在化验结果说一点屁事也没有的时候。我很少有想打人的<br>念头的。人家是在很痛很痛着呢！
<img src ="http://www.cppblog.com/hainan/aggbug/21066.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-01 22:39 <a href="http://www.cppblog.com/hainan/archive/2007/04/01/21066.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>