﻿<?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++博客-It is just c plus plus.-随笔分类-C++ Tech</title><link>http://www.cppblog.com/zmllegtui/category/8634.html</link><description>Nothing in my mind.</description><language>zh-cn</language><lastBuildDate>Thu, 19 Sep 2013 20:15:50 GMT</lastBuildDate><pubDate>Thu, 19 Sep 2013 20:15:50 GMT</pubDate><ttl>60</ttl><item><title>Void and void pointer</title><link>http://www.cppblog.com/zmllegtui/archive/2009/11/22/101630.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Sun, 22 Nov 2009 08:14:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2009/11/22/101630.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/101630.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2009/11/22/101630.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/101630.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/101630.html</trackback:ping><description><![CDATA[<strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1.概述<br><br></strong>　　许多初学者对C/C++语言中的void及void指针类型不甚理解，因此在使用上出现了一些错误。本文将对void关键字的深刻含义进行解说，并详述void及void指针类型的使用方法与技巧。<br><br>　　<strong>2.void的含义</strong><br><br>　　void的字面意思是&#8220;无类型&#8221;，void *则为&#8220;无类型指针&#8221;，void *可以指向任何类型的数据。<br><br>　　void几乎只有&#8220;注释&#8221;和限制程序的作用，因为从来没有人会定义一个void变量，让我们试着来定义： <br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>void a;</td>
        </tr>
    </tbody>
</table>
<br>　　这行语句编译时会出错，提示&#8220;illegal use of type 'void'&#8221;。不过，即使void a的编译不会出错，它也没有任何实际意义。<br><br>　　void真正发挥的作用在于：<br><br>　　（1）对函数返回的限定；<br><br>　　（2）对函数参数的限定。<br><br>　　我们将在第三节对以上二点进行具体说明。<br><br>　　众所周知，如果指针p1和p2的类型相同，那么我们可以直接在p1和p2间互相赋值；如果p1和p2指向不同的数据类型，则必须使用强制类型转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。 <br><br>　　例如：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>float *p1;<br>int *p2;<br>p1 = p2;</td>
        </tr>
    </tbody>
</table>
<br>　　其中p1 = p2语句会编译出错，提示&#8220;'=' : cannot convert from 'int *' to 'float *'&#8221;，必须改为：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>p1 = (float *)p2;</td>
        </tr>
    </tbody>
</table>
<br>　　而void *则不同，任何类型的指针都可以直接赋值给它，无需进行强制类型转换： <br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>void *p1;<br>int *p2;<br>p1 = p2;</td>
        </tr>
    </tbody>
</table>
<br>　　但这并不意味着，void *也可以无需强制类型转换地赋给其它类型的指针。因为&#8220;无类型&#8221;可以包容&#8220;有类型&#8221;，而&#8220;有类型&#8221;则不能包容&#8220;无类型&#8221;。道理很简单，我们可以说&#8220;男人和女人都是人&#8221;，但不能说&#8220;人是男人&#8221;或者&#8220;人是女人&#8221;。下面的语句编译出错：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>void *p1;<br>int *p2;<br>p2 = p1;</td>
        </tr>
    </tbody>
</table>
<br>　　提示&#8220;'=' : cannot convert from 'void *' to 'int *'&#8221;。<br><br><strong>3.void的使用<br><br></strong>　　下面给出void关键字的使用规则：<br><br>　　<strong>规则一如果函数没有返回值，那么应声明为void类型</strong><br><br>　　在C语言中，凡不加返回值类型限定的函数，就会被编译器作为返回整型值处理。但是许多程序员却误以为其为void类型。例如：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>add ( int a, int b )<br>{<br>return a + b;<br>}<br>int main(int argc, char* argv[])<br>{<br>printf ( "2 + 3 = %d", add ( 2, 3) );<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　程序运行的结果为输出：<br><br>　　2 + 3 = 5<br><br>　　这说明不加返回值说明的函数的确为int函数。<br><br>　　林锐博士《高质量C/C++编程》中提到：&#8220;C++语言有很严格的类型安全检查，不允许上述情况（指函数不加类型声明）发生&#8221;。可是编译器并不一定这么认定，譬如在Visual C++6.0中上述add函数的编译无错也无警告且运行正确，所以不能寄希望于编译器会做严格的类型检查。<br><br>　　因此，为了避免混乱，我们在编写C/C++程序时，对于任何函数都必须一个不漏地指定其类型。如果函数没有返回值，一定要声明为void类型。这既是程序良好可读性的需要，也是编程规范性的要求。另外，加上void类型声明后，也可以发挥代码的&#8220;自注释&#8221;作用。代码的&#8220;自注释&#8221;即代码能自己注释自己。<br><br>　　<strong>规则二如果函数无参数，那么应声明其参数为void</strong><br><br>　　在C++语言中声明一个这样的函数：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>int function(void)<br>{<br>return 1;<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　则进行下面的调用是不合法的：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>function(2);</td>
        </tr>
    </tbody>
</table>
<br>　　因为在C++中，函数参数为void的意思是这个函数不接受任何参数。<br><br>　　我们在Turbo C 2.0中编译：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>#include "stdio.h"<br>fun()<br>{<br>return 1;<br>}<br>main()<br>{<br>printf("%d",fun(2));<br>getchar();<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　编译正确且输出1，这说明，在C语言中，可以给无参数的函数传送任意类型的参数，但是在C++编译器中编译同样的代码则会出错。在C++中，不能向无参数的函数传送任何参数，出错提示&#8220;'fun' : function does not take 1 parameters&#8221;。<br><br>　　所以，无论在C还是C++中，若函数不接受任何参数，一定要指明参数为void。<br><br>　　<strong>规则三小心使用void指针类型</strong><br><br>　　按照ANSI(American National Standards Institute)标准，不能对void指针进行算法操作，即下列操作都是不合法的：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>void * pvoid;<br>pvoid++; //ANSI：错误<br>pvoid += 1; //ANSI：错误<br>//ANSI标准之所以这样认定，是因为它坚持：进行算法操作的指针必须是确定知道其指向数据类型大小的。<br>//例如：<br>int *pint;<br>pint++; //ANSI：正确</td>
        </tr>
    </tbody>
</table>
<br>　　pint++的结果是使其增大sizeof(int)。<br><br>　　但是大名鼎鼎的GNU(GNU's Not Unix的缩写)则不这么认定，它指定void *的算法操作与char *一致。<br><br>　　因此下列语句在GNU编译器中皆正确：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>pvoid++; //GNU：正确<br>pvoid += 1; //GNU：正确</td>
        </tr>
    </tbody>
</table>
<br>　　pvoid++的执行结果是其增大了1。<br><br>　　在实际的程序设计中，为迎合ANSI标准，并提高程序的可移植性，我们可以这样编写实现同样功能的代码：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>void * pvoid;<br>(char *)pvoid++; //ANSI：正确；GNU：正确<br>(char *)pvoid += 1; //ANSI：错误；GNU：正确</td>
        </tr>
    </tbody>
</table>
<br>　　GNU和ANSI还有一些区别，总体而言，GNU较ANSI更&#8220;开放&#8221;，提供了对更多语法的支持。但是我们在真实设计时，还是应该尽可能地迎合ANSI标准。<br><br>　　<strong>规则四如果函数的参数可以是任意类型指针，那么应声明其参数为void *</strong><br><br>　　典型的如内存操作函数memcpy和memset的函数原型分别为：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>void * memcpy(void *dest, const void *src, size_t len);<br>void * memset ( void * buffer, int c, size_t num );</td>
        </tr>
    </tbody>
</table>
<br>　　这样，任何类型的指针都可以传入memcpy和memset中，这也真实地体现了内存操作函数的意义，因为它操作的对象仅仅是一片内存，而不论这片内存是什么类型。如果memcpy和memset的参数类型不是void *，而是char *，那才叫真的奇怪了！这样的memcpy和memset明显不是一个&#8220;纯粹的，脱离低级趣味的&#8221;函数！<br><br>　　下面的代码执行正确：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>//示例：memset接受任意类型指针<br>int intarray[100];<br>memset ( intarray, 0, 100*sizeof(int) ); //将intarray清0 <br>//示例：memcpy接受任意类型指针<br>int intarray1[100], intarray2[100];<br>memcpy ( intarray1, intarray2, 100*sizeof(int) ); //将intarray2拷贝给intarray1</td>
        </tr>
    </tbody>
</table>
<br>　　有趣的是，memcpy和memset函数返回的也是void *类型，标准库函数的编写者是多么地富有学问啊！<br><br>　　<strong>规则五 void不能代表一个真实的变量</strong><br><br>　　下面代码都企图让void代表一个真实的变量，因此都是错误的代码：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e7e9e9 border=1>
    <tbody>
        <tr>
            <td>void a; //错误<br>function(void a); //错误</td>
        </tr>
    </tbody>
</table>
<br>　　void体现了一种抽象，这个世界上的变量都是&#8220;有类型&#8221;的，譬如一个人不是男人就是女人（还有人妖？）。<br><br>　　void的出现只是为了一种抽象的需要，如果你正确地理解了面向对象中&#8220;抽象基类&#8221;的概念，也很容易理解void数据类型。正如不能给抽象基类定义一个实例，我们也不能定义一个void（让我们类比的称void为&#8220;抽象数据类型&#8221;）变量。<br><br>　　<strong>4.总结</strong><br><br>　　小小的void蕴藏着很丰富的设计哲学，作为一名程序设计人员，对问题进行深一个层次的思考必然使我们受益匪浅<br>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/101630.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2009-11-22 16:14 <a href="http://www.cppblog.com/zmllegtui/archive/2009/11/22/101630.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Type Attribute aligned</title><link>http://www.cppblog.com/zmllegtui/archive/2009/11/22/101617.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Sun, 22 Nov 2009 06:28:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2009/11/22/101617.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/101617.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2009/11/22/101617.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/101617.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/101617.html</trackback:ping><description><![CDATA[<h4>&nbsp;</h4>
<p>Type attribute <tt>aligned</tt> allows you to specify the alignment of a structure, class, union, or enumeration. The syntax and considerations for specifying alignment factor are the same as for variable attribute <tt>aligned</tt>. Like variable attribute <tt>aligned</tt>, type attribute <tt>aligned</tt> can only increase alignment. Type attribute <tt>packed</tt> is used to decrease alignment.
<p>If the attribute appears immediately after the class, struct, union, or enumeration token or immediately after the closing right curly brace, it applies to the type identifier. It can also be specified on a <strong>typedef</strong> declaration. In a variable declaration, such as
<pre>class A {} a;
</pre>
<p>the placement of the type attribute can be confusing.
<p>In the following definitions, the attribute applies to <tt>A</tt>:
<pre>struct __attribute__((__aligned__(8))) A {};
struct A {} __attribute__((__aligned__(8)))&nbsp;;
struct __attribute__((__aligned__(8))) A {} a;
struct A {} __attribute__((__aligned__(8))) a;
typedef struct __attribute__((__aligned__(8))) A {} a;
typedef struct A {} __attribute__((__aligned__(8))) a;
</pre>
<p>In the following definitions, the attribute applies to <tt>a</tt>:
<pre>__attribute__((__aligned__(8))) struct A {} a;
struct A {} const __attribute__((__aligned__(8))) a;
__attribute__((__aligned__(8))) typedef struct A {} a;
typedef __attribute__((__aligned__(8))) struct A {} a;
typedef struct A {} const __attribute__((__aligned__(8))) a;
typedef struct A {} a __attribute__((__aligned__(8)));
</pre>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/101617.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2009-11-22 14:28 <a href="http://www.cppblog.com/zmllegtui/archive/2009/11/22/101617.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>虚函数与多态（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65386.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:28:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65386.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65386.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65386.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65386.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65386.html</trackback:ping><description><![CDATA[<p>虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。假设我们有下面的类层次：<br>class A<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void foo() { cout &lt;&lt; "A::foo() is called" &lt;&lt; endl;}<br>};<br>class B: public A<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void foo() { cout &lt;&lt; "B::foo() is called" &lt;&lt; endl;}<br>};</p>
<p>那么，在使用的时候，我们可以：<br>A * a = new B();<br>a-&gt;foo(); // 在这里，a虽然是指向A的指针，但是被调用的函数(foo)却是B的!</p>
<p>这个例子是虚函数的一个典型应用，通过这个例子，也许你就对虚函数有了一些概念。它虚就虚在所谓&#8220;推迟联编&#8221;或者&#8220;动态联编&#8221;上，一个类函数的调用并不是在编译时刻被确定的，而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数，所以被成为&#8220;虚&#8221;函数。</p>
<p>虚函数只能借助于指针或者引用来达到多态的效果，如果是下面这样的代码，则虽然是虚函数，但它不是多态的：<br>class A<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void foo();<br>};<br>class B: public A<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void foo();<br>};<br>void bar()<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> A a;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> a.foo(); // A::foo()被调用<br>}</p>
<p>&nbsp;<wbr></p>
<p><strong>1.1</strong> <strong>多态</strong></p>
<p>在了解了虚函数的意思之后，再考虑什么是多态就很容易了。仍然针对上面的类层次，但是使用的方法变的复杂了一些：<br>void bar(A *a)<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> a-&gt;foo(); // 被调用的是A::foo() 还是B::foo()？<br>}</p>
<p>因为foo()是个虚函数，所以在bar这个函数中，只根据这段代码，无从确定这里被调用的是A::foo()还是B::foo()，但是可以肯定的说：如果a指向的是A类的实例，则A::foo()被调用，如果a指向的是B类的实例，则B::foo()被调用。</p>
<p>这种同一代码可以产生不同效果的特点，被称为&#8220;多态&#8221;。</p>
<p>&nbsp;<wbr></p>
<p><strong>1.2</strong> <strong>多态有什么用？</strong><br>　　多态这么神奇，但是能用来做什么呢？这个命题我难以用一两句话概括，一般的C++教程（或者其它面向对象语言的教程）都用一个画图的例子来展示多态的用途，我就不再重复这个例子了，如果你不知道这个例子，随便找本书应该都有介绍。我试图从一个抽象的角度描述一下，回头再结合那个画图的例子，也许你就更容易理解。</p>
<p>在面向对象的编程中，首先会针对数据进行抽象（确定基类）和继承（确定派生类），构成类层次。这个类层次的使用者在使用它们的时候，如果仍然在需要基类的时候写针对基类的代码，在需要派生类的时候写针对派生类的代码，就等于类层次完全暴露在使用者面前。如果这个类层次有任何的改变（增加了新类），都需要使用者&#8220;知道&#8221;（针对新类写代码）。这样就增加了类层次与其使用者之间的耦合，有人把这种情况列为程序中的&#8220;bad smell&#8221;之一。</p>
<p>多态可以使程序员脱离这种窘境。再回头看看1.1中的例子，bar()作为A-B这个类层次的使用者，它并不知道这个类层次中有多少个类，每个类都叫什么，但是一样可以很好的工作，当有一个C类从A类派生出来后，bar()也不需要&#8220;知道&#8221;（修改）。这完全归功于多态--编译器针对虚函数产生了可以在运行时刻确定被调用函数的代码。</p>
<p>&nbsp;<wbr></p>
<p><strong>1.3</strong> <strong>如何</strong><strong>&#8220;</strong><strong>动态联编</strong><strong>&#8221;</strong><br>　　编译器是如何针对虚函数产生可以再运行时刻确定被调用函数的代码呢？也就是说，虚函数实际上是如何被编译器处理的呢？Lippman在深度探索C++对象模型[1]中的不同章节讲到了几种方式，这里把&#8220;标准的&#8221;方式简单介绍一下。</p>
<p>我所说的&#8220;标准&#8221;方式，也就是所谓的&#8220;VTABLE&#8221;机制。编译器发现一个类中有被声明为virtual的函数，就会为其搞一个虚函数表，也就是 VTABLE。VTABLE实际上是一个函数指针的数组，每个虚函数占用这个数组的一个slot。一个类只有一个VTABLE，不管它有多少个实例。派生类有自己的VTABLE，但是派生类的VTABLE与基类的VTABLE有相同的函数排列顺序，同名的虚函数被放在两个数组的相同位置上。在创建类实例的时候，编译器还会在每个实例的内存布局中增加一个vptr字段，该字段指向本类的VTABLE。通过这些手段，编译器在看到一个虚函数调用的时候，就会将这个调用改写，针对1.1中的例子：<br>void bar(A * a)<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> a-&gt;foo();<br>}<br>会被改写为：<br>void bar(A * a)<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> (a-&gt;vptr[1])();<br>}</p>
<p>因为派生类和基类的foo()函数具有相同的VTABLE索引，而他们的vptr又指向不同的VTABLE，因此通过这样的方法可以在运行时刻决定调用哪个foo()函数。</p>
<p>虽然实际情况远非这么简单，但是基本原理大致如此。</p>
<p>&nbsp;<wbr></p>
<p><strong>1.4 overload</strong><strong>和</strong><strong>override</strong></p>
<p>虚函数总是在派生类中被改写，这种改写被称为&#8220;override&#8221;。我经常混淆&#8220;overload&#8221;和&#8220;override&#8221;这两个单词。但是随着各类C++的书越来越多，后来的程序员也许不会再犯我犯过的错误了。但是我打算澄清一下：</p>
<p>override是指派生类重写基类的虚函数，就象我们前面B类中重写了A类中的foo()函数。重写的函数必须有一致的参数表和返回值（C++标准允许返回值不同的情况，这个我会在&#8220;语法&#8221;部分简单介绍，但是很少编译器支持这个feature）。这个单词好象一直没有什么合适的中文词汇来对应，有人译为&#8220;覆盖&#8221;，还贴切一些。<br>overload约定成俗的被翻译为&#8220;重载&#8221;。是指编写一个与已有函数同名但是参数表不同的函数。例如一个函数即可以接受整型数作为参数，也可以接受浮点数作为参数。</p>
<p>&nbsp;<wbr></p>
<p><strong>二</strong><strong>.</strong> <strong>虚函数的语法</strong><br>　　虚函数的标志是&#8220;virtual&#8221;关键字。</p>
<p>&nbsp;<wbr></p>
<p><strong>2.1</strong> <strong>使用</strong><strong>virtual</strong><strong>关键字</strong></p>
<p>考虑下面的类层次：<br>class A<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void foo();<br>};<br><br>class B: public A<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> void foo(); // 没有virtual关键字!<br>};<br><br>class C: public B // 从B继承，不是从A继承！<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> void foo(); // 也没有virtual关键字！<br>};</p>
<p>这种情况下，B::foo()是虚函数，C::foo()也同样是虚函数。因此，可以说，基类声明的虚函数，在派生类中也是虚函数，即使不再使用virtual关键字。</p>
<p>&nbsp;<wbr></p>
<p><strong>2.2</strong> <strong>纯虚函数</strong><br>　　如下声明表示一个函数为纯虚函数：<br>class A<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void foo()=0; // =0标志一个虚函数为纯虚函数<br>};</p>
<p>一个函数声明为纯虚后，纯虚函数的意思是：我是一个抽象类！不要把我实例化！纯虚函数用来规范派生类的行为，实际上就是所谓的&#8220;接口&#8221;。它告诉使用者，我的派生类都会有这个函数。</p>
<p>&nbsp;<wbr></p>
<p><strong>2.3</strong> <strong>虚析构函数</strong><br>　　析构函数也可以是虚的，甚至是纯虚的。例如：<br>class A<br>{<br>public:<br>virtual ~A()=0; // 纯虚析构函数<br>};</p>
<p>当一个类打算被用作其它类的基类时，它的析构函数必须是虚的。考虑下面的例子：<br>class A<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> A() { ptra_ = new char[10];}<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ~A() { delete[] ptra_;} // 非虚析构函数<br>private:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> char * ptra_;<br>};<br>class B: public A<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> B() { ptrb_ = new char[20];}<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ~B() { delete[] ptrb_;}<br>private:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> char * ptrb_;<br>};<br>void foo()<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> A * a = new B;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> delete a;<br>}</p>
<p>在这个例子中，程序也许不会象你想象的那样运行，在执行delete a的时候，实际上只有A::~A()被调用了，而B类的析构函数并没有被调用！这是否有点儿可怕？</p>
<p>如果将上面A::~A()改为virtual，就可以保证B::~B()也在delete a的时候被调用了。因此基类的析构函数都必须是virtual的。</p>
<p>纯虚的析构函数并没有什么作用，是虚的就够了。通常只有在希望将一个类变成抽象类（不能实例化的类），而这个类又没有合适的函数可以被纯虚化的时候，可以使用纯虚的析构函数来达到目的。</p>
<p>&nbsp;<wbr></p>
<p><strong>2.4</strong> <strong>虚构造函数？</strong><br>　　构造函数不能是虚的。</p>
<p>&nbsp;<wbr></p>
<p><strong>三</strong><strong>.</strong> <strong>虚函数使用技巧</strong></p>
<p><strong>3.1 private</strong><strong>的虚函数</strong></p>
<p>考虑下面的例子：<br>class A<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> void foo() { bar();}<br>private:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void bar() { ...}<br>};<br>class B: public A<br>{<br>private:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void bar() { ...}<br>};</p>
<p>在这个例子中，虽然bar()在A类中是private的，但是仍然可以出现在派生类中，并仍然可以与public或者protected的虚函数一样产生多态的效果。并不会因为它是private的，就发生A::foo()不能访问B::bar()的情况，也不会发生B::bar()对A::bar ()的override不起作用的情况。</p>
<p>这种写法的语意是：A告诉B，你最好override我的bar()函数，但是你不要管它如何使用，也不要自己调用这个函数。</p>
<p>&nbsp;<wbr></p>
<p><strong>3.2</strong> <strong>构造函数和析构函数中的虚函数调用</strong></p>
<p>一个类的虚函数在它自己的构造函数和析构函数中被调用的时候，它们就变成普通函数了，不&#8220;虚&#8221;了。也就是说不能在构造函数和析构函数中让自己&#8220;多态&#8221;。例如：<br>class A<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> A() { foo();} // 在这里，无论如何都是A::foo()被调用！<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ~A() { foo();} // 同上<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void foo();<br>};<br>class B: public A<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void foo();<br>};<br>void bar()<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> A * a = new B;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> delete a;<br>}</p>
<p>如果你希望delete a的时候，会导致B::foo()被调用，那么你就错了。同样，在new B的时候，A的构造函数被调用，但是在A的构造函数中，被调用的是A::foo()而不是B::foo()。</p>
<p>&nbsp;<wbr></p>
<p><strong>3.4</strong> <strong>什么时候使用虚函数</strong></p>
<p>在你设计一个基类的时候，如果发现一个函数需要在派生类里有不同的表现，那么它就应该是虚的。从设计的角度讲，出现在基类中的虚函数是接口，出现在派生类中的虚函数是接口的具体实现。通过这样的方法，就可以将对象的行为抽象化。</p>
<p>以设计模式[2]中Factory Method模式为例，Creator的factoryMethod()就是虚函数，派生类override这个函数后，产生不同的Product类，产生的Product类被基类的AnOperation()函数使用。基类的AnOperation()函数针对Product类进行操作，当然 Product类一定也有多态（虚函数）。</p>
<p>另外一个例子就是集合操作，假设你有一个以A类为基类的类层次，又用了一个std:: vector来保存这个类层次中不同类的实例指针，那么你一定希望在对这个集合中的类进行操作的时候，不要把每个指针再cast回到它原来的类型（派生类），而是希望对他们进行同样的操作。那么就应该将这个&#8220;一样的操作&#8221;声明为virtual。</p>
<p>现实中，远不只我举的这两个例子，但是大的原则都是我前面说到的&#8220;如果发现一个函数需要在派生类里有不同的表现，那么它就应该是虚的&#8221;。这句话也可以反过来说：&#8220;如果你发现基类提供了虚函数，那么你最好override它&#8221;。</p>
<p>&nbsp;<wbr></p>
<p><strong>附：</strong><strong>C++</strong><strong>中的虚函数和纯虚函数用法</strong></p>
<p>1.虚函数和纯虚函数可以定义在同一个类(class)中，含有纯虚函数的类被称为抽象类（abstract class），而只含有虚函数的类（class）不能被称为抽象类（abstract class）。</p>
<p>2.虚函数可以被直接使用，也可以被子类（sub class）重载以后以多态的形式调用，而纯虚函数必须在子类（sub class）中实现该函数才可以使用，因为纯虚函数在基类（base class）<br>只有声明而没有定义。</p>
<p>3.虚函数和纯虚函数都可以在子类（sub class）中被重载，以多态的形式被调用。</p>
<p>4.虚函数和纯虚函数通常存在于抽象基类（abstract base class -ABC）之中，被继承的子类重载，目的是提供一个统一的接口。</p>
<p>5.虚函数的定义形式：virtual {method body} ；纯虚函数的定义形式：virtual { } = 0; 在虚函数和纯虚函数的定义中不能有static标识符，原因很简单，被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定（run-time bind），而且被两者修饰的函数生命周期（life recycle）也不一样。</p>
<p>6.如果一个类中含有纯虚函数，那么任何试图对该类进行实例化的语句都将导致错误的产生，因为抽象基类（ABC）是不能被直接调用的。必须被子类继承重载以后，根据要求调用其子类的方法。</p>
<p>以下为一个简单的虚函数和纯虚寒数的使用演示，目的是抛砖引玉！<br>//father class<br>class Virtualbase<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void Demon()= 0; //prue virtual function<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual void Base() {cout&lt;&lt;"this is farther class"&lt;};<br>};<br>//sub class<br>class SubVirtual :public Virtualbase<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> void Demon() { cout&lt;&lt;" this is SubVirtual!"&lt;&lt;endl;}</p>
<p>void Base() {cout&lt;&lt;"this is subclass Base"&lt;&lt;endl;}<br>};<br><br>void main()<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> Virtualbase* inst = new SubVirtual(); //multstate pointer<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> inst-&gt;Demon();<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> inst-&gt;Base();<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> // inst = new Virtualbase();<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> // inst-&gt;Base()<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> return ;<br>}</p>
<p>----------------------------------------------------------------------------------------------</p>
<p>&nbsp;<wbr></p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 虚函数是在类中被声明为virtual的成员函数，当编译器看到通过指针或引用调用此类函数时，对其执行晚绑定，即通过指针（或引用）指向的类的类型信息来决定该函数是哪个类的。通常此类指针或引用都声明为基类的，它可以指向基类或派生类的对象。多态指同一个方法根据其所属的不同对象可以有不同的行为。</p>
<p>早绑定指编译器在编译期间即知道对象的具体类型并确定此对象调用成员函数的确切地址；而晚绑定是根据指针所指对象的类型信息得到类的虚函数表指针进而确定调用成员函数的确切地址。</p>
<p>编译器对每个包含虚函数的类创建一个表（称为vtable）。在vtable中，编译器放置特定类的虚函数地址。在每个带有虚函数的类中，编译器秘密地置一指针，称为vpointer（缩写为vptr），指向这个对象的vtable。通过基类指针做虚函数调用时（也就是做多态调用时），编译器静态地插入取得这个vptr，并vtable表中查找函数地址的代码，这样就能调用正确的函数使晚捆绑发生。为每个类设置vtable、初始化vptr、为虚函数调用插入代码，所有这些都是自动发生的，所以我们不必担心这些。利用虚函数，这个对象的合适的函数就能被调用，哪怕在编译器还不知道这个对象的特定类型的情况下。</p>
<p>在任何类中不存在显示的类型信息，可对象中必须存放类信息，否则类型不可能在运行时建立。那这个类信息是什么呢？我们来看下面几个类：</p>
<p>class&nbsp;<wbr>no_virtual<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>fun1()&nbsp;<wbr>const{}<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>&nbsp;<wbr>fun2()&nbsp;<wbr>const&nbsp;<wbr>{&nbsp;<wbr>return&nbsp;<wbr>a;&nbsp;<wbr>}<br>private:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>a;<br>}</p>
<p>class&nbsp;<wbr>one_virtual<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>virtual&nbsp;<wbr>void&nbsp;<wbr>fun1()&nbsp;<wbr>const{}<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>&nbsp;<wbr>fun2()&nbsp;<wbr>const&nbsp;<wbr>{&nbsp;<wbr>return&nbsp;<wbr>a;&nbsp;<wbr>}<br>private:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>a;<br>}</p>
<p>class&nbsp;<wbr>two_virtual<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>virtual&nbsp;<wbr>void&nbsp;<wbr>fun1()&nbsp;<wbr>const{}<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>virtual&nbsp;<wbr>int&nbsp;<wbr>&nbsp;<wbr>fun2()&nbsp;<wbr>const&nbsp;<wbr>{&nbsp;<wbr>return&nbsp;<wbr>a;&nbsp;<wbr>}<br>private:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>a;<br>}</p>
<p>以上三个类中：<br>no_virtual没有虚函数，sizeof(no_virtual)=4，类no_virtual的长度就是其成员变量整型a的长度；<br>one_virtual有一个虚函数，sizeof(one_virtual)=8；<br>two_virtual有两个虚函数，sizeof(two_virtual)=8；&nbsp;<wbr>有一个虚函数和两个虚函数的类的长度没有区别，其实它们的长度就是no_virtual的长度加一个void指针的长度，它反映出，如果有一个或多个虚函数，编译器在这个结构中插入一个指针（&nbsp;<wbr>vptr）。在one_virtual&nbsp;<wbr>和two_virtual之间没有区别。这是因为vptr指向一个存放地址的表，只需要一个指针，因为所有虚函数地址都包含在这个表中。这个VPTR就可以看作类的类型信息。</p>
<p>那我们来看看编译器是怎么建立VPTR指向的这个虚函数表的。先看下面两个类：<br>class&nbsp;<wbr>base<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>bfun(){}<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>virtual&nbsp;<wbr>void&nbsp;<wbr>vfun1(){}<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>virtual&nbsp;<wbr>int&nbsp;<wbr>vfun2(){}<br>private:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>a;<br>}<br>class&nbsp;<wbr>derived&nbsp;<wbr>:&nbsp;<wbr>public&nbsp;<wbr>base<br>{<br>public:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>dfun(){}<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>virtual&nbsp;<wbr>void&nbsp;<wbr>vfun1(){}<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>virtual&nbsp;<wbr>int&nbsp;<wbr>vfun3(){}<br>private:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>b;<br>}<br>两个类VPTR指向的虚函数表（VTABLE）分别如下：<br>base类<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>——————<br>VPTR——&gt;&nbsp;<wbr>|&amp;base::vfun1&nbsp;<wbr>|<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>——————<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>|&amp;base::vfun2&nbsp;<wbr>|<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>——————<br><br>derived类<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ———————<br>VPTR——&gt;&nbsp;<wbr>|&amp;derived::vfun1&nbsp;<wbr>|<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>———————<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>|&amp;base::vfun2&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>|<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>———————<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>|&amp;derived::vfun3&nbsp;<wbr>|<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>———————</p>
<p>每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时，编译器就为这个类创建一个VTABLE，如上图所示。在这个表中，编译器放置了在这个类中或在它的基类中所有已声明为virtual的函数的地址。如果在这个派生类中没有对在基类中声明为virtual的函数进行重新定义，编译器就使用基类的这个虚函数地址。（在derived的VTABLE中，vfun2的入口就是这种情况。）然后编译器在这个类中放置VPTR。当使用简单继承时，对于每个对象只有一个VPTR。VPTR必须被初始化为指向相应的VTABLE，这在构造函数中发生。</p>
<p>一旦VPTR被初始化为指向相应的VTABLE，对象就"知道"它自己是什么类型。但只有当虚函数被调用时这种自我认知才有用。</p>
<p>VPTR常常位于对象的开头，编译器能很容易地取到VPTR的值，从而确定VTABLE的位置。VPTR总指向VTABLE的开始地址，所有基类和它的子类的虚函数地址（子类自己定义的虚函数除外）在VTABLE中存储的位置总是相同的，如上面base类和derived类的VTABLE中vfun1和vfun2的地址总是按相同的顺序存储。编译器知道vfun1位于VPTR处，vfun2位于VPTR+1处，因此在用基类指针调用虚函数时，编译器首先获取指针指向对象的类型信息（VPTR），然后就去调用虚函数。如一个base类指针pBase指向了一个derived对象，那pBase-&gt;vfun2()被编译器翻译为&nbsp;<wbr>VPTR+1&nbsp;<wbr>的调用，因为虚函数vfun2的地址在VTABLE中位于索引为1的位置上。同理，pBase-&gt;vfun3()被编译器翻译为&nbsp;<wbr>VPTR+2的调用。这就是所谓的晚绑定。</p>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/65386.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:28 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65386.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>多重继承（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65385.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:27:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65385.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65385.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65385.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65385.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65385.html</trackback:ping><description><![CDATA[转载内容。另有其他更好的转载参考资料，在博文最末尾。<br /><br />&nbsp;&nbsp;&nbsp;&nbsp;面向对象编程语言中的多重继承指的是一个类别可以同时从多于一个父类继承行为与特征的功能。与单一继承相对，单一继承指一个类别只可以继承自一个父类。
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>重温Java，发现Java竟然不支持类多重继承（直接继承类），却允许接口的多重继承。。</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>C++中类可以多重继承，Java中为什么不实现这个功能呢？多重继承会带来哪些问题，从而导致Java放弃类的多重继承？</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>再一深究，发现多年以来，多重继承一直是个敏感话题，赞成者看到的是免去笨拙的混合继承的利处，反对者看到的是多处混淆的弊端，例如变量的二义性，并且是多个变量。所以关于它的好处与风险之间孰轻孰重成为OO界多年争论的焦点。</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>其实最大的问题是出现拓补图，也就是出现钻石型继承结构（DOD），个人感觉这是个致命伤。正如其名：Diamond of Death。</div>
<div>&nbsp;<wbr></div>
<div>举个简单的例子：</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>比如一个基类：动物。它有三个派生类：哺乳类动物，卡通类动物，宠物（确实都形成ISA关系）。现在有一个子类猫，从关系上推，它可以继承自哺乳类，卡通类，宠物，都符合ISA，如果要体现所有的特性，就需要全部继承，这样就形成了多重继承，却也形成了DOD，这样以后问题就出现了，从猫到动物的继承有三条路径，如果哺乳类，卡通类与宠物类中有相同的成员函数或变量，这样的数据组织方式会形成多义。</div>
<div>&nbsp;<wbr></div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>C++怎么解决这个问题的呢？虚继承。结果就是不得不牺牲一些内存开销，因为一个功能要在多处被重写。并且函数表里的函数指针必须调整，这样即使可以满足功能，在后期的维护也很复杂。</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 所以，Java才会采用这样折中的方法，硬生生的将类多重继承题了出去。</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 并且，网上也有不少建议，要尽可能避免多重继承，不惜一切代价去避免钻石结构，以避免后期不可挽回的大返工。</div>
<div>&nbsp;<wbr></div>
<div>&nbsp;<wbr></div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 多重继承的概念：<span style="color: #ffff00;">C++允许为一个派生类指定多个基类，这样的继承结构被称做多重继承</span><span style="color: #ffff00;">。</span></div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 举个例子，交通工具类可以派生出汽车和船连个子类，但拥有汽车和船共同特性水陆两用汽车就必须继承来自汽车类与船类的共同属性。<br />　　由此我们不难想出如下的图例与代码：
<p align="center"><img alt="" src="http://www.pconline.com.cn/pcedu/empolder/gj/c/0503/pic/21cppc01.gif" border="0" /></p>
<p>当一个派生类要使用多重继承的时候，必须在派生类名和冒号之后列出所有基类的类名，并用逗好分隔。　</p>
<p>&nbsp;<wbr></p>
<p>//程序作者:管宁&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />//站点:www.cndev-lab.com&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />//所有稿件均有版权,如要转载,请务必著名出处和作者&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />&nbsp;<wbr><br />#include&nbsp;<wbr>&lt;iostream&gt;&nbsp;<wbr><br />using&nbsp;<wbr>namespace&nbsp;<wbr>std;&nbsp;<wbr><br />&nbsp;<wbr><br />class&nbsp;<wbr>Vehicle&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>public:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Vehicle(int&nbsp;<wbr>weight&nbsp;<wbr>=&nbsp;<wbr>0)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Vehicle::weight&nbsp;<wbr>=&nbsp;<wbr>weight;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>SetWeight(int&nbsp;<wbr>weight)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"重新设置重量"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Vehicle::weight&nbsp;<wbr>=&nbsp;<wbr>weight;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>virtual&nbsp;<wbr>void&nbsp;<wbr>ShowMe()&nbsp;<wbr>=&nbsp;<wbr>0;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>protected:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>weight;&nbsp;<wbr><br />};&nbsp;<wbr><br />class&nbsp;<wbr>Car:public&nbsp;<wbr>Vehicle//汽车&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>public:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Car(int&nbsp;<wbr>weight=0,int&nbsp;<wbr>aird=0):Vehicle(weight)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Car::aird&nbsp;<wbr>=&nbsp;<wbr>aird;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>ShowMe()&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"我是汽车！"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>protected:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>aird;&nbsp;<wbr><br />};&nbsp;<wbr><br />&nbsp;<wbr><br />class&nbsp;<wbr>Boat:public&nbsp;<wbr>Vehicle//船&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>public:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Boat(int&nbsp;<wbr>weight=0,float&nbsp;<wbr>tonnage=0):Vehicle(weight)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Boat::tonnage&nbsp;<wbr>=&nbsp;<wbr>tonnage;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>ShowMe()&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"我是船！"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>protected:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>float&nbsp;<wbr>tonnage;&nbsp;<wbr><br />};&nbsp;<wbr><br />&nbsp;<wbr><br />class&nbsp;<wbr>AmphibianCar:public&nbsp;<wbr>Car,public&nbsp;<wbr>Boat//水陆两用汽车,多重继承的体现&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>public:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>AmphibianCar(int&nbsp;<wbr>weight,int&nbsp;<wbr>aird,float&nbsp;<wbr>tonnage)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>:<span style="color: #ffff00;">Vehicle(weight)</span>,Car(weight,aird),Boat(weight,tonnage)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>//<span style="color: #ffff00;">多重继承要注意调用基类构造函数&nbsp;</span><wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>ShowMe()&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"我是水陆两用汽车！"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />};&nbsp;<wbr><br />int&nbsp;<wbr>main()&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>AmphibianCar&nbsp;<wbr>a(4,200,1.35f);//错误&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>a.SetWeight(3);//错误&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>system("pause");&nbsp;<wbr>&nbsp;<wbr><br />}</p>
<p>　　上面的代码从表面看，看不出有明显的语法错误，但是它是不能够通过编译的。这有是为什么呢？<br />　　这是由于多重继承带来的继承的模糊性带来的问题。</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 先看如下的图示：</p>
<p>&nbsp;<wbr></p>
<p align="center"><img alt="" src="http://www.pconline.com.cn/pcedu/empolder/gj/c/0503/pic/21cppc02.gif" border="0" /></p>
<p>　　在图中深红色标记出来的地方正是主要问题所在，水陆两用汽车类继承了来自Car类与Boat类的属性与方法，Car类与Boat类同为AmphibianCar类的基类，在内存分配上AmphibianCar获得了来自两个类的SetWeight()成员函数，当我们调用a.SetWeight(3)的时候计算机不知道如何选择分别属于两个基类的被重复拥有了的类成员函数SetWeight()。</p>
<p>　　由于这种模糊问题的存在同样也导致了AmphibianCar a(4,200,1.35f);执行失败，系统会产生Vehicle&#8221;不是基或成员的错误。</p>
<p>　　以上面的代码为例，我们要想让AmphibianCar类既获得一个Vehicle的拷贝，而且又同时共享用Car类与Boat类的数据成员与成员函数就必须通过C++所提供的<span style="color: #ffff00;">虚拟继承</span>技术来实现。</p>
<p>　　我们在Car类和Boat类继承Vehicle类出，在前面加上virtual关键字就可以实现虚拟继承，<span style="color: #ffff00;">使用虚拟继承后，当系统碰到多重继承的时候就会自动先加入一个Vehicle的拷贝，当再次请求一个Vehicle的拷贝的时候就会被忽略，保证继承类成员函数的唯一性。</span><br />　　修改后的代码如下，注意观察变化：</p>
<p class="code">//程序作者:管宁&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />//站点:www.cndev-lab.com&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />//所有稿件均有版权,如要转载,请务必著名出处和作者&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />&nbsp;<wbr><br />#include&nbsp;<wbr>&lt;iostream&gt;&nbsp;<wbr><br />using&nbsp;<wbr>namespace&nbsp;<wbr>std;&nbsp;<wbr><br />&nbsp;<wbr><br />class&nbsp;<wbr>Vehicle&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>public:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Vehicle(int&nbsp;<wbr>weight&nbsp;<wbr>=&nbsp;<wbr>0)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Vehicle::weight&nbsp;<wbr>=&nbsp;<wbr>weight;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"载入Vehicle类构造函数"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>SetWeight(int&nbsp;<wbr>weight)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"重新设置重量"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Vehicle::weight&nbsp;<wbr>=&nbsp;<wbr>weight;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>virtual&nbsp;<wbr>void&nbsp;<wbr>ShowMe()&nbsp;<wbr>=&nbsp;<wbr>0;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>protected:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>weight;&nbsp;<wbr><br />};&nbsp;<wbr><br />class&nbsp;<wbr>Car:<u>virtual&nbsp;<wbr>public</u>&nbsp;<wbr>Vehicle//汽车，这里是虚拟继承&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>public:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Car(int&nbsp;<wbr>weight=0,int&nbsp;<wbr>aird=0):Vehicle(weight)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Car::aird&nbsp;<wbr>=&nbsp;<wbr>aird;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"载入Car类构造函数"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>ShowMe()&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"我是汽车！"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>protected:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>aird;&nbsp;<wbr><br />};&nbsp;<wbr><br />&nbsp;<wbr><br />class&nbsp;<wbr>Boat:<u>virtual&nbsp;<wbr>public</u>&nbsp;<wbr>Vehicle//船,这里是虚拟继承&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>public:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Boat(int&nbsp;<wbr>weight=0,float&nbsp;<wbr>tonnage=0):Vehicle(weight)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>Boat::tonnage&nbsp;<wbr>=&nbsp;<wbr>tonnage;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"载入Boat类构造函数"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>ShowMe()&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"我是船！"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>protected:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>float&nbsp;<wbr>tonnage;&nbsp;<wbr><br />};&nbsp;<wbr><br />&nbsp;<wbr><br />class&nbsp;<wbr>AmphibianCar:public&nbsp;<wbr>Car,public&nbsp;<wbr>Boat//水陆两用汽车,多重继承的体现&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>public:&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>AmphibianCar(int&nbsp;<wbr>weight,int&nbsp;<wbr>aird,float&nbsp;<wbr>tonnage)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>:Vehicle(weight),Car(weight,aird),Boat(weight,tonnage)&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>//多重继承要注意调用基类构造函数&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"载入AmphibianCar类构造函数"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>ShowMe()&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"我是水陆两用汽车！"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void&nbsp;<wbr>ShowMembers()&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"重量："&lt;&lt;weight&lt;&lt;"吨，"</p>
<p class="code">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> &lt;&lt;"空气排量："&lt;&lt;aird&lt;&lt;"CC，"</p>
<p class="code">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> &lt;&lt;"排水量："&lt;&lt;tonnage&lt;&lt;"吨"&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}&nbsp;<wbr><br />};&nbsp;<wbr><br />int&nbsp;<wbr>main()&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>AmphibianCar&nbsp;<wbr>a(4,200,1.35f);&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>a.ShowMe();&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>a.ShowMembers();&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>a.SetWeight(3);&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>a.ShowMembers();&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>system("pause");&nbsp;<wbr>&nbsp;<wbr><br />}</p>
<p>　　注意观察类构造函数的构造顺序。<br /><strong>　　</strong>虽然说虚拟继承与虚函数有一定相似的地方，但读者务必要记住，他们之间是绝对没有任何联系的！</p>
<p>==================================================================&nbsp;<wbr></p>
<p>补充：</p>
<p>1、&nbsp;<wbr>当一个类有多个父类时，每个父类在内存中依次排列，然后该类自己的成员。<br />2、&nbsp;<wbr>每一个父类的镜像中，都包含有独立的虚函数表<br />3、&nbsp;<wbr>当把子类的指针Upcast的时候，两种Upcast的方式得到的结果分别指向各自的父类镜像<br />4、&nbsp;<wbr>当两个父类重载的虚函数不同时，会使用Thunk机制，也就是说，虚函数表中的函数指针并不指向实际的虚函数，而是指向一小段代码。在这一小段代码中，会修改This指针（ECX寄存器），使之指向合适的父类镜像，然后再跳转到实际的虚函数体。<br />5、&nbsp;<wbr>当不使用虚继承时，共同基类的成员对象，在子类中会有独立两分（从两个父类各自继承了一份）。<br />6、&nbsp;<wbr>当使用虚继承时，共同基类的成员对象也会在虚函数表中记录，访问它必须先查找虚函数表。<br /><br />普通多重继承下的虚函数表：<br /><a href="http://blog.csdn.net/tangaowen/article/details/5830803">http://blog.csdn.net/tangaowen/article/details/5830803<br /></a><a href="http://www.cnblogs.com/itech/archive/2009/02/28/1399995.html">http://www.cnblogs.com/itech/archive/2009/02/28/1399995.html<br /></a>虚继承下的虚函数表：<br /><a href="http://www.cnblogs.com/itech/archive/2009/02/27/1399996.html">http://www.cnblogs.com/itech/archive/2009/02/27/1399996.html</a><a href="http://blog.csdn.net/tangaowen/article/details/5830803"><br /></a></p>
</div><img src ="http://www.cppblog.com/zmllegtui/aggbug/65385.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:27 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65385.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>字节序（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65384.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:27:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65384.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65384.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65384.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65384.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65384.html</trackback:ping><description><![CDATA[      谈到字节序的问题，必然牵涉到两大CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列采用big endian方式存储数据，而x86系列则采用little endian方式存储数据。那么究竟什么是big endian，什么又是little endian呢？
      字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序，通常有小端、大端两种字节顺序。小端字节序指低字节数据存放在内存低地址处，高字节数据存放在内存高地址处；大端字节序是高字节数据存放在低地址处，低字节数据存放在高地址处。基于X86平台的PC机是小端字节序的，而有的嵌入式平台则是大端字节序的。因而对int、uint16、uint32等多于1字节类型的数据，在这些嵌入式平台上应该变换其存储顺序。通常我们认为，在空中传输的字节的顺序即网络字节序为标准顺序，考虑到与协议的一致以及与同类其它平台产品的互通，在程序中发数据包时，将主机字节序转换为网络字节序，收数据包处将网络字节序转换为主机字节序。
      其实big endian是指低地址存放最高有效字节（MSB），而little endian则是低地址存放最低有效字节（LSB）。&#160;
      用文字说明可能比较抽象，下面用图像加以说明。比如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示：对于0x12345678，
<p>Big endian：低------->高：12 34 56 78</p>
<p>Little endian：低------->高：78 56 34 12</p>
<p>&#160;<wbr>&#160;<wbr>&#160;<wbr> 为什么要注意字节序的问题呢？你可能这么问。当然，如果你写的程序只在单机环境下面运行，并且不和别人的程序打交道，那么你完全可以忽略字节序的存在。但是，如果你的程序要跟别人的程序产生交互呢？在这里我想说说两种语言。C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的，而JAVA编写的程序则唯一采用big endian方式来存储数据。试想，如果你用C/C++语言在x86平台下编写的程序跟别人的JAVA程序互通时会产生什么结果？就拿上面的0x12345678来说，你的程序传递给别人的一个数据，将指向0x12345678的指针传给了JAVA程序，由于JAVA采取big endian方式存储数据，很自然的它会将你的数据翻译为0x78563412。什么？竟然变成另外一个数字了？是的，就是这种后果。因此，在你的C程序传给JAVA程序之前有必要进行字节序的转换工作。<br>&#160;<wbr>&#160;<wbr>&#160;<wbr> 所有网络协议也都是采用big endian的方式来传输数据的。所以有时我们也会把big endian方式称之为网络字节序。当两台采用不同字节序的主机通信时，在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。</p>
<p>&#160;<wbr>&#160;<wbr>&#160;<wbr> 判断小端还是大端规则的方法：</p>
<p>&#160;<wbr>int x = 1;<br>&#160;<wbr>if(*(char *)&x == 1)//取x指针强制转换为char*类型再取值，此时取到的值是int最低字节值<br>&#160;<wbr>&#160;<wbr>&#160;<wbr>&#160;<wbr> printf("little-endian\n");<br>&#160;<wbr>else<br>&#160;<wbr>&#160;<wbr>&#160;<wbr>&#160;<wbr> printf("big-endian\n");</p>
<p>&#160;<wbr></p>
<p>&#160;<wbr>&#160;<wbr>&#160;<wbr>&#160;<wbr> 另外补充：</p>
<p>1．BIG-ENDIAN、LITTLE-ENDIAN、跟CPU有关的，每一种CPU不是BIG-ENDIAN就是LITTLE-ENDIAN。网络字节序是指数据在网络上传输时是大头还是小头的，在Internet的网络字节序是BIG-ENDIAN。所谓的JAVA字节序指的是在JAVA虚拟机中多字节类型数据的存放顺序，JAVA字节序也是BIG-ENDIAN。</p>
<p>2．所以在用C/C++写通信程序时，在发送数据前务必用htonl和htons去把整型和短整型的数据进行从主机字节序到网络字节序的转换，而接收数据后对于整型和短整型数据则必须调用ntohl和ntohs实现从网络字节序到主机字节序的转换。如果通信的一方是JAVA程序、一方是C/C++程序时，则需要在C/C++一侧使用以上几个方法进行字节序的转换，而JAVA一侧，则不需要做任何处理，因为JAVA字节序与网络字节序都是BIG-ENDIAN，只要C/C++一侧能正确进行转换即可（发送前从主机序到网络序，接收时反变换）。如果通信的双方都是JAVA，则根本不用考虑字节序的问题了。</p>
 <img src ="http://www.cppblog.com/zmllegtui/aggbug/65384.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:27 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65384.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>拷贝构造函数和赋值运算符重载（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65382.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:26:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65382.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65382.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65382.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65382.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65382.html</trackback:ping><description><![CDATA[<p>以下讨论中将用到的例子:</p>
<p>class CExample<br />{<br />public:<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CExample(){pBuffer=NULL; nSize=0;}<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ~CExample(){delete pBuffer;}<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> void Init(int n){ pBuffer=new char[n]; nSize=n;}<br />private:<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> char *pBuffer; //类的对象中包含指针,指向动态分配的内存资源<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> int nSize;<br />};&nbsp;</p><p><wbr></p>
<p>这个类的主要特点是包含指向其他资源的指针。 pBuffer指向堆中分配的一段内存空间。&nbsp;</p><p><wbr></p>
<p>一、拷贝构造函数</p>
<p>int main(int argc, char* argv[])<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CExample theObjone;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> theObjone.Init(40);<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> //现在需要另一个对象,需要将他初始化为theObjone的状态<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CExample theObjtwo=theObjone;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ...<br />}&nbsp;</p><p><wbr></p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 语句"CExample theObjtwo=theObjone;"<strong style="color: #ffff00;">用theObjone初始化theObjtwo</strong>。其完成方式是内存拷贝，复制所有成员的值。完成后，theObjtwo.pBuffer==theObjone.pBuffer。即它们将指向同样的地方，指针虽然复制了，但所指向的空间并没有复制，而是由两个对象共用了。这样不符合要求，对象之间不独立了，并为空间的删除带来隐患。所以需要采用必要的手段来避免此类情况。</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 回顾一下此语句的具体过程:首先建立对象theObjtwo，并调用其构造函数，然后成员被拷贝。可以在构造函数中添加操作来解决指针成员的问题。所以C++语法中除了提供缺省形式的构造函数外，还规范了另一种特殊的构造函数：拷贝构造函数。</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 上面的语句中，如果类中定义了拷贝构造函数，这对象建立时，调用的将是拷贝构造函数，在拷贝构造函数中，可以根据传入的变量，复制指针所指向的资源。</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 拷贝构造函数的格式为:构造函数名(对象的引用)</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 提供了拷贝构造函数后的CExample类定义为:</p>
<p>class CExample<br />{<br />public:<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CExample(){pBuffer=NULL; nSize=0;}<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ~CExample(){delete pBuffer;}<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CExample(const CExample&amp;); //拷贝构造函数<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> void Init(int n){ pBuffer=new char[n]; nSize=n;}<br />private:<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> char *pBuffer; //类的对象中包含指针,指向动态分配的内存资源<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> int nSize;<br />};</p>
<p>CExample::CExample(const CExample&amp; RightSides) //拷贝构造函数的定义<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> nSize=RightSides.nSize; //复制常规成员<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> pBuffer=new char[nSize]; //复制指针指向的内容<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));<br />}&nbsp;</p><p><wbr></p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 这样，定义新对象，并用已有对象初始化新对象时，CExample(const CExample&amp; RightSides)将被调用，而已有对象用别名RightSides传给构造函数，以用来作复制。</p>
<p>&nbsp;&nbsp;<wbr>&nbsp;<wbr> 原则上，应该为所有包含动态分配成员的类都提供拷贝构造函数。</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 拷贝构造函数的另一种调用：当对象直接作为参数传给函数时，函数将建立对象的临时拷贝，这个拷贝过程也将调用拷贝构造函数。</p>
<p>例如</p>
<p>BOOL testfunc(CExample obj);</p>
<p>testfunc(theObjone); //对象直接作为参数。</p>
<p>BOOL testfunc(CExample obj)<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> //针对obj的操作实际上是针对复制后的临时拷贝进行的<br />}&nbsp;</p><p><wbr></p>
<p>还有一种情况，也是与临时对象有关的，当函数中的局部对象被被返回给函数调者时，也将建立此局部对象的一个临时拷贝，拷贝构造函数也将被调用</p>
<p>CTest func()</p><p>{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CTest theTest;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> return theTest;<br />}&nbsp;</p><p><wbr></p>
<p>二、赋值符的重载</p>
<p>下面的代码与上例相似</p>
<p>int main(int argc, char* argv[])<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CExample theObjone;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> theObjone.Init(40);<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CExample theObjthree;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> theObjthree.Init(60);</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> //现在需要一个对象赋值操作,被赋值对象的原内容被清除，并用右边对象的内容填充。<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> theObjthree=theObjone;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> return 0;<br />}</p><p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 也用到了"="号，但与之前的例子并不同，<strong style="color: #ffff00;">之前的例子中，"="在对象声明语句中，表示初始化。更多时候,这种初始化也可用括号表示。</strong>例如 CExample theObjone(theObjtwo); <strong style="color: #ffff00;">而本例子中，"="表示赋值操作。</strong>将对象theObjone的内容复制到对象theObjthree;，这其中涉及到对象theObjthree原有内容的丢弃，新内容的复制。但"="的缺省操作只是将成员变量的值相应复制。旧的值被自然丢弃。由于对象内包含指针，将造成不良后果:指针的值被丢弃了，但指针指向的内容并未释放。指针的值被复制了，但指针所指内容并未复制。</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 因此，包含动态分配成员的类除提供拷贝构造函数外，还应该考虑重载"="赋值操作符号。</p>
<p>类定义变为:</p>
<p>class CExample<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ...<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CExample(const CExample&amp;); //拷贝构造函数<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CExample&amp; operator = (const CExample&amp;); //赋值符重载<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ...<br />};</p><p>//赋值操作符重载<br />CExample &amp; CExample::operator = (const CExample&amp; RightSides)<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> nSize=RightSides.nSize; //复制常规成员<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> char *temp=new char[nSize]; //复制指针指向的内容<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> delete []pBuffer; //删除原指针指向内容&nbsp;<wbr> (将删除操作放在后面，避免X=X特殊情况下，内容的丢失)<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> pBuffer=temp;&nbsp;<wbr>&nbsp;<wbr> //建立新指向<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> return *this<br />}&nbsp;</p><p><wbr></p>
<p>三、拷贝构造函数使用赋值运算符重载的代码。</p>
<p>CExample::CExample(const CExample&amp; RightSides)<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> pBuffer=NULL;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> *this=RightSides&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> //调用重载后的"="<br />}</p><img src ="http://www.cppblog.com/zmllegtui/aggbug/65382.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:26 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65382.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>struct和class区别（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65381.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:25:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65381.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65381.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65381.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65381.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65381.html</trackback:ping><description><![CDATA[<p>1、默认继承权限。如果不明确指定，来自class的继承按照private继承处理，来自struct的继承按照public继承处理；<br />2、成员的默认访问权限。class的成员默认是private权限，struct默认是public权限。<br /><br />ps:<br />struct和class对于初始化都是需要在初始化列表中进行，或者在构造函数中赋值。<br /><br /></p><img src ="http://www.cppblog.com/zmllegtui/aggbug/65381.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:25 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65381.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>容器内指针的new和delete（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65380.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:23:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65380.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65380.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65380.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65380.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65380.html</trackback:ping><description><![CDATA[<p>&nbsp;<wbr> 容器在STL中被认为是智能的。它们支持向前和向后的迭代器；它们能告诉你它所保存的对象类型（通过typedef value_type）；在插入和删除过程中它们进行了良好的内存管理；它们将报告自己包含了多少对象和自己最多能包含多少对象（分别通过size和max_size取得）；并且，当容器销毁时，它自动销毁每个被包含的对象。</p>
<p>&nbsp;<wbr> 拥有如此聪明的容器，许多程序员自己不再担心清理问题。他们认为容器会为他们操心。多数情况下，他们正确，但是当容器包括由new生产对象指针时，他们就不是太正确。毫无疑问，指针容器在销毁时，会销毁它所包容的每一个元素，但是指针的&#8220;析构函数&#8221;只是一个空操作。它不会调用delete。</p>
<p>&nbsp;<wbr> 结果是，以下代码直接导致内存资源泄漏：</p>
<p align=left>void doSomething()</p>
<p align=left>{</p>
<p align=left>vector&lt;Widget*&gt; vwp;</p>
<p align=left>for (int i = 0; i &lt; SOME_MAGIC_NUMBER; ++i)</p>
<p align=left>vwp.push_back(new Widget);</p>
<p align=left>&#8230; // use vwp</p>
<p align=left>} //Widgets are leaked here!</p>
<p>&nbsp;<wbr> 当离开vwp的作用范围时，vwp的每一个元素都会被销毁，但是这并不改变new所产生的对象没有被delete这个事实。这个删除动作是程序员的责任，而不是vector的。这其实是一个功能，因为只有程序员才知道指针是否需要删除。</p>
<p>&nbsp;<wbr> 通常，程序员希望它们那样（删除指针）。在那种情况（上例）中，使它发生其实很简单。</p>
<p align=left>void doSomething()</p>
<p align=left>{</p>
<p align=left>vector&lt;Widget*&gt; vwp;</p>
<p align=left>&#8230; // as before</p>
<p align=left>for (vector&lt;Widget*&gt;::iterator i = vwp.begin();i != vwp.end(),++i) {</p>
<p align=left>delete *i;</p>
<p align=left>}</p>
<p>｝</p>
<p>&nbsp;<wbr> 这能行，如果你不是十分在意它只是&#8220;能行&#8221;。问题之一是新的for循环做了很多for_each做的事，但它不像for_each一样清析。另一个问题是代码不是异常安全。如果一个异常在vwp填上指针之后，而这些指针还没有删除之前被抛出。资源泄漏再次出现。幸运的是两个问题都可以克服。</p>
<p>&nbsp;<wbr> 修改for_each类似的代码以使用真正的for_each，需要将delete操作置于（仿）函数对象中。这像一个儿童游戏，假设你有一个喜欢与STL一起玩游戏的小孩。</p>
<p align=left>template&lt;typename T&gt;</p>
<p align=left>struct DeleteObject: &nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>// Item 40 describes why</p>
<p align=left>public unary_function&lt;const T*, void&gt; { //this inheritance is here</p>
<p align=left>&nbsp;<wbr></p>
<p align=left>void operator()(const T* ptr) const</p>
<p align=left>delete ptr;</p>
<p align=left>}</p>
<p align=left>};</p>
<p>&nbsp;<wbr> 现在你可以这样做：</p>
<p align=left>void doSomething()</p>
<p align=left>{</p>
<p align=left>&#8230; // as before</p>
<p align=left>for_each(vwp.begin(), vwp.end(), DeleteObject&lt;Widget&gt;);</p>
<p align=left>}</p>
<p>&nbsp;<wbr> 不太走运，它要求指明DeleteObject删除的对象类型（这里是：Widget ）。令人讨厌，vwp是一个vector&lt;Widget*&gt;，因此DeteleObject删除的当然是Widget指针！这种冗余不只是令人讨厌，因为它可能导致难以检查的bug出现。试想一下，例如，有人故意决定要从string继承：</p>
<p align=left>class SpecialString: public string { ...};</p>
<p>&nbsp;<wbr> 这是一种危险的产物，因为string与其它标准STL容器一样，没有virtual析构函数。公有继承一个没有虚析构函数的对象是C++一个主要的误区（a major C++ no-no）(近一步的细节，在任何一个很好的C++书中都有讨论。在Effective C++中，它被放在Item 14)。不论如何，有人如此作了。考虑一下以下代码的行为：</p>
<p align=left>void doSomething()</p>
<p align=left>{</p>
<p align=left>deque&lt;SpecialString*&gt; dssp;</p>
<p align=left>&#8230;</p>
<p align=left>for_each( dssp.begin(), dssp.end(), // undefined behavior! Deletion</p>
<p align=left>DeleteObject&lt;string&gt;()); //of a derived object via a base</p>
<p align=left>} // class pointer where there is</p>
<p align=left>//no virtual destructor</p>
<p>&nbsp;<wbr> 注意dssp声明为保存SpecialString的指针，但是for_each循环的作者告诉DeleteObject，它准备删除string的指针。很容易发现什么样的错误会发生。SpecialString无疑在很大程度上表现为string。因此有人会忘记它的用户，他们会不记得他们使用的是SpecialString而不是string。</p>
<p>&nbsp;<wbr> 可以排除这个错误（也可以减少DeleteObject用户敲打键盘的次数）使用编译器推绎出传给DeleteObject::operator()的指针类型。所有工作只是把模板从DeleteObject类移到operator()上。</p>
<p align=left>struct DeleteObject { // templatization and base</p>
<p align=left>// class removed here</p>
<p align=left>template&lt;typename T&gt; <em>II</em> templatization added here</p>
<p align=left>void operator()(const T* ptr) const</p>
<p align=left>{</p>
<p align=left>delete ptr;</p>
<p align=left>}</p>
<p align=left>}</p>
<p>&nbsp;<wbr> 编译器知道传递给DeleteObject::operator（）的指针类型，因此它将自动为该类型指针生成一个operator的实例。这种类型推绎的产生，取决于我们放弃DeleteObject的可适应性。考虑一下DeleteObject被设计为如何使用，就很难找出可能发生问题的地方。</p>
<p>&nbsp;<wbr> 使用这一新版的DeleteObject，SpecialString客户的代码看起来像这样：</p>
<p align=left>void doSomething()</p>
<p align=left>{</p>
<p align=left>deque&lt;SpecialString*&gt; dssp;</p>
<p align=left>&#8230;</p>
<p align=left>for_each( dssp.begin(), dssp.end(),</p>
<p align=left>DeleteObject ()); // ah! well-defined behavior!</p>
<p align=left>}</p>
<p>&nbsp;<wbr> 直接而且类型安全，就像我们所喜欢的那样。</p>
<p>&nbsp;<wbr> 但是它还不是异常安全。如果SpecialString生产了，但还没有调用for_each，一个异常被抛出，泄漏将出现。这个问题可以用很多方法解决，但最简单的也许是使用智能指针容器取代指针容器，通常使用一个引用记数的智能指针（如果不熟悉智能指针的概念，可以在中高级C++读物中找到。在More Effective C++中，这些材料在Item 28。）</p>
<p>&nbsp;<wbr> STL本身并不包括引用记数的智能指针，编写一个好的－所有情况下都正确－太富有技巧，因此除非真的需要，并不需要这样做。我（作者）1996年在More Effective C++发布了了一个引用记数的智能指针，尽管它基于一些确定的智能指针实现，而且在发布前由多位有经验的开发者讨论过，但是这些年还是有一堆准确的Bug被发现。很多引用记数的智能指针可能失败的微妙情况被说明。（细节在More Effective C++勘误中讨论）</p>
<p>&nbsp;<wbr> 幸运地，几乎不需要自己写一个智能指针，因为已验正的实现并不难找。在Boost库（参考条款50）中就有一个这样的share_ptr。使用Boost的share_ptr，本条款最初的例子可以重写为：</p>
<p align=left>void doSomething()</p>
<p align=left>{</p>
<p align=left>typedef boost::shared_ ptr&lt;Widget&gt; SPW; //SPW = "shared_ptr</p>
<p align=left>// to Widget"</p>
<p align=left>vector&lt;SPW&gt; vwp;</p>
<p align=left>for (int i = 0; i &lt; SOME_MAGIC_NUMBER; ++i)</p>
<p align=left>vwp.push_back(SPW new Widget); &nbsp;<wbr>&nbsp;<wbr>// create a SPW from a</p>
<p align=left>// Widget*, then do a</p>
<p align=left>//push_back on it</p>
<p align=left>&#8230; &nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>// use vwp</p>
<p align=left>} &nbsp;<wbr>&nbsp;<wbr>// no Widgets are leaked here, not</p>
<p align=left>// even if an exception is thrown</p>
<p align=left>//in the code above</p>
<p>&nbsp;<wbr> 千万不能被auto_ptr愚弄了，不要认为创建auto_ptr的容器，指针会被自动删除。这是可怕是想法，它是如此危险，我准备用整个条款8来说明你不应使用它。</p>
<p>&nbsp;<wbr> 应记住的是STL容器是智能的，但它不足以知道是否要删除它包含的指针。为了避免资源泄漏，使用指针容器时应删除指针。你需要使用智能指针或在容器销毁前手工删除每一个指针。</p>
<p>&nbsp;<wbr> 最后，一个类似于DeleteObject的结构可以方便地避免使用指针容器时的资源泄漏，这也许会使你联想起，也许可能创建一个类似的DeleteArray，避免使用数组指针容器时的资源泄漏。当然，这是可能的，但是是否明智就是另一个问题了。条款13解释了为什么动态申请数组总是不如vector和string对象。所以在你坐下来写DeleteArray之前，请先看一看条款13。如果幸运，DeleteArray的时代将永远不会到来。</p>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/65380.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:23 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65380.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>CString（MFC）（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65379.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:21:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65379.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65379.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65379.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65379.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65379.html</trackback:ping><description><![CDATA[<font size=5>一、主要函数&nbsp;<wbr></font>
<p><font style="FONT-SIZE: 14px" size=3>vc中最主要函数不易理解。</font></p>
<p><a name=主要函数的实现></a><font style="FONT-SIZE: 14px" size=3>CString::CString(char *p)</font></p>
<p><font style="FONT-SIZE: 14px" size=3>{</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> int n=strlen(p);</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>m_data = new char[n+1];</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>strcpy(m_data,p);</font></p>
<p><font style="FONT-SIZE: 14px" size=3>}</font></p>
<p><font style="FONT-SIZE: 14px" size=3>CString::CString(CString &amp;other)</font></p>
<p><font style="FONT-SIZE: 14px" size=3>{</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int n=strlen(other.m_data);</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>m_data = new char[n+1];</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>strcpy(m_data,other.m_data);</font></p>
<p><font style="FONT-SIZE: 14px" size=3>}</font></p>
<p><font style="FONT-SIZE: 14px" size=3>CString&amp; CString::operator = (CString&amp; other)</font></p>
<p><font style="FONT-SIZE: 14px" size=3>{</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>delete[] m_data;</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int n=strlen(other.m_data);</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>m_data = new char[n+1];</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>strcpy(m_data,other.m_data);</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>return *this;</font></p>
<p><font style="FONT-SIZE: 14px" size=3>}</font></p>
<p><font style="FONT-SIZE: 14px" size=3>String::~String()</font></p>
<p><font style="FONT-SIZE: 14px" size=3>{</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>delete [] m_data;</font></p>
<p><font style="FONT-SIZE: 14px" size=3>}</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr></font></p>
<p><a name=常用函数></a><font style="FONT-SIZE: 14px" size=3>1、Collate，Compare&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></font></p>
<p><font style="FONT-SIZE: 14px" size=3>与一个字符长指针所指的字符串进行比较，与strcmp相同，它们的区别是，分别调用_tcscoll，_tcsicmp。</font></p>
<p>&nbsp;<wbr></p>
<p><font style="FONT-SIZE: 14px" size=3>2、Delete</font></p>
<p><font style="FONT-SIZE: 14px"><strong>int Delete( int</strong> <em>nIndex</em><strong>, int</strong> <em>nCount</em> <strong>= 1 )</strong></font></p>
<p><font style="FONT-SIZE: 14px">返回值是被删除前的字符串的长度，nIndex是第一个被删除的字符，nCount是一次删除几个字符。根据我实验得出的结果：当nCount&gt;字符串的长度时会出错，当nCount过大，没有足够的字符删除时，此函数不执行。</font></p>
<p>&nbsp;<wbr></p>
<p><font style="FONT-SIZE: 14px" size=3>3、FindOneOf</font></p>
<p><font style="FONT-SIZE: 14px"><strong>int</strong> <strong>FindOneOf(</strong> <strong>LPCTSTR</strong> <em>lpszCharSet</em> <strong>)</strong> <strong>const;</strong></font></p>
<p><font style="FONT-SIZE: 14px">此函数的功能是在查找lpszCharSet中的任意一个字符，查到一个就把位置返回，没有查到返回0。如：</font></p>
<p><font style="FONT-SIZE: 14px" size=3>CString str = "0123456789";</font></p>
<p><font style="FONT-SIZE: 14px" size=3>int x = str.FindOneOf("31");</font></p>
<p><font style="FONT-SIZE: 14px" size=3>x的值是1。</font></p>
<p>&nbsp;<wbr></p>
<p><font style="FONT-SIZE: 14px" size=3>4、Find</font></p>
<p><font style="FONT-SIZE: 14px"><strong>int</strong> <strong>Find(</strong> <strong>TCHAR</strong> <em>ch</em> <strong>)</strong> <strong>const;</strong></font></p>
<p><font style="FONT-SIZE: 14px"><strong>int</strong> <strong>Find(</strong> <strong>LPCTSTR</strong> <em>lpszSub</em> <strong>)</strong> <strong>const;</strong></font></p>
<p><font style="FONT-SIZE: 14px"><strong>int Find( TCHAR</strong> <em>ch</em><strong>, int</strong> <em>nStart</em> <strong>) const;</strong></font></p>
<p><font style="FONT-SIZE: 14px"><strong>int Find( LPCTSTR</strong> <em>pstr</em><strong>, int</strong> <em>nStart</em> <strong>) const;</strong></font></p>
<p><font style="FONT-SIZE: 14px" size=3>返回值查找到的序号，ch待搜索的字符，lpszSub待搜索的字符子串，nStart 从那里开始搜索。如：</font></p>
<p><font style="FONT-SIZE: 14px" size=3>CString str = "0123456789";</font></p>
<p><font style="FONT-SIZE: 14px" size=3>int x = str.Find("34",4);</font></p>
<p><font style="FONT-SIZE: 14px" size=3>返回的值是-1.</font></p>
<p>&nbsp;<wbr></p>
<p><font style="FONT-SIZE: 14px" size=3>5、GetAt</font></p>
<p><font style="FONT-SIZE: 14px"><strong>TCHAR</strong> <strong>GetAt(</strong> <strong>int</strong> <em>nIndex</em> <strong>)</strong> <strong>const;</strong></font></p>
<p><font style="FONT-SIZE: 14px">返回标号为nIndex的字符，你可以把字符串理解为一个数组，GetAt类似于[].注意nIndex的范围，如果不合适会有调试错误。</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr></font></p>
<p><font style="FONT-SIZE: 14px" size=3>6、Insert</font></p>
<p><font style="FONT-SIZE: 14px"><strong>int Insert( int</strong> <em>nIndex</em><strong>, TCHAR</strong> <em>ch</em> <strong>)</strong><br><strong>int Insert( int</strong> <em>nIndex</em><strong>, LPCTSTR</strong> <em>pstr</em></font> <font style="FONT-SIZE: 14px"><strong>)<br></strong><strong>返回修改后的长度，</strong><strong>nIndex</strong><strong>字符（或字符串）插入后的标号。</strong></font></p>
<p>&nbsp;<wbr></p>
<p><font style="FONT-SIZE: 14px" size=3>7、Left</font></p>
<p><font style="FONT-SIZE: 14px"><strong>CString</strong> <strong>Left(</strong> <strong>int</strong> <em>nCount</em> <strong>)</strong> <strong>const;</strong></font></p>
<p><font style="FONT-SIZE: 14px"><strong>返回的字符串的前</strong><strong>nCount</strong><strong>个字符。</strong></font></p>
<p><font style="FONT-SIZE: 14px" size=3>Right与Left类似</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr></font></p>
<p><font style="FONT-SIZE: 14px" size=3>8、MakeLower ，MakeUpper</font><font style="FONT-SIZE: 14px" size=3>改变字符的大小写</font></p>
<p><font style="FONT-SIZE: 14px" size=3>MakeReverse字符倒置，如：</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CString str = "X0123456789";</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> str.MakeReverse();</font></p>
<p><font style="FONT-SIZE: 14px" size=3>str变为"9876543210X"</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr></font></p>
<p><font style="FONT-SIZE: 14px" size=3>9、+=</font></p>
<p><font style="FONT-SIZE: 14px"><strong>const</strong> <strong>CString&amp;</strong> <strong>operator</strong> <strong>+=(</strong> <strong>const</strong> <strong>CString&amp;</strong> <em>string</em> <strong>);</strong><br><strong>const</strong> <strong>CString&amp;</strong> <strong>operator</strong> <strong>+=(</strong> <strong>TCHAR</strong> <em>ch</em> <strong>);</strong><br><strong>const</strong> <strong>CString&amp;</strong> <strong>operator</strong> <strong>+=(</strong> <strong>LPCTSTR</strong> <em>lpsz</em> <strong>);</strong></font></p>
<p><font style="FONT-SIZE: 14px" size=3>将参数合并到自己身上。</font></p>
<p><font style="FONT-SIZE: 14px" size=3>如：&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> CString str = "0123456789";</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> &nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> str+="ha";</font></p>
<p><font style="FONT-SIZE: 14px" size=3>str为"0123456789ha"；</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr></font></p>
<p><font style="FONT-SIZE: 14px" size=3>10、str[]</font></p>
<p><font style="FONT-SIZE: 14px"><strong>TCHAR</strong> <strong>operator</strong> <strong>[](</strong> <strong>int</strong> <em>nIndex</em> <strong>)</strong> <strong>const;</strong></font></p>
<p><font style="FONT-SIZE: 14px" size=3>象处理字符数组一样处理字符串。</font></p>
<p><font style="FONT-SIZE: 14px" size=3>注意是只读的。</font></p>
<p><font style="FONT-SIZE: 14px" size=3>CString str = "0123456789";</font></p>
<p><font style="FONT-SIZE: 14px" size=3>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> str[0]='x';</font></p>
<p><font style="FONT-SIZE: 14px" size=3>是错误的。</font></p>
<p><font style="FONT-SIZE: 14px">&nbsp;<wbr></font></p>
<p align=left><font style="FONT-SIZE: 14px">11、TrimLeft,TrimRight</font></p>
<p><font style="FONT-SIZE: 14px">void TrimLeft( );</font></p>
<p><font style="FONT-SIZE: 14px">void CString::TrimLeft( TCHAR chTarget );</font></p>
<p><font style="FONT-SIZE: 14px">void CString::TrimLeft( LPCTSTR lpszTargets );</font></p>
<p><font style="FONT-SIZE: 14px">void TrimRight( );</font></p>
<p><font style="FONT-SIZE: 14px">void CString::TrimRight( TCHAR chTarget );</font></p>
<p><font style="FONT-SIZE: 14px">void CString::TrimRight( LPCTSTR lpszTargets );</font></p>
<p align=left><font style="FONT-SIZE: 14px">&nbsp;<wbr></font></p>
<p align=left><font style="FONT-SIZE: 14px">CString str = "\n\t a";</font></p>
<p align=left><font style="FONT-SIZE: 14px">str.TrimLeft();</font></p>
<p align=left><font style="FONT-SIZE: 14px">str为&#8220;a&#8221;;</font></p>
<p><font style="FONT-SIZE: 14px">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>如果没有参数,从左删除字符(\n\t空格等),至到遇到一个非此类字符.</font></p>
<p><font style="FONT-SIZE: 14px">当然你也可以指定删除那些字符.</font></p>
<p><font style="FONT-SIZE: 14px">如果指定的参数是字符串,那么遇上其中的一个字符就删除.</font></p>
<p align=left><font style="FONT-SIZE: 14px">CString str = "abbcadbabcadb ";</font></p>
<p><font style="FONT-SIZE: 14px">str.TrimLeft("ab");</font></p>
<p><font style="FONT-SIZE: 14px">结果"cadbabcadb "</font></p>
<p><font style="FONT-SIZE: 14px">&nbsp;<wbr></font></p>
<p align=left><font style="FONT-SIZE: 14px">12、int CString::Remove( TCHAR ch );</font></p>
<p align=left><font style="FONT-SIZE: 14px">ch删除的字符.</font></p>
<p align=left><font style="FONT-SIZE: 14px">返回删除字符的个数,有多个时都会删除.</font></p>
<p><font style="FONT-SIZE: 14px" size=2>&nbsp;<wbr></font></p>
<p><a name=与字符数组之间的相互转换></a><font style="FONT-SIZE: 22px" size=3>&nbsp;<wbr> 二、CString 与char []之间的转换.&nbsp;<wbr>&nbsp;<wbr></font></p>
<p><font style="FONT-SIZE: 14px" size=3>char str[100] = &#8221;str&#8221;;</font></p>
<p><font style="FONT-SIZE: 14px" size=3>CString sstr = &#8220;sstr&#8221;;</font></p>
<p><font style="FONT-SIZE: 14px" size=3>str.Format(&#8220;%s&#8221;,str);</font></p>
<p><font style="FONT-SIZE: 14px" size=3>str = LPCTSTR sstr;</font></p>
<p><font style="FONT-SIZE: 14px" size=3>strcpy(str,(LPCTSTR)sstr);</font></p>
<p><font style="FONT-SIZE: 14px">如果是赋值,则要:</font></p>
<p><font style="FONT-SIZE: 14px">CString s(_T("This is a test "));<br>LPTSTR p = s.GetBuffer();<br>// 在这里添加使用p的代码<br>if(p != NULL) *p = _T('\0');<br>s.ReleaseBuffer();<br>// 使用完后及时释放，以便能使用其它的CString成员函数<br></font></p>
<p><font style="FONT-SIZE: 14px">str的值变了.</font></p>
<p><font style="FONT-SIZE: 14px">&nbsp;<wbr></font></p>
<p><font style="FONT-SIZE: 14px"><font style="FONT-SIZE: 22px">&nbsp;<wbr> 三、将NULL字节放入CString中<br></font>1,CString str("abc\0""def", 7);<br>&nbsp;<wbr> str +="g";<br>&nbsp;<wbr> int nLength = str.GetLength();<br>&nbsp;<wbr> nLength为8.<br>2,CString str("My name is hedan!");<br>&nbsp;<wbr> str.SetAt(5, 0);<br>&nbsp;<wbr> int nLength = str.GetLength();<br>注意:不是所有的CString成员函数都可以，在使用时一定要小心。</font></p>
<p><font style="FONT-SIZE: 14px">实例:动态配置数据源<br>CString strDsn,strDBQ;<br>strDsn = "DSN=test";<br>strDBQ.Format("DBQ=%s",strSourceMDBName);<br>CString strConnect = strDsn + " " + strDBQ ;<br>strConnect.SetAt(strDsn.GetLength(),'\0');&nbsp;<wbr><br>SQLConfigDataSource(NULL, ODBC_ADD_SYS_DSN, "Microsoft Access Driver (*.mdb)", strConnect);</font></p>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/65379.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:21 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65379.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Debug和Release Build（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65378.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:20:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65378.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65378.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65378.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65378.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65378.html</trackback:ping><description><![CDATA[Debug版本包括调试信息，所以要比Release版本大很多（可能大数百K至数M）。
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 至于是否需要DLL支持，主要看你采用的编译选项。如果是基于 ATL的，则Debug和Release版本对DLL的要求差不多。如果采用的编译选项为使用MFC动态库，则需要MFC42D.DLL等库支持，而Release版本需要MFC42.DLL支持。</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> Release Build不对源代码进行调试，不考虑MFC的诊断宏，使用的是MFC Release库，编译时对应用程序的速度进行优化，而Debug Build则正好相反，它允许对源代码进行调试，可以定义和使用MFC的诊断宏，采用MFC Debug库，对速度没有优化。&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br><br>一、Debug 和 Release 编译方式的本质区别 &nbsp;<wbr><br>&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> Debug通常称为调试版本，它包含调试信息，并且不作任何优化，便于程序员调试程序。Release 称为发布版本，它往往是进行了各种优化，使得程序在代码大小和运行速度上都是最优的，以便用户很好地使用。 &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> Debug 和&nbsp;<wbr>Release 的真正秘密，在于一组编译选项。下面列出了分别针对二者的选项（当然除此之外还有其他一些，如/Fd、/Fo，但区别并不重要，通常他们也不会引起 &nbsp;<wbr>Release &nbsp;<wbr>版错误，在此不讨论）&nbsp;<wbr>&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 1、Debug &nbsp;<wbr>版本： &nbsp;<wbr><br>/MDd /MLd&nbsp;<wbr>或 /MTd：使用 Debug runtime library(调试版本的运行时刻函数库) &nbsp;<wbr><br>/Od：关闭优化开关 &nbsp;<wbr><br>/D "_DEBUG"：相当于 #define _DEBUG,打开编译调试代码开关(主要针对assert函数) &nbsp;<wbr><br>/ZI：创建 &nbsp;<wbr>Edit &nbsp;<wbr>and &nbsp;<wbr>continue(编辑继续)数据库，这样在调试过程中如果修改了源代码不需重新编译 &nbsp;<wbr><br>/GZ：可以帮助捕获内存错误 &nbsp;<wbr><br>/Gm：打开最小化重链接开关，减少链接时间&nbsp;<wbr>&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 2、Release &nbsp;<wbr>版本： &nbsp;<wbr> &nbsp;<wbr><br>/MD /ML 或&nbsp;<wbr>/MT &nbsp;<wbr>使用发布版本的运行时刻函数库 &nbsp;<wbr><br>/O1&nbsp;<wbr>或 /O2：优化开关，使程序最小或最快 &nbsp;<wbr><br>/D "NDEBUG"：关闭条件编译调试代码开关(即不编译assert函数) &nbsp;<wbr><br>/GF：合并重复的字符串，并将字符串常量放到只读内存，防止被修改&nbsp;<wbr>&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 实际上，Debug &nbsp;<wbr>和 &nbsp;<wbr>Release &nbsp;<wbr>并没有本质的界限，他们只是一组编译选项的集合，编译器只是按照预定的选项行动。事实上，我们甚至可以修改这些选项，从而得到优化过的调试版本或是带跟踪语句的发布版本。 &nbsp;<wbr><br>&nbsp;<wbr><br>二、哪些情况下 &nbsp;<wbr>Release &nbsp;<wbr>版会出错 &nbsp;<wbr><br>&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 有了上面的介绍，我们再来逐个对照这些选项看看 &nbsp;<wbr>Release &nbsp;<wbr>版错误是怎样产生的。</p>
<p>&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 1.Runtime Library：</p>
<p>链接哪种运行时刻函数库通常只对程序的性能产生影响。调试版本的Runtime Library 包含了调试信息，并采用了一些保护机制以帮助发现错误，因此性能不如发布版本。编译器提供的 Runtime Library 通常很稳定，不会造成&nbsp;<wbr>Release 版错误；倒是由于 Debug 的 Runtime&nbsp;<wbr>Library&nbsp;<wbr>加强了对错误的检测，如堆内存分配，有时会出现 Debug 有错但&nbsp;<wbr>Release 正常的现象。应当指出的是，如果 Debug&nbsp;<wbr>有错，即&nbsp;<wbr>Release &nbsp;<wbr>正常，程序肯定是有&nbsp;<wbr>Bug&nbsp;<wbr>的，只不过可能是 Release&nbsp;<wbr>版的某次运行没有表现出来而已。&nbsp;<wbr></p>
<p>&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 2.优化：</p>
<p>这是造成错误的主要原因，因为关闭优化时源程序基本上是直接翻译的，而打开优化后编译器会作出一系列假设。这类错误主要有以下几种：</p>
<p>(1) &nbsp;<wbr>帧指针(Frame Pointer)省略（简称FPO）：</p>
<p>在函数调用过程中，所有调用信息（返回地址、参数）以及自动变量都是放在栈中的。若函数的声明与实现不同（参数、返回值、调用方式），就会产生错误。但 Debug 方式下，栈的访问通过 &nbsp;<wbr>EBP &nbsp;<wbr>寄存器保存的地址实现，如果没有发生数组越界之类的错误（或是越界&#8220;不多&#8221;），函数通常能正常执行。Release 方式下，优化会省略 EBP 栈基址指针，这样通过一个全局指针访问栈就会造成返回地址错误是程序崩溃。C++ 的强类型特性能检查出大多数这样的错误，但如果用了强制类型转换，就不行了。你可以在 Release 版本中强制加入 /Oy- 编译选项来关掉帧指针省略，以确定是否此类错误。</p>
<p>此类错误通常有：&nbsp;<wbr></p>
<p>MFC 消息响应函数书写错误。</p>
<p>正确的应为 &nbsp;<wbr><br>afx_msg &nbsp;<wbr>LRESULT &nbsp;<wbr>OnMessageOwn(WPARAM &nbsp;<wbr>wparam, &nbsp;<wbr>LPARAM &nbsp;<wbr>lparam); &nbsp;<wbr><br>ON_MESSAGE &nbsp;<wbr>宏包含强制类型转换。防止这种错误的方法之一是重定义 ON_MESSAGE 宏，把下列代码加到 stdafx.h 中（在#include "afxwin.h"之后）,函数原形错误时编译会报错 &nbsp;<wbr><br>#undef ON_MESSAGE &nbsp;<wbr><br>#define ON_MESSAGE(message,&nbsp;<wbr>memberFxn) \ &nbsp;<wbr><br>{&nbsp;<wbr>message, 0,&nbsp;<wbr>0,&nbsp;<wbr>0, AfxSig_lwl, \ &nbsp;<wbr><br>(AFX_PMSG)(AFX_PMSGW)(static_cast&lt;&nbsp;<wbr>LRESULT (AFX_MSG_CALL \ &nbsp;<wbr><br>CWnd::*)(WPARAM, LPARAM)&nbsp;<wbr>&gt; (&amp;memberFxn)&nbsp;<wbr>},&nbsp;<wbr>&nbsp;<wbr><br>(2) &nbsp;<wbr>volatile &nbsp;<wbr>型变量：</p>
<p>volatile 告诉编译器该变量可能被程序之外的未知方式修改（如系统、其他进程和线程）。优化程序为了使程序性能提高，常把一些变量放在寄存器中（类似于 register 关键字），而其他进程只能对该变量所在的内存进行修改，而寄存器中的值没变。如果你的程序是多线程的，或者你发现某个变量的值与预期的不符而你确信已正确的设置了，则很可能遇到这样的问题。这种错误有时会表现为程序在最快优化出错而最小优化正常。把你认为可疑的变量加上 volatile 试试。&nbsp;<wbr>&nbsp;<wbr><br>(3) &nbsp;<wbr>变量优化：</p>
<p>优化程序会根据变量的使用情况优化变量。例如，函数中有一个未被使用的变量，在&nbsp;<wbr>Debug&nbsp;<wbr>版中它有可能掩盖一个数组越界，而在 Release 版中，这个变量很可能被优化，此时数组越界会破坏栈中有用的数据。当然，实际的情况会比这复杂得多。与此有关的错误有：&nbsp;<wbr>&nbsp;<wbr><br>非法访问，包括数组越界、指针错误等。例如 &nbsp;<wbr><br>void &nbsp;<wbr>fn(void) &nbsp;<wbr><br>{ &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> int &nbsp;<wbr>i; &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> i &nbsp;<wbr>= &nbsp;<wbr>1; &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> int &nbsp;<wbr>a[4]; &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> { &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> int &nbsp;<wbr>j; &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> j &nbsp;<wbr>= &nbsp;<wbr>1; &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> } &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> a[-1] &nbsp;<wbr>= &nbsp;<wbr>1;//当然错误不会这么明显，例如下标是变量 &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> a[4] &nbsp;<wbr>= &nbsp;<wbr>1; &nbsp;<wbr><br>} &nbsp;<wbr><br>j 虽然在数组越界时已出了作用域，但其空间并未收回，因而 i&nbsp;<wbr>和 j 就会掩盖越界。而&nbsp;<wbr>Release&nbsp;<wbr>版由于&nbsp;<wbr>i、j 并未其很大作用可能会被优化掉，从而使栈被破坏。&nbsp;<wbr>&nbsp;<wbr></p>
<p><br>3. _DEBUG 与 NDEBUG：</p>
<p>当定义了&nbsp;<wbr>_DEBUG 时，assert()&nbsp;<wbr>函数会被编译，而 NDEBUG&nbsp;<wbr>时不被编译。除此之外，VC++中还有一系列断言宏。这包括：&nbsp;<wbr>&nbsp;<wbr><br>ANSI C 断言 void assert(int expression ); &nbsp;<wbr><br>C Runtime Lib 断言 _ASSERT( booleanExpression ); &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> _ASSERTE( booleanExpression ); &nbsp;<wbr><br>MFC &nbsp;<wbr>断言 &nbsp;<wbr>ASSERT( &nbsp;<wbr>booleanExpression &nbsp;<wbr>); &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> VERIFY( &nbsp;<wbr>booleanExpression &nbsp;<wbr>);&nbsp;<wbr>&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ASSERT_VALID( &nbsp;<wbr>pObject &nbsp;<wbr>); &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ASSERT_KINDOF( &nbsp;<wbr>classname, &nbsp;<wbr>pobject &nbsp;<wbr>); &nbsp;<wbr><br>ATL &nbsp;<wbr>断言 &nbsp;<wbr>ATLASSERT( &nbsp;<wbr>booleanExpression &nbsp;<wbr>); &nbsp;<wbr><br>此外，TRACE() &nbsp;<wbr>宏的编译也受 &nbsp;<wbr>_DEBUG &nbsp;<wbr>控制。&nbsp;<wbr>&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 所有这些断言都只在 &nbsp;<wbr>Debug版中才被编译，而在 &nbsp;<wbr>Release &nbsp;<wbr>版中被忽略。唯一的例外是 &nbsp;<wbr>VERIFY() 。事实上，这些宏都是调用了 &nbsp;<wbr>assert() &nbsp;<wbr>函数，只不过附加了一些与库有关的调试代码。如果你在这些宏中加入了任何程序代码，而不只是布尔表达式（例如赋值、能改变变量值的函数调用等），那么 &nbsp;<wbr>Release &nbsp;<wbr>版都不会执行这些操作，从而造成错误。初学者很容易犯这类错误，查找的方法也很简单，因为这些宏都已在上面列出，只要利用 &nbsp;<wbr>VC++ &nbsp;<wbr>的 &nbsp;<wbr>Find &nbsp;<wbr>in &nbsp;<wbr>Files 功能在工程所有文件中找到用这些宏的地方再一一检查即可。另外，有些高手可能还会加入 &nbsp;<wbr>#ifdef &nbsp;<wbr>_DEBUG &nbsp;<wbr>之类的条件编译，也要注意一下。 &nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 顺便值得一提的是 &nbsp;<wbr>VERIFY() &nbsp;<wbr>宏，这个宏允许你将程序代码放在布尔表达式里。这个宏通常用来检查 &nbsp;<wbr>Windows &nbsp;<wbr>API &nbsp;<wbr>的返回值。有些人可能为这个原因而滥用&nbsp;<wbr>VERIFY() ，事实上这是危险的，因为&nbsp;<wbr>VERIFY() 违反了断言的思想，不能使程序代码和调试代码完全分离，最终可能会带来很多麻烦。因此，专家们建议尽量少用这个宏。&nbsp;<wbr>&nbsp;<wbr></p>
<p><br>4. &nbsp;<wbr>/GZ &nbsp;<wbr>选项：</p>
<p>这个选项会做以下这些事&nbsp;<wbr>：&nbsp;<wbr><br>(1) &nbsp;<wbr>初始化内存和变量。</p>
<p>包括用 &nbsp;<wbr>0xCC &nbsp;<wbr>初始化所有自动变量，0xCD &nbsp;<wbr>( &nbsp;<wbr>Cleared &nbsp;<wbr>Data &nbsp;<wbr>) &nbsp;<wbr>初始化堆中分配的内存（即动态分配的内存，例如 &nbsp;<wbr>new &nbsp;<wbr>），0xDD &nbsp;<wbr>( &nbsp;<wbr>Dead &nbsp;<wbr>Data &nbsp;<wbr>) &nbsp;<wbr>填充已被释放的堆内存（例如 &nbsp;<wbr>delete &nbsp;<wbr>），0xFD( &nbsp;<wbr>deFencde &nbsp;<wbr>Data &nbsp;<wbr>) &nbsp;<wbr>初始化受保护的内存（debug &nbsp;<wbr>版在动态分配内存的前后加入保护内存以防止越界访问），其中括号中的词是微软建议的助记词。这样做的好处是这些值都很大，作为指针是不可能的（而且 &nbsp;<wbr>32 &nbsp;<wbr>位系统中指针很少是奇数值，在有些系统中奇数的指针会产生运行时错误），作为数值也很少遇到，而且这些值也很容易辨认，因此这很有利于在 &nbsp;<wbr>Debug &nbsp;<wbr>版中发现 &nbsp;<wbr>Release &nbsp;<wbr>版才会遇到的错误。要特别注意的是，很多人认为编译器会用 &nbsp;<wbr>0 &nbsp;<wbr>来初始化变量，这是错误的（而且这样很不利于查找错误）。 &nbsp;<wbr><br>(2) &nbsp;<wbr>通过函数指针调用函数时，会通过检查栈指针验证函数调用的匹配性。（防止原形不匹配） &nbsp;<wbr><br>(3) &nbsp;<wbr>函数返回前检查栈指针，确认未被修改。（防止越界访问和原形不匹配，与第二项合在一起可大致模拟帧指针省略 &nbsp;<wbr>FPO &nbsp;<wbr>）&nbsp;<wbr>&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 通常 &nbsp;<wbr>/GZ &nbsp;<wbr>选项会造成 &nbsp;<wbr>Debug &nbsp;<wbr>版出错而 &nbsp;<wbr>Release &nbsp;<wbr>版正常的现象，因为 &nbsp;<wbr>Release &nbsp;<wbr>版中未初始化的变量是随机的，这有可能使指针指向一个有效地址而掩盖了非法访问。&nbsp;<wbr>&nbsp;<wbr><br><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 除此之外，/Gm &nbsp;<wbr>/GF &nbsp;<wbr>等选项造成错误的情况比较少，而且他们的效果显而易见，比较容易发现。&nbsp;<wbr>&nbsp;<wbr><br></p>
<p>简短地说：<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> Release是发行版本,比Debug版本有一些优化，文件比Debug文件小。Debug是调试版本，包括的程序信息更多。Release方法： build-&gt;batch，build-&gt;build就OK.&nbsp;<wbr>只有DEBUG版的程序才能设置断点、单步执行、使用TRACE/ASSERT等调试输出语句。REALEASE不包含任何调试信息，所以体积小、运行速度快。</p>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/65378.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:20 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65378.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>函数指针（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65377.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:20:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65377.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65377.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65377.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65377.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65377.html</trackback:ping><description><![CDATA[<p>1、函数指针同样是可以作为参数传递给函数的。</p>
<p class="code">#include&nbsp;<wbr>&lt;iostream&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />#include&nbsp;<wbr>&lt;string&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />using&nbsp;<wbr>namespace&nbsp;<wbr>std;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />int&nbsp;<wbr>test(int);&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />int&nbsp;<wbr>test2(int&nbsp;<wbr>(*ra)(int),int);&nbsp;<wbr><br />void&nbsp;<wbr>main(int&nbsp;<wbr>argc,char*&nbsp;<wbr>argv[])&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />{&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;test&lt;&lt;endl;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>typedef&nbsp;<wbr>int&nbsp;<wbr>(*fp)(int);&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>fp&nbsp;<wbr>fpi;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>fpi=test;&nbsp;<wbr>&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;test2(fpi,1)&lt;&lt;endl;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cin.get();&nbsp;<wbr><br />}&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />int&nbsp;<wbr>test(int&nbsp;<wbr>a)&nbsp;<wbr><br />{&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>return&nbsp;<wbr>a-1;&nbsp;<wbr><br />}&nbsp;<wbr><br />int&nbsp;<wbr>test2(int&nbsp;<wbr>(*ra)(int),int&nbsp;<wbr>b)//这里定义了一个名字为ra的函数指针&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>int&nbsp;<wbr>c=ra(10)+b;//在调用之后,ra已经指向fpi所指向的函数地址即test函数&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>return&nbsp;<wbr>c;&nbsp;<wbr><br />}</p>
<p>2、利用函数指针，我们可以构成指针数组，更明确点的说法是构成指向函数的指针数组，这么说可能就容易理解的多了。</p>
<p class="code">#include&nbsp;<wbr>&lt;iostream&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />#include&nbsp;<wbr>&lt;string&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />using&nbsp;<wbr>namespace&nbsp;<wbr>std;&nbsp;<wbr><br />void&nbsp;<wbr>t1(){cout&lt;&lt;"test1";}&nbsp;<wbr><br />void&nbsp;<wbr>t2(){cout&lt;&lt;"test2";}&nbsp;<wbr><br />void&nbsp;<wbr>t3(){cout&lt;&lt;"test3";}&nbsp;<wbr><br />void&nbsp;<wbr>main(int&nbsp;<wbr>argc,char*&nbsp;<wbr>argv[])&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>void*&nbsp;<wbr>a[]={t1,t2,t3};&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;"比较t1()的内存地址和数组a[0]所存储的地址是否一致"&lt;&lt;t1&lt;&lt;"|"&lt;&lt;a[0]&lt;&lt;endl;&nbsp;<wbr>&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;a[0]();//错误!指针数组是不能利用数组下标操作调用函数的&nbsp;<wbr>&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>typedef&nbsp;<wbr>void&nbsp;<wbr>(*fp)();//自定义一个函数指针类型&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>fp&nbsp;<wbr>b[]={t1,t2,t3};&nbsp;<wbr>//利用自定义类型fp把b[]定义成一个指向函数的指针数组&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>b[0]();//现在利用指向函数的指针数组进行下标操作就可以进行函数的间接调用了;&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cin.get();&nbsp;<wbr><br />}</p>
<p class="code">另外：</p>
<p class="code">void* a[]={t1,t2,t3};<br />cout&lt;&lt;"比较t1()的内存地址和数组a[0]所存储的地址是否一致"&lt;&lt;t1&lt;&lt;"|"&lt;&lt;a[0]&lt;&lt;endl;<br />cout&lt;&lt;a[0]();</p>
<p>上面的这一小段中的错误行，为什么不能这么调用呢？指针数组元素所保存的只是一个内存地址，既然只是个内存地址就不可能进行a[0]()这样地址带括号的操作，而函数指针不同，它是一个例外，函数指针只所以这么叫它就是因为它是指向函数指向内存的代码区的指针，它被系统授予允许与()括号操作的权利，进行<strong>间接的函数调用</strong>，既然函数指针允许这么操作，那么被定义成函数指针的数组就一定是可以一样的操作的。</p>
<p>&nbsp;<wbr></p>
<p>3、类的成员函数：</p>
<p><span><span>class CA<br />{<br />&nbsp;<wbr>public:<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> char lcFun(int a){ return; }<br />};<br />CA ca;<br />typedef char (CA::*PTRFUN)(int);<br />PTRFUN pFun;<br />void main()<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> pFun = CA::lcFun;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> ca.(*pFun)(2);<br />}</span> <span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US"><br /><span style="font-size: 10.5pt; font-family: 宋体">在这里，指针的定义与使用都加上了</span><span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US">&#8220;</span><span style="font-size: 10.5pt; font-family: 宋体">类限制</span><span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US">&#8221;</span><span style="font-size: 10.5pt; font-family: 宋体">或</span><span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US">&#8220;</span><span style="font-size: 10.5pt; font-family: 宋体">对象</span><span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US">&#8221;</span><span style="font-size: 10.5pt; font-family: 宋体">，用来指明指针指向的函数是那个类的这里的类对象也可以是使用</span><span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US">new</span><span style="font-size: 10.5pt; font-family: 宋体">得到的。比如：</span> <span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US"><br />CA *pca = new CA;<br />pca-&gt;(*pFun)(2);<br />delete pca;<br /></span><span style="font-size: 10.5pt; font-family: 宋体">而且这个类对象指针可以是类内部成员变量，你甚至可以使用</span><span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US">this</span><span style="font-size: 10.5pt; font-family: 宋体">指针。比如：</span><span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US"><br /></span><span style="font-size: 10.5pt; font-family: 宋体">类</span><span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US">CA</span><span style="font-size: 10.5pt; font-family: 宋体">有成员变量</span><span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US">PTRFUN m_pfun;<br />void CA::lcFun2()<br />{&nbsp;<wbr><br />&nbsp;<wbr>&nbsp;<wbr> (this-&gt;*m_pFun)(2);<br />}<br /></span><span style="font-size: 10.5pt; font-family: 宋体">一句话，使用类成员函数指针必须有</span><span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US">&#8220;-&gt;*&#8221;</span><span style="font-size: 10.5pt; font-family: 宋体">或</span><span lang="EN-US" style="font-size: 10.5pt; font-family: 'Times New Roman'" xml:lang="EN-US">&#8220;.*&#8221;</span><span style="font-size: 10.5pt; font-family: 宋体">的调用。</span></span></span></p><img src ="http://www.cppblog.com/zmllegtui/aggbug/65377.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:20 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65377.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>typedef（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65376.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:19:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65376.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65376.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65376.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65376.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65376.html</trackback:ping><description><![CDATA[<p>一、基本用法：</p>
<p align=left>1、普通类型</p>
<p align=left>typedef unsigned int UINT;</p>
<p align=left>2、struct</p>
<p align=left>typedef struct {int x; int y;} Point;</p>
<p align=left>或者</p>
<p align=left>typedef struct t_node {</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> int Value;</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> struct t_node *next;</p>
<p align=left>} Node;</p>
<p align=left>或者</p>
<p align=left>typedef strcut t_node Node;</p>
<p align=left>struct t_node {</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> int Value;</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> Node *next;</p>
<p align=left>};</p>
<p align=left>3、函数指针</p>
<p align=left>typedef void (*FUNCADDR)(int);</p>
<p align=left>4、类类型</p>
<p align=left>typedef class {</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> private:</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> int a;</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> public:</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> int b;</p>
<p align=left>} MyClass;</p>
<p align=left>&nbsp;<wbr></p>
<p align=left>二、深入讨论：</p>
<p align=left><strong><u>1、关于const 和指针</u></strong></p>
<p align=left>typedef char * pstr;<br>int mystrcmp(pstr, pstr);<br>　　这里将带我们到达第一个 typedef 陷阱。标准函数 strcmp()有两个&#8216;const char *'类型的参数。因此，它可能会误导人们象下面这样声明 mystrcmp()：</p>
<p align=left>int mystrcmp(const pstr, const pstr);<br>　　这是错误的，按照顺序，&#8216;const pstr'被解释为&#8216;char * const'（一个指向 char 的常量指针），而不是&#8216;const char *'（指向常量 char 的指针）。</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 这个问题很容易解决：</p>
<p align=left>typedef const char * cpstr;<br>int mystrcmp(cpstr, cpstr); // 现在是正确的</p>
<p align=left>2、文本替换</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> typedef 行为有点像&nbsp;<wbr>#define&nbsp;<wbr>宏，用其实际类型替代同义字。不同点是 typedef 在编译时被解释，因此让编译器来应付超越预处理器能力的文本替换。</p>
<p align=left>例如:</p>
<p align=left>typedef int&nbsp;<wbr>(*PF)&nbsp;<wbr>(const char&nbsp;<wbr>*, const char&nbsp;<wbr>*);</p>
<p align=left>如果要使用下列形式的函数声明，那么上述这个 typedef 是不可或缺的：</p>
<p align=left>PF Register(PF pf);<br>展示一下如果不用 typedef，我们是如何实现这个声明的：</p>
<p align=left>int (*Register (int (*pf)(const char *, const char *)))(const char *, const char *);<br>3、存储类关键字</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> typedef 就像 auto，extern，mutable，static，和 register 一样，是一个存储类关键字。</p>
<p align=left>第二个陷阱：</p>
<p align=left>typedef register int FAST_COUNTER; // 错误<br>编译通不过。问题出在你不能在声明中有多个存储类关键字。因为符号 typedef 已经占据了存储类关键字的位置，在 typedef 声明中不能用 register（或任何其它存储类关键字）。</p>
<p align=left>4、定义机器无关的类型</p>
<p align=left>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 例如，你可以定义一个叫 REAL 的浮点类型，在目标机器上它可以i获得最高的精度：</p>
<p align=left>typedef long double REAL;<br>在不支持 long double 的机器上，该 typedef 看起来会是下面这样：</p>
<p align=left>typedef double REAL;<br>并且，在连 double 都不支持的机器上，该 typedef 看起来会是这样：</p>
<p align=left>typedef float REAL;<br>　　你不用对源代码做任何修改，便可以在每一种平台上编译这个使用 REAL 类型的应用程序。唯一要改的是 typedef 本身。在大多数情况下，甚至这个微小的变动完全都可以通过奇妙的条件编译来自动实现。不是吗? 标准库广泛地使用 typedef 来创建这样的平台无关类型：size_t，ptrdiff 和 fpos_t 就是其中的例子。此外，象 std::string 和 std::ofstream 这样的 typedef 还隐藏了长长的，难以理解的模板特化语法，例如：basic_string&lt;char, char_traits&lt;char&gt;，allocator&lt;char&gt;&gt; 和 basic_ofstream&lt;char, char_traits&lt;char&gt;&gt;。</p>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/65376.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:19 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65376.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>构造函数中的this指针（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65375.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:19:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65375.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65375.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65375.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65375.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65375.html</trackback:ping><description><![CDATA[<p>-------------------------------原理-------------------------------</p>
<p>某些人认为不应该在构造函数中使用this指针，因为这时this对象还没有完全形成。</p>
<p>但是，只要小心，是可以在构造函数中使用this指针的：</p>
<p>●在函数体中</p>
<p>●初始化列表中</p>
<p>因为&#8220;对象还没有完全形成&#8221;不意味着&#8220;什么都没有&#8221;。</p>
<p>在进入构造函数（及其chaining）之前，Compiler会：</p>
<p>●给class的instance分配内存</p>
<p>●建立运行时刻系统所需的信息（如vtbl等）</p>
<p>●##缺省地## 构造所有类成员<br><img src="http://www.vckbase.com/document/journal/vckbase26/images/this-pattern.gif" width=441 height=450><br>-----------------------------【能】---------------------------------</p>
<p>构造函数的函数体（或构造函数所调用的函数）【能】可靠地访问：</p>
<p>●基类中声明的数据成员</p>
<p>●构造函数所属类声明的数据成员</p>
<p>这是因为所有这些数据成员被保证在构造函数函数体开始执行时已经被完整的建立。</p>
<p>-----------------------------【不能】---------------------------------</p>
<p>构造函数的函数体（或构造函数所调用的函数）【不能】向下调用：</p>
<p>●被派生类重定义的虚函数</p>
<p>这是因为在基类的构造函数执行期间，&#8220;对象还不是一个派生类的对象&#8221;。</p>
<p>---------------------------【有时】-----------------------------------</p>
<p>以下是【有时】可行的：</p>
<p>●传递 this 对象的任何一个数据成员给另一个数据成员的初始化程序</p>
<p>你必须确保该数据成员已经被初始化。好消息是你能使用一些不依赖于你所使用的编译器的显著的语言规则，来确定那个数据成员是否已经（或者还没有）被初始化。坏消息是你必须知道这些语言规则（例如，基类子对象首先被初始化（如果有多重和／或虚继承，则查询这个次序！），然后类中定义的数据成员根据在类中声明的次序被初始化）。如果你不知道这些规则，则不要从this对象传递任何数据成员（不论是否显式的使用了this关键字）给任何其他数据成员的初始化程序！如果你知道这些规则，则需要小心。</p>
<p>----------------------------用途----------------------------------</p>
<p>好的OO设计强调&#8220;高聚合&#8221;，这样会产生很多小的责任单一的对象(其实&#8220;单一责任原则&#8221;根本就是最基础的OO原则)。</p>
<p>那么，小对象之间的协作就需要配置(其实&#8220;协作可配置&#8221;本身就是我们希望的灵活性所在)：</p>
<p>●比如Observer模式中subject和observer的协作需要调subject.RegistorObserver(observer)来配置</p>
<p>●再比如多媒体框架DirectShow中filterGraph和videoWindow的协作需要调filterGraph.SetVideoWindow(videoWindow)来配置</p>
<p>而构造函数是很典型的配置时机，举例如下：</p>
<p><br>class CMyWindow : public CWnd<br>{<br>private:<br>CFilterGraph filterGraph;<br>public<br>CMyWindow() { filterGraph.SetVideoWindow(this); };<br>};<br>--------------------------附录------------------------------------</p>
<p>需要明了的是，【构造/析构/普通】和【虚/非虚】是完全独立的分类方式：<br>●只要是&#8220;构造/析构&#8221;就&#8220;串联(chaining)&#8221;<br>●只要是&#8220;虚函数&#8221;就&#8220;可能obj.vfun()&#8221;</p>
<p>它们可以&#8220;一起生效&#8221;但&#8220;不互相干扰&#8221;，比如虚析构函数的情况，看下面的例子：</p>
<pre>class superclass <br>{ <br>&nbsp;&nbsp;&nbsp;virtual ~superclass() <br>&nbsp;&nbsp;&nbsp;{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;println("superclass::~superclass()"); <br>&nbsp;&nbsp;&nbsp;} <br>}; <br>class subclass : public superclass <br>{ <br>&nbsp;&nbsp;&nbsp;virtual ~subclass() <br>&nbsp;&nbsp;&nbsp;{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;println("subclass::~subclass()"); <br>&nbsp;&nbsp;&nbsp;} <br>};</pre>
执行
<pre>superclass * super = new subclass(); </pre>
<pre>delete super;</pre>
的结果是打印出
<pre>subclass::~subclass() </pre>
<pre>superclass::~superclass()</pre>
这意味着当执行delete super;时：<br>●是否是chaining式call呢？是。因为是析构函数。<br>●那chaining call从哪里开始呢？从subclass::~subclass() ==〉superclass::~superclass()，<br>因为superclass * super的实际对象的类型是subclass。 
<img src ="http://www.cppblog.com/zmllegtui/aggbug/65375.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:19 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65375.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>this指针（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65372.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:16:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65372.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65372.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65372.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65372.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65372.html</trackback:ping><description><![CDATA[有下面的一个简单的类：
<div style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 95%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<div><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;<wbr>CNullPointCall<br>{<br></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span style="COLOR: #0000ff">static</span><span style="COLOR: #000000">&nbsp;<wbr></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>Test1();<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>Test2();<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>Test3(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;<wbr>iTest);<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>Test4();<br><br></span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span style="COLOR: #0000ff">static</span><span style="COLOR: #000000">&nbsp;<wbr></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;<wbr>m_iStatic;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;<wbr>m_iTest;<br>};<br><br></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;<wbr>CNullPointCall::m_iStatic&nbsp;<wbr></span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;<wbr></span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br><br></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>CNullPointCall::Test1()<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&nbsp;<wbr></span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;<wbr>m_iStatic&nbsp;<wbr></span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;<wbr>endl;<br>}<br><br></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>CNullPointCall::Test2()<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&nbsp;<wbr></span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;<wbr></span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">Very&nbsp;<wbr>Cool!</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">&nbsp;<wbr></span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;<wbr>endl;&nbsp;<wbr><br>}<br><br></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>CNullPointCall::Test3(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;<wbr>iTest)<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&nbsp;<wbr></span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;<wbr>iTest&nbsp;<wbr></span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;<wbr>endl;&nbsp;<wbr><br>}<br><br></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>CNullPointCall::Test4()<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&nbsp;<wbr></span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;<wbr>m_iTest&nbsp;<wbr></span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;<wbr>endl;&nbsp;<wbr><br>}</span></div>
</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 那么下面的代码都正确吗？都会输出什么？</div>
<div style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 95%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<div><span style="COLOR: #000000">CNullPointCall&nbsp;<wbr></span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">pNull&nbsp;<wbr></span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;<wbr>NULL;&nbsp;<wbr></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;<wbr>没错，就是给指针赋值为空</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">pNull</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">Test1();</span> <span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;<wbr>call&nbsp;<wbr>1</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">pNull</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">Test2();&nbsp;<wbr></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;<wbr>call&nbsp;<wbr>2</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">pNull</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">Test3(</span><span style="COLOR: #000000">13</span><span style="COLOR: #000000">);&nbsp;<wbr></span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;<wbr>call&nbsp;<wbr>3</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">pNull</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">Test4();&nbsp;<wbr><span style="COLOR: #008000">/</span></span><span style="COLOR: #008000">/</span><span style="COLOR: #008000">&nbsp;<wbr>call&nbsp;<wbr>4</span></div>
</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 你肯定会很奇怪我为什么这么问。一个值为NULL的指针怎么可以用来调用类的成员函数呢？！可是实事却很让人吃惊：除了call 4那行代码以外，其余3个类成员函数的调用都是成功的，都能正确的输出结果，而且包含这3行代码的程序能非常好的运行。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 经过细心的比较就可以发现，call 4那行代码跟其他3行代码的本质区别：类CNullPointCall的成员函数中用到了this指针。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 对于类成员函数而言，并不是一个对象对应一个单独的成员函数体，而是此类的所有对象共用这个成员函数体。当程序被编译之后，此成员函数地址即已确定。而成员函数之所以能把属于此类的各个对象的数据区别开, 就是靠这个this指针。函数体内所有对类数据成员的访问，都会被转化为this-&gt;数据成员的方式。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 而一个对象的this指针并不是对象本身的一部分，不会影响sizeof（&#8220;对象&#8221;）的结果。this作用域是在类内部，当在类的非静态成员函数中访问类的非静态成员的时候，编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说，即使你没有写上this指针，编译器在编译的时候也是加上this的，它作为非静态成员函数的隐含形参，对各成员的访问均通过this进行。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 对于上面的例子来说，this的值也就是pNull的值。也就是说this的值为NULL。而Test1()是静态函数，编译器不会给它传递this指针，所以call 1那行代码可以正确调用（这里相当于CNullPointCall::Test1()）；对于Test2()和Test3()两个成员函数，虽然编译器会给这两个函数传递this指针，但是它们并没有通过this指针来访问类的成员变量，因此call 2和call 3两行代码可以正确调用；而对于成员函数Test4()要访问类的成员变量，因此要使用this指针，这个时候发现this指针的值为NULL，就会造成程序的崩溃。&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 其实，我们可以想象编译器把Test4()转换成如下的形式：</div>
<div style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 95%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<div><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>CNullPointCall::Test4(CNullPointCall&nbsp;<wbr></span><span style="COLOR: #000000">*</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">)<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&nbsp;<wbr></span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;<wbr></span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">m_iTest&nbsp;<wbr></span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;<wbr>endl;&nbsp;<wbr><br>}</span></div>
</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 而把call 4那行代码转换成了下面的形式：</div>
<div style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 95%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<div><span style="COLOR: #000000">CNullPointCall::Test4(pNull);</span></div>
</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 所以会在通过this指针访问m_iTest的时候造成程序的崩溃。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 下面通过查看上面代码用VC 2005编译后的汇编代码来详细解释一下神奇的this指针。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 上面的C++代码编译生成的汇编代码是下面的形式：</div>
<div style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 95%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<div><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>CNullPointCall&nbsp;<wbr></span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">pNull&nbsp;<wbr></span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;<wbr>NULL;<br>0041171E&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>dword&nbsp;<wbr>ptr&nbsp;<wbr>[pNull],</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>pNull</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">Test1();<br></span><span style="COLOR: #000000">00411725</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>call&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>CNullPointCall::Test1&nbsp;<wbr>(411069h)&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>pNull</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">Test2();<br>0041172A&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ecx,dword&nbsp;<wbr>ptr&nbsp;<wbr>[pNull]&nbsp;<wbr><br>0041172D&nbsp;<wbr>&nbsp;<wbr>call&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>CNullPointCall::Test2&nbsp;<wbr>(4111E0h)&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>pNull</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">Test3(</span><span style="COLOR: #000000">13</span><span style="COLOR: #000000">);<br></span><span style="COLOR: #000000">00411732</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>push&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>0Dh&nbsp;<wbr>&nbsp;<wbr><br></span><span style="COLOR: #000000">00411734</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ecx,dword&nbsp;<wbr>ptr&nbsp;<wbr>[pNull]&nbsp;<wbr><br></span><span style="COLOR: #000000">00411737</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>call&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>CNullPointCall::Test3&nbsp;<wbr>(41105Ah)&nbsp;<wbr><br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>pNull</span><span style="COLOR: #000000">-&gt;</span><span style="COLOR: #000000">Test4();<br>0041173C&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ecx,dword&nbsp;<wbr>ptr&nbsp;<wbr>[pNull]&nbsp;<wbr><br>0041173F&nbsp;<wbr>&nbsp;<wbr>call&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>CNullPointCall::Test4&nbsp;<wbr>(411032h)&nbsp;<wbr></span></div>
</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 通过比较静态函数Test1()和其他3个非静态函数调用所生成的的汇编代码可以看出：非静态函数调用之前都会把指向对象的指针pNull（也就是this指针）放到ecx寄存器中（mov ecx,dword ptr [pNull]）。这就是this指针的特殊之处。看call 3那行C++代码的汇编代码就可以看到this指针跟一般的函数参数的区别：一般的函数参数是直接压入栈中（push 0Dh），而this指针却被放到了ecx寄存器中。在类的非成员函数中如果要用到类的成员变量，就可以通过访问ecx寄存器来得到指向对象的this指针，然后再通过this指针加上成员变量的偏移量来找到相应的成员变量。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 下面再通过另外一个例子来说明this指针是怎样被传递到成员函数中和如何使用this来访问成员变量的。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 依然是一个很简单的类：</div>
<div style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 95%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<div><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000">&nbsp;<wbr>CTest<br>{<br></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>SetValue();<br><br></span><span style="COLOR: #0000ff">private</span><span style="COLOR: #000000">:<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;<wbr>m_iValue1;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;<wbr>m_iValue2;<br>};<br><br></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>CTest::SetValue()<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>m_iValue1&nbsp;<wbr></span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;<wbr></span><span style="COLOR: #000000">13</span><span style="COLOR: #000000">;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>m_iValue2&nbsp;<wbr></span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;<wbr></span><span style="COLOR: #000000">13</span><span style="COLOR: #000000">;<br>}</span></div>
</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 用如下的代码调用成员函数：</div>
<div style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 95%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<div><span style="COLOR: #000000">CTest&nbsp;<wbr>test;<br>test.SetValue();<br></span></div>
</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 上面的C++代码的汇编代码为：</div>
<div style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 95%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<div><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>CTest&nbsp;<wbr>test;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>test.SetValue();<br>004117DC&nbsp;<wbr>&nbsp;<wbr>lea&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ecx,[test]&nbsp;<wbr><br>004117DF&nbsp;<wbr>&nbsp;<wbr>call&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>CTest::SetValue&nbsp;<wbr>(4111CCh)&nbsp;<wbr></span></div>
</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 同样的，首先把指向对象的指针放到ecx寄存器中；然后调用类CTest的成员函数SetValue()。地址4111CCh那里存放的其实就是一个转跳指令，转跳到成员函数SetValue()内部。</div>
<div style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 95%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<div><span style="COLOR: #000000">004111CC&nbsp;<wbr>&nbsp;<wbr>jmp&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>CTest::SetValue&nbsp;<wbr>(411750h)</span></div>
</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 而411750h才是类CTest的成员函数SetValue()的地址。</div>
<div style="BORDER-RIGHT: windowtext 0.5pt solid; PADDING-RIGHT: 5.4pt; BORDER-TOP: windowtext 0.5pt solid; PADDING-LEFT: 5.4pt; BACKGROUND: #e6e6e6; PADDING-BOTTOM: 4px; BORDER-LEFT: windowtext 0.5pt solid; WIDTH: 95%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">
<div><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;<wbr>CTest::SetValue()<br>{<br></span><span style="COLOR: #000000">00411750</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>push&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ebp&nbsp;<wbr>&nbsp;<wbr><br></span><span style="COLOR: #000000">00411751</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ebp,esp&nbsp;<wbr><br></span><span style="COLOR: #000000">00411753</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>sub&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>esp,0CCh&nbsp;<wbr><br></span><span style="COLOR: #000000">00411759</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>push&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ebx&nbsp;<wbr>&nbsp;<wbr><br>0041175A&nbsp;<wbr>&nbsp;<wbr>push&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>esi&nbsp;<wbr>&nbsp;<wbr><br>0041175B&nbsp;<wbr>&nbsp;<wbr>push&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>edi&nbsp;<wbr>&nbsp;<wbr><br>0041175C&nbsp;<wbr>&nbsp;<wbr>push&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ecx</span> <span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;<wbr>1&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">0041175D&nbsp;<wbr>&nbsp;<wbr>lea&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>edi,[ebp</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">0CCh]&nbsp;<wbr><br></span><span style="COLOR: #000000">00411763</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ecx,33h&nbsp;<wbr><br></span><span style="COLOR: #000000">00411768</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>eax,0CCCCCCCCh&nbsp;<wbr><br>0041176D&nbsp;<wbr>&nbsp;<wbr>rep&nbsp;<wbr>stos&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>dword&nbsp;<wbr>ptr&nbsp;<wbr>es:[edi]&nbsp;<wbr><br>0041176F&nbsp;<wbr>&nbsp;<wbr>pop&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ecx</span> <span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;<wbr>2&nbsp;<wbr></span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">00411770</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>dword&nbsp;<wbr>ptr&nbsp;<wbr>[ebp</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">8</span><span style="COLOR: #000000">],ecx</span> <span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;<wbr>3</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>m_iValue1&nbsp;<wbr></span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;<wbr></span><span style="COLOR: #000000">13</span><span style="COLOR: #000000">;<br></span><span style="COLOR: #000000">00411773</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>eax,dword&nbsp;<wbr>ptr&nbsp;<wbr>[</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">]</span> <span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;<wbr>4</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">00411776</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>dword&nbsp;<wbr>ptr&nbsp;<wbr>[eax],0Dh</span> <span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;<wbr>5</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>m_iValue2&nbsp;<wbr></span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;<wbr></span><span style="COLOR: #000000">13</span><span style="COLOR: #000000">;<br>0041177C&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>eax,dword&nbsp;<wbr>ptr&nbsp;<wbr>[</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">]</span> <span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;<wbr>6</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">0041177F&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>dword&nbsp;<wbr>ptr&nbsp;<wbr>[eax</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">4</span><span style="COLOR: #000000">],0Dh</span> <span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;<wbr>7</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">}<br></span><span style="COLOR: #000000">00411786</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>pop&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>edi&nbsp;<wbr>&nbsp;<wbr><br></span><span style="COLOR: #000000">00411787</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>pop&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>esi&nbsp;<wbr>&nbsp;<wbr><br></span><span style="COLOR: #000000">00411788</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>pop&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ebx&nbsp;<wbr>&nbsp;<wbr><br></span><span style="COLOR: #000000">00411789</span><span style="COLOR: #000000">&nbsp;<wbr>&nbsp;<wbr>mov&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>esp,ebp&nbsp;<wbr><br>0041178B&nbsp;<wbr>&nbsp;<wbr>pop&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>ebp&nbsp;<wbr>&nbsp;<wbr><br>0041178C&nbsp;<wbr>&nbsp;<wbr>ret&nbsp;<wbr></span></div>
</div>
<div>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 下面对上面的汇编代码中的重点行进行分析：<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 1、将ecx寄存器中的值压栈，也就是把this指针压栈。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 2、ecx寄存器出栈，也就是this指针出栈。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 3、将ecx的值放到指定的地方，也就是this指针放到[ebp-8]内。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 4、取this指针的值放入eax寄存器内。此时，this指针指向test对象，test对象只有两个int型的成员变量，在test对象内存中连续存放，也就是说this指针目前指向m_iValue1。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 5、给寄存器eax指向的地址赋值0Dh（十六进制的13）。其实就是给成员变量m_iValue1赋值13。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 6、同4。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 7、给寄存器eax指向的地址加4的地址赋值。在4中已经说明，eax寄存器内存放的是this指针，而this指针指向连续存放的int型的成员变量m_iValue1。this指针加4（sizeof(int)）也就是成员变量m_iValue2的地址。因此这一行就是给成员变量m_iValue2赋值。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 通过上面的分析，我们可以从底层了解了C++中this指针的实现方法。虽然不同的编译器会使用不同的处理方法，但是C++编译器必须遵守C++标准，因此对于this指针的实现应该都是差不多的。</div>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/65372.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:16 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65372.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>引用（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65371.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:15:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65371.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65371.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65371.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65371.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65371.html</trackback:ping><description><![CDATA[<p>----引用的地址：</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 由于引用本身就是目标的一个别名，引用本身的地址是一个没有意义的值，所以在c++中是无法取得引用的内存地址的。取引用的地址就是取目标的地址，c++本身就根本不提供获取引用内存地址的方法。</p>
<p>&nbsp;<wbr></p>
<p>----引用的赋值</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 引用一但初始化,就不在能够被指向其它的目标，虽然编译不会出错，但操作是不起作用的，实际上还是指向最先指向的目标。<br />
int&nbsp;<wbr>a=10;&nbsp;<wbr>&nbsp;<wbr><br />
int&nbsp;<wbr>b=20;&nbsp;<wbr>&nbsp;<wbr><br />
int&nbsp;<wbr>&amp;rn=a;&nbsp;<wbr>&nbsp;<wbr><br />
rn=b;//把引用指向另一个目标----变量b&nbsp;<wbr><br />
上面代码中的rn=b实际在计算机看来就是a=b，所以修改的还是a的值。</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> void修饰是不能够声明引用的，<span style="color: #ffff00;"><strong>引用是不能够声明数组的</strong></span>，即不能够声明引用数组，因为数组是一个由若干个元素所组成的集合，所以无法建立一个数组的别名。</p>
<p>&nbsp;<wbr></p>
<p>----返回类成员引用：</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 可以返回类成员的引用，但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则（business rule）相关联的时候，其赋值常常与某些其它属性或者对象的状态有关，因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用（或指针），那么对该属性的单纯赋值就会破坏业务规则的完整性。</p>
<p><br />
----引用与一些操作符的重载：<br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 流操作符&lt;&lt;和&gt;&gt;：这两个操作符常常希望被连续使用，例如：cout &lt;&lt; "hello" &lt;&lt; endl;　因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括：返回一个流对象和返回一个流对象指针。但是对于返回一个流对象，程序必须重新（拷贝）构造一个新的流对象，也就是说，连续的两个&lt;&lt;操作符实际上是针对不同对象的！这无法让人接受。对于返回一个流指针则不能连续使用&lt;&lt;操作符。因此，返回一个流对象引用是惟一选择。这个唯一选择很关键，它说明了引用的重要性以及无可替代性，也许这就是C++语言中引入引用这个概念的原因吧。</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 赋值操作符=，这个操作符象流操作符一样，是可以连续使用的，例如：x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值，以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。</p>
<p>#include &lt;iostream.h&gt;<br />
int &amp;put(int n);<br />
int vals[10];<br />
int error=-1;<br />
void main()<br />
{<br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> put(0)=10; //以put(0)函数值作为左值，等价于vals[0]=10;&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> put(9)=20; //以put(9)函数值作为左值，等价于vals[9]=10;<br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> cout&lt;&lt;vals[0];&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> cout&lt;&lt;vals[9];<br />
}<br />
int &amp;put(int n)<br />
{<br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> if (n&gt;=0 &amp;&amp; n&lt;=9 ) return vals[n];<br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> else { cout&lt;&lt;"subscript error"; return error; }<br />
}</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>在另外的一些操作符中，却千万不能返回引用：+-*/ 四则运算符。它们不能返回引用，Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect，因此，它们必须构造一个对象作为返回值，可选的方案包括：返回一个对象、返回一个局部变量的引用，返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则，第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。</p>
<p>&nbsp;<wbr></p>
<p>----引用和多态：<br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 引用是除指针外另一个可以产生多态效果的手段。这意味着，一个基类的引用可以指向它的派生类实例。<br />
class 　A;<br />
class 　B：public A{&#8230;&#8230;};<br />
B 　b;<br />
A 　&amp;Ref = b; // 用派生类对象初始化基类对象的引用<br />
Ref 只能用来访问派生类对象中从基类继承下来的成员，是基类引用指向派生类。如果A类中定义有虚函数，并且在B类中重写了这个虚函数，就可以通过Ref产生多态效果。<br />
</p>
<p>&nbsp;<wbr></p>
<p>----引用与函数返回值</p>
<p>1、</p>
<p class="code">#include&nbsp;<wbr>&lt;iostream&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
#include&nbsp;<wbr>&lt;string&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
using&nbsp;<wbr>namespace&nbsp;<wbr>std;&nbsp;<wbr>&nbsp;<wbr><br />
<br />
float&nbsp;<wbr>c;&nbsp;<wbr><br />
float&nbsp;<wbr>test(float,float);&nbsp;<wbr><br />
void&nbsp;<wbr>main(int&nbsp;<wbr>argc,char*&nbsp;<wbr>argv[])&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
{&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>float&nbsp;<wbr>pn=test(3.0f,1.2f);&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;pn;&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cin.get();&nbsp;<wbr><br />
}&nbsp;<wbr><br />
&nbsp;<wbr><br />
float&nbsp;<wbr>test(float&nbsp;<wbr>a,float&nbsp;<wbr>b)&nbsp;<wbr><br />
{&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>c=a*b;&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>return&nbsp;<wbr>c;&nbsp;<wbr><br />
}</p>
<p>　　在上面的代码中我们可能以为函数返回的就是c变量，这么想可能就错了，普通情况下我们在函数内进行普通值返回的时候在内存栈空间内其实是自动产生了一个临时变量temp，它是返回值的一个副本一个copy，函数在return的时候其实是return的这个临时产生的副本。</p>
<p>2、把返回值赋给引用：</p>
<p class="code">#include&nbsp;<wbr>&lt;iostream&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
#include&nbsp;<wbr>&lt;string&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
using&nbsp;<wbr>namespace&nbsp;<wbr>std;&nbsp;<wbr><br />
&nbsp;<wbr><br />
float&nbsp;<wbr>c;&nbsp;<wbr><br />
float&nbsp;<wbr>test(float,float);&nbsp;<wbr><br />
void&nbsp;<wbr>main(int&nbsp;<wbr>argc,char*&nbsp;<wbr>argv[])&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
{&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>float&nbsp;<wbr>&amp;pn=test(3.0f,1.2f);//警告:返回的将是临时变量,pn引用将成为临时变量的别名!&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;pn;&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cin.get();&nbsp;<wbr><br />
}&nbsp;<wbr><br />
&nbsp;<wbr><br />
float&nbsp;<wbr>test(float&nbsp;<wbr>a,float&nbsp;<wbr>b)&nbsp;<wbr><br />
{&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>c=a*b;&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>return&nbsp;<wbr>c;&nbsp;<wbr><br />
}</p>
<p>　　float &amp;pn=test(3.0f,1.2f);这句在bc中能够编译通过，因为bc扩展设置为临时变量设置引用，那么临时变量的生命周期将和引用的生命周期一致，但在vc中却不能通过编译，因为一但test()执行过后临时变量消失在栈空间内，这时候pn将成为一个没有明确目标的引用，严重的时候会导致内存出错。</p>
<p>3、返回引用给变量的情况：</p>
<p class="code">#include&nbsp;<wbr>&lt;iostream&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
#include&nbsp;<wbr>&lt;string&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
using&nbsp;<wbr>namespace&nbsp;<wbr>std;&nbsp;<wbr><br />
&nbsp;<wbr><br />
<strong>float&nbsp;<wbr>c;&nbsp;</strong><wbr><br />
float&amp;&nbsp;<wbr>test(float,float);&nbsp;<wbr><br />
void&nbsp;<wbr>main(int&nbsp;<wbr>argc,char*&nbsp;<wbr>argv[])&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
{&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>float&nbsp;<wbr>pn=test(3.0f,1.2f);&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;pn;&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cin.get();&nbsp;<wbr><br />
}&nbsp;<wbr><br />
&nbsp;<wbr><br />
float&nbsp;<wbr>&amp;test(float&nbsp;<wbr>a,float&nbsp;<wbr>b)&nbsp;<wbr><br />
{&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>c=a*b;&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>return&nbsp;<wbr>c;&nbsp;<wbr><br />
}</p>
<p>　　这种返回引用给变量的情况下，在内存中，test()所在的栈空间内并没有产生临时变量，而是直接将全局变量c的值给了变量pn，这种方式是我们最为推荐的操作方式，因为不产生临时变量直接赋值的方式可以节省内存空间提高效率，程序的可读性也是比较好的。但是仅限于返回的变量是栈中分配的空间，或全局变量，否则返回的依然是临时变量。一般对于内置类型，可以使用第一种情况直接返回副本。</p>
<p>4、最后的一种情况是函数返回引用,并且发值赋给一个引用的情况：</p>
<p class="code">#include&nbsp;<wbr>&lt;iostream&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
#include&nbsp;<wbr>&lt;string&gt;&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
using&nbsp;<wbr>namespace&nbsp;<wbr>std;&nbsp;<wbr><br />
&nbsp;<wbr><br />
float&nbsp;<wbr>c;&nbsp;<wbr><br />
float&amp;&nbsp;<wbr>test(float,float);&nbsp;<wbr><br />
void&nbsp;<wbr>main(int&nbsp;<wbr>argc,char*&nbsp;<wbr>argv[])&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr><br />
{&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>float&nbsp;<wbr>&amp;pn=test(3.0f,1.2f);&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cout&lt;&lt;pn;&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>cin.get();&nbsp;<wbr><br />
}&nbsp;<wbr><br />
&nbsp;<wbr><br />
float&nbsp;<wbr>&amp;test(float&nbsp;<wbr>a,float&nbsp;<wbr>b)&nbsp;<wbr><br />
{&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>c=a*b;&nbsp;<wbr><br />
&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>return&nbsp;<wbr>c;&nbsp;<wbr><br />
}</p>
<p>　　这种情况同样也不产生临时变量，可读和性能都很好，但有一点容易弄错，就是当c是非main的局部变量或者是在堆内存中临时开辟后来又被fee掉了以后的区域，这种情况和返回的指针是局部指针的后果一样严重，会导致引用指向了一个不明确的地址。</p>
<p>----其他用法：</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 在引用的使用中，单纯给某个变量取个别名是毫无意义的，引用的目的主要用于在函数参数传递中，解决大块数据或对象的传递效率和空间不如意的问题。用引用传递函数的参数，能保证参数传递中不产生副本，提高传递的效率，且通过const的使用，保证了引用传递的安全性。引用与指针的区别是，指针通过某个指针变量指向一个对象后，对它所指向的变量间接操作。程序中使用指针，程序的可读性差；而引用本身就是目标变量的别名，对引用的操作就是对目标变量的操作。</p><img src ="http://www.cppblog.com/zmllegtui/aggbug/65371.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:15 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65371.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C语言与C++语言的互相调用（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65370.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:14:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65370.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65370.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65370.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65370.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65370.html</trackback:ping><description><![CDATA[<p>一、C语言中调用C++函数<br><br>将 C++ 函数声明为``extern "C"''（在你的 C++ 代码里做这个声明），然后调用它（在你的 C 或者 C++ 代码里调用）。例如：</p>
<p>// C++ code:</p>
<p>extern "C" void f(int);</p>
<p>void f(int i)</p>
<p>{</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> // ...</p>
<p>}</p>
<p>然后，你可以这样使用 f()：</p>
<p>&nbsp;<wbr></p>
<p>void f(int);</p>
<p>void cc(int i)</p>
<p>{</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> f(i);</p>
<p>&nbsp;<wbr>&nbsp;<wbr></p>
<p>}</p>
<p>当然，这招只适用于非成员函数。如果你想要在 C 里调用成员函数（包括虚函数），则需要提供一个简单的包装（wrapper）。例如：</p>
<p>// C++ code:</p>
<p>class C</p>
<p>{</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> // ...</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> virtual double f(int);</p>
<p>};</p>
<p>extern "C" double call_C_f(C* p, int i) // wrapper function</p>
<p>{</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> return p-&gt;f(i);</p>
<p>}</p>
<p>然后，你就可以这样调用 C::f()：</p>
<p>&nbsp;<wbr></p>
<p>double call_C_f(struct C* p, int i);</p>
<p>void ccc(struct C* p, int i)</p>
<p>{</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> double d = call_C_f(p,i);</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></p>
<p>}</p>
<p>如果你想在 C 里调用重载函数，则必须提供不同名字的包装，这样才能被 C 代码调用。例如：</p>
<p>// C++ code:</p>
<p>void f(int);</p>
<p>void f(double);</p>
<p>extern "C" void f_i(int i) { f(i); }</p>
<p>extern "C" void f_d(double d) { f(d); }</p>
<p>然后，你可以这样使用每个重载的 f()：</p>
<p>&nbsp;<wbr></p>
<p>void f_i(int);</p>
<p>void f_d(double);</p>
<p>void cccc(int i,double d)</p>
<p>{</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> f_i(i);</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> f_d(d);</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></p>
<p>}</p>
<p>注意，这些技巧也适用于在 C 里调用 C++ 类库，即使你不能（或者不想）修改 C++ 头文件。</p>
<p><br>二、C++中调用C函数<br><br>要让你的C代码既能被C代码又能被C++调用虽说容易，但是还是有需要注意的地方。</p>
<p style="FONT-SIZE: 12pt">现有三个文件分别如下：</p>
<p style="FONT-SIZE: 12pt">#ifndef TESTC_H<br>#define TESTC_H<br>&nbsp;<br>#ifdef __cplusplus<br>extern "C" {<br>#endif<br>&nbsp;<br>int add(int a, int b);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>#ifdef __cplusplus<br>}<br>#endif<br>&nbsp;<br>#endif<br>&nbsp;<br>&nbsp;<br>#include "TestC.h"<br>&nbsp;<br>int add(int a, int b)<br>{<br>&nbsp;&nbsp;&nbsp; return (a + b);<br>}<br>&nbsp;<br>#include "stdio.h"<br>#include "TestC.h"<br>&nbsp;<br>int main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("add = %d\n", add(2, 5));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;<br>}<br>&nbsp;<br>说明：<br>file TestC.h是C的头文件，file TestC.c是其实现文件，file TestCpp.cpp是调用C函数的C++文件。<br>文件TestC.h中的TESTC_H定义是为了头文件保护，&#8221; #ifdef __cplusplus&#8221;这个不能缺少，你可以去查看C的标准库头文件中都有这个，如&#8221;stdio.h&#8221;。有了这个宏编译器就知道现在是C还是C++在调用它。<br>为什么要区分C与C++调用呢？其深层次原因是因为C和C++编译器在编译和链接时对于函数的处理不一样。C++为了支持函数重载在编译时会加入函数参数及类型信息。如上面的add方法，C编译器编译后在符号库中的名字为_add，而C++编译器则会产生像_add_int_int之类的名字。C++正是依靠这种机制实现了函数的重载。<br>extern关键字表示将函数或变量声明为全局类型，与之相对应的是static。static限定函数或变量的作用域为本文件。extern还有一个作用就是与&#8221;C&#8221;连在一起使用，即extern &#8220;C&#8221;通知编译器将extern &#8220;C&#8221;所包含的代码按照C的方式编译和链接</p>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/65370.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:14 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65370.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>union（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65368.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:10:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65368.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65368.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65368.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65368.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65368.html</trackback:ping><description><![CDATA[<div class="articleContent" id="articleBody">
<p>前言<br />　　熟悉C的程序员都知道union（联合体）的用法，利用union可以用相同的存储空间存储不同型别的数据类型，从而节省内存空间。当访问其内成员时可用"."和"-&gt;"来直接访问。在C++出现后，它继承了union并保留了其在C中的特性。但是在C++中的union又有了新的扩展，这需要大家了解，要不然你会感到费解和迷惑。下面我讲两点。<br /><br />　　一、在union中存储对象<br />　　在C中union中可以存储任意类型的内置数据类型，那么在C++中union是否可以存储对象呢？还是让我们看一个例子吧，这比任何言语都能说明问题，不是吗？<br />#pragma warning(disable : 4786)<br />#include<br />using namespace std;<br />class TestUnion<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> public:<br />　&nbsp;<wbr> TestUnion(long l):data_(l)<br />　&nbsp;<wbr> {<br />　　};<br />　&nbsp;<wbr> int data_;<br />};<br />typedef union _tagUtype_<br />{<br />　&nbsp;<wbr> <span style="color: #ffff00;"><strong>TestUnion</strong></span> obj;<br />}UT;<br />int main (void)<br />{<br />　&nbsp;<wbr> return 0;<br />}<br />　　这样不行，union中不可以存储TestUnion类的对象，但在C中union可以存储struct呀，为什么不能存储类的对象呢？很简单，请问，在C中union可以存储带有构造函数的struct吗？对了，在C中的struct是没有构造函数的。所以如果C++中union可以存储有构造函数的类的对象就不太符合逻辑，那不是说C++和C完全兼容吗？不错，正因为这一点，<strong style="color: #ffff00;">C++中union不可以存储有构造函数的类的对象</strong>，但是可以存储<strong style="color: #ffff00;">不带构造函数</strong>的类的对象，这样就和C保持一致了，不想信你试试。对TestUnion类的声明进行如下修改：<br />class TestUnion<br />{<br />　&nbsp;<wbr> public:<br />　&nbsp;<wbr> int data_;<br />};<br />　　再进行编译，一切OK！。但是这样却失去了C++的构造初始化特性，这样做是没有任何意义的，我只是在说其在C++中的语义，并不是推荐大家使用（绝对不推荐）。但是我们可以在union中存储对象的指针，从而引用不同的对象类型。不用我再多说了吧，大家还是试试吧！　</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 同理，除了不能加构造函数，析构函数/拷贝构造函数/赋值运算符也是不可以加。此外，如果我们的类中包含了任何virtual函数，编译时，我们将收到如下的错误信息:<br />　　error C2621: union &#8216;__unnamed&#8216; : member &#8216;obj&#8216; has copy constructor<br />　　所以，打消在union中包含有构造函数/析构函数/拷贝构造函数/赋值运算符/虚函数的类的对象的念头，老老实实用你的<strong style="color: #ffff00;">C风格struct</strong>吧!<br /><br />二、类中union的初始化<br />　　由于union的共享内存特点，我们可以使我们的类存储不同的型别而不浪费内存空间，在类中我们可以声明一个union存储不同型别的指针，示例如下：<br />#pragma warning(disable : 4786)<br />#include<br />using namespace std;<br />class TestUnion<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> enum StoreType{Long,Const_CharP};<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> union<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> {<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> const char* ch_;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> long l_;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>}data_;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> StoreType stype_;</p>
<p>public:</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> TestUnion(TestUnion&amp;);<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> TestUnion&amp; operator=(const TestUnion&amp;);<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> TestUnion(const char* ch);<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> TestUnion(long l);<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> operator const char*() const {return data_.ch_;}<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> operator long() const {return data_.l_;}<br />};<br />TestUnion::TestUnion(const char* ch):data_.ch_(ch),stype_(Const_CharP)<br />{<br />}<br />TestUnion::TestUnion(long l):data_.l_(l),stype_(Long)<br />{<br />}<br />int main (void)<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> TestUnion pszobj("yuankai");<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> TestUnion lobj(1234);<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> cout&lt;(pszobj)&lt; cout&lt;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> return 0;<br />}<br />真是不幸，编译都通不过，好象没有什么问题呀，为什么呢？data_.ch_(ch)和data_.l_(l)有问题吗？如果你问一个C程序员他会告诉你，绝对没问题。你不会去怀疑编译器有问题吧！不好意思！我一开始就是这么想的，真是惭愧。费解，迷惑。让我们来看看构造TestUnion对象时发生了什么，这样你就会明白了。当创建TestUnion对象时，自然要调用其相应的构造函数，在构造函数中当然要调用其成员的构造函数，所以其要去<strong style="color: #ffff00;">调用union成员的构造函数，但是其为匿名的，有没有构造函数可调用，所以出错</strong>。很明显在C++中union和class一样它可以有构造函数，不能如此直接引用其成员。struct同样有这限制。只要我们给其定义一个构造函数什么问题都解决了。示例如下：<br /><br />class TestUnion<br />{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> enum StoreType{Long,Const_CharP};<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> union DataUnion //<strong style="color: #ffff00;">不能匿名</strong><br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>{<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> DataUnion(const char*); //声明const char*构造函数<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> DataUnion(long); //声明long构造函数<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> const char* ch_;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> long l_;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> }data_;<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> StoreType stype_;<br />public:<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> TestUnion(TestUnion&amp;);<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> TestUnion&amp; operator=(const TestUnion&amp;);<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> TestUnion(const char* ch);<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> TestUnion(long l);<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> operator const char*() const {return data_.ch_;}<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>operator long() const {return data_.l_;}<br />};<br />TestUnion::TestUnion(const char* ch):data_(ch),stype_(Const_CharP)<br />{//注意data_(ch)，这里直接引用data_<br />}<br />TestUnion::TestUnion(long l):data_(l),stype_(Long)<br />{//注意data_(l)，这里直接引用data_<br />}<br />TestUnion::DataUnion::DataUnion(const char* ch):ch_(ch)<br />{<br />}<br />TestUnion::DataUnion::DataUnion(long l):l_(l)<br />{<br />}<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 不过我更推荐如下的编程风格:<br />class TestUnion<br />{<br />　　union DataUnion//<span style="color: #ffff00;">其实仅仅是非匿名</span><br />　　{<br />　　&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> const char* ch_;<br />　　&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> long l_;<br />　　} data_;<br />public:<br />　　TestUnion(const char* ch);<br />　　TestUnion(long l);<br />};<br />TestUnion::TestUnion(const char* ch)<br />{<br />　　data_.ch_ = ch;<br />}<br />TestUnion::TestUnion(long l)<br />{<br />　　data_.l_ = l;<br />}</p>
</div><img src ="http://www.cppblog.com/zmllegtui/aggbug/65368.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:10 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65368.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>随机数生成（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65367.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:09:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65367.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65367.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65367.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65367.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65367.html</trackback:ping><description><![CDATA[<div class=articleContent id=articleBody>
<p>#include&lt;time.h&gt;<br>#include&lt;iostream&gt;<br>using namespace std;<br>void main()<br>{<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> int num = 0;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <strong><font color=#006600>srand((unsigned)time(NULL));<br></font></strong>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> for (int i=0; i&lt;10; ++i)<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> {<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <font color=#006600><strong>num = rand()%10;<br></strong></font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> cout&lt;&lt;num&lt;&lt;endl;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> }</p>
<p>}<br></p>
</div>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/65367.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:09 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65367.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>内置类型最小存储空间（32位机参考）（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65365.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:02:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65365.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65365.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65365.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65365.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65365.html</trackback:ping><description><![CDATA[<p>bool&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> -</p>
<p>char&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;&nbsp;&nbsp;&nbsp;8bits</p>
<p>wchar_t&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 16bits</p>
<p>short&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;&nbsp;&nbsp;&nbsp;16bits（半个机器字长）</p>
<p>int&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;16bits（一个机器字长，通常大小32bits，16bits也许是最小存储空间）</p>
<p>long&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;&nbsp;&nbsp;&nbsp;32bits（一或两个机器字长）</p>
<p>float&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;&nbsp;&nbsp;&nbsp; <wbr>&nbsp;32bits（6位有效数字，一般一个机器字长）</p>
<p>double&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp; 64bits（10位有效数字，一般两个机器字长）</p>
<p>long double&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>96或128bits（10位有效数字，一般3或4机器字长）</p>
<font face=微软雅黑 color=#8e5f00 size=5></font>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/65365.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:02 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65365.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>const（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65364.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:01:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65364.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65364.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65364.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65364.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65364.html</trackback:ping><description><![CDATA[<p><span lang="EN-US" style="font-size: 9pt; font-family: 宋体; mso-bidi-font-family: 宋体; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA" xml:lang="EN-US"><font face="Times New Roman"><font size="3">O、</font></font></span></p>
<p><font face="Times New Roman" size="3">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></font> <font size="3"><font face="Times New Roman">const int r[ ]={1,2,3,4};<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> struct S {int a,b;};<br />&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>const S s[ ]={(1,2),(3.4)}; //</font>以上两种都是常量集合，编译器会为其分配内存，所以不能在编译期间使用其中的值，例如：<font face="Times New Roman">int temp[r[2]];</font>这样的编译器会报告不能找到常量表达式。</font></p>
<p>&nbsp;<wbr></p>
<p><font size="3">一、对于指针</font></p>
<p><font face="Times New Roman"><font size="3">1.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <font size="3">const int *r=&amp;x; //</font></font><font size="3">声明<font face="Times New Roman">r</font>为一个指向常量的<font face="Times New Roman">x</font>的指针，<font face="Times New Roman">r</font>指向的对象不能被修改，但他可以指向任何地址的常量。</font></p>
<p><font face="Times New Roman"><font size="3">2.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <font size="3">int const *r=&amp;x; //</font></font><font size="3">与用法<font face="Times New Roman">1</font>完全等价，没有任何区别。</font></p>
<p><font face="Times New Roman"><font size="3">3.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <font size="3">int * const r=&amp;x; //</font></font><font size="3">声明<font face="Times New Roman">r</font>为一个常量指针，他指向<font face="Times New Roman">x</font>，<font face="Times New Roman">r</font>这个指针的指向不能被修改，但他指向的地址的内容可以修改。</font></p>
<p><font face="Times New Roman"><font size="3">4.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <font size="3">const int * const r=&amp;x; //</font></font><font size="3">综合<font face="Times New Roman">1</font>、<font face="Times New Roman">3</font>用法，<font face="Times New Roman">r</font>是一个指向常量的常量型指针。</font></p>
<p>&nbsp;<wbr></p>
<p><font size="3">二、对于类型检查</font></p>
<p><font size="3">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 可以把一个非<font face="Times New Roman">const</font>对象赋给一个指向<font face="Times New Roman">const</font>的指针，因为有时候我们不想从这个指针来修改其对象的值（参数传递）；但是不可以把一个<font face="Times New Roman">const</font>对象赋值给一个非<font face="Times New Roman">const</font>指针，因为这样可能会通过这个指针改变指向对象的值，但也存在使这种操作通过的合法化写法，使用类型强制转换可以通过指针改变<font face="Times New Roman">const</font>对象：</font><br /><font face="Times New Roman" size="3">const int r=100;<br />int * ptr = const_cast&lt;int*&gt;(&amp;r);&nbsp;<wbr> //C++</font><font size="3">标准，<font face="Times New Roman">C</font>语言使用：<font face="Times New Roman">int * ptr =(int*)&amp;r;（具体请见强制类型转换的详解）</font></font></p>
<p>&nbsp;<wbr></p>
<font size="3">三、对于字符数组</font>
<p><font size="3">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 如<font face="Times New Roman">char * name = &#8220;china&#8221;;</font> 这样的语句，在编译时是能够通过的，但是<font face="Times New Roman">&#8221;china&#8221;</font>是常量字符数组，任何想修改他的操作也能通过编译但会引起运行时错误，如果我们想修改字符数组的话就要使用<font face="Times New Roman">char name[ ] = &#8220;china&#8221;;</font> 这种形式。</font></p>
<p>&nbsp;<wbr></p>
<p><font size="3">四、对于函数</font></p>
<p><font face="Times New Roman"><font size="3">1.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <font size="3">void Fuction1 ( const int r ); //</font></font><font size="3">此处为参数传递<font face="Times New Roman">const</font>值，意义是变量初值不能被函数改变。</font></p>
<p><font face="Times New Roman"><font size="3">2.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <font size="3">const int Fuction1 (int); //</font></font><font size="3">此处返回<font face="Times New Roman">const</font>值，意思指返回的原函数里的变量的初值不能被修改，但是函数按值返回的这个变量被制成副本，能不能被修改就没有了意义，它可以被赋给任何的<font face="Times New Roman">const</font>或非<font face="Times New Roman">const</font>类型变量，完全不需要加上这个<font face="Times New Roman">const</font>关键字。但这只对于内部类型而言（因为内部类型返回的肯定是一个值，而不会返回一个变量，不会作为左值使用），对于用户自定义类型，返回值是常量是非常重要的，见下面条款<font face="Times New Roman">3</font>。</font></p>
<p><font face="Times New Roman"><font size="3">3.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></font></p>
<p><font face="Times New Roman"><font size="3">Class CX; //</font></font><font size="3">内部有构造函数，声明如</font><font size="3"><font face="Times New Roman">CX(int r =0)<br />CX&nbsp;<wbr> Fuction1 () { return CX(); }<br />const CX Fuction2 () { return CX(); }<br /></font>如有上面的自定义类<font face="Times New Roman">CX</font>，和函数<font face="Times New Roman">Fuction1()</font>和<font face="Times New Roman">Fuction2(),</font>我们进行如下操作时：</font><br /><font face="Times New Roman" size="3">Fuction1() = CX(1); //</font><font size="3">没有问题，可以作为左值调用</font><br /><font face="Times New Roman" size="3">Fuction2() = CX(1); //</font><font size="3">编译错误，<font color="#ff0000"><font face="Times New Roman"><span style="color: #ffff00;">const</span></font><span style="color: #ffff00;">返回值禁止作为左值调用，因为左值把返回值作为变量会修改其返回值，</span><font face="Times New Roman"><span style="color: #ffff00;">const</span></font><span style="color: #ffff00;">声明禁止这种修改</span></font>。</font></p>
<p><font face="Times New Roman"><font size="3">4.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></font> <font size="3">函数中指针的<font face="Times New Roman">const</font>传递和返回：</font><br /><font face="Times New Roman" size="3">int F1 (const char * pstr); //</font><font size="3">作为传递的时候使用<font face="Times New Roman">const</font>修饰可以保证不会通过这个指针来修改传递参数的初值，这里在函数内部任何修改<font face="Times New Roman">*pstr</font>的企图都会引起编译错误。</font><br /><font face="Times New Roman" size="3">const char * F2 (); //</font><font size="3">意义是函数返回的指针指向的对象是一个<font face="Times New Roman">const</font>对象，它必须赋给一个同样是指向<font face="Times New Roman">const</font>对象的指针。</font><br /><font face="Times New Roman" size="3">const char * const F3(); //</font><font size="3">比上面多了一个<font face="Times New Roman">const</font>，这个<font face="Times New Roman">const</font>的意义只是在他被用作左值时有效，它表明了这个指针除了指向<font face="Times New Roman">const</font>对象外，它本身也不能被修改，所以就不能当作左值来处理。</font></p>
<p><font face="Times New Roman"><font size="3">5.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></font> <font size="3">函数中引用的<font face="Times New Roman">const</font>传递：</font><br /><font face="Times New Roman" size="3">void F1 ( const X&amp; px); //</font><font size="3">这样的一个<font face="Times New Roman">const</font>引用传递和最普通的函数按值传递的效果是一模一样的，他禁止对引用的对象的一切修改，唯一不同的是按值传递会先建立一个类对象的副本，然后传递过去，而它直接传递地址，所以这种传递比按值传递更有效。</font><br /><font size="3"><font face="Times New Roman">&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></font> 另外只有引用的<font face="Times New Roman">const</font>传递可以传递一个<strong>临时对象</strong>，因为临时对象都是<font face="Times New Roman">const</font>属性，且是不可见的，他短时间存在一个局部域中，所以不能使用指针，只有引用的<font face="Times New Roman">const</font>传递能够捕捉到这个家伙。</font></p>
<p>&nbsp;<wbr></p>
<p><font size="3">五、</font><font style="font-size: 16px">对于类</font></p>
<p><font face="Times New Roman"><font size="3">1.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></font> <font size="3">首先，对于<font face="Times New Roman">const</font>的成员变量，只能在构造函数里使用初始化成员列表来初始化，试图在构造函数体内进行初始化<font face="Times New Roman">const</font>成员变量会引起编译错误。初始化成员列表形如：</font><br /><font face="Times New Roman" size="3">X:: X ( int ir ): r(ir) {} //</font><font size="3">假设<font face="Times New Roman">r</font>是类<font face="Times New Roman">X</font>的<font face="Times New Roman">const</font>成员变量</font></p>
<p><font face="Times New Roman"><font size="3">2.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> <font size="3">const</font></font><font size="3">成员函数。提到这个概念首先要谈到类的<font face="Times New Roman">const</font>对象，正像内置类型能够定义<font face="Times New Roman">const</font>对象一样（<font face="Times New Roman">const int r=10;</font>），用户自定义类型也可以定义<font face="Times New Roman">const</font>对象<font face="Times New Roman">(const X px(10);)</font>，编译器要保证这个对象在其生命周期内不能够被改变。如果你定义了这样的一个<font face="Times New Roman">const</font>对象，那么</font><span style="font-size: medium;">编译器为了保证对象的</span><font face="Times New Roman" style="font-size: medium;">const</font><span style="font-size: medium;">特性，</span><font size="3"><span style="color: #ffff00;">对于这个对象的一切非</span><font face="Times New Roman"><span style="color: #ffff00;">const</span></font><span style="color: #ffff00;">成员函数的调用</span>都会被禁止并在编译期间报错（除构造函数和析构函数）。所以如果你想让你的成员函数能够在<font face="Times New Roman">const</font>对象上进行操作的话，就要把这个函数声明为<font face="Times New Roman">const</font>成员函数。<br /></font>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;<font size="3">假如<font face="Times New Roman">f( )</font>是类中的成员函数的话，它的声明形如：<br /></font><font face="Times New Roman" size="3">int f( ) const; //const</font><font size="3">放在函数的最后<br />编译器会对这个函数进行检查，在这个函数中的任何试图改变成员变量和调用非<font face="Times New Roman">const</font>成员函数的操作都被视为非法。</font><font size="3">类的构造和析构函数都不能是<font face="Times New Roman">const</font>函数。</font></p>
<p><font face="Times New Roman"><font size="3">3.</font>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr></font> <font size="3">建立了一个<font face="Times New Roman">const</font>成员函数，但仍然想用这个函数改变对象内部的数据，这样的要求也会经常遇到，尤其是在一个苛刻的面试考官那里。首先我们要弄清楚考官的要求，因为有两种方法可以实现，如果这位考官要求不改变原来类的任何东西，只让你从当前这个<font face="Times New Roman">const</font>成员函数入手，那么你只有使用前面提到的类型强制转换方法。实例如下：</font><br /><font face="Times New Roman" size="3">//</font><font size="3">假如有一个叫做<font face="Times New Roman">X</font>的类，它有一个<font face="Times New Roman">int</font>成员变量<font face="Times New Roman">r</font>，我们需要通过一个<font face="Times New Roman">const</font>成员函数<font face="Times New Roman">f( )</font>来对这个<font face="Times New Roman">r</font>进行<font face="Times New Roman">++r</font>操作，代码如下</font><br /><font face="Times New Roman" size="3">void X::f( ) const<br />{&nbsp;<wbr> (const_cast&lt;X*&gt;(this)) -&gt; ++r; &nbsp;<wbr>} //</font><font size="3">通过<font face="Times New Roman">this</font>指针进行类型强制转换实现</font><br /><font size="3">另外一种方法就是使用关键字：<strong><font face="Times New Roman">mutable</font></strong><strong>。</strong>如果你的成员变量在定义时是这个样子的：</font><br /><font face="Times New Roman" size="3">mutable int r ;<br /></font><font size="3">那么它就告诉编译器这个成员变量可以通过<font face="Times New Roman">const</font>成员函数改变。编译器就不会再理会对他的检查了。</font></p><img src ="http://www.cppblog.com/zmllegtui/aggbug/65364.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:01 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65364.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title># 预处理预编译（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65363.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 12:00:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65363.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65363.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65363.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65363.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65363.html</trackback:ping><description><![CDATA[<p>一、预处理的由来：<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 在C++的历史发展中，有很多的语言特征（特别是语言的晦涩之处）来自于C语言，预处理就是其中的一个。C++从C语言那里把C语言预处理器（被Bjarne博士简称为Cpp，不知道是不是C Program Preprocessor的简称）继承过来。</p>
<p><br>二、常见的预处理功能：<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 预处理器的主要作用就是把通过预处理的内建功能对一个资源进行等价替换，最常见的预处理有：文件包含，条件编译、布局控制和宏替换4种。</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 文件包含：</p>
<p>#include 是一种最为常见的预处理，主要是做为文件的引用组合源程序正文。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 条件编译：</p>
<p>#if，#ifndef，#ifdef，#endif，#undef等也是比较常见的预处理，主要是进行编译时进行有选择的挑选，注释掉一些指定的代码，以达到版本控制、防止对文件重复包含的功能。</p>
<p>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 布局控制：</p>
<p>#progma，这也是我们应用预处理的一个重要方面，主要功能是为编译程序提供非常规的控制流信息。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 宏替换：</p>
<p>#define，这是最常见的用法，它可以定义符号常量、函数功能、重新命名、字符串的拼接等各种功能。</p>
<p><br>三、预处理指令：<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 预处理指令的格式如下：<br>#directive tokens<br>#符号应该是这一行的第一个非空字符，一般我们把它放在起始位置。如果指令一行放不下，可以通过进行控制，例如：<br>#define Error if(error) exit(1)</p>
<p>等价于<br>#define Error<br>if(error) exit(1)<br>不过我们为了美化起见，一般都不怎么这么用，更常见的方式如下：<br># ifdef __BORLANDC__<br>if_true&lt;(is_convertible&lt;Value,named_template_param_base&gt;::value)&gt;::<br>template then&lt;make_named_arg, make_key_value&gt;::type Make;<br># else<br>enum { is_named = is_named_parameter&lt;Value&gt;::value };<br>typedef typename if_true&lt;(is_named)&gt;::template<br>then&lt;make_named_arg, make_key_value&gt;::type Make;<br># endif<br>下面我们看一下常见的预处理指令：<br>#define 宏定义<br>#undef 未定义宏<br>#include 文本包含<br>#ifdef 如果宏被定义就进行编译<br>#ifndef 如果宏未被定义就进行编译<br>#endif 结束编译块的控制<br>#if 表达式非零就对代码进行编译<br>#else 作为其他预处理的剩余选项进行编译<br>#elif 这是一种#else和#if的组合选项<br>#line 改变当前的行数和文件名称<br>#error 输出一个错误信息<br>#pragma 为编译程序提供非常规的控制流信息<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 下面我们对这些预处理进行一一的说明，考虑到宏的重要性和繁琐性，我们把它放到最后讲。<br><br>四、文件包含指令： #include<br>这种预处理使用方式是最为常见的，平时我们编写程序都会用到，最常见的用法是：<br>#include &lt;iostream&gt; //标准库头文件<br>#include &lt;iostream.h&gt; //旧式的标准库头文件<br>#include "IO.h" //用户自定义的头文件<br>#include "../file.h" //UNIX下的父目录下的头文件<br>#include "/usr/local/file.h" //UNIX下的完整路径<br>#include "..file.h" //Dos下的父目录下的头文件<br>#include "usrlocalfile.h" //Dos下的完整路径<br>这里面有2个地方要注意：<br>1、我们用&lt;iostream&gt;还是&lt;iostream.h&gt;?<br>我们主张使用&lt;iostream&gt;，而不是&lt;iostream.h&gt;,为什么呢？我想你可能还记得我曾经给出过几点理由，这里我大致的说一下：首先，.h格式的头文件早在98年9月份就被标准委员会抛弃了，我们应该紧跟标准，以适合时代的发展。其次，iostream.h只支持窄字符集，iostream则支持窄/宽字符集。还有，标准对iostream作了很多的改动，接口和实现都有了变化。最后，iostream组件全部放入namespace std中，防止了名字污染。<br>2、&lt;io.h&gt;和"io.h"的区别？<br>其实他们唯一的区别就是搜索路径不同：<br>对于#include &lt;io.h&gt; ，编译器从标准库路径开始搜索<br>对于#include "io.h" ，编译器从用户的工作路径开始搜索<br><br>五、编译控制指令：<br>这些指令的主要目的是进行编译时进行有选择的挑选，注释掉一些指定的代码，以达到版本控制、防止对文件重复包含的功能。使用格式，如下：<br>1、如果identifier为一个定义了的符号，your code就会被编译，否则剔除<br>#ifdef identifier<br>your code<br>#endif<br>2、如果identifier为一个未定义的符号，your code就会被编译，否则剔除<br>#ifndef identifier<br>your code<br>#endif<br>3、如果expression非零，your code就会被编译，否则剔除<br>#if expression<br>your code<br>#endif<br>4、如果identifier为一个定义了的符号，your code1就会被编译，否则your code2就会被编译<br>#ifdef identifier<br>your code1<br>#else<br>your code2<br>#endif<br>5、如果epression1非零，就编译your code1，否则，如果expression2非零，就编译your code2，否则，就编译your code3<br>#if expressin1<br>your code1<br>#elif expression2 //呵呵，elif<br>your code2<br>#else<br>your code3<br>#enif<br><br>六、其他预编译指令<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 除了上面我们说的集中常用的编译指令，还有3种不太常见的编译指令：#line、#error、#pragma，我们接下来就简单的谈一下。<br>1、#line的语法如下：<br>#line number filename<br>例如：#line 30 a.h 其中，文件名a.h可以省略不写。</p>
<p>这条指令可以改变当前的行号和文件名，例如上面的这条预处理指令就可以改变当前的行号为30，文件名是a.h。初看起来似乎没有什么用，不过，他还是有点用的，那就是用在编译器的编写中，我们知道编译器对C++源码编译过程中会产生一些中间文件，通过这条指令，可以保证文件名是固定的，不会被这些中间文件代替，有利于进行分析。<br>2、#error语法如下：<br>#error info<br>例如：</p>
<p>#ifndef UNIX<br>#error This software requires the UNIX OS.<br>#endif<br>这条指令主要是给出错误信息，上面的这个例子就是，如果没有在UNIX环境下，就会输出This software requires the UNIX OS.然后诱发编译器终止。所以总的来说，这条指令的目的就是在程序崩溃之前能够给出一定的信息。<br>3、#pragma</p>
<p>它是非统一的，他要依靠各个编译器生产者，例如，在SUN C++编译器中：<br>// 把name和val的起始地址调整为8个字节的倍数<br>#progma align 8 (name, val)<br>char name[9];<br>double val;</p>
<p>或是如下用法：<br>//在程序执行开始，调用函数MyFunction<br>#progma init (MyFunction)</p>
<p><br>七、预定义标识符<br>为了处理一些有用的信息，预处理定义了一些预处理标识符，虽然各种编译器的预处理标识符不尽相同，但是他们都会处理下面的4种：<br>__FILE__ 正在编译的文件的名字<br>__LINE__ 正在编译的文件的行号<br>__DATE__ 编译时刻的日期字符串，例如： "25 Dec 2000"<br>__TIME__ 编译时刻的时间字符串，例如： "12:30:55"<br>例如：cout&lt;&lt;"The file is :"&lt;&lt;__FILE__"&lt;&lt;"! The lines is:"&lt;&lt;__LINE__&lt;&lt;endl;</p>
<p><br>八、预处理何去何从<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 如何取代#include预处理指令，我们在这里就不再一一讨论了。C++并没有为#include提供替代形式，但是namespace提供了一种作用域机制，它能以某种方式支持组合，利用它可以改善#include的行为方式，但是我们还是无法取代#include。<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> #progma应该算是一个可有可无的预处理指令，按照C++之父Bjarne的话说，就是："#progma被过分的经常的用于将语言语义的变形隐藏到编译系统里，或者被用于提供带有特殊语义和笨拙语法的语言扩充。&#8221;<br>&nbsp;<wbr>&nbsp;<wbr>&nbsp;<wbr> 对于#ifdef，我们仍然束手无策，就算是我们利用if语句和常量表达式，仍然不足以替代她，因为一个if语句的正文必须在语法上正确，满足类检查，即使他处在一个绝不会被执行的分支里面。</p>
<img src ="http://www.cppblog.com/zmllegtui/aggbug/65363.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 20:00 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65363.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>explicit 和类的转换（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65362.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 11:59:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65362.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65362.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65362.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65362.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65362.html</trackback:ping><description><![CDATA[<p>// explicit关键字</p>
<p>&nbsp;&nbsp;&nbsp; c++中的explicit关键字用来修饰类的构造函数，表明该构造函数是显式的，即拒绝构造时的隐式转换。如果c++类的构造函数有一个参数，那么在编译的时候就会有一个缺省的转换操作：将该构造函数对应数据类型的数据转换为该类对象，如下面所示：</p>
<p>class MyClass<br />{<br />public:<br />&nbsp;&nbsp;&nbsp; MyClass( int num );<br />}<br />....<br />MyClass obj = 10; //ok,convert int to MyClass<br />&nbsp;&nbsp;&nbsp; 在上面的代码中编译器自动将整型转换为MyClass类对象，实际上等同于下面的操作：<br />MyClass temp(10);<br />MyClass obj = temp;&nbsp;&nbsp; <br />&nbsp;&nbsp;&nbsp; 上面的所有的操作即是所谓的"隐式转换"。</p>
<p>&nbsp;&nbsp;&nbsp; 如果要避免这种自动转换的功能，可以将类的构造函数声明为"显示"，也就是在声明构造函数的时候前面添加上explicit即可，这样就可以防止这种自动的转换操作，如果我们修改上面的MyClass类的构造函数为显示的，那么下面的代码就不能够编译通过了，如下所示：<br />class MyClass<br />{<br />public:<br />&nbsp;&nbsp;&nbsp; explicit MyClass( int num );<br />}<br />....<br />MyClass obj = 10; //err,can't non-explict convert<br />&nbsp;<br />// 隐式类类型转换<br />&nbsp;<br />&nbsp;&nbsp;&nbsp; 这种效果一般都是通过类构造函数实现的。那些可以用单个实参来调用的构造函数，定义了从形参类型到该类类型的一个隐式转换。例如如下情况：<br />xxx(valueOfType1);//对于接受ClassType类型参数的函数xxx，参数传递中执行了隐式转换。<br />xxx(ClassType(valueOfType1));//与上面一句效果相同，只是显示的使用了对应的构造函数。<br />&nbsp;&nbsp;&nbsp; 另外需要注意上面两个语句中传递的类类型参数的生命周期仅限制在xxx函数中。<br />&nbsp;&nbsp;&nbsp; 如果需要抑制这种由构造函数定义的隐式转换，可以如上面所说将构造函数声明为explicit（且仅用于声明）。<br />&nbsp;<br />// 从类类型的转换<br />&nbsp;&nbsp;&nbsp; 与隐式类类型转换不同，前者是到类类型的转换，而现在是利用<strong style="color: #ffff00;">转换操作符</strong>，使给定类类型的对象转换成为其他类型对象。这种转换为什么有用呢？这种方法为了使类支持混合类型表达式，且可以减少类需要的支持功能的操作符数目。定义方法：通用形式为 operator type();<br />class A<br />{<br />public:<br />&nbsp;&nbsp;&nbsp; A()<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; operator B() const<br />&nbsp;&nbsp;&nbsp; {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return AtoB(A());<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; &#8230;&#8230;<br />};<br />&nbsp;&nbsp;&nbsp; 转换函数一般不应该改变被转换的对象，因此转换操作符通常应该定义为const。<br />&nbsp;<br />// Reference<br />&nbsp;&nbsp;&nbsp; 详情请见《C++ Primer》4th Edi中的5.12、12.4、14.9，还有google资源。</p><img src ="http://www.cppblog.com/zmllegtui/aggbug/65362.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 19:59 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65362.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>内存空间分配（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65331.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 07:05:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65331.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65331.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65331.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65331.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65331.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 在C++中，内存分成5个区，他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。<br>栈，就是那些由编译器在需要的时候分配，在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。 <br>堆，就是那些由new分配的内存块，他们的释放编译器不去管，由我们的应用程序去控制，一般一个new就要对应一个delete。如果程序员没有释放掉，那么在程序结束后，操作系统会自动回收。<br>……<br><br><br>&nbsp;&nbsp;<a href='http://www.cppblog.com/zmllegtui/archive/2008/10/28/65331.html'>阅读全文</a><img src ="http://www.cppblog.com/zmllegtui/aggbug/65331.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 15:05 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65331.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>虚析构函数（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65328.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 06:46:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65328.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65328.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65328.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65328.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65328.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 我们知道，用C++开发的时候，用来做基类的类的析构函数一般都是虚函数。可是，为什么要这样做呢？下面用一个小例子来说明：<br>有下面的两个类：<br>class ClxBase<br>{<br>public:<br>……<br><br>&nbsp;&nbsp;<a href='http://www.cppblog.com/zmllegtui/archive/2008/10/28/65328.html'>阅读全文</a><img src ="http://www.cppblog.com/zmllegtui/aggbug/65328.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 14:46 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65328.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>auto_ptr（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65323.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 06:29:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65323.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65323.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65323.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65323.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65323.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要:  很多人听说过标准auto_ptr智能指针机制，但并不是每个人都天天使用它。这真是个遗憾，因为auto_ptr优雅地解决了C++设计和编码中常见的问题，正确地使用它可以生成健壮的代码。本文阐述了如何正确运用auto_ptr来让你的代码更加安全——以及如何避免对auto_ptr危险但常见的误用，这些误用会引发间断性发作、难以诊断的bug。<br>……<br><br>&nbsp;&nbsp;<a href='http://www.cppblog.com/zmllegtui/archive/2008/10/28/65323.html'>阅读全文</a><img src ="http://www.cppblog.com/zmllegtui/aggbug/65323.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 14:29 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65323.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Templete（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65316.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Tue, 28 Oct 2008 05:46:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65316.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65316.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/28/65316.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65316.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65316.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 一、什么是模板<br>      模板是根据参数类型生成函数和类的机制（有时称为“参数决定类型”）。通过使用模板，可以只设计一个类来处理多种类型的数据，而不必为每一种类型分别创建类。<br>      例如，创建一个类型安全函数来返回两个参数中较小的一个，如果不使用Templates，必须要编写一系列如下的函数：<br>……<br><br>&nbsp;&nbsp;<a href='http://www.cppblog.com/zmllegtui/archive/2008/10/28/65316.html'>阅读全文</a><img src ="http://www.cppblog.com/zmllegtui/aggbug/65316.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-28 13:46 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/28/65316.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>STL std::string的内存共享和Copy-On-Write技术（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/27/65255.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Mon, 27 Oct 2008 14:56:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/27/65255.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65255.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/27/65255.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65255.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65255.html</trackback:ping><description><![CDATA[<span>1</span><span>、概念</span><span><br /></span>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;<span>Scott Meyers</span><span>在《</span><span>More Effective C++</span><span>》中举了个例子，不知你是否还记得？在你还在上学的时候，你的父母要你不要看电视，而去复习功课，于是你把自己关在房间里，做出一副正在复习功课的样子，其实你在干着别的诸如给班上的某位女生写情书之类的事，而一旦你的父母出来在你房间要检查你是否在复习时，你才真正捡起课本看书。这就是</span><span>&#8220;</span><span>拖延战术</span><span>&#8221;</span><span>，直到你非要做的时候才去做。</span><span><br /></span>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;<span>当然，这种事情在现实生活中时往往会出事，但其在编程世界中摇身一变，就成为了最有用的技术，正如</span><span>C++</span><span>中的可以随处声明变量的特点一样，</span><span>Scott Meyers</span><span>推荐我们，在真正需要一个存储空间时才去声明变量（分配内存），这样会得到程序在运行时最小的内存花销。执行到那才会去做分配内存这种比较耗时的工作，这会给我们的程序在运行时有比较好的性能。必竟，</span><span>20%</span><span>的程序运行了</span><span>80%</span><span>的时间。</span><span> <span><br /></span></span>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;<span>当然，拖延战术还并不只是这样一种类型，这种技术被我们广泛地应用着，特别是在操作系统当中，当一个程序运行结束时，操作系统并不会急着把其清除出内存，原因是有可能程序还会马上再运行一次（从磁盘把程序装入到内存是个很慢的过程），而只有当内存不够用了，才会把这些还驻留内存的程序清出。</span><span><br /></span>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;<span>写时才拷贝（</span><span>Copy-On-Write</span><span>）技术，就是编程界</span><span>&#8220;</span><span>懒惰行为</span><span>&#8221;&#8212;&#8212;</span><span>拖延战术的产物。举个例子，比如我们有个程序要写文件，不断地根据网络传来的数据写，如果每一次</span><span>fwrite</span><span>或是</span><span>fprintf</span><span>都要进行一个磁盘的</span><span>I/O</span><span>操作的话，都简直就是性能上巨大的损失，因此通常的做法是，每次写文件操作都写在特定大小的一块内存中（磁盘缓存），只有当我们关闭文件时，才写到磁盘上（这就是为什么如果文件不关闭，所写的东西会丢失的原因）。更有甚者是文件关闭时都不写磁盘，而一直等到关机或是内存不够时才写磁盘，</span><span>Unix</span><span>就是这样一个系统，如果非正常退出，那么数据就会丢失，文件就会损坏。</span><span>为了性能我们需要冒这样大的风险，还好我们的程序是不会忙得忘了还有一块数据需要写到磁盘上的，所以这种做法，还是很有必要的。<br /></span><span><br />2</span><span>、</span><span>标准</span><span>C++</span><span>类</span><span>std::string</span><span>的</span><span>Copy-On-Write<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在我们经常使用的</span><span>STL</span><span>标准模板库中的</span><span>string</span><span>类，也是一个具有写时才拷贝技术的类。</span><span>C++</span><span>曾在性能问题上被广泛地质疑和指责过，为了提高性能，</span><span>STL</span><span>中的许多类都采用了</span><span>Copy-On-Write</span><span>技术。这种偷懒的行为的确使使用</span><span>STL</span><span>的程序有着比较高要性能。</span><span><br /></span><span>这里，我想从</span><span>C++</span><span>类或是设计模式的角度为各位揭开</span><span>Copy-On-Write</span><span>技术在</span><span>string</span><span>中实现的面纱，以供各位在用</span><span>C++</span><span>进行类库设计时做一点参考。</span><span><br /></span><span>在讲述这项技术之前，我想简单地说明一下</span><span>string</span><span>类内存分配的概念。通过常，</span><span>string</span><span>类中必有一个私有成员，其是一个</span><span>char*</span><span>，用户记录从堆上分配内存的地址，其在构造时分配内存，在析构时释放内存。因为是从堆上分配内存，所以</span><span>string</span><span>类在维护这块内存上是格外小心的，</span><span>string</span><span>类在返回这块内存地址时，只返回</span><span>const char*</span><span>，也就是只读的，如果你要写，你只能通过</span><span>string</span><span>提供的方法进行数据的改写。<br /></span><span><br />2.1</span><span>、</span><span>特性</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;由表及里，由感性到理性，我们先来看一看</span><span>string</span><span>类的</span><span>Copy-On-Write</span><span>的表面特征。让我们写下下面的一段程序：</span><span><br />#include<br />#include <br />using namespace std;<br />main()<br />{<br />&nbsp; &nbsp; &nbsp;string str1 = "hello world";<br />&nbsp; &nbsp; &nbsp;string str2 = str1; <br />&nbsp; &nbsp; &nbsp;printf ("Sharing the memory:\n");<br />&nbsp; &nbsp; &nbsp;printf ("\tstr1's address: %x\n", str1.c_str() );<br />&nbsp; &nbsp; &nbsp;printf ("\tstr2's address: %x\n", str2.c_str() );&nbsp; &nbsp; &nbsp;<br />&nbsp; <span>&nbsp;&nbsp;</span>str1[1]='q';<br />&nbsp; &nbsp; &nbsp;str2[1]='w'; <br />&nbsp; &nbsp; &nbsp;printf ("After Copy-On-Write:\n");<br />&nbsp; &nbsp; &nbsp;printf ("\tstr1's address: %x\n", str1.c_str() );<br />&nbsp; &nbsp; &nbsp;printf ("\tstr2's address: %x\n", str2.c_str() ); <br />&nbsp; &nbsp; &nbsp;return 0;<br />}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这个程序的意图就是让第二个</span><span>string</span><span>通过第一个</span><span>string</span><span>构造，然后打印出其存放数据的内存地址，然后分别修改</span><span>str1</span><span>和</span><span>str2</span><span>的内容，再查一下其存放内存的地址。程序的输出是这样的（我在</span><span>VC6.0</span><span>和</span><span>g++ 2.95</span><span>都得到了同样的结果）：</span><span><br />&gt; g++ -o stringTest stringTest.cpp<br />&gt; ./stringTest<br />Sharing the memory:<br />&nbsp; &nbsp; str1's address: 343be9<br />&nbsp; &nbsp; str2's address: 343be9<br />After Copy-On-Write:<br />&nbsp; &nbsp; str1's address: 3407a9<br />&nbsp; &nbsp; str2's address: 343be9 &nbsp; &nbsp; &nbsp; <br /></span><span>从结果中我们可以看到，在开始的两个语句后，</span><span>str1</span><span>和</span><span>str2</span><span>存放数据的地址是一样的，而在修改内容后，</span><span>str1</span><span>的地址发生了变化，而</span><span>str2</span><span>的地址还是原来的。从这个例子，我们可以看到</span><span>string</span><span>类的</span><span>Copy-On-Write</span><span>技术。</span><span><br /><br />2.2</span><span>、</span><span>深入</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在深入这前，通过上述的演示，我们应该知道在</span><span>string</span><span>类中，要实现写时才拷贝，需要解决两个问题，一个是内存共享，一个是</span><span>Copy-On-Wirte</span><span>，这两个主题会让我们产生许多疑问，还是让我们带着这样几个问题来学习吧：</span><span><br />1</span><span>、</span><span> Copy-On-Write</span><span>的原理是什么？</span><span><br />2</span><span>、</span><span> string</span><span>类在什么情况下才共享内存的？</span><span><br />3</span><span>、</span><span> string</span><span>类在什么情况下触发写时才拷贝（</span><span>Copy-On-Write</span><span>）</span><span>?<br />4</span><span>、</span><span> Copy-On-Write</span><span>时，发生了什么？</span><span><br />5</span><span>、</span><span> Copy-On-Write</span><span>的具体实现是怎么样的？</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;喔，你说只要看一看</span><span>STL</span><span>中</span><span>stirng</span><span>的源码你就可以找到答案了。当然，当然，我也是参考了</span><span>string</span><span>的父模板类</span><span>basic_string</span><span>的源码。但是，如果你感到看</span><span>STL</span><span>的源码就好像看机器码，并严重打击你对</span><span>C++</span><span>自信心，乃至产生了自己是否懂</span><span>C++</span><span>的疑问，如果你有这样的感觉，那么还是继续往下看我的这篇文章吧。</span><span><br />OK</span><span>，让我们一个问题一个问题地探讨吧，慢慢地所有的技术细节都会浮出水面的。<br /></span><span><br />2.3</span><span>、</span><span>Copy-On-Write</span><span>的原理是什么？</span><span><br /><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;有一定经验的程序员一定知道，</span><span>Copy-On-Write</span><span>一定使用了</span><span>&#8220;</span><span>引用计数</span><span>&#8221;</span><span>，是的，必然有一变量类似于</span><span>RefCnt</span><span>。当第一个类构造时，</span><span>string</span><span>的构造函数会根据传入的参数从堆上分配内存，当有其它类需要这块内存时，这个计数为自动累加，当有类析构时，这个计数会减一，直到最后一个类析构时，此时的</span><span>RefCnt</span><span>为</span><span>1</span><span>或是</span><span>0</span><span>，此时，程序才会真正的</span><span>Free</span><span>这块从堆上分配的内存。</span><span><br /></span><span>是的，引用计数就是</span><span>string</span><span>类中写时才拷贝的原理！</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;不过，问题又来了，这个</span><span>RefCnt</span><span>该存在在哪里呢？如果存放在</span><span>string</span><span>类中，那么每个</span><span>string</span><span>的实例都有各自的一套，根本不能共有一个</span><span>RefCnt</span><span>，如果是声明成全局变量，或是静态成员，那就是所有的</span><span>string</span><span>类共享一个了，这也不行，我们需要的是一个</span><span>&#8220;</span><span>民主和集中</span><span>&#8221;</span><span>的一个解决方法。这是如何做到的呢？呵呵，人生就是一个糊涂后去探知，知道后和又糊涂的循环过程。别急别急，在后面我会给你一一道来的。</span><span><br /><br />2.3.1</span><span>、</span><span>string</span><span>类在什么情况下才共享内存的？</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这个问题的答案应该是明显的，根据常理和逻辑，如果一个类要用另一个类的数据，那就可以共享被使用类的内存了。这是很合理的，如果你不用我的，那就不用共享，只有你使用我的，才发生共享。</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;使用别的类的数据时，无非有两种情况，</span><span>1</span><span>）以别的类构造自己，</span><span>2</span><span>）以别的类赋值。第一种情况时会触发拷贝构造函数，第二种情况会触发赋值操作符。这两种情况我们都可以在类中实现其对应的方法。对于第一种情况，只需要在</span><span>string</span><span>类的拷贝构造函数中做点处理，让其引用计数累加；同样，对于第二种情况，只需要重载</span><span>string</span><span>类的赋值操作符，同样在其中加上一点处理。</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;唠叨几句：</span><span><br />1</span><span>）构造和赋值的差别</span><span><br /></span><span>对于前面那个例程中的这两句：</span><span><br />&nbsp; &nbsp; &nbsp;string str1 = "hello world";<br />&nbsp; &nbsp; &nbsp;string str2 = str1;<br /></span><span>不要以为有</span><span>&#8220;=&#8221;</span><span>就是赋值操作，其实，这两条语句等价于：</span><span><br />&nbsp; &nbsp; &nbsp;string str1 ("hello world"); &nbsp; //</span><span>调用的是构造函数</span><span><br />&nbsp; &nbsp; &nbsp;string str2 (str1); &nbsp; &nbsp; &nbsp; &nbsp; //</span><span>调用的是拷贝构造函数</span><span><br /></span><span>如果</span><span>str2</span><span>是下面的这样情况：</span><span><br />string str2; &nbsp; &nbsp; //</span><span>调用参数默认为空串的构造函数：</span><span>string str2(&#8220;&#8221;);<br />str2 = str1; &nbsp; //</span><span>调用</span><span>str2</span><span>的赋值操作：</span><span>str2.operator=(str1);<br />2) </span><span>另一种情况</span><span><br />&nbsp; &nbsp; &nbsp;char tmp[]=&#8221;hello world&#8221;;<br />&nbsp; &nbsp; &nbsp;string str1 = tmp;<br />&nbsp; &nbsp; &nbsp;string str2 = tmp;<br /></span><span>这种情况下会触发内存的共享吗？想当然的，应该要共享。可是根据我们前面所说的共享内存的情况，两个</span><span>string</span><span>类的声明和初始语句并不符合我前述的两种情况，所以其并不发生内存共享。而且，</span><span>C++</span><span>现有特性也无法让我们做到对这种情况进行类的内存共享。</span><span><br />&nbsp; &nbsp; &nbsp; <br />2.3.2</span><span>、</span><span>string</span><span>类在什么情况下触发写时才拷贝（</span><span>Copy-On-Write</span><span>）</span><span>?<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;哦，什么时候会发现写时才拷贝？很显然，当然是在共享同一块内存的类发生内容改变时，才会发生</span><span>Copy-On-Write</span><span>。比如</span><span>string</span><span>类的</span><span>[]</span><span>、</span><span>=</span><span>、</span><span>+=</span><span>、</span><span>+</span><span>、操作符赋值，还有一些</span><span>string</span><span>类中诸如</span><span>insert</span><span>、</span><span>replace</span><span>、</span><span>append</span><span>等成员函数。</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;修改数据才会触发</span><span>Copy-On-Write</span><span>，不修改当然就不会改啦。这就是托延战术的真谛，非到要做的时候才去做。<br /></span><span><br />2.3.3</span><span>、</span><span>Copy-On-Write</span><span>时，发生了什么？</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;我们可能根据那个访问计数来决定是否需要拷贝，参看下面的代码：</span><span><br />If ( RefCnt&gt;0 ) {<br />&nbsp; char* tmp = (char*) malloc(strlen(_Ptr)+1);<br />&nbsp; strcpy(tmp, _Ptr);<br />&nbsp; _Ptr = tmp;<br />}<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;上面的代码是一个假想的拷贝方法，如果有别的类在引用（检查引用计数来获知）这块内存，那么就需要把更改类进行</span><span>&#8220;</span><span>拷贝</span><span>&#8221;</span><span>这个动作。</span><span><br /></span><span>我们可以把这个拷的运行封装成一个函数，供那些改变内容的成员函数使用。<br /></span><span><br />2.3.4</span><span>、</span><span>Copy-On-Write</span><span>的具体实现是怎么样的？</span>
<p><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;最后的这个问题，我们主要解决的是那个</span><span>&#8220;</span><span>民主集中</span><span>&#8221;</span><span>的难题。请先看下面的代码：</span><span><br />string h1 = &#8220;hello&#8221;;<br />string h2= h1;<br />string h3;<br />h3 = h2;<br />string w1 = &#8220;world&#8221;;<br />string w2(&#8220;&#8221;);<br />w2=w1;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;很明显，我们要让</span><span>h1</span><span>、</span><span>h2</span><span>、</span><span>h3</span><span>共享同一块内存，让</span><span>w1</span><span>、</span><span>w2</span><span>共享同一块内存。因为，在</span><span>h1</span><span>、</span><span>h2</span><span>、</span><span>h3</span><span>中，我们要维护一个引用计数，在</span><span>w1</span><span>、</span><span>w2</span><span>中我们又要维护一个引用计数。</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如何使用一个巧妙的方法产生这两个引用计数呢？我们想到了</span><span>string</span><span>类的内存是在堆上动态分配的，既然共享内存的各个类指向的是同一个内存区，我们为什么不在这块区上多分配一点空间来存放这个引用计数呢？这样一来，所有共享一块内存区的类都有同样的一个引用计数，而这个变量的地址既然是在共享区上的，那么所有共享这块内存的类都可以访问到，也就知道这块内存的引用者有多少了。</span><span><br /></span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;于是，有了这样一个机制，每当我们为</span><span>string</span><span>分配内存时，我们总是要多分配一个空间用来存放这个引用计数的值，只要发生拷贝构造或是赋值时，这个内存的值就会加一。而在内容修改时，</span><span>string</span><span>类为查看这个引用计数是否为</span><span>0</span><span>，如果不为零，表示有人在共享这块内存，那么自己需要先做一份拷贝，然后把引用计数减去一，再把数据拷贝过来。下面的几个程序片段说明了这两个动作：</span><span><br />&nbsp;//</span><span>构造函数（分存内存）</span><span> <br />&nbsp; string::string(const char* tmp)<br />{<br />&nbsp; _Len = strlen(tmp);<br />&nbsp; _Ptr = new char[_Len+1+1];<br />&nbsp; strcpy( _Ptr, tmp );<br />&nbsp; _Ptr[_Len+1]=0; // </span><span>设置引用计数</span><span> &nbsp; <br />}<br />//</span><span>拷贝构造（共享内存）</span><span><br />&nbsp; string::string(const string&amp; str)<br />&nbsp; {<br />&nbsp; &nbsp; &nbsp; if (*this != str){<br />&nbsp; &nbsp; &nbsp; &nbsp; this-&gt;_Ptr = str.c_str(); &nbsp; //</span><span>共享内存</span><span><br />&nbsp; &nbsp; &nbsp; &nbsp; this-&gt;_Len = str.szie();<br />&nbsp; &nbsp; &nbsp; &nbsp; this-&gt;_Ptr[_Len+1] ++; //</span><span>引用计数加一</span><span><br />&nbsp; &nbsp; &nbsp; }<br />}<br />//</span><span>写时才拷贝</span><span>Copy-On-Write<br />char&amp; string::operator[](unsigned int idx)<br />{<br />&nbsp; if (idx &gt; _Len || _Ptr == 0 ) <br />&nbsp; {<br />&nbsp; &nbsp; &nbsp; static char nullchar = 0;<br />&nbsp; &nbsp; &nbsp; return nullchar;<br />&nbsp; }<br />&nbsp; _Ptr[_Len+1]--; &nbsp; //</span><span>引用计数减一</span><span><br />&nbsp; char* tmp = new char[_Len+1+1];<br />&nbsp; strncpy( tmp, _Ptr, _Len+1);<br />&nbsp; _Ptr = tmp;<br />&nbsp; _Ptr[_Len+1]=0; // </span><span>设置新的共享内存的引用计数</span><span><br />&nbsp; <br />&nbsp; return _Ptr[idx];<br />}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;哈哈，整个技术细节完全浮出水面。</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;不过，这和</span><span>STL</span><span>中</span><span>basic_string</span><span>的实现细节还有一点点差别，在你打开</span><span>STL</span><span>的源码时，你会发现其取引用计数是通过这样的访问：</span><span>_Ptr[-1]</span><span>，标准库中，把这个引用计数的内存分配在了前面（我给出来的代码是把引用计数分配以了后面，这很不好），分配在前的好处是当</span><span>string</span><span>的长度扩展时，只需要在后面扩展其内存，而不需要移动引用计数的内存存放位置，这又节省了一点时间。</span><span><br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;STL</span><span>中的</span><span>string</span><span>的内存结构就像我前面画的那个图一样，</span><span>_Ptr</span><span>指着是数据区，而</span><span>RefCnt</span><span>则在</span><span>_Ptr-1 </span><span>或是</span><span> _Ptr[-1]</span><span>处。<br /></span><span><br />2.4</span><span>、</span><span>臭虫</span><span>Bug<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;是谁说的</span><span>&#8220;</span><span>有太阳的地方就会有黑暗</span><span>&#8221;</span><span>？或许我们中的许多人都很迷信标准的东西，认为其是久经考验，不可能出错的。呵呵，千万不要有这种迷信，因为任何设计再好，编码再好的代码在某一特定的情况下都会有</span><span>Bug</span><span>，</span><span>STL</span><span>同样如此，</span><span>string</span><span>类的这个共享内存</span><span>/</span><span>写时才拷贝技术也不例外，而且这个</span><span>Bug</span><span>或许还会让你的整个程序</span><span>crash</span><span>掉！</span><span>不信？！那么让我们来看一个测试案例：</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;假设有一个动态链接库（叫</span><span>myNet.dll</span><span>或</span><span>myNet.so</span><span>）中有这样一个函数返回的是</span><span>string</span><span>类：</span><span><br />string GetIPAddress(string hostname)<br />{<br />&nbsp; static string ip;<br />&nbsp; &#8230;&#8230;<br />&nbsp; &#8230;&#8230;<br />&nbsp; return ip;<br />}<br />&nbsp; &nbsp; &nbsp; <br /></span><span>而你的主程序中动态地载入这个动态链接库，并调用其中的这个函数：</span><span><br />main()<br />{<br />//</span><span>载入动态链接库中的函数</span><span><br />hDll = LoadLibraray(&#8230;..);<br />pFun = GetModule(hDll, &#8220;GetIPAddress&#8221;);<br />//</span><span>调用动态链接库中的函数</span><span><br />string ip = (*pFun)(&#8220;host1&#8221;);<br />&#8230;&#8230;<br />&#8230;&#8230;<br />//</span><span>释放动态链接库</span><span><br />FreeLibrary(hDll);<br />&#8230;&#8230;<br />cout &lt;&lt; ip &lt;&lt; endl;<br />}<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;让我们来看看这段代码，程序以动态方式载入动态链接库中的函数，然后以函数指针的方式调用动态链接库中的函数，并把返回值放在一个</span><span>string</span><span>类中，然后释放了这个动态链接库。释放后，输入</span><span>ip</span><span>的内容。</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;根据函数的定义，我们知道函数是</span><span>&#8220;</span><span>值返回</span><span>&#8221;</span><span>的，所以，函数返回时，一定会调用拷贝构造函数，又根据</span><span>string</span><span>类的内存共享机制，在主程序中变量</span><span>ip</span><span>是和函数内部的那个静态</span><span>string</span><span>变量共享内存（这块内存区是在动态链接库的地址空间的）。而我们假设在整个主程序中都没有对</span><span>ip</span><span>的值进行修改过。那么在当主程序释放了动态链接库后，那个共享的内存区也随之释放。所以，以后对</span><span>ip</span><span>的访问，必然做造成内存地址访问非法，造成程序</span><span>crash</span><span>。即使你在以后没有使用到</span><span>ip</span><span>这个变量，那么在主程序退出时也会发生内存访问异常，因为程序退出时，</span><span>ip</span><span>会析构，在析构时就会发生内存访问异常。</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;内存访问异常，意味着两件事：<br /></span><span>1</span><span>）无论你的程序再漂亮，都会因为这个错误变得暗淡无光，你的声誉也会因为这个错误受到损失。<br /></span><span>2</span><span>）未来的一段时间，你会被这个系统级错误所煎熬（在</span><span>C++</span><span>世界中，找到并排除这种内存错误并不是一件容易的事情）。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这是</span><span>C/C++</span><span>程序员永远的心头之痛，千里之堤，溃于蚁穴。而如果你不清楚</span><span>string</span><span>类的这种特征，在成千上万行代码中找这样一个内存异常，简直就是一场噩梦。</span><span><br /></span><span>备注：要改正上述的</span><span>Bug</span><span>，有很多种方法，这里提供一种仅供参考：</span><span><br />string ip = (*pFun)(&#8220;host1&#8221;).cstr();<br /><br />3</span><span>、</span><span>后记</span><span><br /></span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;文章到这里也应该结束了，这篇文章的主要有以下几个目的：</span><span><br />1</span><span>）</span>&nbsp;<span>向大家介绍一下写时才拷贝</span><span>/</span><span>内存共享这种技术。</span><span><br />2</span><span>）</span>&nbsp;<span>以</span><span>STL</span><span>中的</span><span>string</span><span>类为例，向大家介绍了一种设计模式。</span><span><br />3</span><span>）</span>&nbsp;<span>在</span><span>C++</span><span>世界中，无论你的设计怎么精巧，代码怎么稳固，都难以照顾到所有的情况。智能指针更是一个典型的例子，无论你怎么设计，都会有非常严重的</span><span>BUG</span><span>。</span><span><br />4</span><span>）</span><span>&nbsp;C++</span><span>是一把双刃剑，只有了解了原理，你才能更好的使用</span><span>C++</span><span>。否则，必将引火烧身。如果你在设计和使用类库时有一种</span><span>&#8220;</span><span>玩</span><span>C++</span><span>就像玩火，必须千万小心</span><span>&#8221;</span><span>的感觉，那么你就入门了，等你能把这股</span><span>&#8220;</span><span>火</span><span>&#8221;</span><span>控制的得心应手时，那才是学成了。</span><span><br /><br /></span></p><img src ="http://www.cppblog.com/zmllegtui/aggbug/65255.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-27 22:56 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/27/65255.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Singleton模式（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/27/65252.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Mon, 27 Oct 2008 14:51:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/27/65252.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65252.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/27/65252.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65252.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65252.html</trackback:ping><description><![CDATA[class Singleton<br />{<br />
<p><span>public:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>static Singleton* Instance();</span></p>
<p><span>protected:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>Singleton();</span></p>
<p><span>private:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>static Singleton* _instance;</span></p>
<p><span>};<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span>相应的实现</span><span> cpp </span><span>文件是：</span></p>
<p><span>Singleton* Singleton::_instance;</span></p>
<p><span>Singleton* Singleton::Instance()<br />{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>if( _instance == 0){</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>_instance = new Singleton;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>};</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>return _instance;</span></p>
<p><span>}<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span></span><span>将构造函数设计成</span><span> protected </span><span>的目的是防止在</span><span> class </span><span>外面</span><span> new </span><span>，有人可能会设计成</span><span> private </span><span>，假如考虑到有可能会继续这个类的话，还是将构造函数设计成</span><span> protected </span><span>比较好，还需要加一个</span><span> virtual </span><span>析构函数。为了防止别人复制</span><span> Singleton </span><span>对象：</span></p>
<p><span>Singleton* pSingleton = Singleton::Instance();</span></p>
<p><span>Singleton s1 = *pSingleton;</span></p>
<p><span>Singleton s2 = *pSingleton; </span></p>
<p><span>需要将拷贝构造</span><span>(copy constrUCtor)</span><span>函数变成</span><span> private</span><span>。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span></span><span>但是这里存在的问题是，什么时候</span><span style="color: #ffff00;">删除</span><span style="color: #ffff00;"> Singleton </span><span style="color: #ffff00;">对象</span><span>？按照</span><span> C++ </span><span>的一个基本原则，对象在哪里创建就在哪里销毁，这里还应该放一个</span><span> destroy </span><span>方法来删除</span><span> Singleton </span><span>对象。假如忘了删除就比较麻烦。</span><span>Instance </span><span>函数还存在</span><span style="color: #ffff00;">多线程同时访问的加锁问题</span><span>。假如把</span><span> Instance </span><span>函数开始和结尾放上加锁和解锁，整个函数性能会下降很多。这不是一个好的设计。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span></span><span>有一个小小的改动，可以避免忘了删除</span><span> Singleton </span><span>对象带来内存泄露的问题。那就是用</span><span> </span><span style="color: #ffff00;">std:auto_ptr </span><span>来包含</span><span> Singleton </span><span>对象</span><span>,</span><span>定义一个类静态</span><span>auto_ptr成员</span><span>对象，在析构静态的</span><span>auto_ptr </span><span>变量的时候时候自动删除</span><span> Singleton </span><span>对象。为了不让用户</span><span> delete Singleton </span><span>对象，需要将析构函数由</span><span> public </span><span>变成</span><span> protected</span><span>。以下是头文件</span><span> SingletonAutoPtr.h </span><span>：</span></p>
<p><span>#include &lt;memory&gt;</span></p>
<p><span>using namespace std;</span></p>
<p><span>class CSingletonAutoPtr </span></p>
<p><span>{</span></p>
<p><span>private:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>static auto_ptr&lt;CSingletonAutoPtr&gt; m_auto_ptr; </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>static CSingletonAutoPtr* m_instance;</span></p>
<p><span>protected:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>CSingletonAutoPtr();</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>CSingletonAutoPtr(const CSingletonAutoPtr&amp;);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>virtual ~CSingletonAutoPtr(); </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>//allow auto_ptr to delete, using protected ~CSingletonAutoPtr()</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>friend class auto_ptr&lt;CSingletonAutoPtr&gt;; </span></p>
<p><span>public:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>static CSingletonAutoPtr* GetInstance();</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>void Test();</span></p>
<p><span>};<span>&nbsp;&nbsp; </span></span></p>
<p><span>对应的</span><span> SingletonAutoPtr.cpp </span><span>如下：</span></p>
<p><span>#include "SingletonAutoPtr.h"</span></p>
<p><span>#include &lt;iostream&gt;<br /><br />//initial static member vars here </span></p>
<p><span>CSingletonAutoPtr* CSingletonAutoPtr::m_instance = NULL;</span></p>
<p><span>auto_ptr&lt;CSingletonAutoPtr&gt; CSingletonAutoPtr::m_auto_ptr;</span></p>
<p>&nbsp;</p>
<p><span>/////////////////////////////////////////</span></p>
<p><span>// Construction/Destruction</span></p>
<p><span>/////////////////////////////////////////</span></p>
<p><br />//下列代码没有经过验证！</p>
<p><span>CSingletonAutoPtr::CSingletonAutoPtr()</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>cout &lt;&lt; "CSingletonAutoPtr::CSingletonAutoPtr()" &lt;&lt; endl;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>//put single object into auto_ptr object </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>m_auto_ptr = auto_ptr&lt;CSingletonAutoPtr&gt;(this);</span></p>
<p><span>}</span></p>
<p><span>CSingletonAutoPtr::~CSingletonAutoPtr()</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>cout &lt;&lt; "CSingletonAutoPtr::~CSingletonAutoPtr()" &lt;&lt; endl;</span></p>
<p><span>}</span></p>
<p><span>CSingletonAutoPtr* CSingletonAutoPtr::GetInstance()</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>//begin lock</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>//....<span>&nbsp;&nbsp;&nbsp; <br /></span>&nbsp; &nbsp; if(m_instance == NULL)</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>m_instance = new CSingletonAutoPtr();<span>&nbsp;&nbsp;&nbsp; <br /></span>&nbsp; &nbsp; //end lock</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>//...<span>&nbsp;&nbsp;&nbsp; <br /></span>&nbsp; &nbsp; return m_instance; </span></p>
<p><span>}</span></p>
<p><span>void CSingletonAutoPtr::Test()</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>cout &lt;&lt; "CSingletonAutoPtr::Test()" &lt;&lt; endl;</span></p>
<p><span>}<span>&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span>调用方法：</span></p>
<p><span>CSingletonAutoPtr* pSingleton = CSingletonAutoPtr::GetInstance();</span></p>
<p><span>pSingleton-&gt;Test(); </span></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;写一个 C++ 中的 Singleton 需要这么费劲，大大出乎我们的意料。有很多人从未用过 auto_ptr，而且 std:auto_ptr 本身就并不完美，它是基于对象所有权机制的，相比之下，Apache Log4cxx 中有一个 auto_ptr, 是基于对象计数的，更为好用。只是为了用一个好的 auto_ptr 而不得不用 log4cxx , 对于很多项目来说，也不太好。当然了，ANSI C++ 的 STL 中 std:auto_ptr 对于写上面的例子已经足够用了。&nbsp; &nbsp; &nbsp;</p><p>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;<span>另外一个思路是，把</span><span> GetInstance </span><span>函数设计成</span><span> static member </span><span>可能更好，因为一般来说，</span><span>Singleton </span><span>对象都不大，</span><span>static member </span><span>虽然必须一直占用内存，问题不大。这里的析构函数必须设成</span><span> public </span><span>了。以下是头文件</span><span> SingleStaticObj.h</span></p>
<p><span>class CSingletonStaticObj </span></p>
<p><span>{</span></p>
<p><span>private:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>static CSingletonStaticObj m_instance;</span></p>
<p><span>protected:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>CSingletonStaticObj();</span></p>
<p><span>&nbsp;<span>&nbsp;&nbsp;</span>CSingletonStaticObj(const CSingletonStaticObj&amp;);</span></p>
<p><span>public:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>virtual ~CSingletonStaticObj(); //must public</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>static CSingletonStaticObj&amp; GetInstance();</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>void Test();</span></p>
<p><span>};<span>&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>对应的</span><span> SingleStaticObj.cpp </span><span>文件为：</span><span>#include "SingletonStaticObj.h"</span></p>
<p><span>#include &lt;string&gt;</span></p>
<p><span>#include &lt;iostream&gt; </span></p>
<p><span>using namespace std;</span></p>
<p><span>CSingletonStaticObj CSingletonStaticObj::m_instance;CSingletonStaticObj::CSingletonStaticObj()</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>cout &lt;&lt; "CSingletonStaticObj::CSingletonStaticObj()" &lt;&lt; endl;</span></p>
<p><span>}</span></p>
<p><span>CSingletonStaticObj::~CSingletonStaticObj()</span></p>
<p><span>{</span></p>
<p><span>&nbsp;<span>&nbsp;&nbsp; </span>cout &lt;&lt; "CSingletonStaticObj::~CSingletonStaticObj()" &lt;&lt; endl;</span></p>
<p><span>}</span></p>
<p><span>CSingletonStaticObj&amp; CSingletonStaticObj::GetInstance()</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>return m_instance;</span></p>
<p><span>}</span></p>
<p><span>void CSingletonStaticObj::Test()</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>cout &lt;&lt; "CSingletonStaticObj::Test()" &lt;&lt; endl;</span></p>
<p><span>}</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span></span><span>调用方法：</span></p>
<p><span>CSingletonStaticObj&amp; singleton = CSingletonAutoPtr::GetInstance();</span></p>
<p><span>singleton.Test();<span>&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span>从代码量来说，似乎使用</span><span> static member ref </span><span>更为简单。我更偏向于用这种方法。</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span></span><span>但是，并不是所有情况下面都适合用</span><span> static member singleton</span><span>。比如说，</span><span>GetInstance </span><span>需要动态决定返回不同的</span><span> instance </span><span>的时候，就不能用。举例来说，</span><span>FileSystem::GetInstance, </span><span>在</span><span> windows </span><span>下面运行可能需要返回</span><span> new WinFileSystem, Linux/Unix </span><span>下面运行可能需要返回</span><span> new LinuxFileSystem</span><span>，这个时候还是需要用上面的</span><span> auto_ptr </span><span>包含</span><span> singleton </span><span>指针的方法。</span></p><img src ="http://www.cppblog.com/zmllegtui/aggbug/65252.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-27 22:51 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/27/65252.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>iterator中的前++和后++（C++）</title><link>http://www.cppblog.com/zmllegtui/archive/2008/10/27/65239.html</link><dc:creator>zml_cnnk</dc:creator><author>zml_cnnk</author><pubDate>Mon, 27 Oct 2008 13:22:00 GMT</pubDate><guid>http://www.cppblog.com/zmllegtui/archive/2008/10/27/65239.html</guid><wfw:comment>http://www.cppblog.com/zmllegtui/comments/65239.html</wfw:comment><comments>http://www.cppblog.com/zmllegtui/archive/2008/10/27/65239.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zmllegtui/comments/commentRss/65239.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zmllegtui/services/trackbacks/65239.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: for(iterator it = begin(); it != end(); ++it)<br>      或者<br>for(iterator it = begin(); it != end(); it++) <br>      区别是什么呢？？<br>……<br><br>&nbsp;&nbsp;<a href='http://www.cppblog.com/zmllegtui/archive/2008/10/27/65239.html'>阅读全文</a><img src ="http://www.cppblog.com/zmllegtui/aggbug/65239.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zmllegtui/" target="_blank">zml_cnnk</a> 2008-10-27 21:22 <a href="http://www.cppblog.com/zmllegtui/archive/2008/10/27/65239.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>