??xml version="1.0" encoding="utf-8" standalone="yes"?>久久久女人与动物群交毛片,国产成年无码久久久久毛片,欧美亚洲国产精品久久高清http://www.shnenglu.com/CrazyDev/articles/115699.htmlCrazyDevCrazyDevTue, 18 May 2010 09:20:00 GMThttp://www.shnenglu.com/CrazyDev/articles/115699.htmlhttp://www.shnenglu.com/CrazyDev/comments/115699.htmlhttp://www.shnenglu.com/CrazyDev/articles/115699.html#Feedback0http://www.shnenglu.com/CrazyDev/comments/commentRss/115699.htmlhttp://www.shnenglu.com/CrazyDev/services/trackbacks/115699.htmlstatic_cast?strong>dynamic_cast?strong>reinterpret_cast、和const_cast。下面对它们一一q行介绍?br>
static_cast

用法Q?strong>static_cast < type-id > ( expression )

该运符把expression转换为type-idcdQ但没有q行时类型检查来保证转换的安全性。它主要有如下几U用法:
  • 用于cdơ结构中基类和子cM间指针或引用的{换。进行上行{换(把子cȝ指针或引用{换成基类表示Q是安全的;q行下行转换Q把基类指针或引用{换成子类表示Q时Q由于没有动态类型检查,所以是不安全的?
  • 用于基本数据cd之间的{换,如把int转换成charQ把int转换成enum。这U{换的安全性也要开发h员来保证?
  • 把空指针转换成目标类型的I指针?
  • 把Q何类型的表达式{换成voidcd?/li>
注意Qstatic_cast不能转换掉expression的const、volitale、或者__unaligned属性?br>
dynamic_cast

用法Q?strong>dynamic_cast < type-id > ( expression )

该运符把expression转换成type-idcd的对象。Type-id必须是类的指针、类的引用或者void *Q如果type-id是类指针cdQ那么expression也必L一个指针,如果type-id是一个引用,那么expression也必L一个引用?br>
dynamic_cast主要用于cdơ间的上行{换和下行转换Q还可以用于cM间的交叉转换?br>
在类层次间进行上行{换时Qdynamic_cast和static_cast的效果是一LQ在q行下行转换Ӟdynamic_casthcd查的功能Q比static_cast更安全?br>
 class B{

public:

 int m_iNum;

 virtual void foo();

};

class D:public B{

 public:

 char *m_szName[100];

};

 

void func(B *pb){

 D *pd1 = static_cast<D *>(pb);

 D *pd2 = dynamic_cast<D *>(pb);

}

在上面的代码D中Q如果pb指向一个Dcd的对象,pd1和pd2是一LQƈ且对q两个指针执行Dcd的Q何操作都是安全的Q但是,如果pb指向的是一个Bcd的对象,那么pd1是一个指向该对象的指针,对它q行Dcd的操作将是不安全的(如访问m_szNameQ,而pd2是一个空指针。另外要注意QB要有虚函敎ͼ否则会编译出错;static_cast则没有这个限制。这是由于运行时cd查需要运行时cd信息Q而这个信息存储在cȝ虚函数表Q关于虚函数表的概念Q详l可?lt;Inside c++ object model>Q中Q只有定义了虚函数的cL有虚函数表,没有定义虚函数的cL没有虚函数表的?br>
另外Q?strong>dynamic_castq支持交叉{换(cross castQ。如下代码所C?br>
class A{

public:

 int m_iNum;

 virtual void f(){}

};

 

class B:public A{

};

 

class D:public A{

};

 

void foo(){

 B *pb = new B;

 pb->m_iNum = 100;

 D *pd1 = static_cast<D *>(pb); //copile error

 D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL

 delete pb;

}

在函数foo中,使用static_castq行转换是不被允许的Q将在编译时出错Q而?dynamic_cast的{换则是允许的Q结果是I指针?br>
reinpreter_cast

用法Q?strong>reinpreter_cast<type-id> (expression)

type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针{换成一个整敎ͼ也可以把一个整数{换成一个指针(先把一个指针{换成一个整敎ͼ在把该整数{换成原类型的指针Q还可以得到原先的指针|?br>
该运符的用法比较多?br>
const_cast

用法Q?strong>const_cast<type_id> (expression)

该运符用来修改cd的const或volatile属性。除了const 或volatile修饰之外Q?type_id和expression的类型是一L?br>
帔R指针被{化成非常量指针,q且仍然指向原来的对象;帔R引用被{换成非常量引用,q且仍然指向原来的对象;帔R对象被{换成非常量对象?br>
Voiatile和constc试。D如下一例:
class B{

 public:

 int m_iNum;

}

void foo(){

const B b1;

b1.m_iNum = 100; //comile error

B b2 = const_cast<B>(b1);

b2. m_iNum = 200; //fine
 }

上面的代码编译时会报错,因ؓb1是一个常量对象,不能对它q行改变Q用const_cast把它转换成一个常量对象,可以对它的数据成员L改变。注意:b1和b2是两个不同的对象?

CrazyDev 2010-05-18 17:20 发表评论
]]>
Bjarne Stroustrup的FAQQC++的风g技?/title><link>http://www.shnenglu.com/CrazyDev/articles/115698.html</link><dc:creator>CrazyDev</dc:creator><author>CrazyDev</author><pubDate>Tue, 18 May 2010 09:12:00 GMT</pubDate><guid>http://www.shnenglu.com/CrazyDev/articles/115698.html</guid><wfw:comment>http://www.shnenglu.com/CrazyDev/comments/115698.html</wfw:comment><comments>http://www.shnenglu.com/CrazyDev/articles/115698.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/CrazyDev/comments/commentRss/115698.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/CrazyDev/services/trackbacks/115698.html</trackback:ping><description><![CDATA[译Q左M<br><br>Q译注:本文的翻译相当艰苦。Bjarne Stroustrup不愧是创立C++语言的一代大师,不但思想博大_深Q而且在遣词造句上,也非常精微深奥。有很多地方Q译者反复斟酌,都不能取得理想的效果Q只能尽力而ؓ?br><br>Html格式的文档见译者主:<a title=http://www.wushuang.net target=_blank>http://www.wushuang.net</a><br><br>如果你对q个译E有M意见和徏议,请发信给译者:onekey@163.com?br><br>原文的地址为:<a title=http://www.research.att.com/~bs/bs_faq2.htmlQ?href="http://www.research.att.com/~bs/bs_faq2.htmlQ? target=_blank>http://www.research.att.com/~bs/bs_faq2.htmlQ?/a><br><br>QBjarne Stroustrup博士Q?950q出生于业wQ先后毕业于业w阉K斯大学和英国剑挢大学QAT&T大规模程序设计研I门负责hQAT&T 贝尔实验室和ACM成员?979q_B. S开始开发一U语aQ当时称?C with Class"Q后来演化ؓC++?998q_ANSI/ISO C++标准建立Q同q_B. S推出其经典著作The C++ Programming Language的第三版。)<br><br>q是一些h们经常向我问L有关C++的风g技巧的问题。如果你能提出更好的问题Q或者对q些{案有所Q请务必发Emaill我(<a href="mailto:bs@research.att.com">bs@research.att.com</a>)。请CQ我不能把全部的旉都花在更新我的主上面?br><br>更多的问题请参见我的general FAQ?br><br>关于术语和概念,请参见我的C++术语表(C++ glossary.Q?br><br>h意,q仅仅是一个常见问题与解答的列表。它不能代替一本优U教科书中那些l过_ֿ挑选的范例与解释。它也不能象一本参考手册或语言标准那样Q提供详l和准确的说明。有关C++的设计的问题Q请参见《C++语言的设计和演变》(The Design and Evolution of C++Q。关于C++语言与标准库的用,请参见《C++E序设计语言》(The C++ Programming LanguageQ?br><br><br>目录Q?br><br>我如何写q个非常单的E序Q?br><br>Z么编译要p么长的时_<br><br>Z么一个空cȝ大小不ؓ0Q?br><br>我必dcd明处赋予数据吗?<br><br>Z么成员函数默认不是virtual的?<br><br>Z么析构函数默认不是virtual的?<br><br>Z么不能有虚拟构造函敎ͼ<br><br>Z么重载在l承cM不工作?<br><br>我能够在构造函C调用一个虚拟函数吗Q?br><br>有没?#8220;指定位置删除”(placement delete)Q?br><br>我能防止别hl承我自qcdQ?br><br>Z么不能ؓ模板参数定义U束QconstraintsQ?<br><br>既然已经有了优秀的qsort()函数Qؓ什么还需要一个sort()Q?br><br>什么是函数对象Qfunction objectQ?<br><br>我应该如何对付内存泄漏?<br><br>我ؓ什么在捕获一个异怹后就不能l箋Q?br><br>Z么C++中没有相当于realloc()的函敎ͼ<br><br>如何使用异常Q?br><br>怎样从输入中d一个字W串Q?br><br>Z么C++不提?#8220;finally”的构造?<br><br>什么是自动指针Qauto_ptrQ,Z么没有自动数l(auto_arrayQ?<br><br>可以混合使用C风格与C++风格的内存分z与重新分配吗?<br><br>我ؓ什么必M用一个造型来{?voidQ?br><br>我如何定义一个类内部Qin-classQ的帔RQ?br><br>Z么delete不会操作数|?Q?br><br>我能够写“void main()”吗?<br><br>Z么我不能重蝲点符P::QsizeofQ等{?<br><br>怎样一个整型D{换ؓ一个字W串Q?br><br>“int* p”正确q是“int *p”正确Q?br><br>对于我的代码Q哪一U布局风格Qlayout styleQ是最好的Q?br><br>我应该将“const”攑֜cd之前q是之后Q?br><br>使用宏有什么问题?<br><br>我如何写q个非常单的E序Q?br><br><br>特别是在一个学期的开始,我常常收到许多关于编写一个非常简单的E序的询问。这个问题有一个很具代表性的解决ҎQ那是Q在你的E序中)d几个数字Q对它们做一些处理,再把l果输出。下面是一个这样做的例子:<br><br> <div id="3x9bjj9" class=quote> <div id="9t9jdjh" class=quote-title><u></u></div> <div id="z3txzx3" class=quote-content>#include<iostream><br><br>#include<vector><br><br>#include<algorithm><br><br>usingnamespace std;<br><br>intmain()<br><br>{<br>vector<double>v;<br><br>doubled;<br><br>while(cin>>d)v.push_back(d); // d元素<br><br>if(!cin.eof()) { // 查输入是否出?br><br>cerr<< "format error\n";<br><br>return1; // q回一个错?br><br>}<br><br>cout<< "read " << v.size() << " elements\n";<br><br>reverse(v.begin(),v.end());<br><br>cout<< "elements in reverse order:\n";<br><br>for(int i = 0; i<v.size(); ++i) cout << v[i] << '\n';<br><br>return0; // 成功q回<br>}</div> <br>对这D늨序的观察Q?br>q是一D|准的ISO C++E序Q用了标准?standard library)。标准库工具在命名空间std中声明,装在没?h后缀的头文g中?br><br>如果你要在Windows下编译它Q你需要将它编译成一?#8220;控制台程?#8221;QconsoleapplicationQ。记得将源文件加?cpp后缀Q否则编译器可能会以为它是一DC代码而不是C++?br><br>是的Qmain()函数q回一个int倹{?br><br>d一个标准的向量(vector)中,可以避免在随意确定大的~冲中溢出的错误。读C个数l?array)中,而不产生“单错?#8221;(silly error)Q这已经出了一个新手的能力——如果你做到了,那你已经不是一个新手了。如果你Ҏ表示怀疑,我徏议你阅读我的文章“标准C++作ؓ一U新的语a来学?#8221;("Learning Standard C++as a New Language")Q你可以在本作列?my publications list)中下载到它?br><br>!cin.eof()是对的格式的检查。事实上Q它查@环是否终l于发现一个end-of-file(如果不是q样Q那么意味着输入没有按照l定的格?。更多的说明Q请参见你的C++教科书中?#8220;状?#8221;(stream state)部分?br><br>vector知道它自q大小Q因此我不需要计元素的数量?br><br>q段E序没有包含昑ּ的内存管理。Vectorl护一个内存中的栈Q以存放它的元素。当一个vector需要更多的内存Ӟ它会分配一些;当它不再生存Ӟ它会释放内存。于是,使用者不需要再兛_vector中元素的内存分配和释N题?br><br>E序在遇到输入一?#8220;end-of-file”时结束。如果你在UNIXq_下运行它Q?#8220;end-of-file”{于键盘上的Ctrl+D。如果你在Windowsq_下,那么׃一个BUG它无法L?#8220;end-of-file”字符Q你可能們֐于用下面这个稍E复杂些的版本,它用一个词“end”来表C入已l结束?br></div> <div id="933t9b3" class=quote> <div id="blfjtr9" class=quote-title><u></u></div> <div id="9lzbt99" class=quote-content>#include<iostream><br><br>#include<vector><br><br>#include<algorithm><br><br>#include<string><br><br>usingnamespace std;<br><br>intmain()<br><br>{<br>vector<double>v;<br><br>doubled;<br><br>while(cin>>d)v.push_back(d); // d一个元?br><br>if(!cin.eof()) { // 查输入是否失?br><br>cin.clear(); //清除错误状?br><br>strings;<br><br>cin>> s; // 查找l束字符<br><br>if(s != "end") {<br><br>cerr<< "format error\n";<br><br>return1; // q回错误<br><br>}<br><br>}<br><br>cout<< "read " << v.size() << " elements\n";<br><br>reverse(v.begin(),v.end());<br><br>cout<< "elements in reverse order:\n";<br><br>for(int i = 0; i<v.size(); ++i) cout << v[i] << '\n';<br><br>return0; // 成功q回<br>}<br><br>更多的关于用标准库事情简化的例子Q请参见《C++E序设计语言》中?#8220;漫游标准?#8221;("Tour of the StandardLibrary")一章?br>Z么编译要p么长的时_<br><br>你的~译器可能有问题。也许它太老了Q也怽安装它的时候出了错Q也怽用的计算机已l是个古董。在诸如此类的问题上Q我无法帮助你?br><br>但是Q这也是很可能的Q你要编译的E序设计得非常糟p,以至于编译器不得不检查数以百计的头文件和C行代码。理Z来说Q这是可以避免的。如果这是你购买的库的设计问题,你对它无计可施(除了换一个更好的库)Q但你可以将你自q代码l织得更好一些,以求得将修改代码后的重新~译工作降到最。这L设计会更好,更有可维护性,因ؓ它们展示了更好的概念上的分离?br><br>看看q个典型的面向对象的E序例子Q?/div> </div> <div id="zf9n3j9" class=quote> <div id="pr9z9tn" class=quote-title><u></u></div> <div id="9p3vz9f" class=quote-content>classShape {<br><br>public: //使用Shapes的用L接口<br><br>virtualvoid draw() const;<br><br>virtualvoid rotate(int degrees);<br><br>//...<br><br>protected: //common data (for implementers of Shapes)<br><br>Pointcenter;<br><br>Colorcol;<br><br>//...<br><br>};<br><br>classCircle : public Shape {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int) { }<br><br>//...<br><br>protected:<br><br>intradius;<br><br>//...<br><br>};<br><br>classTriangle : public Shape {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int);<br><br>//...<br><br>protected:<br><br>Pointa, b, c;<br><br>//...<br><br>}; </div> <br>设计思想是,用户通过Shape的public接口来操U它们,而派生类Q例如Circle和TriangleQ的实现部分则共享由protected成员表现的那部分实现QimplementationQ?br><br>q不是一件容易的事情Q确定哪些实现部分是Ҏ有的zc都有用的,q将之共享出来。因此,与public接口相比Qprotected成员往往要做多得多的改动。D例来_虽然理论?#8220;中心”(center)Ҏ有的囑Ş都是一个有效的概念Q但当你要维护一个三角Ş?#8220;中心”的时候,是一仉帔R烦的事情——对于三角ŞQ当且仅当它实被需要的时候,计算q个中心才是有意义的?br><br>protected成员很可能要依赖于实现部分的l节Q而Shape的用P译注Quser此处译ؓ用户Q指使用Shapecȝ代码Q下同)却不见得必须依赖它们。D例来_很多Q大多数Q)使用Shape的代码在逻辑上是?#8220;颜色”无关的,但是׃Shape?#8220;颜色”q个定义的存在,却可能需要一堆复杂的头文Ӟ来结合操作系l的颜色概念?br><br>当protected部分发生了改变时Q用Shape的代码必重新编译——即使只有派生类的实现部分才能够讉Kprotected成员?br><br>于是Q基cM?#8220;实现相关的信?#8221;(informationhelpful to implementers)对用h说变成了象接口一h感的东西Q它的存在导致了实现部分的不E_Q用户代码的无谓的重~译Q当实现部分发生改变ӞQ以及将头文件无节制地包含进用户代码中(因ؓ“实现相关的信?#8221;需要它们)。有时这被称?#8220;脆弱的基c问?#8221;(brittle baseclass problem)?br><br>一个很明显的解x案就是,忽略基类中那些象接口一栯使用?#8220;实现相关的信?#8221;。换句话_使用接口Q纯_的接口。也是_用抽象基cȝ方式来表C接口:</div> <div id="9rf9999" class=quote> <div id="1bp399r" class=quote-title><u></u></div> <div id="9pjh999" class=quote-content>classShape {<br><br>public: //使用Shapes的用L接口<br><br>virtualvoid draw() const = 0;<br><br>virtualvoid rotate(int degrees) = 0;<br><br>virtualPoint center() const = 0;<br><br>//...<br><br>//没有数据<br><br>};<br><br><br>classCircle : public Shape {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int) { }<br><br>Pointcenter() const { return center; }<br><br>//...<br><br>protected:<br><br>Pointcent;<br><br>Colorcol;<br><br>intradius;<br><br>//...<br><br>};<br><br><br>classTriangle : public Shape {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int);<br><br>Pointcenter() const;<br><br>//...<br><br>protected:<br><br>Colorcol;<br><br>Pointa, b, c;<br><br>//...<br><br>}; </div> <br>现在Q用户代码与zcȝ实现部分的变化之间的关系被隔M。我曄见过q种技术得编译的旉减少了几个数量?br><br><br>但是Q如果确实存在着Ҏ有派生类Q或仅仅Ҏ些派生类Q都有用的公׃息时怎么办呢Q可以简单把q些信息装成类Q然后从它派生出实现部分的类Q?br><br></div> <div id="dxvr39f" class=quote> <div id="139znvd" class=quote-title><u></u></div> <div id="91f9drz" class=quote-content>classShape {<br><br>public: //使用Shapes的用L接口<br><br>virtualvoid draw() const = 0;<br><br>virtualvoid rotate(int degrees) = 0;<br><br>virtualPoint center() const = 0;<br><br>//...<br><br><br>//no data<br><br>};<br><br>structCommon {<br><br>Colorcol;<br><br>//...<br><br>};<br><br><br>classCircle : public Shape, protected Common {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int) { }<br><br>Pointcenter() const { return center; }<br><br>//...<br><br>protected:<br><br>Pointcent;<br><br>intradius;<br><br>};<br><br><br><br>classTriangle : public Shape, protected Common {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int);<br><br>Pointcenter() const;<br><br>//...<br><br>protected:<br><br>Pointa, b, c;<br><br>}; </div> </div> <br><br>Z么一个空cȝ大小不ؓ0Q?br><br><br><br>要清楚,两个不同的对象的地址也是不同的。基于同L理由QnewLq回指向不同对象的指针?br><br>看看Q?br><br><br><br> <div id="9bdxtjp" class=quote> <div id="13x9l99" class=quote-title><u></u></div> <div id="phfh399" class=quote-content>classEmpty { };<br><br><br><br>voidf()<br><br>{<br><br>Emptya, b;<br><br>if(&a == &b) cout << "impossible: report error to compilersupplier";<br><br><br><br>Empty*p1 = new Empty;<br><br>Empty*p2 = new Empty;<br><br>if(p1 == p2) cout << "impossible: report error to compilersupplier";<br><br>} </div> </div> <br><br>有一条有的规则Q一个空的基cdƈ不一定有分隔字节? <div id="vlxr93v" class=quote> <div id="1399999" class=quote-title><u></u></div> <div id="1fp93fp" class=quote-content>structX : Empty {<br><br>inta;<br><br>//...<br><br>};<br><br><br><br>voidf(X* p)<br><br>{<br><br>void*p1 = p;<br><br>void*p2 = &p->a;<br><br>if(p1 == p2) cout << "nice: good optimizer";<br><br>}</div> </div> <br><br>q种优化是允许的Q可以被q泛使用。它允许E序员用空cM表现一些简单的概念。现在有些编译器提供q种“I基cM?#8221;(empty base classoptimization)?br><br><br><br>我必dcd明处赋予数据吗?<br><br><br><br>不必R如果一个接口不需要数据时Q无d作ؓ接口定义的类中赋予数据。代之以在派生类中给出它们。参?#8220;Z么编译要p么长的时_”?br><br><br><br>有时候,你必d一个类中赋予数据。考虑一下复数类的情况:<br><br><br><br> <div id="vfzt399" class=quote> <div id="tnxr39v" class=quote-title><u></u></div> <div id="nx33jtz" class=quote-content>template<classScalar> class complex {<br><br>public:<br><br>complex(): re(0), im(0) { }<br><br>complex(Scalarr) : re(r), im(0) { }<br><br>complex(Scalarr, Scalar i) : re(r), im(i) { }<br><br>//...<br><br><br><br>complex&operator+=(const complex& a)<br><br>{re+=a.re; im+=a.im; return *this; }<br><br>//...<br><br>private:<br><br>Scalarre, im;<br><br>};</div> </div> <br><br>设计q种cd的目的是它当做一个内建(built-inQ类型一栯使用。在声明处赋值是必须的,以保证如下可能:建立真正的本地对象(genuinely localobjectsQ?比如那些在栈中而不是在堆中分配的对?Q或者某些单操作被适当地inline化。对于那些支持内建的复合cd的语a来说Q要获得它们提供的效率,真正的本地对象和inline化都是必要的?br><br><br><br>Z么成员函数默认不是virtual的?<br><br><br><br>因ؓ很多cdƈ不是被设计作为基cȝ。例如复数类?br><br><br><br>而且Q一个包含虚拟函数的cȝ对象Q要占用更多的空间以实现虚拟函数调用机制——往往是每个对象占用一个字(word)。这个额外的字是非常可观的,而且在涉及和其它语言的数据的兼容性时Q可能导致麻?例如C或Fortran语言)?br><br><br><br>要了解更多的设计原理Q请参见《C++语言的设计和演变》(The Design and Evolution of C++Q?br><br><br><br>Z么析构函数默认不是virtual的?<br><br><br><br>因ؓ很多cdƈ不是被设计作为基cȝ。只有类在行Z是它的派生类的接口时(q些zcd往在堆中分配,通过指针或引用来讉K)Q虚拟函数才有意义?br><br><br><br>那么什么时候才应该析构函数定义ؓ虚拟呢?当类臛_拥有一个虚拟函数时。拥有虚拟函数意味着一个类是派生类的接口,在这U情况下Q一个派生类的对象可能通过一个基cL针来销毁。例如:<br><br><br><br> <div id="9v9r39f" class=quote> <div id="939993p" class=quote-title><u></u></div> <div id="139xv9z" class=quote-content>classBase {<br><br>//...<br><br>virtual~Base();<br><br>};<br><br><br><br>classDerived : public Base {<br><br>//...<br><br>~Derived();<br><br>};<br><br><br><br>voidf()<br><br>{<br><br>Base*p = new Derived;<br><br>deletep; // 虚拟析构函数保证~Derived函数被调?br><br>}</div> </div> <br><br>如果基类的析构函C是虚拟的Q那么派生类的析构函数将不会被调用——这可能产生p糕的结果,例如zcȝ资源不会被释放?br><br><br><br>Z么不能有虚拟构造函敎ͼ<br><br><br><br>虚拟调用是一U能够在l定信息不完?given partialinformation)的情况下工作的机制。特别地Q虚拟允许我们调用某个函敎ͼ对于q个函数Q仅仅知道它的接口,而不知道具体的对象类型。但是要建立一个对象,你必L有完全的信息。特别地Q你需要知道要建立的对象的具体cd。因此,Ҏ造函数的调用不可能是虚拟的?br><br><br><br>当要求徏立一个对象时Q一U间接的技术常常被当作“虚拟构造函?#8221;来用。有关例子,请参见《C++E序设计语言》第三版15.6.2.节?br><br><br><br>下面q个例子展示一U机Ӟ如何使用一个抽象类来徏立一个适当cd的对象?br><br> <div id="1t9rl93" class=quote> <div id="v3vr93b" class=quote-title><u></u></div> <div id="99t39p9" class=quote-content>structF { // 对象建立函数的接?br><br>virtualA* make_an_A() const = 0;<br><br>virtualB* make_a_B() const = 0;<br><br>};<br><br><br><br>voiduser(const F& fac)<br><br>{<br><br>A*p = fac.make_an_A(); // A作ؓ合适的cd<br><br>B*q = fac.make_a_B(); // B作ؓ合适的cd<br><br>//...<br><br>}<br><br><br><br>structFX : F {<br><br>A*make_an_A() const { return new AX(); } // AX是A的派?br><br>B*make_a_B() const { return new BX(); } // AX是B的派?br><br>};<br><br><br><br>structFY : F {<br><br>A*make_an_A() const { return new AY(); } // AY是A的派?br><br>B*make_a_B() const { return new BY(); } // BY是B的派?br><br><br><br>};<br><br><br><br>intmain()<br><br>{<br><br>user(FX()); //此用户徏立AX与BX<br><br>user(FY()); //此用户徏立AY与BY<br><br>//...<br><br>}</div> </div> <br><br>q是所谓的“工厂模式”(the factory pattern)的一个变形。关键在于,user函数与AX或AYq样的类的信息被完全分离开来了?br><br><br><br>Z么重载在l承cM不工作?<br><br><br><br>q个问题Q非常常见)往往出现于这L例子中:<br><br> <div id="1d99z9r" class=quote> <div id="p9hxb3l" class=quote-title><u></u></div> <div id="199vflb" class=quote-content>#include<iostream><br><br>usingnamespace std;<br><br><br><br>classB {<br><br>public:<br><br>intf(int i) { cout << "f(int): "; return i+1; }<br><br>//...<br><br>};<br><br><br><br>classD : public B {<br><br>public:<br><br>doublef(double d) { cout << "f(double): "; return d+1.3; }<br><br>//...<br><br>};<br><br><br><br>intmain()<br><br>{<br><br>D*pd = new D;<br><br><br><br>cout<< pd->f(2) << '\n';<br><br>cout<< pd->f(2.3) << '\n';<br><br>}</div> </div> <br><br>它输出的l果是:<br><br><br><br> <div id="13p9tt9" class=quote> <div id="x33tnlr" class=quote-title><u></u></div> <div id="ptxh9hr" class=quote-content>f(double):3.3<br><br>f(double):3.6</div> </div> <br><br>而不是象有些人猜想的那样Q?br><br><br><br> <div id="1h9t39n" class=quote> <div id="td3ptrn" class=quote-title><u></u></div> <div id="hjln3lt" class=quote-content>f(int):3<br><br>f(double):3.6</div> </div> <br><br>换句话说Q在B和D之间q没有发生重载的解析。编译器在D的区域内LQ找C一个函数double f(double)Qƈ执行了它。它永远不会涉及Q被装的)B的区域。在C++中,没有跨越区域的重载——对于这条规则,l承cM不例外。更多的l节Q参见《C++语言的设计和演变》和《C++E序设计语言》?br><br><br><br>但是Q如果我需要在基类和承类之间建立一l重载的f()函数呢?很简单,使用using声明Q?br><br> <div id="139993z" class=quote> <div id="bl39t9d" class=quote-title><u></u></div> <div id="nrj9hrf" class=quote-content>classD : public B {<br><br>public:<br><br>usingB::f; // make every f from B available<br><br>doublef(double d) { cout << "f(double): "; return d+1.3; }<br><br>//...<br><br>};</div> </div> <br><br>q行q个修改之后Q输出结果将是:<br><br> <div id="33vbfvb" class=quote> <div id="jbvfd99" class=quote-title><u></u></div> <div id="3l33zpv" class=quote-content>f(int):3<br><br>f(double):3.6</div> </div> <br><br>q样Q在B的f()和D的f()之间Q重载确实实CQƈ且选择了一个最合适的f()q行调用?br><br><br><br>我能够在构造函C调用一个虚拟函数吗Q?br><br><br><br>可以Q但是要心。它可能不象你期望的那样工作。在构造函CQ虚拟调用机制不起作用,因ؓl承cȝ重蝲q没有发生。对象先从基c被创徏Q?#8220;基类先于l承c?base beforederived)”?br><br><br><br>看看q个Q?br><br> <div id="rbv333l" class=quote> <div id="jztvbhf" class=quote-title><u></u></div> <div id="zt39h93" class=quote-content>#include<string><br><br>#include<iostream><br><br>usingnamespace std;<br><br><br><br>classB {<br><br>public:<br><br>B(conststring& ss) { cout << "B constructor\n"; f(ss); }<br><br>virtualvoid f(const string&) { cout << "B::f\n";}<br><br>};<br><br><br><br>classD : public B {<br><br>public:<br><br>D(conststring & ss) :B(ss) { cout << "D constructor\n";}<br><br>voidf(const string& ss) { cout << "D::f\n"; s = ss; }<br><br>private:<br><br>strings;<br><br>};<br><br><br><br>intmain()<br><br>{<br><br>Dd("Hello");<br><br>}</div> </div> <br><br>E序~译以后会输出:<br><br> <div id="v3pznnb" class=quote> <div id="9djlpvb" class=quote-title><u></u></div> <div id="h9lf399" class=quote-content>Bconstructor<br><br>B::f<br><br>Dconstructor</div> </div> <br><br>注意不是D::f。设想一下,如果Z不同的规则,B::B()可以调用D::f()的话Q会产生什么样的后果:因ؓ构造函数D::D()q没有运行,D::f()会试图一个还没有初始化的字符串s赋予它的参数。结果很可能是导致立卛_溃?br><br><br><br>析构函数?#8220;l承cd于基c?#8221;的机制下q行Q因此虚拟机制的行ؓ和构造函CP只有本地定义(local definitions)被用——不会调用虚拟函敎ͼ以免触及对象中的Q现在已l被销毁的Q承类的部分?br><br><br><br>更多的细节,参见《C++语言的设计和演变?3.2.4.2和《C++E序设计语言?5.4.3?br><br><br><br>有h暗示Q这只是一条实现时的h为制造的规则。不是这L。事实上Q要实现q种不安全的Ҏ倒是非常Ҏ的:在构造函C直接调用虚拟函数Q就象调用其它函C栗但是,q样意味着QQ何虚拟函数都无法~写了,因ؓ它们需要依靠基cȝ固定的创?invariantsestablished by base classes)。这会D一片؜乱?br><br><br><br>有没?#8220;指定位置删除”(placement delete)Q?br><br><br><br>没有Q不q如果你需要的话,可以自己写一个?br><br><br><br>看看q个指定位置创徏(placement new)Q它对象放q了一pdArena中;<br><br> <div id="9h9xjr9" class=quote> <div id="dx9bntp" class=quote-title><u></u></div> <div id="tp3dnl3" class=quote-content>class Arena {<br><br>public:<br><br>void* allocate(size_t);<br><br>void deallocate(void*);<br><br>// ...<br><br>};<br><br><br><br>void* operator new(size_t sz, Arena& a)<br><br>{<br><br>return a.allocate(sz);<br><br>}<br><br><br><br>Arena a1(some arguments);<br><br>Arena a2(some arguments);<br><br></div> </div> q样实现了之后,我们可以这么写Q?br><br><br><br> <div id="b39tdb9" class=quote> <div id="xvh9d9b" class=quote-title><u></u></div> <div id="vhjv9x9" class=quote-content>X* p1 = new(a1) X;<br><br>Y* p2 = new(a1) Y;<br><br>Z* p3 = new(a2) Z;<br><br>// ...<br><br></div> </div> 但是Q以后怎样正确地销毁这些对象呢Q没有对应于q种“placement new”的内建的“placement delete”Q原因是Q没有一U通用的方法可以保证它被正地使用。在C++的类型系l中Q没有什么东西可以让我们认Qp1一定指向一个由Arenacd的a1分派的对象。p1可能指向M东西分派的Q何一块地斏V?br><br><br><br>然而,有时候程序员是知道的Q所以这是一U方法:<br><br><br><br> <div id="rhbdjp9" class=quote> <div id="31j3tv9" class=quote-title><u></u></div> <div id="3jxzbht" class=quote-content>template<class T> void destroy(T* p, Arena& a)<br><br>{<br><br>if (p) {<br><br>p->~T(); // explicit destructor call<br><br>a.deallocate(p);<br><br>}<br><br>}</div> </div> <br><br>现在我们可以q么写:<br><br> <div id="jn9zlb3" class=quote> <div id="zz9tddf" class=quote-title><u></u></div> <div id="p9pb9j9" class=quote-content>destroy(p1,a1);<br><br>destroy(p2,a2);<br><br>destroy(p3,a3);</div> </div> <br><br>如果Arenal护了它保存着的对象的U烦Q你甚至可以自己写一个析构函敎ͼ以避免它发生错误?br><br><br><br>q也是可能的Q定义一对相互匹配的操作Wnew()和delete()Q以l护《C++E序设计语言?5.6中的cȝ承体pR参见《C++语言的设计和演变?0.4和《C++E序设计语言?9.4.5?br><br><br><br>我能防止别hl承我自qcdQ?br><br><br><br>可以Q但你ؓ什么要那么做呢Q这是两个常见的回答Q?br><br><br><br>效率Q避免我的函数被虚拟调用<br><br>安全Q保证我的类不被用作一个基c(例如Q保证我能够复制对象而不用担心出事)<br><br><br><br>Ҏ我的l验Q效率原因往往是不必要的担心。在C++中,虚拟函数调用是如此之快,以致于它们在一个包含虚拟函数的cM被实际用时Q相比普通的函数调用Q根本不会生值得考虑的运行期开支。注意,仅仅通过指针或引用时Q才会用虚拟调用机制。当直接通过对象名字调用一个函数时Q虚拟函数调用的开支可以被很容易地优化掉?br><br><br><br>如果实有真正的需要,要将一个类闭h以防止虚拟调用,那么可能首先应该问问Z么它们是虚拟的。我看见q一些例子,那些性能表现不佳的函数被讄拟,没有其他原因Q仅仅是因ؓ“我们习惯q么q?#8221;?br><br><br><br>q个问题的另一个部分,׃逻辑上的原因如何防止c被l承Q有一个解x案。不q的是,q个Ҏq不完美。它建立在这样一个事实的基础之上Q那是Q大多数的承类必须建立一个虚拟的基类。这是一个例子:<br><br> <div id="v3lxppd" class=quote> <div id="z39rtr9" class=quote-title><u></u></div> <div id="zjdvhn9" class=quote-content>classUsable;<br><br><br><br>classUsable_lock {<br><br>friendclass Usable;<br><br>private:<br><br>Usable_lock(){}<br><br>Usable_lock(constUsable_lock&) {}<br><br>};<br><br><br><br>classUsable : public virtual Usable_lock {<br><br>//...<br><br>public:<br><br>Usable();<br><br>Usable(char*);<br><br>//...<br><br>};<br><br><br><br>Usablea;<br><br><br><br>classDD : public Usable { };<br><br><br><br>DDdd; // 错误: DD::DD() 不能讉K<br><br>// Usable_lock::Usable_lock()是一个私有成?br><br></div> </div> (来自《C++语言的设计和演变?1.4.3)<br><br><br><br>Z么不能ؓ模板参数定义U束QconstraintsQ?<br><br><br><br>可以的,而且Ҏ非常单和通用?br><br><br><br>看看q个Q?br><br><br><br> <div id="rb3vp9r" class=quote> <div id="hbt9bzz" class=quote-title><u></u></div> <div id="zjb9j99" class=quote-content>template<class Container><br><br>void draw_all(Container& c)<br><br>{<br><br>for_each(c.begin(),c.end(),mem_fun(&Shape::draw));<br><br>}</div> </div> <br><br>如果出现cd错误Q可能是发生在相当复杂的for_each()调用时。例如,如果容器的元素类型是intQ我们将得到一个和for_each()相关的含义模p的错误(因ؓ不能够对对一个intD用Shape::draw的方??br><br><br><br>Z提前捕捉q个错误Q我q样写:<br><br><br><br> <div id="t3nhl39" class=quote> <div id="rr3vhft" class=quote-title><u></u></div> <div id="rv93p9z" class=quote-content>template<class Container><br><br>void draw_all(Container& c)<br><br>{<br><br>Shape* p = c.front(); // accept only containers of Shape*s<br><br><br><br>for_each(c.begin(),c.end(),mem_fun(&Shape::draw));<br><br>}</div> </div> <br><br>对于现在的大多数~译器,中间变量p的初始化会触发一个易于了解的错误。这个窍门在很多语言中都是通用的,而且在所有的标准创徏中都必须q样做。在成品的代码中Q我也许可以q样写:<br><br><br><br> <div id="31z9dlt" class=quote> <div id="vv33hhf" class=quote-title><u></u></div> <div id="t3tvpnb" class=quote-content>template<classContainer><br><br>void draw_all(Container& c)<br><br>{<br><br>typedef typename Container::value_type T;<br><br>Can_copy<T,Shape*>(); // accept containers of only Shape*s<br><br><br><br>for_each(c.begin(),c.end(),mem_fun(&Shape::draw));<br><br>}</div> </div> <br><br>q样很清楚了,我在建立一个断a(assertion)。Can_copy模板可以q样定义Q?br><br> <div id="vh9t3v3" class=quote> <div id="fhn39zb" class=quote-title><u></u></div> <div id="hrdxh9l" class=quote-content>template<classT1, class T2> struct Can_copy {<br><br>staticvoid constraints(T1 a, T2 b) { T2 c = a; b = a; }<br><br>Can_copy(){ void(*p)(T1,T2) = constraints; }<br><br>};</div> </div> <br><br>Can_copy(在运行时)查T1是否可以被赋值给T2。Can_copy<T,Shape*>查T是否是Shape*cdQ或者是一个指向由Shapecdq承而来的类的对象的指针Q或者是被用戯{换到Shape*cd的某个类型。注意这个定义被_C最:<br><br><br><br>一行命名要查的U束Q和要检查的cd<br><br>一行列出指定的要检查的U束(constraints()函数)<br><br>一行提供触发检查的Ҏ(通过构造函?<br><br><br><br>注意q个定义有相当合理的性质Q?br><br><br><br>你可以表达一个约束,而不用声明或复制变量Q因此约束的~写者可以用不着去设惛_量如何被初始化,对象是否能够被复Ӟ被销毁,以及诸如此类的事情?当然Q约束要查这些属性的情况时例外?<br><br>使用现在的编译器Q不需要ؓU束产生代码<br><br>定义和用约束,不需要用宏<br><br>当约束失败时Q编译器会给出可接受的错误信息,包括“constraints”q个词(l用户一个线索)Q约束的名字Q以及导致约束失败的详细错误Q例?#8220;无法用double*初始化Shape*”Q?br><br><br><br>那么Q在C++语言中,有没有类gCan_copy——或者更好——的东西呢?在《C++语言的设计和演变》中Q对于在C++中实现这U通用U束的困难进行了分析。从那以来,出现了很多方法,来让U束cd得更加容易编写,同时仍然能触发良好的错误信息。例如,我信L在Can_copy中用的函数指针的方式,它源自Alex Stepanov和Jeremy Siek。我q不认ؓCan_copy()已经可以标准化了——它需要更多的使用。同P在C++C֌中,各种不同的约束方式被使用Q到底是哪一U约束模板在q泛的用中被证明是最有效的,q没有达成一致的意见?br><br><br><br>但是Q这U方式非常普遍,比语a提供的专门用于约束检查的机制更加普遍。无论如何,当我们编写一个模板时Q我们拥有了C++提供的最丰富的表辑֊量。看看这个:<br><br> <div id="999nnd9" class=quote> <div id="3bxjtr9" class=quote-title><u></u></div> <div id="hj399nh" class=quote-content>template<classT, class B> struct Derived_from {<br><br>staticvoid constraints(T* p) { B* pb = p; }<br><br>Derived_from(){ void(*p)(T*) = constraints; }<br><br>};</div> </div> <div id="9nxrl9l" class=quote> <div id="v3xz3fd" class=quote-title><u></u></div> <div id="zbv33lb" class=quote-content>template<classT1, class T2> struct Can_copy {<br><br>staticvoid constraints(T1 a, T2 b) { T2 c = a; b = a; }<br><br>Can_copy(){ void(*p)(T1,T2) = constraints; }<br><br>};</div> </div> <div id="z3rtnvr" class=quote> <div id="rdn39xn" class=quote-title><u></u></div> <div id="npzbdl3" class=quote-content>template<classT1, class T2 = T1> struct Can_compare {<br><br>staticvoid constraints(T1 a, T2 b) { a==b; a!=b; a<b; }<br><br>Can_compare(){ void(*p)(T1,T2) = constraints; }<br><br>};</div> </div> <div id="t9bfh39" class=quote> <div id="pzj9t99" class=quote-title><u></u></div> <div id="j3j39pv" class=quote-content>template<classT1, class T2, class T3 = T1> struct Can_multiply {<br><br>staticvoid constraints(T1 a, T2 b, T3 c) { c = a*b; }<br><br>Can_multiply(){ void(*p)(T1,T2,T3) = constraints; }<br><br>};</div> </div> <div id="rt9pbhx" class=quote> <div id="9f9p999" class=quote-title><u></u></div> <div id="vnpzlhp" class=quote-content>structB { };<br><br>structD : B { };<br><br>structDD : D { };<br><br>structX { };<br><br><br><br>intmain()<br><br>{<br><br>Derived_from<D,B>();<br><br>Derived_from<DD,B>();<br><br>Derived_from<X,B>();<br><br>Derived_from<int,B>();<br><br>Derived_from<X,int>();<br><br><br><br>Can_compare<int,float>();<br><br>Can_compare<X,B>();<br><br>Can_multiply<int,float>();<br><br>Can_multiply<int,float,double>();<br><br>Can_multiply<B,X>();<br><br><br><br>Can_copy<D*,B*>();<br><br>Can_copy<D,B*>();<br><br>Can_copy<int,B*>();<br><br>}</div> </div> <br><br>//典型?#8220;元素必须l承自Mybase*”U束:<br><br> <div id="33vhb99" class=quote> <div id="99lv9bj" class=quote-title><u></u></div> <div id="n3vphhd" class=quote-content>template<classT> class Container : Derived_from<T,Mybase> {<br><br>//...<br><br>};</div> </div> <br><br>事实上,Derived_fromq不查来源(derivationQ,而仅仅检查{换(conversionQ,不过q往往是一个更好的U束。ؓU束想一个好名字是很隄?br><br><br><br>既然已经有了优秀的qsort()函数Qؓ什么还需要一个sort()Q?br><br><br><br>对于初学者来_<br><br> <div id="xprtl3j" class=quote> <div id="jr9993r" class=quote-title><u></u></div> <div id="hzbtflb" class=quote-content>qsort(array,asize,sizeof(elem),elem_compare);</div> </div> <br><br>看上d古怪了Q而且比这个更隄解:<br><br> <div id="npjlfdz" class=quote> <div id="phjdplt" class=quote-title><u></u></div> <div id="dnpzjhf" class=quote-content>sort(vec.begin(),vec.end());</div> </div> <br><br>对于专家来说Q在元素与比较方式(comparisoncriteriaQ都相同的情况下Qsort()比qsort()更快Q这是很重要的。而且Qqsort()是通用的,所以它可以用于不同容器cd、元素类型、比较方式的L有意义的l合。D例来_<br><br> <div id="jlxzbpf" class=quote> <div id="xrtnnnl" class=quote-title><u></u></div> <div id="ddnxzhd" class=quote-content>structRecord {<br><br>stringname;<br><br>//...<br><br>};<br><br><br><br>structname_compare { // 使用"name"作ؓ键比较Record<br><br>booloperator()(const Record& a, const Record& b) const<br><br>{return a.name<b.name; }<br><br>};<br><br><br><br>voidf(vector<Record>& vs)<br><br>{<br><br>sort(vs.begin(),vs.end(), name_compare());<br><br>//...<br><br>} </div> </div> <br><br>而且Q很多hƣ赏sort()是因为它是类型安全的Q用它不需要进行造型QcastQ,没有人必d为基本类型写一个compare()函数?br><br><br><br>更多的细节,参见我的文章《将标准C++作ؓ一U新的语a来学习》(Learning C++ as a NewlanguageQ,可以从我的文章列表中扑ֈ?br><br><br><br>sort()胜过qsort()的主要原因是Q比较操作在内联QinlinesQ上做得更好?br><br><br><br>什么是函数对象Qfunction objectQ?<br><br><br><br>֐思义Q就是在某种方式上表现得象一个函数的对象。典型地Q它是指一个类的实例,q个cd义了应用操作Woperator()?br><br><br><br>函数对象是比函数更加通用的概念,因ؓ函数对象可以定义跨越多次调用的可持久的部分(cM静态局部变量)Q同时又能够从对象的外面q行初始化和查(和静态局部变量不同)。例如:<br><br> <div id="pbdnfdd" class=quote> <div id="fzjvxdb" class=quote-title><u></u></div> <div id="ddxzthx" class=quote-content>classSum {<br><br>intval;<br><br>public:<br><br>Sum(inti) :val(i) { }<br><br>operatorint() const { return val; } // 取得?br><br><br><br>intoperator()(int i) { return val+=i; } // 应用<br><br>};<br><br><br><br>voidf(vector v)<br><br>{<br><br>Sums = 0; // initial value 0<br><br>s= for_each(v.begin(), v.end(), s); // 求所有元素的?br><br>cout<< "the sum is " << s << "\n";<br><br><br><br>//或者甚臻I<br><br>cout<< "the sum is " << for_each(v.begin(), v.end(), Sum(0))<< "\n";<br><br>}</div> </div> <br><br>注意一个拥有应用操作符的函数对象可以被完美地内联化QinlineQ,因ؓ它没有涉及到M指针Q后者可能导致拒l优化。与之Ş成对比的是,现有的优化器几乎不能Q或者完全不能?Q将一个通过函数指针的调用内联化?br><br><br><br>在标准库中,函数对象被广泛地使用以获得弹性?br><br><br><br>我应该如何对付内存泄漏?<br><br><br><br>写出那些不会DM内存泄漏的代码。很明显Q当你的代码中到处充满了new 操作、delete操作和指针运的话,你将会在某个地方搞晕了头Q导致内存泄漏,指针引用错误Q以及诸如此cȝ问题。这和你如何心地对待内存分配工作其实完全没有关p:代码的复杂性最lL会超q你能够付出的时间和努力。于是随后生了一些成功的技巧,它们依赖于将内存分配QallocationsQ与重新分配QdeallocationQ工作隐藏在易于理的类型之后。标准容器(standardcontainersQ是一个优U的例子。它们不是通过你而是自己为元素管理内存,从而避免了产生p糕的结果。想象一下,没有string和vector的帮助,写出q个Q?br><br> <div id="htdxpnt" class=quote> <div id="bdnxtpn" class=quote-title><u></u></div> <div id="frttvlj" class=quote-content>#include<vector><br><br>#include<string><br><br>#include<iostream><br><br>#include<algorithm><br><br>usingnamespace std;<br><br><br><br>intmain() // small program messing around with strings<br><br>{<br><br>cout<< "enter some whitespace-separated words:\n";<br><br>vector<string>v;<br><br>strings;<br><br>while(cin>>s) v.push_back(s);<br><br><br><br>sort(v.begin(),v.end());<br><br><br><br>stringcat;<br><br>typedefvector<string>::const_iterator Iter;<br><br>for(Iter p = v.begin(); p!=v.end(); ++p) cat += *p+"+";<br><br>cout<< cat << '\n';<br><br>}</div> </div> <br><br>你有多少Z在第一ơ就得到正确的结果?你又怎么知道你没有导致内存泄漏呢Q?br><br><br><br>注意Q没有出现显式的内存理Q宏Q造型Q溢出检查,昑ּ的长度限Ӟ以及指针。通过使用函数对象和标准算法(standard algorithmQ,我可以避免用指针——例如用P代子QiteratorQ,不过对于一个这么小的程序来说有点小题大作了?br><br><br><br>q些技巧ƈ不完,要系l化C用它们也q不L那么Ҏ。但是,应用它们产生了惊人的差异Q而且通过减少昑ּ的内存分配与重新分配的次敎ͼ你甚臛_以余下的例子更加容易被跟踪。早?981q_我就指出Q通过我必须昑ּ地跟t的对象的数量从几万个减到几打Qؓ了ɽE序正确q行而付出的努力从可怕的苦工Q变成了应付一些可理的对象,甚至更加单了?br><br><br><br>如果你的E序q没有包含将昑ּ内存理减少到最限度的库,那么要让你程序完成和正确q行的话Q最快的途径也许是先徏立一个这L库?br><br><br><br>模板和标准库实现了容器、资源句柄以及诸如此cȝ东西Q更早的使用甚至在多q以前。异常的使用使之更加完善?br><br><br><br>如果你实在不能将内存分配/重新分配的操作隐藏到你需要的对象中时Q你可以使用资源句柄Qresource handleQ,以将内存泄漏的可能性降x低。这里有个例子:我需要通过一个函敎ͼ在空闲内存中建立一个对象ƈq回它。这时候可能忘记释放这个对象。毕竟,我们不能_仅仅x当这个指针要被释攄时候,谁将负责d。用资源句柄,q里用了标准库中的auto_ptrQ需要ؓ之负责的地方变得明确了?br><br> <div id="zrllpdb" class=quote> <div id="dnzbthf" class=quote-title><u></u></div> <div id="lnpzlrp" class=quote-content>#include<memory><br><br>#include<iostream><br><br>usingnamespace std;<br><br><br><br>structS {<br><br>S(){ cout << "make an S\n"; }<br><br>~S(){ cout << "destroy an S\n"; }<br><br>S(constS&) { cout << "copy initialize an S\n"; }<br><br>S&operator=(const S&) { cout << "copy assign an S\n"; }<br><br>};<br><br><br><br>S*f()<br><br>{<br><br>returnnew S; // 谁该负责释放q个SQ?br><br>};<br><br><br><br>auto_ptr<S>g()<br><br>{<br><br>returnauto_ptr<S>(new S); // 昑ּ传递负责释放这个S<br><br>}<br><br><br><br>intmain()<br><br>{<br><br>cout<< "start main\n";<br><br>S*p = f();<br><br>cout<< "after f() before g()\n";<br><br>// S*q = g(); // 被~译器捕?br><br>auto_ptr<S>q = g();<br><br>cout<< "exit main\n";<br><br>//*p产生了内存泄?br><br>//*q被自动释?br><br>}</div> </div> <br><br>在更一般的意义上考虑资源Q而不仅仅是内存?br><br><br><br>如果在你的环境中不能pȝ地应用这些技巧(例如Q你必须使用别的地方的代码,或者你的程序的另一部分直是原始人类Q译注:原文是NeanderthalsQ尼安d特hQ旧矛_时代q泛分布在欧z的猿hQ写的,如此{等Q,那么注意使用一个内存泄漏检器作ؓ开发过E的一部分Q或者插入一个垃圾收集器Qgarbage collectorQ?br><br><br><br>我ؓ什么在捕获一个异怹后就不能l箋Q?br><br><br><br>换句话说QC++Z么不提供一U简单的方式Q让E序能够回到异常抛出点之后,ql执行?<br><br><br><br>主要的原因是Q如果从异常处理之后l箋Q那么无法预知掷出点之后的代码如何对待异常处理,是否仅仅l箋执行Q就象什么也没有发生一栗异常处理者无法知道,在l之前,有关的上下文环境QcontextQ是否是“正确”的。要让这L代码正确执行Q抛出异常的~写者与捕获异常的编写者必d彼此的代码与上下文环境都非常熟悉才行。这样会产生非常复杂的依赖性,因此无论在什么情况下Q都会导致一pd严重的维护问题?br><br><br><br>当我设计C++的异常处理机制时Q我曄认真地考虑q允许这Ul的可能性,而且在标准化的过E中Q这个问题被非常详细地讨。请参见《C++语言的设计和演变》中的异常处理章节?br><br><br><br>在一ơ新ȝ的讨ZQ我曄以一U稍微不同的方式回答q这个问题?br><br><br><br>Z么C++中没有相当于realloc()的函敎ͼ<br><br><br><br>如果你需要,你当然可以用realloc()。但是,realloc()仅仅保证能工作于q样的数l之上:它们被malloc()Q或者类似的函数Q分配,包含一些没有用户定义的复制构造函敎ͼcopy constructorsQ的对象。而且Q要CQ与通常的期望相反,realloc()有时也必d制它的参数数l?br><br><br><br>在C++中,处理内存重新分配的更好的Ҏ是,使用标准库中的容器,例如vectorQƈ让它自我增长?br><br><br><br>如何使用异常Q?br><br><br><br>参见《C++E序设计语言》第4章,W?.3节,以及附录E。这个附录针对的是如何在要求苛刻的程序中写出异常安全的代码的技巧,而不是针对初学者的。一个关键的技术是“资源获得卛_始化”Qresourceacquisiton is initializationQ,它用一些有析构函数的类Q来实现强制的资源管理?br><br><br><br>怎样从输入中d一个字W串Q?br><br><br><br>你可以用q种方式d一个单独的以空格结束的词:<br><br><br><br> <div id="zldfxvl" class=quote> <div id="bvnxhff" class=quote-title><u></u></div> <div id="vxxrbbz" class=quote-content>#include<iostream><br><br>#include<string><br><br>usingnamespace std;<br><br><br><br>intmain()<br><br>{<br><br>cout<< "Please enter a word:\n";<br><br><br><br>strings;<br><br>cin>>s;<br><br><br><br>cout<< "You entered " << s << '\n';<br><br>}</div> </div> <br><br>注意Q这里没有显式的内存理Q也没有可能D溢出的固定大的~冲区?br><br><br><br>如果你确实想得到一行而不是一个单独的词,可以q样做:<br><br><br><br> <div id="hrbnpnl" class=quote> <div id="zztlpnl" class=quote-title><u></u></div> <div id="nprvxdl" class=quote-content>#include<iostream><br><br>#include<string><br><br>usingnamespace std;<br><br><br><br>intmain()<br><br>{<br><br>cout<< "Please enter a line:\n";<br><br><br><br>strings;<br><br>getline(cin,s);<br><br><br><br>cout<< "You entered " << s << '\n';<br><br>}</div> </div> <br><br>在《C++E序设计语言》(可在U获得)的第3章,可以扑ֈ一个对诸如字符串与这L标准库工L介。对于用C与C++q行单输入输出的详细比较Q参见我的文章《将标准C++作ؓ一U新的语a来学习?Learning Standard C++ as aNew Language)Q你可以在本作列?my publications list)中下载到它?br><br><br><br>Z么C++不提?#8220;finally”的构造?<br><br><br><br>因ؓC++提供了另外一U方法,它几乎L更好的:“资源获得卛_始化”Qresourceacquisiton is initializationQ技术。基本的思\是,通过一个局部对象来表现资源Q于是局部对象的析构函数会释放资源。这PE序员就不会忘记释放资源了。D例来_<br><br> <div id="jtnxjxf" class=quote> <div id="rjtvxtb" class=quote-title><u></u></div> <div id="pptlvtt" class=quote-content>classFile_handle {<br><br>FILE*p;<br><br>public:<br><br>File_handle(constchar* n, const char* a)<br><br>{p = fopen(n,a); if (p==0) throw Open_error(errno); }<br><br>File_handle(FILE*pp)<br><br>{p = pp; if (p==0) throw Open_error(errno); }<br><br><br><br>~File_handle(){ fclose(p); }<br><br><br><br>operatorFILE*() { return p; }<br><br><br><br>//...<br><br>};<br><br><br><br>voidf(const char* fn)<br><br>{<br><br>File_handlef(fn,"rw"); //打开fnq行d<br><br>//通过f使用文g<br><br>}</div> </div> <br><br>在一个系l中Q需要ؓ每一个资源都使用一?#8220;资源句柄”cR无论如何,我们不需要ؓ每一个资源获得都写出“finally”语句。在实时pȝ中,资源获得要远q多于资源的U类Q因此和使用“finally”构造相比,“资源获得卛_始化”技术会产生得多的代码?br><br><br><br>什么是自动指针Qauto_ptrQ,Z么没有自动数l(auto_arrayQ?<br><br><br><br>auto_ptr是一个非常简单的句柄cȝ例子Q在<memory>中定义,通过“资源获得卛_始化”技术支持异常安全。auto_ptr保存着一个指针,能够象指针一栯使用Qƈ在生存期l束旉放指向的对象。D例:<br><br> <div id="npbjljz" class=quote> <div id="tfpbvjr" class=quote-title><u></u></div> <div id="dvxpjhn" class=quote-content>#include<memory><br><br>usingnamespace std;<br><br><br><br>structX {<br><br>intm;<br><br>//..<br><br>};<br><br><br><br>voidf()<br><br>{<br><br>auto_ptr<X>p(new X);<br><br>X*q = new X;<br><br><br><br>p->m++; //象一个指针一样用p<br><br>q->m++;<br><br>//...<br><br><br><br>deleteq;<br><br>}</div> </div> <br><br>如果?..部分抛出了一个异常,p持有的对象将被auto_ptr的析构函数正地释放Q而q指向的X对象则生了内存泄漏。更多的l节Q参见《C++E序设计语言?4.4.2节?br><br><br><br>auto_ptr是一个非常简单的cR特别地Q它不是一个引用计敎ͼreference countedQ的指针。如果你一个auto_ptr赋值给另一个,那么被赋值的auto_ptr持有指针,而原来的auto_ptr持?。D例:<br><br><br><br> <div id="vfrrvrp" class=quote> <div id="rtvfrpf" class=quote-title><u></u></div> <div id="tnxrrpn" class=quote-content>#include<memory><br><br>#include<iostream><br><br>usingnamespace std;<br><br><br><br>structX {<br><br>intm;<br><br>//..<br><br>};<br><br><br><br>intmain()<br><br>{<br><br>auto_ptr<X>p(new X);<br><br>auto_ptr<X>q(p);<br><br>cout<< "p " << p.get() << " q " <<q.get() << "\n";<br><br>}</div> </div> <br><br>会打印Z个指?的指针和一个指向非0的指针。例如:<br><br> <div id="fxbtxtr" class=quote> <div id="hhldplb" class=quote-title><u></u></div> <div id="bbdfrxt" class=quote-content>p0x0 q 0x378d0</div> </div> <br><br>auto_ptr::get()q回那个辅助的指针?br><br><br><br>q种“转移”语义不同于通常?#8220;复制”语义Q这是o人惊讶的。特别地Q永q不要用auto_ptr作ؓ一个标准容器的成员。标准容器需要通常?#8220;复制”语义。例如:<br><br><br><br> <div id="zrlfrfd" class=quote> <div id="dxprdlh" class=quote-title><u></u></div> <div id="btfnzxv" class=quote-content>std::vector<auto_ptr<X>>v; // 错误</div> </div> <br><br>auto_ptr只持有指向一个单独元素的指针Q而不是指向一个数l的指针Q?br><br><br><br> <div id="rbvfhhn" class=quote> <div id="fprhljp" class=quote-title><u></u></div> <div id="rbddhnt" class=quote-content>voidf(int n)<br><br>{<br><br>auto_ptr<X>p(new X[n]); //错误<br><br>//...<br><br>}</div> </div> <br><br>q是错误的,因ؓ析构函数会调用delete而不是delete[]来释放指针,q样׃会调用余下的n-1个X的析构函数?br><br><br><br>那么我们需要一个auto_array来持有数l吗Q不。没有auto_array。原因是Ҏ没有q种需要。更好的解决Ҏ是用vectorQ?br><br><br><br> <div id="vhztxdt" class=quote> <div id="xzllnlj" class=quote-title><u></u></div> <div id="pzztflb" class=quote-content>voidf(int n)<br><br>{<br><br>vector<X>v(n);<br><br>//...<br><br>}</div> </div> <br><br>?..部分发生异常Ӟv的析构函C被正地调用?br><br><br><br>可以混合使用C风格与C++风格的内存分z与重新分配吗?<br><br><br><br>在这U意义上是可以的Q你可以在同一个程序中使用malloc()和new?br><br><br><br>在这U意义上是不行的Q你不能使用malloc()来徏立一个对象,又通过delete来释攑֮。你也不能用new建立一个新的对象,然后通过free()来释攑֮Q或者通过realloc()在数l中再徏立一个新的?br><br><br><br>C++中的new和delete操作可以保证正确的构造和析构Q构造函数和析构函数在需要它们的时候被调用。C风格的函数alloc(), calloc(), free(), 和realloc()却不能保证这一炏V此外,用new和delete来获得和释放的原始内存,q不一定能保证与malloc()和free()兼容。如果这U؜合的风格在你的系l中能够q用Q只能说是你走运——暂时的?br><br><br><br>如果你觉得需要用realloc()——或者要做更多——考虑使用标准库中的vector。例如:<br><br><br><br>//从输入中词dC个字W串vector?br><br> <div id="ddnprpv" class=quote> <div id="jbjlpnt" class=quote-title><u></u></div> <div id="bdxhjzn" class=quote-content>vector<string>words;<br><br>strings;<br><br>while(cin>>s && s!=".") words.push_back(s);</div> </div> <br><br>vector会视需要自动增ѝ?br><br><br><br>更多的例子与讨论Q参见我的文章《将标准C++作ؓ一U新的语a来学习?Learning Standard C++ as aNew Language)Q你可以在本作列?my publications list)中下载到它?br><br><br><br>我ؓ什么必M用一个造型来{?voidQ?br><br><br><br>在C语言中,你可以隐式地?void转换?T。这是不安全的。考虑一下:<br><br><br><br> <div id="nxrlnlz" class=quote> <div id="rbvpjzf" class=quote-title><u></u></div> <div id="lnnpbzp" class=quote-content>#include<stdio.h><br><br><br><br>intmain()<br><br>{<br><br>chari = 0;<br><br>charj = 0;<br><br>char*p = &i;<br><br>void*q = p;<br><br>int*pp = q; /* 不安全的Q在C中可以,C++不行 */<br><br><br><br>printf("%d%d\n",i,j);<br><br>*pp= -1; /* 覆盖了从i开始的内存 */<br><br>printf("%d%d\n",i,j);<br><br>}</div> </div> <br><br>使用一个ƈ不指向Tcd的T*是一场灾难。因此,在C++中,如果从一个void*得到一个T*Q你必须q行昑ּ转换。D例来_要得C列程序的q个令h别扭的效果,你可以这样写Q?br><br> <div id="jtvxxfd" class=quote> <div id="rblnhvb" class=quote-title><u></u></div> <div id="bdfrtrf" class=quote-content>int*pp = (int*)q;</div> </div> <br><br>或者用一个新的类型造型Q以使这U没有检查的cd转换操作变得更加清晰Q?br><br> <div id="vpprtrz" class=quote> <div id="fhblxfd" class=quote-title><u></u></div> <div id="bdxpbpn" class=quote-content>int*pp = static_cast<int*>(q);</div> </div> <br><br>造型被最好地避免了?br><br><br><br>在C语言中,q种不安全的转换最常见的应用之一Q是malloc()的结果赋予一个合适的指针。例如:<br><br> <div id="pzrldbr" class=quote> <div id="lfxjlhx" class=quote-title><u></u></div> <div id="xxhbvbz" class=quote-content>int*p = malloc(sizeof(int));</div> </div> <br><br>在C++中,使用cd安全的new操作W:<br><br><br><br> <div id="fhrdfdb" class=quote> <div id="btvhjxd" class=quote-title><u></u></div> <div id="fxbtnlj" class=quote-content>int*p = new int;</div> </div> <br><br>附带圎ͼnew操作W还提供了胜qmalloc()的新Ҏ:<br><br><br><br>new不会偶然分配错误的内存数量;<br><br>new会隐式地查内存耗尽情况Q而且<br><br>new提供了初始化?br><br><br><br>举例Q? <div id="htprbrf" class=quote> <div id="fhrtvtr" class=quote-title><u></u></div> <div id="bvnzbhn" class=quote-content><br><br>typedefstd::complex<double> cmplx;<br><br><br><br>/*C风格: */<br><br>cmplx*p = (cmplx*)malloc(sizeof(int)); /* 错误Q类型不正确 */<br><br>/*忘记试p==0 */<br><br>if(*p == 7) { /* ... */ } /*p糕Q忘C初始?p */<br><br><br><br>//C++风格:<br><br>cmplx*q = new cmplx(1,2); // 如果内存耗尽Q将抛出一个bad_alloc异常<br><br>if(*q == 7) { /* ... */ }</div> </div> <br><br>我如何定义一个类内部Qin-classQ的帔RQ?br><br><br><br>如果你需要一个通过帔R表达式来定义的常量,例如数组的范_你有两种选择Q? <div id="pbbdpnd" class=quote> <div id="tlxhjhp" class=quote-title><u></u></div> <div id="lnpblth" class=quote-content><br><br>classX {<br><br>staticconst int c1 = 7;<br><br>enum{ c2 = 19 };<br><br><br><br>charv1[c1];<br><br>charv2[c2];<br><br><br><br>//...<br><br>};<br><br></div> </div> 乍看hQc1的声明要更加清晰Q但是要注意的是Q用这U类内部的初始化语法的时候,帔R必须是被一个常量表辑ּ初始化的整型或枚丄型,而且必须是static和const形式。这是很严重的限Ӟ <div id="phjnfvj" class=quote> <div id="rttvxvv" class=quote-title><u></u></div> <div id="zlnppfd" class=quote-content><br><br>classY {<br><br>constint c3 = 7; // 错误Q不是static<br><br>staticint c4 = 7; // 错误Q不是const<br><br>staticconst float c5 = 7; // 错误Q不是整?br><br>};</div> </div> <br><br>我們֐使用枚D的方式,因ؓ它更加方便,而且不会׃我去使用不规范的cd初始化语法?br><br><br><br>那么Qؓ什么会存在q种不方便的限制呢?一般来_cd一个头文g中被声明Q而头文g被包含到许多互相调用的单元去。但是,Z避免复杂的编译器规则QC++要求每一个对象只有一个单独的定义。如果C++允许在类内部定义一个和对象一样占据内存的实体的话Q这U规则就被破坏了。对于C++在这个设计上的权衡,请参见《C++语言的设计和演变》?br><br><br><br>如果你不需要用帔R表达式来初始化它Q那么可以获得更大的Ҏ:<br><br> <div id="vfpbdrh" class=quote> <div id="dfzblbp" class=quote-title><u></u></div> <div id="rjvnhpv" class=quote-content>classZ {<br><br>staticchar* p; // 在定义中初始?br><br>constint i; // 在构造函C初始?br><br>public:<br><br>Z(intii) :i(ii) { }<br><br>};<br><br><br><br>char*Z::p = "hello, there";<br><br></div> </div> 你可以获取一个static成员的地址Q当且仅当它有一个类外部的定义的时候:<br><br> <div id="vnhtlbz" class=quote> <div id="fpttxnl" class=quote-title><u></u></div> <div id="vfjtflz" class=quote-content>classAE {<br><br>//...<br><br>public:<br><br>staticconst int c6 = 7;<br><br>staticconst int c7 = 31;<br><br>};<br><br><br><br>constint AE::c7; // 定义<br><br><br><br>intf()<br><br>{<br><br>constint* p1 = &AE::c6; // 错误Qc6没有左?br><br>constint* p2 = &AE::c7; // ok<br><br>//...<br><br>}</div> </div> <br><br>Z么delete不会操作数|?Q?br><br><br><br>考虑一下: <div id="zztnzxt" class=quote> <div id="tvxjdbh" class=quote-title><u></u></div> <div id="lxxrtzx" class=quote-content><br><br>deletep;<br><br>//...<br><br>deletep;<br><br></div> </div> 如果?..部分没有涉及到p的话Q那么第二个“delete p;”是一个严重的错误Q因为C++的实玎ͼ译注Q原文ؓa C++ implementationQ当指VC++q样的实CC++标准的具体工P不能有效地防止这一点(除非通过非正式的预防手段Q。既然delete 0从定义上来说是无害的Q那么一个简单的解决Ҏ是Q不在什么地Ҏ行了“delete p;”Q随后都执行“p=0;”。但是,C++q不能保证这一炏V?br><br><br><br>一个原因是Qdelete的操作数q不需要一个左|lvalueQ。考虑一下:<br><br> <div id="phrlnnb" class=quote> <div id="hjtlxnt" class=quote-title><u></u></div> <div id="fhjtdtj" class=quote-content>deletep+1;<br><br>deletef(x);<br><br></div> </div> 在这里,被执行的deleteq没有拥有一个可以被赋予0的指针。这些例子可能很见Q但它们的确指出了,Z么保?#8220;M指向被删除对象的指针都ؓ0”是不可能的。绕q这?#8220;规则”的一个简单的Ҏ是,有两个指针指向同一个对象:<br><br> <div id="hblnpvd" class=quote> <div id="dnzrlrp" class=quote-title><u></u></div> <div id="zrlnxdt" class=quote-content>T*p = new T;<br><br>T*q = p;<br><br>deletep;<br><br>deleteq; // p糕Q?br><br></div> </div> C++昑ּ地允许delete操作操作数左值置0Q而且我曾l希望C++的实现能够做到这一点,但这U思想看来q没有在C++的实C变得行?br><br><br><br>如果你认为指针置0很重要,考虑使用一个销毁的函数Q?br><br> <div id="nfhjndr" class=quote> <div id="rbbfxfl" class=quote-title><u></u></div> <div id="ttnxzpn" class=quote-content>template<classT> inline void destroy(T*& p) { delete p; p = 0; }</div> </div> <br><br>考虑一下,q也是ؓ什么需要依靠标准库的容器、句柄等{,来将对new和delete的显式调用降到最低限度的另一个原因?br><br><br><br>注意Q通过引用来传递指针(以允许指针被|?Q有一个额外的好处Q能防止destroy()在右gQrvalueQ被调用Q?br><br> <div id="rbvnhxn" class=quote> <div id="tvfhzhn" class=quote-title><u></u></div> <div id="rldhzxn" class=quote-content>int*f();<br><br>int*p;<br><br>//...<br><br>destroy(f()); //错误Q应该用一个非帔RQnon-constQ的引用传递右?br><br>destroy(p+1); //错误Q应该用一个非帔RQnon-constQ的引用传递右?br><br></div> </div> 我能够写“void main()”吗?<br><br><br><br>q种定义Q?br><br><br><br> <div id="tvprbrx" class=quote> <div id="htlxzfd" class=quote-title><u></u></div> <div id="vnhzlzz" class=quote-content>voidmain() { /* ... */ }</div> </div> <br><br>在C++中从未被允许Q在C语言中也是一栗参见ISO C++标准3.6.1[2]或者ISO C标准5.1.2.2.1。规范的实现接受q种方式Q?br><br><br><br> <div id="jpjvfft" class=quote> <div id="xpttxdr" class=quote-title><u></u></div> <div id="vffjthx" class=quote-content>intmain() { /* ... */ }</div> </div> <br><br>?br><br><br><br> <div id="hjllflb" class=quote> <div id="bdxxjhn" class=quote-title><u></u></div> <div id="xxbtnvj" class=quote-content>intmain(int argc, char* argv[]) { /* ... */ }</div> </div> <br><br>一个规范的实现可能提供许多版本的main()Q但它们都必返回intcd。main()q回的int|是程序返回一个值给调用它的pȝ的方式。在那些不具备这U方式的pȝ中,q回D忽略了,但这q不?#8220;void main()”在C++或C中成为合法的。即使你的编译器接受?#8220;void main()”Q也要避免用它Q否则你冒着被C和C++E序员视为无知的风险?br><br><br><br>在C++中,main()q不需要包含显式的return语句。在q种情况下,q回值是0Q表C执行成功。例如:<br><br> <div id="bdfzrpf" class=quote> <div id="dprrljz" class=quote-title><u></u></div> <div id="bvzrtrp" class=quote-content>#include<iostream><br><br><br><br>intmain()<br><br>{<br><br>std::cout<< "This program returns the integer value 0\n";<br><br>}<br><br></div> </div> 注意Q无论是ISO C++q是C99Q都不允许在声明中漏掉类型。那是_与C89和ARM C++形成对照Q当声明中缺类型时Qƈ不会保证?#8220;int”。于是: <div id="xrlfpnb" class=quote> <div id="tvxprpx" class=quote-title><u></u></div> <div id="vfhjtrh" class=quote-content><br><br>#include<iostream><br><br><br><br>main(){ /* ... */ }</div> </div> <br><br>是错误的Q因为缺main()的返回类型?br><br><br><br>Z么我不能重蝲点符P::QsizeofQ等{?<br><br><br><br>大多数的q算W能够被E序员重载。例外的是:<br><br> <div id="hzzlvbz" class=quote> <div id="ztvprxl" class=quote-title><u></u></div> <div id="dxrlllz" class=quote-content>.(点符? :: ?: sizeof</div> </div> <br><br>q没有什么根本的原因要禁止重?:。仅仅是因ؓQ我没有发现有哪U特D的情况需要重载一个三元运符。注意一个重载了 表达?Q表辑ּ2Q表辑ּ3 的函敎ͼ不能够保证表辑ּ2Q表辑ּ3中只有一个会被执行?br><br><br><br>Sizeof不能够被重蝲是因为内建的操作Qbuilt-in operationsQ,诸如对一个指向数l的指针q行增量操作Q必M靠它。考虑一下: <div id="vnzjdjr" class=quote> <div id="nxrlnnj" class=quote-title><u></u></div> <div id="lnhjbtz" class=quote-content><br><br>Xa[10];<br><br>X*p = &a[3];<br><br>X*q = &a[3];<br><br>p++; //p指向a[4]<br><br>//那么p的整型值必Lq的整型值大Z个sizeof(X)</div> </div> <br><br>所以,sizeof(X)不能q序员来赋予一个不同的新意义,以免q反基本的语法?br><br><br><br>在N::m中,无论Nq是m都不是值的表达式;N和m是编译器知道的名字,::执行一个(~译期的Q范围解析,而不是表辑ּ求倹{你可以惌一下,允许重蝲x::y的话Qx可能是一个对象而不是一个名字空_namespaceQ或者一个类Q这样就会导致——与原来的表现相反——生新的语法(允许 表达?::表达?Q。很明显Q这U复杂性不会带来Q何好处?br><br><br><br>理论上来_.Q点q算W)可以通过使用?>一L技术来q行重蝲。但是,q样做会D一个问题,那就是无法确定操作的是重载了.的对象呢Q还是通过.引用的一个对象。例如:<br><br><br><br> <div id="nphjndr" class=quote> <div id="rbddhfv" class=quote-title><u></u></div> <div id="fptdnlj" class=quote-content>classY {<br><br>public:<br><br>voidf();<br><br>//...<br><br>};<br><br><br><br>classX { // 假设你能重蝲.<br><br>Y*p;<br><br>Y&operator.() { return *p; }<br><br>voidf();<br><br>//...<br><br>};<br><br><br><br>voidg(X& x)<br><br>{<br><br>x.f(); //X::fq是Y::fq是错误Q?br><br>}</div> </div> <br><br>q个问题能够用几U不同的Ҏ解决。在标准化的时候,哪种Ҏ最好还没有定论。更多的l节Q请参见《C++语言的设计和演变》?br><br><br><br>怎样一个整型D{换ؓ一个字W串Q?br><br><br><br>最单的Ҏ是用一个字W串(stringstreamQ:<br><br> <div id="pbnnrxf" class=quote> <div id="jlvzjzp" class=quote-title><u></u></div> <div id="blfxjpl" class=quote-content>#include<iostream><br><br>#include<string><br><br>#include<sstream><br><br>usingnamespace std;<br><br><br><br>stringitos(int i) // int转换成string<br><br>{<br><br>stringstreams;<br><br>s<< i;<br><br>returns.str();<br><br>}<br><br><br><br>intmain()<br><br>{<br><br>inti = 127;<br><br>stringss = itos(i);<br><br>constchar* p = ss.c_str();<br><br><br><br>cout<< ss << " " << p << "\n";<br><br>}<br><br></div> </div> 自然圎ͼq种技术能够将M使用<<输出的类型{换ؓ字符丌Ӏ对于字W串的更多说明Q参见《C++E序设计语言?1.5.3节?br><br><br><br>“int* p”正确q是“int *p”正确Q?br><br><br><br>二者都是正的Q因Z者在C和C++中都是有效的Q而且意义完全一栗就语言的定义与相关的编译器来说Q我们还可以?#8220;int*p”或?#8220;int * p”?br><br><br><br>?#8220;int* p”?#8220;int *p”之间的选择与正或错误无关Q而只关乎风格与侧重点。C侧重表达式;对声明往往比可能带来的问题考虑得更多。另一斚wQC++则非帔R视类型?br><br><br><br>一?#8220;典型的CE序?#8221;写成“int *p”Qƈ且解释说“*p表示一个什么样的int”以强调语法,而且可能指出CQ与C++Q的语法来证明这U风格的正确性。是的,在语法上*被绑定到名字p上?br><br><br><br>一?#8220;典型的C++E序?#8221;写成“int* p”Qƈ且解释说“p是一个指向int的指针类?#8221;以强调类型。是的,p是一个指向int的指针类型。我明确地們֐于这U侧重方向,而且认ؓ对于学好更多的高UC++q是很重要的?br><br><br><br>严重的؜乱(仅仅Q发生在当h们试囑֜一条声明中声明几个指针的时候:<br><br> <div id="tvfzlrp" class=quote> <div id="jjlffvl" class=quote-title><u></u></div> <div id="jrdnpnd" class=quote-content>int*p, p1; // 也许是错的:p1不是一个int*</div> </div> <br><br>?攑ֈ名字q一边,看来也不能有效地减少q种错误Q?br><br><br><br> <div id="xzrtxlb" class=quote> <div id="pbjdfbz" class=quote-title><u></u></div> <div id="fprtnll" class=quote-content>int*p, p1; // 也许是错的?</div> </div> <br><br>为每一个名字写一条声明最大程度地解决了问题——特别是当我们初始化变量的时候。h们几乎不会这样写Q? <div id="fpzdvtr" class=quote> <div id="dxzjltr" class=quote-title><u></u></div> <div id="jbnppvv" class=quote-content><br><br>int*p = &i;<br><br>intp1 = p; // 错误Qint用一个int*初始化了</div> </div> <br><br>如果他们真的q么q了Q编译器也会指出?br><br><br><br>每当事情可以有两U方法完成,有h׃qh。每当事情仅仅是一个风格的问题Q争论就会没完没了。ؓ每一个指针写一条声明,而且永远都要初始化变量,q样Q؜׃源就消失了。更多的关于C的声明语法的讨论Q参见《C++语言的设计和演变》?br><br><br><br>对于我的代码Q哪一U布局风格Qlayout styleQ是最好的Q?br><br><br><br>q种风格问题属于个h的爱好。h们往往对布局风格的问题持有强烈的意见Q不q,也许一贯性比某种特定的风格更加重要。象大多Ch一P我花了很长的旉Q来为我的偏好作Z个固定的l论?br><br><br><br>我个Z用通常UCؓ“K&R”的风根{当使用C语言没有的构造函数时Q需要增加新的习惯,q样变成了一U有时被UCؓ“Stroustrup”的风根{例如: <div id="fhjvvvj" class=quote> <div id="nnhrtbz" class=quote-title><u></u></div> <div id="lnpbtjz" class=quote-content><br><br>classC : public B {<br><br>public:<br><br>//...<br><br>};<br><br><br><br>voidf(int* p, int max)<br><br>{<br><br>if(p) {<br><br>//...<br><br>}<br><br><br><br>for(int i = 0; i<max; ++i) {<br><br>//...<br><br>}<br><br>}</div> </div> <br><br>比大多数布局风格更好Q这U风g留了垂直的空|我喜Ƣ尽可能地在合理的情况下寚w屏幕。对函数开头的大括弧的攄Q有助于我第一眼就分别出类的定义和函数的定义?br><br><br><br>~进是非帔R要的?br><br><br><br>设计问题Q诸如作Z要接口的抽象基类的用,使用模板以表现有Ҏ的cd安全的抽象,以及正确C用异总表现错误Q比布局风格的选择要重要得多?br><br><br><br>我应该将“const”攑֜cd之前q是之后Q?br><br><br><br>我把它放在前面,但那仅仅是个人爱好问题?#8220;const T”?#8220;T const”L都被允许的,而且是等效的。例如:<br><br> <div id="hzjlxvr" class=quote> <div id="llvhjhf" class=quote-title><u></u></div> <div id="zzbbvdz" class=quote-content>constint a = 1; // ok<br><br>intconst b = 2; // also ok<br><br></div> </div> 我猜想第一U版本可能会让少敎ͼ更加固守语法规范Q的E序员感到迷惑?br><br><br><br>Z么?当我发明“const”Q最初的名称叫做“readonly”Qƈ且有一个对应的“writeonly”Q的时候,我就允许它出现在cd之前或之后,因ؓq样做不会带来Q何不明确。标准之前的C和C++规定了很的Q如果有的话Q特定的序规范?br><br><br><br>我不记得当时有过M有关序问题的深入思考或讨论。那Ӟ早期的一些用者——特别是我——仅仅喜Ƣ这U样子:<br><br><br><br> <div id="dpbvvtb" class=quote> <div id="nvxrdrx" class=quote-title><u></u></div> <div id="zjtfzxl" class=quote-content>constint c = 10;</div> </div> <br><br>看v来比q种更好Q?br><br> <div id="hztfhfl" class=quote> <div id="fzlfxnt" class=quote-title><u></u></div> <div id="bnnzjrn" class=quote-content>intconst c = 10;</div> </div> <br><br>也许我也受了q种影响Q在我最早的一些?#8220;readonly”的例子中<br><br> <div id="zrdvhxt" class=quote> <div id="zrlvzxv" class=quote-title><u></u></div> <div id="xxrbfdj" class=quote-content>readonlyint c = 10;</div> </div> <br><br>比这个更h可读性:<br><br> <div id="vnpbdth" class=quote> <div id="fpzdfvb" class=quote-title><u></u></div> <div id="dvhrljx" class=quote-content>intreadonly c = 10;</div> </div> <br><br>我创造的那些最早的使用“const”的(C或C++Q代码,看来已经在全球范围内取代?#8220;readonly”?br><br><br><br>我记得这个语法的选择在几个h——例如Dennis Ritchie——当中讨Q但我不记得当时我們֐于哪U语a了?br><br><br><br>注意在固定指针(const pointerQ中Q?#8220;const”永远出现?#8220;*”之后。例如:<br><br> <div id="lvhjtzz" class=quote> <div id="rztnxvl" class=quote-title><u></u></div> <div id="tlnpbrn" class=quote-content>int*const p1 = q; // 指向int变量的固定指?br><br>intconst* p2 = q; //指向int帔R的指?br><br>constint* p3 = q; //指向int帔R的指?br><br></div> </div> 使用宏有什么问题?<br><br><br><br>宏不遵@C++中关于范围和cd的规则。这l常D一些微妙的或不那么微妙的问题。因此,C++提供更适合其他的C++Q译注:原文为the rest of C++Q当指C++除了兼容C以外的部分)的替代品Q例如内联函数、模板与名字I间?br><br><br><br>考虑一下:<br><br> <div id="xpzjnbj" class=quote> <div id="dnfzbzx" class=quote-title><u></u></div> <div id="jjdnhft" class=quote-content>#include"someheader.h"<br><br><br><br>structS {<br><br>intalpha;<br><br>intbeta;<br><br>};</div> </div> <br><br>如果某hQ不明智圎ͼ地写了一个叫“alpha”?#8220;beta”的宏Q那么它不会被~译Q或者被错误地编译,产生不可预知的结果。例如,“someheader.h”可能包含Q?br><br> <div id="zjtnhvt" class=quote> <div id="hrvvxxd" class=quote-title><u></u></div> <div id="jlnhjxn" class=quote-content>#definealpha 'a'<br><br>#definebeta b[2]<br><br></div> </div> 宏Q而且仅仅是宏Q全部大写的习惯Q会有所帮助Q但是对于宏q没有语a层次上的保护机制。例如,虽然成员的名字包含在l构体的内部Q但q无于事:在编译器能够正确地L别这一点之前,宏已l将E序作ؓ一个字W流q行了处理。顺便说一句,q是C和C++E序开发环境和工具能够被简化的一个主要原因:Z~译器看到的是不同的东西?br><br><br><br>不幸的是Q你不能假设别的E序员L能够避免q种你认?#8220;相当白痴”的事情。例如,最q有人报告我Q他们遇C一个包含goto的宏。我也见q这U情况,而且听到q一些——在很脆q时候——看h实有理的意见。例如: <div id="npptvjj" class=quote> <div id="blfjjjx" class=quote-title><u></u></div> <div id="ffzldjr" class=quote-content><br><br>#defineprefix get_ready(); int ret__<br><br>#defineReturn(i) ret__=i; do_something(); goto exit<br><br>#definesuffix exit: cleanup(); return ret__<br><br><br><br>voidf()<br><br>{<br><br>prefix;<br><br>//...<br><br>Return(10);<br><br>//...<br><br>Return(x++);<br><br>//...<br><br>suffix;<br><br>}<br><br></div> </div> 作ؓ一个维护的E序员,׃产生q种印象Q将?#8220;隐藏”C个头文g中——这q不|见——得这U?#8220;法”更难以被辨别?br><br><br><br>一个常见的微妙问题是,一个函数风格的宏ƈ不遵守函数参C递的规则。例如: <div id="dlnprpn" class=quote> <div id="dvhhtrh" class=quote-title><u></u></div> <div id="ffxjlbp" class=quote-content><br><br>#definesquare(x) (x*x)<br><br><br><br>voidf(double d, int i)<br><br>{<br><br>square(d); //?br><br>square(i++); //p糕Q这表示 (i++*i++)<br><br>square(d+1); //p糕Q这表示(d+1*d+1); 也就?(d+d+1)<br><br>//...<br><br>}</div> </div> <br><br>“d+1”的问题,可以通过?#8220;调用”时或宏定义时d一对圆括号来解冻I<br><br> <div id="xprtfdz" class=quote> <div id="vvxjlhp" class=quote-title><u></u></div> <div id="xztvnnj" class=quote-content>#definesquare(x) ((x)*(x)) /*q样更好 */</div> </div> <br><br>但是Q?i++被执行了两次Q可能ƈ不是有意要这么做Q的问题仍然存在?br><br><br><br>是的Q我实知道有些Ҏ的宏q不会导致C/C++预处理宏q样的问题。但是,我无心去发展C++中的宏。作为替代,我推荐用C++语言中合适的工具Q例如内联函敎ͼ模板Q构造函敎ͼ用来初始化)Q析构函敎ͼ用来清除Q,异常Q用来退Z下文环境Q,{等? <img src ="http://www.shnenglu.com/CrazyDev/aggbug/115698.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/CrazyDev/" target="_blank">CrazyDev</a> 2010-05-18 17:12 <a href="http://www.shnenglu.com/CrazyDev/articles/115698.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++与Flash的交?/title><link>http://www.shnenglu.com/CrazyDev/articles/115651.html</link><dc:creator>CrazyDev</dc:creator><author>CrazyDev</author><pubDate>Tue, 18 May 2010 02:36:00 GMT</pubDate><guid>http://www.shnenglu.com/CrazyDev/articles/115651.html</guid><wfw:comment>http://www.shnenglu.com/CrazyDev/comments/115651.html</wfw:comment><comments>http://www.shnenglu.com/CrazyDev/articles/115651.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/CrazyDev/comments/commentRss/115651.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/CrazyDev/services/trackbacks/115651.html</trackback:ping><description><![CDATA[<p>研究Flash嵌入游戏中的可行?......</p> <p>渲染问题已解?<br>事g响应已解?<br>下面是C++与Flash AS的交? 以MFCZ: <br>1. 新徏一个MFC DialogE序</p> <p>2. d一个Flash控g</p> <p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddActiveX.JPG"></p> <p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddFlash.JPG"></p> <p>3. 把Flash控gd一个变?/p> <p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddVariable.JPG"><br><br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddflashUI.JPG"></p> <p>4. 在OnInitDialog()中添加蝲.swf文g</p> <p> <!--startfragment --> <img src="file:///C:/DOCUME~1/Yuan/LOCALS~1/Temp/%25QT13(ZV[L4%25%7B%7D_U0%7BJ3~TV.jpg"> </p> <p>5. 制作一个flash, 放一个Button上去, 导出一下就可以在MFC中看C</p> <p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_FlashButton.JPG"><br><br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_MFCDialog.JPG"> </p> <p>6. flash调用C++. </p> <p>q个很简? 在flash的那个Buttonlg的动作中d脚本:</p> <p><!--startfragment --> <img src="file:///C:/DOCUME~1/Yuan/LOCALS~1/Temp/E()UIB]N[%7DLL0A9$%25H205RW.jpg">  </p> <p>然后在MFC中添加事件响?</p> <p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddEvent.JPG" width=471 height=341><br><br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddEventMethod.JPG"></p> <p><!--startfragment --> <img src="file:///C:/DOCUME~1/Yuan/LOCALS~1/Temp/~AXX%7D%25BR2AB8CH8W$BF149L.jpg">  </p> <p>7. C++调用Flash. </p> <p>首先在Flash中注册回调函?</p> <p> <!--startfragment --> <img src="file:///C:/DOCUME~1/Yuan/LOCALS~1/Temp/PS~TFW1IBUFL7T5G_8AMC1N.jpg">  </p> <p>然后在C++中添加调?</p> <p><!--startfragment --> <img src="file:///C:/DOCUME~1/Yuan/LOCALS~1/Temp/(J2OJZNHWKEMJ_CRS]%60SI@4.jpg"> <br><br>另外, q可以通过GetVariable()和SetVariable()来设|flash中定义的变量</p> <p>8. 导出一下flash, ~译一下C++, 可以看到效果了:</p> <p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_CallCpp.JPG"><br><br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_CallFlash.JPG"></p> <p> </p> <p> </p> <p>本文来自CSDN博客Q{自:<a >http://blog.csdn.net/xoyojank/archive/2008/10/22/3122679.aspx</a></p> <img src ="http://www.shnenglu.com/CrazyDev/aggbug/115651.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/CrazyDev/" target="_blank">CrazyDev</a> 2010-05-18 10:36 <a href="http://www.shnenglu.com/CrazyDev/articles/115651.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>调试游戏E序的学问(转)http://www.shnenglu.com/CrazyDev/articles/113737.htmlCrazyDevCrazyDevTue, 27 Apr 2010 12:48:00 GMThttp://www.shnenglu.com/CrazyDev/articles/113737.htmlhttp://www.shnenglu.com/CrazyDev/comments/113737.htmlhttp://www.shnenglu.com/CrazyDev/articles/113737.html#Feedback0http://www.shnenglu.com/CrazyDev/comments/commentRss/113737.htmlhttp://www.shnenglu.com/CrazyDev/services/trackbacks/113737.htmlhttp://blog.csdn.net/fannyfish/

版权声明

  • 作者:Steve Rabin, Nintendo of America Inc.
  • 邮箱Qsteve@aiwisdom.com
  • 译者:沙鹰
  • 校对Q万太^

概述

调试游戏E序Q和调试M其它软g的代码一P都可能是一艰巨的d。一般说来,有经验的E序员能q速地识别q纠正哪怕是最隄bugQ但是对于新手而言Q改bug可能更像是一仉以处理的Qƈ且容易人灰心气的d。更p的是,当你初步着手开始寻找bug的根源时Q永q也不会知道I竟要花费多长时间才能找到。此时不必慌张,要像个训l有素的E序员,集中_֊Lbug。一旦你消化了本文介l的技巧和知识Q你能够击退最“凶猛”的bugQ重获对游戏的控制?/p>

q用本文描述的五步调试法Q困隄调试q程也可能变得简单一些。训l有素地q用该方法,确保你p最的旉在寻扑֒定位每一个bug上。在你着手对付一些有隑ֺ的bugӞ牢记一些专家技巧也很重要,因此本文也收集了一些有价值的、经q时间考验的技巧。然后本文还列出了一些有隑ֺ的调试情境,解释了当遇到一些特定的bug模式时应当做些什么。因为好的工具对于调试Q何游戏都很重要,本文q将讨论一些特定的工具Q你可将q些工具嵌入你的游戏中,从而帮助调试一些游戏编E所独有的调试情形。最后让我们回顾一些在前期预防bug的简单技术?/p>

五步调试?/h2>

老练的程序员们具有一U超能力Q能够迅速地、驾d熟地捕捉到即使是最不可思议的bug。他们L奇地、近乎直觉地知道错误源自何方Q这一点实在o人敬畏。他们之所以显得天才,除了因ؓ拥有丰富的经验外Q还因ؓ他们对于勘探和减需排查的可能的原因的方法训l有素、融会诏通。下面给出的五步调试法旨在重C们所熟练掌握的技能,助你在跟tbug的问题上形成一U有pȝ的、且注意力集中的风格?/p>

W一步:始终如一地重现问?/h3>

不论是什么bugQ重要的是,你应当了解如何能够始l如一地重现它?/p>

试图U正一个随机出现的bug怼使h感到挫|Q而且通常不过是浪Ҏ间。事实是Q几乎所有的bug都会在特定的情境下可靠地重现Q因此发现这个情景和规律成Z的、或贵公司测试部同仁的工作?/p>

让我们D一个假想的游戏bugZQ在试员报告里写道Q?#8220;有时候,游戏会在玩家杀L人时LQCrashQ?#8221;不幸圎ͼ像这Lbug报告太过于含p,而且׃q个问题看上M是百分之百会出现的,多数时候玩家仍可以正常地摧毁敌人。因此当游戏crashӞ必然q有一些其它相兛_素?/p>

对于不容易重现的bugQ理x形是创徏一pd“重现步骤QRepro StepsQ?#8221;Q说明每ơ应怎样才能重现bug。例如,下面的步骤极大地改善了之前的bug报告?/p>

重现步骤Q?/p>

  1. 开始单人游戏?
  2. 选择在第44号地图上q行Skirmish也就是多人练习模式的游戏?
  3. 扑ֈ敌h营地?
  4. 在一定的距离开外,使用投射cL器(Projectile WeaponQ攻d营地里的敌h?
  5. l果Q?0Q的时候游戏死机?

昄Q重现步骤是一U很好的ҎQ测试h员藉此帮助其他h重现bug。不q,_可能Dbug发生的事仉QChain of EventsQ的q程也是臛_重要的,其原因有三。第一Q对当时bugZ发生提供了有价值的U烦。第二:提供了一U比较系l地试bug是否已被dҎ的方法。第三:可用于回归测试,保bug不再卷土重来?/p>

管q里的信息没有告诉我们bug的直接诱因,它我们能够始终重现bug。一旦你定了bug发生的环境,你就可以q行下一步骤Q开始搜集有用的U烦?/p>

W二步:搜集U烦

现在你能够可靠地使bug重现Q下一步请你戴上侦探的鸭舌帽ƈ搜集U烦。每条蛛丝马qw是排除一个可能的原因q羃短疑点列表的Z。有了够的U烦Qbug的发源地会变得明显。因此ؓ了明了每条线索ƈ理解其潜台词Q付出的努力是值得的?/p>

不过有一点要注意Q你应当L在心里质疑每一条已发现的线索,是不是误导的Q或不正的。D例来_我们被告知某个bugd生在爆炸之后。尽这可能是一条非帔R要的U烦Q但它仍然可能是一个虚假的误导。时d备着攑ּ那些与收集来的信息冲H的U烦?/p>

q是以上面的bug报告ZQ我们了解到游戏的crash发生在玩家用投类武器d某个特定的敌地的时候。究竟关于投类武器和从q处dq两者,有什么特别之处?q是需要深思的重点Q但也不要耗费太多旉思考。亲临其境,观察错误I竟是如何发生的Q因为我们需要获取更多的凿的证据,而留q于表面的线索是获得实际证据最不有效的方式?/p>

在本例中Q当我们q入游戏Qƈ实际观察错误的发生时Q我们会发现游戏L发生在一?#8220;?#8221;对象里,错误的症状是一个无效指针。进一步的查显C,该指针本来是应当指向那个发射此箭的角色的。在此情况下Q这支箭原本要向其发者报告它M了某个敌人,使发者ؓ该次成功的攻击获得一定的l验倹{但管看上LC原因所在,我们对真实的潜原因仍然一无所知。我们必首先找出是什么扰׃q个指针?/p>

W三步:查明错误的源?/h3>

当你认ؓ攉到的U烦已经够多Ӟ到了专注于搜烦和查明错误的源头的时候了。有两个主要ҎQ第一个方法是先提出关于bug发生原因的假设,接着对该假设q行验证Q或证明它不正确Q;W二个方法是较ؓpȝ的分而治之的Ҏ?/p>

Ҏ1Q假设法

搜集了够的U烦Q你会开始怀疑有些什么事情导致了bug发生。这是你的假设QHypothesisQ。当你能够在心里清楚地陈q这假设Q你可以开始设计一些能验证该假设,或反证证明该假设不正的试用例?/p>

在我们的例子里,通过试得出了以下线索和关于游戏设计的信息:

  • 当一支箭出的时候,该箭被赋予一个指向射h的指针?
  • 当一支箭中某个敌h的时候,奖励送给箭人?
  • 游戏L发生在一支箭试图通过一个无效指针向箭Z回奖励?

我们的第一个假讑֏能是q样Q指针的值在的飞行途中被损坏。基于此U假设,我们开始设计测试,q搜集数据来支持或推L原因。例如我们可以让每一支箭都将箭人的指针注册到同一个备份区域。当我们又捕捉到crashӞ可以查备份下来的数据Q看无效指针的值是否与q支在被射出的时候所赋予的值相同? 

不幸的是在我们所丄例子里,最后发现这条假设是不正的。备份的指针和导致游戏死机的指针h相同的倹{这样一来,我们面临着一个抉择。是再提一个假讑ƈq行验证Q还是重头寻找更多的U烦Q现在让我们试着再提一条假设?/p>

如果的发射人指针从没有被破坏(新线索)Q或总射出到射中敌人的q段旉里,q个发射删除了。ؓ了检查这点,让我们记录下敌h营地里死亡的每个角色的指针。当crash发生Ӟ我们可以出错指针和Mq从内存中删除的敌h的列表进行比较。这栯行,很快p实原因正是如此。射hLQ箭q在飞行途中?/p>

Ҏ2Q分L

两个假设使我们找ZbugQ同时也表现了分而治之的概念。我们知道指针的值无效,但我们不知道它是因ؓD修改q而损坏,或者这个指针在更早些的时候就已经无效。通过试W一个假设,我们排除了两个可能性中的一个。像歇洛?#183;尔摩斯QSherlock HolmesQ曾说过的:“……当你排除了不可能的情况后Q其余的情况Q尽多么不可能Q却必定是真实的?#8221;[译注Q绿玉皇冠案Q柯?#183;道尔Q]

有h分而治之的Ҏ单Ş容ؓ定故障发生的时刻,q从输入开始回溯而发现错误。比如有一个ƈ不会造成L的bugQ在某个时刻发生的初始错误将影响层层传递,最l导致故障发生。确定初始错误通常通过在所有输入分支上讄Q有条g或无条g的)断点QBreakpointQ来q行Q直到找到那个不能正常输出——也是Dbug的输入?/p>

当从故障发生的时d始回溯,你在局部变量和栈里面的上函数中寻找Q何异常。对于死机bug来说Q通常你会试图L一个空|NULLQ或极大的数字倹{如果是关于点数的bugQ在栈上LNAN或极大的数字?/p>

无论是对问题q行有根据的推测Q检验假设,q是有系l地搜捕肇事代码Q最l你会找到问题所在。在q个q程中你要相信自己,q保持清醒。本文接下来的部分将详细讨论一些可用于在这步骤中的专门技术?/p>

W四步:U正问题

当我们发现bug的真正根源,接下来要做的便是提出和实C个解x案。无论如何,修改必须寚w目所处的阶段是恰当的。例如,在开发的后期Q通常不能只ؓ了纠正一个bugQ就修改底部的数据结构或E序体系l构。参照开发工作所处的阶段Q主E序员或pȝ架构师将军_应当q行何种cd的修攏V在关键的时刻,个别工程师(初或中U)常常做出不好的决定,因ؓ他们没有全盘考虑?/p>

此外需要特别注意的是,理想情况下,代码的编写者应当负责修改自׃码里的bug。不q如果必M改别人的代码Q你臛_应当在进行修改前和原作者进行讨论。讨论将使你了解一些方面,例如在以往对于cM的问题是怎么处理的,如果实施你的Ҏ提议可能会造成什么媄响等。MQ在未彻底理解由别h~写的代码的上下文前Q急于q行修改是非常危险的?/p>

l箋讨论我们的例子,L源于一个指向了一个不复存在的对象的无效指针。对此类问题模式的一个好的解x案是使用一层间接引用,使crash不再发生。通常Q正是因个理由,游戏使用对象的句柄而不是直接指针。这是一个合理的修改?/p>

但是Q如果游戏项目因为某个里E碑、或一个重要的演示版交付日q在眉睫Q而需要快速完成修改,你可能会們֐于对现有的特D情况实C个较为直接的修改ҎQ例如让箭者在自n被删除的时候其射出的中关于自n的指针失效)。如果在E序里打上了q一cȝ快速补丁(Quick HackQ,你要记得有关的注释文档化,以其在q截止期限后被重新评估。开发中q样的情况屡见不鲜:快速补丁被Z遗忘Q而在几个月后才造成了难于发现和解决的麻烦?/p>

虽然看上L们发Cbugq且定了一U修改(使用句柄而非指针Q,探烦其他可能造成同样问题出现的途径是很关键的。这虽然需要额外的旉Q但是ؓ了确保bug从根本上被消灭,而非只是消除了bug的一U表现Ş式,q努力是值得的。在我们的例子中Q可能其他类型的投射cL器同样会造成游戏LQ但其它非武器对象的关系、甚臌色之间的关系也会受到同一个设计缺L影响。应扑և所有这些相关的场合Q你的修改Ҏ针对的是问题的核心,而非仅仅是问题的某一U征兆?/p>

W五步:Ҏ作的修改q行试

解决Ҏ实施后,q必进行测试以认它的修补了错误。第一步要保先前有效的重现步骤不会导致bug重现。通常应当让bug修改者以外的其他人,例如试员,独立地确认bug被修复与否?/p>

W二步还要确保没有新的bug被引入游戏。你应当让游戏运行一D可观的旉Q确保所作的修改没有影响其它部分。这是非帔R要的Q因为很多时候,其是在目开发周期接q尾声的时候,Z改bug所作的改动Q会D其他pȝ出错。在目的后期,你还应当让主E序员或其他开发者来视每一个修改,q额外的可靠性检验要保证新的修改不会对版本有负面影响?/p>

高调试技?/h2>

如果你遵循以上所q的基本调试步骤Q你应能扑ֈq修复大多数bug。不q在你尝试提出假设、验?否决一个候选的原因、或者尝试找出出错位|的时候,或许你会愿意考虑下列的技巧?/p>

分析你的假设

调试E序的时候要保持心胸开阔是很重要的Q而且不要作太多假设。如果你假设某些貌似单的东西L正确的,你可能就q早地羃了搜烦范围Q从而完全错q了扑և真相的机会。D例来_不要L惛_然地认ؓ你正在用最新的软g或程序库。检验你的假设是否正常常是值得的?/p>

交互和q扰最化

有时Q多个系l之间会以某U方式交互,q会使调试复杂化。试试看关闭那些你认为和问题无关的子pȝQ例如,关闭声音子系l)Q从而将pȝ之间的交互降到最低限度。有时候这有助于识别问题,因ؓ原因可能在你关闭的pȝ中,q样你就知道接下来该看那里?/p>

随机性最化

通常Qbug之所以难于重玎ͼ要归咎于从速率和实际随机数{方面引入的可变性。如果你的游戏没有采取固定的帧速率Q试试看?#8220;在每帧内逝的旉”锁定为常量。至于随机数Q可以关闭随机数发生器,或给它固定的常数作ؓ随机发生U子Q这hơ运行都会得到同L序列。不q地是,玩家会给游戏带来无法控制的显著的随机性。如果连q玩家带来的随机性也必须得到控制Q请考虑玩家的输入记录下来Q从而能以可预料的方式将输入记录直接送入游戏QDawson01Q?/p>

复杂的计算拆分成几步进?/h3>

若某行代码含有大量计,或许这行拆分ؓ多个步骤会有助于识别问题。例如,可能其中的某段计算产生了类型{换错误,或某个函数ƈ未返回你期望它返回的|或运进行的序q不是你所想的那样。这也你能够检查每一步中间过E的计算?/p>

查边界条?/h3>

几乎我们中的每一个h都曾被经典的“差一错误”QOff-by-oneQ问题折过。要查算法的边界条gQ特别是在@环结构中?/p>

分解q行计算

如果你怀疑程序里的竞争条ӞRace ConditionQ不同的执行序会生不同的l果Q,试试看将代码改写Z行的Q然后检查bug是否消失。在U程中,增加额外的gq,观察是否问题也随之变化。问题范围能~小——若你能够确定问题是竞争条gQƈ通过试验问题孤立出来?/p>

充分利用调试器提供的工具

明白和懂得如何用条件断炏V内存watch、寄存器watch、栈Q以及汇~/混合调试。工兯帮你LU烦和确凿的证据Q这是识别bug的关键?/p>

查新q改动的代码

调试也可以通过源代码版本控制来q行Q这真是一个o人惊讶的Ҏ。如果你清楚地记得在某个日期前程序还是工作的Q但是从某天开始就q了,你就可以专注于期间改动过的代码,从而较快地扑ֈ引入~陷的代码段。至,也可以将搜烦范围~小x个特定子pȝQ或某几个文件?/p>

另一个利用版本控制的Ҏ是生成游戏在bug出现之前的一个版本。当你看不清问题的时候这其有用。将新老版本分别在调试器中q行Q将g相比较,你就可能扑և问题的关键所在?/p>

向其他h解释bug

常常在你向他释bug的时候,你会q忆起一些步骤,q意识到一些遗漏或忘记查的地方。与其他的程序员交流的益处还在于他们可能会精辟地提出别样的值得验的假设。不要低估和他h交谈的作用,也永q不要羞于寻求他人的。你团队中的同事是你的伙_也是你与最有难度的bug战斗时最_良的武器之一?/p>

和同事一赯?/h3>

q通常是很合算的,因ؓ每个人在对付bug上都有自q独门l验和策略。你也能学到新的技术,学会从从未尝试过的角度入手处理bug。让某h看着你进行调试,q可能是q捕bug最有效的方法之一?/p>

暂时放下问题

有的时候,你已l如此接q问题,以至于无法再清楚全面地看待它。试试看改变一下环境,出门闲逛一下。当你放松,再回到问题上Q你可能会有新的认识。有时候,当你军_让自׃息一下时Q你的心里下意识地还在思考问题,q后{案p然QC?/p>

L外部的帮?/h3>

获得帮助有多U很好的途径。如果是在开发视频游戏,那么每家游戏机制造商都有一整班的hQ他们将在你遇到ȝ的时候协助你。了解他们的联系方式。三大游戏机刉商现在都提供电话支持、电子邮件支持、和开发者互相帮助的新闻讨论l?/p>

困难的调试情景和模式

消灭bug常有模式可@。在艰苦的调试情景中Q模式是关键。在此经验v了很大作用。如果你曄见过某个模式Q你可能迅速地扑ևbug所在。希望下列情景和模式能给你一些方向?/p>

Bug仅在发布版里出现Q调试版则正?/h3>

通常QBug只出C发布版(Release BuildQ中意味着q是数据未初始化Q或与代码优化有关的bug。一般来_即你没有特地编写进行初始化的代码,调试版(Debug BuildQ也会自动将变量初始化ؓ零。而这隐式初始化在发布版中是不存在的,因而出Cbug?/p>

扑և原因的另一个策略是Q在调试版里Q慢慢地逐一打开优化开兟뀂对每一点优化都q行试Q你可以扑ֈ|魁R。例如,在调试版里,函数一般都不是内联的。但在优化后有些函数自动q行了内联,有时某个bugp样发作了?/p>

q有一点值得注意的是Q在发布版中也可以打开调试W号QDebug SymbolQ。这使得在一定程度上Q虽然一般ƈ不)对优化过的代码进行调试成为可能,你甚臛_以让一部分调试pȝ保持开启。D例来_你可以让你的异常处理函数在崩溃的现场执行一个全面的堆栈回溯Q这需要符P。这是非常有用的Q因为当试员必运行优化过的游戏版本的时候,你还是可以回溯程序崩溃?/p>

在作了一些无害的改动后,bug不见?/h3>

如果bug在一些完全无关的改动Q例如添加了一行无害的代码Q后不见了,那么q就像是一个时序问题,或内存覆盖问题。尽表面上bug已经消失了,但是实际上可能只是{Ud了代码的另一个部分。不要错q这个找出bug的机会。Bug在那儿Q将来迟早有一天它肯定会不知不觉地、狡猑֜害你?/p>

实h间歇性的问题

像前面提q的那样Q许多问题会在合适的环境下稳定地重现。但如果你无法控制环境,那就必须要趁问题抬v它丑陋的脑袋时抓住问题。这里的关键是在捕捉到问题的时候要C可能多的信息,以便随后可在必要时检查。机会可不是很多的,因此要充分利用每一ơ出错的Z。还有一个有效的技巧就是将E序出错时收集得到的数据和程序正常时攉的数据进行比较,发现其中差异?/p>

无法解释的行?/h3>

有时当你在单步执行代码的时候,却发现变量自说自话地被修改了。这U真正怪异的现象通常表示pȝ或调试器失去了同步。解x案是试试?#8220;加快清除~存的频?#8221;Qɾpȝ重获同步?/p>

感谢Scott Bilas为清除缓存归U_如下?#8220;四重”斚w?/p>

  • 重试QRetryQ:清除游戏的当前状态再q行?
  • 重徏QRebuildQ:删除已编译过的中间对象,q进行彻底的版本重徏?
  • 重启QRebootQ:通过复位,你机器里的内存擦除?
  • 重装QReinstallQ:通过重装Q恢复你的工具和操作pȝ中的文g和设|?

在这“四重”里,“重徏”是最重要的。有时候,~译器不能正地识别代码间的依赖关系Q导致受牵连的代码不能通过~译。症状常常是不可思议的怪异。一ơ彻底的重徏有时p解决问题?/p>

处理q些无法解释的行为的时候,一定要预先猜测调试器会l出何种l果。通过printf函数输出q检验变量的实际|因ؓ调试器有时候会被迷惑,而无法准地反映真实的倹{?/p>

~译器内部错?/h3>

偶尔你会到q种情况Q编译器承认它无法理解你的代码,从而抛Z个编译器内部错误QInternal Compiler ErrorQ。这些错误可能显C在代码中存在合法性问题,也可能根本是~译器Y件自w的问题Q例如,出了内存上限,或无法处理你如同天书一般的模板代码Q。遇到编译器内部错误的时候,执行如下步骤Q?/p>

  1. q行完整的版本重建?
  2. 重启电脑Q再q行一ơ完整的版本重徏?
  3. 查是否正在用最新版本的~译器?
  4. 查Q何正在用的库是否是最新版本?
  5. 试验同样的代码是否能在其他电脑上通过~译?

如果q些步骤不能解决问题Q试试确定究竟是那段代码引v了错误。如果可能的话,用分L减少~译到的代码Q直至编译器内部错误消失。当故障的位|已l确定后Q检视这D代码ƈ保证它看上去没错Q最好能多请几个它)。如果代码看上去的确合理Q下一步试着重新l织一下代码,希望~译器能报告出更有意义的错误信息。最后你q可以尝试用旧版本的~译器来~译。很可能在最新版的编译器里存在bugQ而用旧版本的编译器p利完成~译?/p>

如果q些办法都不奏效Q试试看在网上搜索相似的问题。如果还是没有用Q向~译器的刉商L额外的帮助?/p>

当你怀疑问题不是出在自q代码?/h3>

不象话,应该L怀疑自q代码Q不q,如果你确信不是你们的代码的问题,最好的行动斚w是到|站上寻找所使用的函数库或编译器的更新补丁。详l阅dreadme文gQ或者在|上搜烦关于此函数库或编译器的已知问题。很多时候,其他的h也碰C怼的问题,解决办法或补丁也已经有了?/p>

不过Q你发现的bug来自他h提供的函数库Q或来自有故障的gQ碰巧你是第一个发现它的hQ的几率不大。虽然不太可能,但有时还是会发生的。最快解x法是~写一段例程问题隔d来。然后你可以把这D늨序emaill函数库的作者,或硬件生产商Q以便他们进一步就此问题进行调查。如果这真是其他人造成的bugQ由于你的帮助,他h可以快速地识别和重现问题,从而bug以最快速度得到Ҏ?/p>

理解底层pȝ

有时Z扑ֈ一些难度很高的bugQ你必须了解底层pȝ。仅仅通晓C或C++q远q不够。ؓ了成Z个优U的程序员Q你必须懂得~译器是如何实现较高层次的概念,必须懂汇~语aQ还必须了解g的细节(其是对游戏机游戏开发而言Q。虽然认为高U语a掩盖了所有的复杂性ƈ没有错,但是事实是当pȝ崩溃时你会感觉手x措,除非你的理解深刻x象以下。若要进一步讨论高层抽象会如何造成隐患Q请参见“The Law of Leaky Abstractions”QSpolsky02Q?/p>

那么Q有哪些底层l节需要了解呢Q就游戏而言Q你应当了解如下事项Q?/p>

  • 了解~译器实C码的原理。熟悉ѝ虚函数调用、调用约定、异常是如何实现的。懂得编译器如何分配内存和处理内存对齐?
  • 了解你所使用的硬件的l节。例如,懂得与某个特定硬件的高速缓存有关的问题Q缓存中的数据何时会和主存储器中不同Q、内存对齐的限制、字节顺序(EndiannessQ高位还是低位字节在前)、栈的大、类型的大小Q如整型int、长整型long、布型boolQ?
  • 了解汇编语言的工作原理,能够阅读汇编代码。这在调试器无法跟踪源代码时Q例如在优化后的版本里查N题时Q很有帮助?

如不能牢牢掌握这些知识,在对付真正困隄bug的时候,你的致命q׃暴露出来。所以必ȝ解底层的pȝQ熟悉其规则?/p>

增加有助于调试的基础设施

没有合适的工具的帮助,在真IZ调试E序必定会很费劲。解军_法是走另一个极端,直接好的调试工h合到游戏里。下列工兯极大地帮助修理bug?/p>

允许在运行中修改游戏变量

调试和重现bugӞ在运行中修改游戏变量的值的功能是非常有用的。实现此功能的经典界面是通过游戏中的一个调试命令行接口QCLIQCommand-Line InterfaceQ用键盘修改变量。按下某个键后,调试信息覆盖昄在游戏屏q上Q提CZ用键盘进行输入。例如,当你x游戏里的天气Ҏ狂风暴雨Q你可以在提CZ输入“weather stormy”。此cȝ面在调节和检查变量的值或特定游戏状态的时候也很好用?/p>

可视化的AI诊断

在调试中Q好的工h无h之宝Q而标准调试器在诊断AI问题的时候L那么力不从心。各U调试器虽然在某个具体时刻能l出很好的深度,但在解答AIpȝ怎样随着游戏q行而变化这个问题上完全无用。解军_法是在游戏里直接构造能够监控Q意角色的诊断数据的可视化版本。通过文字和3DU条l合hQ一些重要的AIpȝ如寻路(PathfindingQ、警觉边界(Awareness BoundariesQ、当前目标等Q会较容易跟t和查错QTozour02QELaming03Q?/p>

日志的能?/h3>

通常Q我们在游戏里有成堆的角色彼此交互和通讯Q以得到非常复杂的行为。当交互p|Qbug出现之时Q关键在于能够记录导致bug的每个角色的个别状态及事g。通过Ҏ个角色创建单独的日志Q将带有时戳的关键事件记录下来,我们可能通过查日志来发现错误?/p>

记录和回攄能力

像前面提到的那样Q找出bug的关键在于可重现性。极致的可重现性需要通过记录和回攄家的输入来实玎EDawson01Q。对于那些概率很的LbugQ记录和回放是找出确切原因的关键工具。但是ؓ了支持记录和回放Q你必须让游戏的行ؓ是可预料的,也就是说对于同样的初始状态,同样的玩家输入必定会得到同样的输出结果。这q不意味着你的游戏对玩家来说是可以预知的,只是意味着你应当小心处理随机数的生ELecky-Thompson00QEFreeman-Hargis03Q、初始状态、输入等斚wQƈ能在E序崩溃时将输入序列保存下来QDawson99Q?/p>

跟踪存储分配事g

q样实现你的存储分配子Q其对每次分配操作都进行全面的栈跟t。通过不断地记录究竟是谁在甌内存Q你不再有内存泄漏问题需要解冟?/p>

崩溃时打印出可能多的信?/h3>

“事后调试QPost-mortem DebugQ?#8221;是很重要的。程序崩溃时Q理想的情况下,你会希望能够捕捉到调用堆栈、寄存器以及所有其它可能相关的状态信息。这些信息可以显C在屏幕上,写入某个文gQ或自动发送至开发者的电子信箱。这一cȝ工具让你q速找出崩溃的源头Q只消几分钟而不是几个小时。尤其是当故障发生在工或策划同仁的机器上,而他们ƈ不记得是怎样触发q次崩溃的时候?/p>

Ҏ个团队进行培?/h3>

虽然qƈ非一个能够编E实现的l构Q但是你应当定团队正确使用你创建的工具。请他们不要忽视错误对话框,信他们知道怎样搜集信息从而不会丢失已扑ֈ的bug{等。花旉来培训测试员、美工、策划是值得的?/p>

预防bug

关于调试的讨论,若没有一D|字指导如何在W一旉避免bugQ便不能完整。遵照这些指导方针,你或可避免编写出有bug的代码,或可在偶然之间发现自׃知不觉写出来的bug。不论是什么结果,都会最后帮你排除bug?/p>

编译器的警告别(Warning levelQ调到最高,q指C将警告当作错误处理QEnable warnings as errorsQ。首先尽可能多地排除警告Q最后才?pragma剩下的警告关闭掉。有Ӟ自动cd转换及其它一些警告的问题会带来潜在的bug?/p>

使你的游戏能在多个编译器上编译通过。如果你保游戏用多个编译器、面向多个^台都能编译通过Q不同的~译器之间在警告和错误方面的差异保证你的代码M上更可靠。例如,~写d堂GameCube™游戏Z的程序的Z可以在Win32下生成一个功能稍q版本。这也你能够判断某个bug是否是具体^台所Ҏ的?/p>

~写你自q内存理器。这对于游戏机游戏是臛_重要的。你必须清楚地知道正在用那几块内存Qƈ对内存上溢进行保护。由于内存溢Z带来一些最难查处的bugQ首先确保不发生溢出是很重要的。在调试版本中用预留的上溢和下溢保护内存块能bug更早地暴露n份。对PC开发者来_~写自己的内存管理器不是必须的,因ؓVC++里的内存pȝ功能已经很强了,而且q有像SmartHeap之类的好工具可以用来定内存错误?/p>

用assert来检验假设。在函数的开头加上assert来检验关于参数的假设Q例如指针非I或范围查)。另外,如果switch语句的default情况不应该被执行刎ͼ在其中加上assert。还有,标准assert可以被扩展以得到更好的调试性能QRabin00bQ。例如,让assert调用堆栈打印出来是很有用的?/p>

L在声明变量的时候初始化它们。如果你无法在声明某个变量时赋予它一个有意义的|那么q它赋一个将来一眼就能认出它有没有被初始化过的容易L认的倹{有时候我们会?xDEADBEEF?xCDCDCDCDQ或直接使用零?/p>

L@环体和if语句体用花括P{}Q括h。也是你所想的代码老老实实地包v来,使代码所实现的功能更直观?/p>

变量起名要容易区分彼此。例如,m_objectITime和m_objectJTime看上d乎一模一栗此c问题的典型例子是把“i”?#8220;j”用作循环计数变量?#8220;i”?#8220;j”看上d怼Q你很容易把其中一个误认ؓ另一个。可供选择的方法是Q你可以?#8220;i”?#8220;k”Q或者干脆用更能描q其意义的名字。更多有兛_量命名的认知差异的信息可以在QMcConnell93Q中扑ֈ?/p>

避免在多处重复同L代码。一模一L代码同时出现在几个不同的地方Q这是不利的。如果对其中一处代码作了改动,其余几个地方不一定也会被改动。如果看上去重复代码是必要的Q重新考虑一下其核心功能Q尽量将大多数的代码集中C处?/p>

避免使用那些中固定写ȝ“奇?#8221;QMagic numbersQ。当单独一个数字出现在代码中,其意义可能是完全不ؓ人知的。如果没有写注释Q就无法让h理解之所以选择q个数字的理由,及这个数字代表什么。如果必M用神奇数Q将它们声明为有字面意义的常量或define?/p>

试的时候要注意代码覆盖率。在~写完一D代码之后,应验证它的每一个分支都能正地执行。若其中一个分支从未被执行q,那么很可能其中正潜伏着bug。在试不同分支的过E中你可能会发现q样一个bugQ即其中某个分支是根本不可能被执行到的。这Lbug早发现p好?/p>

l论

本文向你介绍了有效率地调试游戏所需的工兗调试有时候被形容Z门艺术,但那只是׃Z有l验做得越好。当你把五步调试法融会诏通,又学会了识别bug模式Qƈ自q调试工具集成到游戏中Q再形成自己在调试上的个人风格和l招Q很快地Q你熟l地有系l地q捕到ƈ且消灭最困难的bug。最后再加上一炚wԌ我想你的游戏开发会一帆风,一个bug都没有也说不定?/p>

致谢

感谢Scott Bilas和Jack MatthewsQ他们提了极好的Qƈ为本文A献了一些个人经验和智慧。h们看待调试有各自的角度,因此他们的意见在推敲本文的时候v了非常大的作用?/p>

参考文?/h2>
  • [Dawson99] Dawson, Bruce, “Structured Exception Handling,” Game Developer Magazine (Jan 1999), pp. 52–54.
  • [Dawson01] Dawson, Bruce, “Game Input Recording and Playback,” Game Programming Gems 2, Charles River Media, 2001.
  • [Freeman-Hargis03] Freeman-Hargis, James, “The Statistics of Random Numbers,” AI Game Programming Wisdom 2, Charles River Media, 2003.
  • [Laming03] Laming, Brett, “The Art of Surviving a Simulation Title,” AI Game Programming Wisdom 2, Charles River Media, 2003.
  • [Lecky-Thompson00] Lecky-Thompson, Guy, “Predictable Random Numbers,” Game Programming Gems, Charles River Media, 2000.
  • [McConnell93] McConnell, Steve, Code Complete: A Practical Handbook of Software Construction, Microsoft Press, 1993.
  • [Rabin00a] Rabin, Steve, “Designing a General Robust AI Engine,” Game Programming Gems, Charles River Media, 2000.
  • [Rabin00b] Rabin, Steve, “Squeezing More Out of Assert,” Game Programming Gems, Charles River Media, 2000.
  • [Rabin02] Rabin, Steve, “Implementing a State Machine Language,” AI Game Programming Wisdom, Charles River Media, 2000.
  • [Spolsky02] Spolsky, Joel, “The Law of Leaky Abstractions,” Joel on Software, 2002, available online at www.joelonsoftware.com/articles/LeakyAbstractions.html.
  • [Tozour02] Tozour, Paul, “Building an AI Diagnostic Toolset,” AI Game Programming Wisdom, Charles River Media, 2002.



CrazyDev 2010-04-27 20:48 发表评论
]]>
99þþƷѿ| þҹɫƷ鶹| Ʒþøһ| ޾Ʒþþwww| þþþһƷ| þseƷһƷ| ˾Ʒһþ| þþþþݴۺϾƷ | þþþùһëƬ| 999Ʒþþþþ| ݺɫ˾þþƷۺ| þ| ھƷþۺ88| ۺϾƷ㽶þ| ƷþþƷ| ھƷ˾þþþվ| þþƷƷʢۿ| þۺ97ɫ| ٸ޾þþþþ| ߳߳þþ91 | þþþó˾Ʒ| 97þþþ | ƷþþþþĻһ| Ʒһþ| þþƷ޸| պƷþĻ| Ʒþþþþù| 㽶þþþþúݺɫ| ƷþþþþþþѼ| þۺ¶þü| þþƷAAƬһ| Ӱһþþþó˾Ʒۺ | þҹɫƷ鶹| Ʒþˬ| þþûɫƬ| һһþþƷۺ| ҹƷþþþþþ| 㽶99þùۺϾƷլ| ƷҹþøƬ| þþƷAV| ݲݾþþר|