改善程序设计技术的50个有效做法课件

上传人:公**** 文档编号:569526007 上传时间:2024-07-30 格式:PPT 页数:172 大小:426.52KB
返回 下载 相关 举报
改善程序设计技术的50个有效做法课件_第1页
第1页 / 共172页
改善程序设计技术的50个有效做法课件_第2页
第2页 / 共172页
改善程序设计技术的50个有效做法课件_第3页
第3页 / 共172页
改善程序设计技术的50个有效做法课件_第4页
第4页 / 共172页
改善程序设计技术的50个有效做法课件_第5页
第5页 / 共172页
点击查看更多>>
资源描述

《改善程序设计技术的50个有效做法课件》由会员分享,可在线阅读,更多相关《改善程序设计技术的50个有效做法课件(172页珍藏版)》请在金锄头文库上搜索。

1、改善程序设计技术的50个有效做法第二版2002.3ScottMeyers侯捷译如何完成较好的设计如何避免常见的问题如何提高效率的一些准则不是放之四海而皆准的唯一真理C+新标准新的类型bool 有两个值true,false.typedefintbool;constboolfalse=0;constbooltrue=1;新的转型动作static_cast(expression)/将表达式expression转为type类型const_cast(expression)/将常数类型expression转为非常数类型dynamic_cast(expression)/安全向下转型见39reinterpre

2、t_cast(expression)/函数指针类型转换不常用1.尽量以const和inline取代#define#define是一个宏,只能被预处理,而不被编译,用它定义的常量甚至不被编译器看见,因此不能发现使用中的错误。用#define定义一个简单函数,必须为每一个参数加上一个括号,容易造成错误。用内联函数高效准确。defineratio1.653/编译器看不见ratio,只看见1.653/一旦出错,不会报告constdoubleratio=1.653;constchar*constname=“ScottMeyers”;/字符串常量InClass常量用静态变量类内声明,类外定义。classE

3、ngineerConstantsprivate:staticconstdoubleFactor;constdoubleEngineerConstants:Factor=1.35;2.尽量以取代scanfprintf函数不能扩充用来输入输出自定义类型的变量。cinix;coutix;可以扩展,方便得多改变旧有的改变旧有的C习惯(习惯(shifting from C to C+)1.尽量以const和inline取代#define#define 是一个宏,只能被预处理,而不被编译,用它定义的常量甚至不被编译器看见,因此不能发现使用中的错误。用#define定义一个简单函数,必须为每一个参数加上一个

4、括号,容易造成错误。用内联函数高效准确。3.尽量以new和delete取代malloc和freemalloc和free不能调用构造函数,析构函数new和delete则可。不能混用newdeletemallocfree必要用C库函数时检查是否用到malloc重新用new和delete改过。4.尽量使用C+风格的注释形式/*/要保证成对出现,不小心错一大片。/好看好读可以混合使用当心!definelight_speed3e8/m/sec(inavacum)内存管理内存管理(memory management)new隐式调用构造函数,delete隐式调用析构函数,可以重载operatornew和op

5、eratordelete.不小心运用new和delete会导致各种错误。5.使用相同形式的new和deletestring*a=newstring10;deletea;/出错deletea;/正确string*b=newstring;deleteb;/出错deleteb;/正确typedefstringaddresslines4;string*a=newaddresslines;deletea;/出错deletea;/正确不要对数组类型用typedef,不容易记住用哪一种delete6.记得在析构函数中以delete对付指针成员如果类中有指针数据成员,在每个构造函数中为指针成员配置内存,否则将

6、它初始化为0(NULL指针)。若构造函数中用new配置了内存,一定要在析构函数中用delete释放在赋值运算重载时要将原有指针的内存删除,重新分配内存。要在析构函数中删除这个指针。不要用delete删除一个未完成初始化的指针,不要删除一个未分配内存的指针。不要delete从一个类外面传来的指针。7.为内存不足的状况预作准备不能认为“检查内存是否分配成功”是多此一举。否则会出现严重后果。必须建立一个错误处理策略。当operatornew无法满足需求时,在抛出异常之前,会调用一个内存不足处理函数newhandler,这个函数由头文件提供。typedefvoid(*new_handle)();new

7、_handlerset_new_handler(new_handlerp)throw();new_handler是一个函数指针,无参,无返回值。函数set_new_handler用来配置new_handler,参数和返回值都是函数指针,new_handler类型。它确定新的new_handler函数(参数),保留旧的new_handler函数(返回)。可以自定义新的new_handler函数,用set_new_handler确认。voidnomoreMemory()cerr“Unabletosatisfyformemeoryn”abort();/exitintmain()set_new_han

8、dler(nomoreMemory);int*pBigDataArray=newint100000000;当operatornew无法配置10000000个整数空间时,系统调用nomoreMemory,然后结束。设计new_handler函数,令其完成如下任务:-让更多的内存可用。预留一块内存,new_handler第一次被调用时释放,同时发出警告。安装新的new_handler以取代自己。new_handler中调用C+标准库函数set_new_handler即可。卸载这个new_handler,返回NULL指针,并抛出bad_alloc(或其继承)类型的异常。直接调用abort或exit终

9、止程序。C+不支持不支持class中中专用的专用的new_handler,但仍可以在一个但仍可以在一个class中重载中重载operator new, 和和set_new_handler函数,让它函数,让它调用特定的调用特定的new_handler函数,而不用系统给出的全局函数,而不用系统给出的全局new_handler。classXpublic:static new_handler set_new_handler(new_handler p);staticvoid*operatornew(size_tsiz);private:staticnew_handlercurrentHandler;n

10、ew_handlerX:currentHandler;/初始化为0new_handlerX:set_new_handler(new_handlerp)new_handleroldHandler=currentHandler;/保留当前new_handlercurrentHandler=p;/再设置当前new_handlerreturnoldHandler;void*X:operatornew(size_tsize)newHandlerglobalHandler=std:set_new_handler(currentHandler);/配置新new_handler保存globalHandlerv

11、oid*memory;trymemory=:opratornew(size);/试分配内存catch(std:bad_alloc&)std:set_new_handler(globalHandler);/恢复原有处理方法throw;/传播异常std:set_new_handler(globalHandler):/恢复原有处理方法returnmemory;/调用一次特定处理方法,用毕恢复/应用voidnomoreMemory();X:set_new_handler(nomoreMemory);X*px1=newX;/如果内存分配失败,调用nomoreMemory()string*ps=newst

12、ring;/如果内存分配失败,调用globalHandlerX:set_new_handler(0);X*px2=newX;/如果内存分配失败,立即抛出异常可以做一个混合风格基类允许“设定class专属new_handler”templateclassNewHandlerSupportpublic:static new_handler set_ new_handler(new_handler p);staticvoid*operatornew(size_tsiz);private:staticnew_handlercurrentHandler;templatenew_handlerNewHan

13、dlerSupport:set_new_handler(new_handlerp)new_handleroldHandler=currentHandler;/保留当前new_handlercurrentHandler=p;/再设置当前new_handlerreturnoldHandler;templatevoid * NewHandlerSupport:operator new(size_t size)newHandlerglobalHandler=std:set_new_handler(currentHandler);/配置新new_handler保存globalHandlervoid*me

14、mory;trymemory=:opratornew(size);/试分配内存catch(std:bad_alloc&)std:set_new_handler(globalHandler);/恢复原有处理方法throw;/传播异常std:set_new_handler(globalHandler):/恢复原有处理方法returnmemory;new_handlerNewHandlerSupport:currentHandler;/初始化为0classX:publicNewHandlerSupport/不必声明set_new_handler和operatornew类X不必改动原有的程序代码,就可

15、以继续运作。1993年前C+要求operatornew在无法满足内存需求时返回0,新标准则是抛出一个bad_alloc类型异常。失败便转为0的传统被保留为“nothrow”不抛出异常。头文件中定义了一个nothrow对象classWidget;Widget*pw1=newWidget;/如果失败抛出std:bad_alloc异常if(pw1=0)/无效widget*wp2=new(nothrow)Widget;/如果失败,返回0if(wp2=0)/有效8.撰写operatornew和operatordelete时应遵守的公约当你有必要重载operatornew时,你的new函数的行为应该与系统

16、原有的new函数的行为保持一致。应该有正确的返回值:返回一个指针指向分配的内存。如果内存不足,抛出一个bad_alloc类型的异常。不能覆盖系统原有的new函数。/new函数的伪码void*operatornew(size_tsize)if(size=0)size=1;/将0内存需求,看成1内存需求,避免与无内存混淆while(true)/无穷循环直到内存被分配或抛出异常attempttoallocatesizebytes;if(theallocationwassuccessful)return(apointertothememory);new_handleglobalHandle=set_n

17、ew_handler(0)/利用NULL,找出目前的错误处理函数set_new_handler(globalHandler);/重新设定为原本的函数if(globalHandler)(*globalHandler)()elsethrowstd:bad_alloc();无穷循环可以让更多的内存可用,或安装一个不同的new_handler,或卸载new_handler,或抛出一个异常,或直接结束程序。operatornew可以被继承,但要小心,否则会导致问题classBasepublic:staticvoid*opratornew(size_tsize);;classDerived:publicB

18、ase;/导出类中没有operatornew函数Derived*p=newDerived;/调用Base类中的operatornew出错这里导出类内存比基类要大。改进的办法:void*operatornew(size_tsize)if(size!=sizeof(Base)return:opratornew(size);/回到标准operatornew函数重写operatordeletevoidoperatordelete(void*rawMemory)if(rawMemory=0)return;/与C+标准delete保持一致DeallocatethememorypointedtobyrawM

19、emory;return;/member版classBasepublic:staticvoid*operatornew(size_tsize);static void operator delete(void* rawMemory,size_t size);voidBase:operatordelete(void*rawMemory,size_tsize);if(rawMemory=0)return;if(size!=sizeof(Base)/如果大小错误:operatordelete(rawMemory);/用标准版delete处理return;deallocatethememorypoin

20、tedtobyrawmeMemory;return;9.避免覆盖new的正规形式解决办法(1)再写的一个专用的operatornew函数,让它支持正规的newclassXpublic:voidf();staticvoid*operatornew(size_tsize,new_handlerp);staticvoid*operatornew(sise_tsize)return:operatornew(size);X*p1=new(specialErrorHandler)X;/调用X:operatornew(size_tsize,new_handlerp);X*p2=newX;/调用X:opera

21、tornew(size_tsize);(2)为operatornew的每一个参数提供默认值(缺省值)10.如果写了一个operatornew不要忘记写一个operatordelete需要动态分配大量小额内存空间的应用程序,有时需要重载operatornew。classAirplaneRep;classAirplanepublic:private:AirplaneRep*rep;/唯一数据成员是指针;Airplane*p=newAirplane;/要求内存不大分配的内存比实际所需要的内存要大,这是为了delete这块内存时,系统能知道其大小。pa纪录内存大小的数据Airplane对象所需的内存为

22、了节省内存需要定制内存管理。定制内存管理。classAirplanepublic:staticvoid*operatornew(size_tsize);static void operator delete(void*deadObject, size_t size);private:unionAirplaneRep*rep;Airplane*next;/两个指针公用一个内存staticconstintBLOCK_SIZE;staticAirplane*headOfFreeList;/用链表配置一片内存,整个类只须一个链;void*Airplane:operatornew(size_tsize)

23、;if(size!=sizeof(Airplane)return:operatornew(size);Airplane*p=headOfFreeList;/p指向链表头if(p)headOfFreeList=p-next;/表头后移,p可用elseAirplane*newBlock=static_cast(:operatornew(BLOCK_SIZE*sizeof(Airplane);for(inti=1;iBLOCKSIZE-1;+i)/保留第一块newBlocki.next=&newBlocki+1;newBlockBLOCK_SIZE-1.next=0;/置表尾p=newBlok;/p

24、可用headOfFreeList=&newBlock1;returnp;只有当:operatornew失败时,这里的operatornew才失败。这时:operatornew会调用new_handler直到抛出异常,因此我们不需要再写一次new_handler处理具体实现文件中要先对静态成员初始化,Airplane*Airplane:headOfFreeList;/headOfFreeList置0constintAirplane:BLOCK_SIZE=512;这个版本的operatornew可以运作良好,速度快过两个数量级。还要在Airplane类中写一个operatordeletevoidA

25、irplane:operatordelete(void*deadObject,size_tsize)if(deadObject=0)return;if(size!=sizeof(Airplane):operatordelete(deadObject);/与operatornew处理保持一致return;Airplane*carcass=static_cast(deadObject);carcass-next=headOfFreeList;HeadOfFreeList=carcass;如果没有定义相应的delete函数,而使用了原有的delete,结果会出现意想不到的错误,有时是严重的错误。如果

26、用member版本不要忘记定义virtual析构函数。这里的delete函数没有memory leak 问题。问题。 这是因为用了memorypool一次分配一块内存,逐步使用逐步释放,不必再专门释放memorypool.定义一个memorypool类,使每一个pool对象都是一个内存配置器。classPoolpublic:Pool(size_tn);void*alloc(size_tn);/为一个对象配置足够/的内存遵循operatornew的规矩voidfree(void*p,size_tn);/将p的内存送回/pool遵循operatordelete的规矩pool();/释放pool中所

27、有内存;用Pool对象来配置内存,当被销毁时,配置的内存自动被释放。于是memoryleak就可以避免。classAirplanepublic:staticvoid*operatornew(size_tsize);staticvoidoperatordelete(void*p,size_tsize);private:AirplaneRep*rep;staticPoolmemPool;/Airplane的memorypool;inlinevoidAirline:operatornew(size_tsize)returnmemPool.alloc(size);inlinevoidAirline:o

28、peratordelete(void*p,size_tsize)memPool.free(p,size);为Airplane的memPool初始化,要放在Airplane类实现的文件里PoolAirplane:memPool(sizeof(Airplane);构造函数、析构函数和赋值运算符构造函数、析构函数和赋值运算符构造函数、析构函数和赋值运算用来产生一个新对象并初始化,撤销一个对象并收回占有的内存,为已有的对象赋一个新值。不能有错,必须将他们彻底搞清楚。11. class内有成员指针并动态配置内存时,一定要有拷贝构造函数,赋值运算符重载classStringpublic:String(co

29、nstchar*value);String();/没有拷贝构造函数,/也没有赋值运算符重载private:char*data;String:String(constchar*value)if(value)data=newcharstrlen(value)+1;strcopy(data,value);elsedata=newchar1;*data=“0”;inlineString:String()deletedata;Stringa(“Hello”);Stringb(“World”);b=a;HelloWorldabdatadata由于没有自定义的赋值函数,只能用C+产生的默认赋值函数,它简单地

30、将b的成员指针data指向a.data,引起字符串“World”占有的内存遗失。而且a.data与b.data指向同一个内存,其中一个被析构时另一个就丢失了。拷贝构造函数用来传值,voiddonothing(Stringla)Strings=“thetruthisoutofthere”;donothing(s);当函数donothing完成任务后,参数s所含的指针被析构,la被删除。即便la不再使用,将来又一次析构la会造成问题。解决的办法就是自己定义拷贝构造函数,赋值函数重载。如果确信永不使用这些函数,把他们定义为私有函数,而且不实现。一旦出错,编译器会给出错误提示。12构造函数中尽量以初始

31、化代替赋值一个类中的const成员数据和reference引用数据只能被初始化,不能被赋值。即便没有const成员数据和reference引用数据,初始化也比赋值效率高。构造函数分两个阶段实现:1.数据成员初始化。2.调用构造函数。数据成员赋值要调用构造函数,再调用赋值函数,做两次调用影响效率。初始化也容易维护,修改。有一种例外:一个类内有大量数据成员时,赋值比初始化效率高。classManyDataMbspublic:ManyDataMbs()ManyDataMbs(constManyDataMbs&x);private:inta,b,c,d,e,f,g,h;doublei,j,k,l,m;

32、voidinit();/用来将数据成员初始化,不做他用;voidManyDataMbs:init()a=b=c=d=e=f=g=h=1;i=j=k=l=m=0;ManyDataMbs:ManyDataMbs()init();ManyDataMbs:ManyDataMbs(constManyDataMbs&x)init();静态数据成员staticclassmember不应该在构造函数中初始化。静态数据成员只能初始化一次,不能初始化多次。12.数据成员初始化的次序应该和类内声明的次序相同templateclassArray/有上下界的数组public:Array(intlowBound,inth

33、ighBound);private:vectordata;/数组数据存储于一个vector对象data中size_tsize;/数组中元素的个数intlBound,hBound;/上下界;templateArray:Array(intlowBound,inthighBound):size(highBound-lowBound+1),lBound(lowBound),hBound(highBound),data(size)实际初始化中,data先被初始化,然后size,lBound,hBound.这样数组中,究竟有多少个元素无法确定。基类成员总是比导出类先初始化。多重继承时初始化的先后次序要十分

34、小心。14. 总是让基类拥有虚析构函数总是让基类拥有虚析构函数一个军事应用软件classEnemyTargetpublic:EnemyTarget()+numTargets;EnemyTarget(constEnemyTarget&)+numTargets;EnemyTarget()-numTargets;staticsize_tnumberOfTargets()returnnumTargets;virtualbooldestroy();/摧毁敌方目标是否成功private:staticsize_tnumTargets;/对象计数器;size_tEnemyTarget:numTargets;/

35、静态成员初始化为0,放在类外classEnemyTank:publicEnemyTargetpublic:EnemyTank()+numTanks;EnemyTank(constEnemyTank&)+numTanks;EnemyTank()-numTanks;staticsize_tnumberOfTanks()returnnumTanks;virtualbooldestroy();/摧毁敌方坦克是否成功private:staticsize_tnumTanks;/敌方坦克计数器;EnemyTarget*targetPtr=newEnemyTank;deletetargetPtr;/未定义,计

36、数出错,影响战斗胜败解决办法,把EnemyTarget类中的析构函数定义为virtual即可。几乎所有的基类都有虚函数,只要有一个虚函数,就要把析构函数定义为虚函数。没有虚函数的类,有继承派生类对象析构,也要定义虚析构函数。但虚函数会增加内存开销。完全不必要时不要用虚析构函数。声明一个抽象类,可以加一个纯虚析构函数。15. 让让operator=返回返回*this的引用的引用referenceC语言中operator=的原型C&C:operator=(constC&);charx,y,z;x=y=z=a;x,operator=(y.operator=(z.operator=a);z.opera

37、tor=的返回值是y.operator=的实参。他们应该有相同的类型。但不要让operator=返回void类型,const类型Strin&String:operator=(constString&rhs)return*this;/返回一个引用指向左侧对象Strin&String:operator=(constString&rhs)returnrhs;/返回一个引用指向右侧对象,错误后一个返回值,编译器无法编译,无法返回const类型.如果参数中去掉const变成:Strin&String:operator=(String&rhs);X=a;/无法编译rhs应该是一个变量。结论:必须返回*th

38、is;16. 在在operator=中为所有的数据成员赋值中为所有的数据成员赋值基类中这不成问题,在派生类中要小心。正确的赋值运算Derived&Derived:operator=(constDrived&rhs)if(this=&rhs)return*this;Base:operator=(rhs);/调用基类的赋值运算data=rhs.data;return*this;Derived&Derived:operator=(constDrived&rhs)if(this=&rhs)return*this;static_cast(*this)=rhs;/*this强制转换成基类的引用赋值基类成员

39、data=rhs.data;return*this;拷贝构造函数中要调用基类构造函数。用第一种方法在在operator=中检查是否中检查是否“自己赋值给自己自己赋值给自己”classX;Xa;X&b=a;/b是a的别名(aliasing)a=b;/自己赋值给自己合法在赋值函数中要特别谨慎的处理自己的别名赋值给自己的问题。提高效率先做检查,一发现自己赋值给自己立即返回。导出类的赋值运算重载中一定要先检查,可以节省许多工作确保正确性赋值运算通常要先将左边对象的资源释放,再行赋值。如果有自己赋值给自己的现象,这个资源可能丢失,不可挽回了。如何判断两个对象是同一个对象?不是对象的内容相同,而是看他们的

40、地址是否相同。X&X:operator=(constX&rhs)if(this=&rhs)return*this;aliasing问题不限于赋值运算内,只要用到指针或引用,就可能出现。这时我们就要当心,不要误删了有用的资源。类和函数的设计和申明类和函数的设计和申明设计一个高效率的类型(class型别),必须先回答下列问题对象如何产生和销毁?确定构造函数和析构函数的设计。对象的初始化和赋值有什么不同?决定构造函数和赋值函数的设计。对象如何传值决定拷贝构造函数的设计确定合法的范围 成员数据的定义域确定做什么检查,何时抛出异常判断是否能从已有的类继承如果能继承,注意受基类哪些约束,哪些要用虚函数。允

41、许那种类型转换构造函数可以用作隐式类型转换,显式类型转换要自定义。新类型需要哪些运算和函数确定class的接口。哪些运算和函数必须禁用放到private成员中。新类型的对象可调用哪些函数确定公有成员函数,保护成员函数,私有成员函数。是否通用类型确定是否要用类模板18努力让接口完满(努力让接口完满(complete)且最小化且最小化客户端接口客户端接口(clientinterface)指公有成员,一般只有公有函数,不要有公有数据。完满接口完满接口 允许客户做合理要求的任意事情。最小化接口最小化接口尽量让函数个数最少。不能有功能重叠的函数。太多函数不容易被理解,不易维护,浪费资源。如果增加一个函数

42、,使新类型更方便使用,就可以增加。T&operator(intindex);/传回数组的一个元素,可读,可写constT&operator(intindex)const;/传回数组的一个元素,可读,不可写19区分成员函数、非成员函数区分成员函数、非成员函数 和友元函数和友元函数成员函数可以动态绑定,可以用virtual非成员函数不能用virtual,非成员函数能不做友元尽量不做友元函数。非成员函数要调用类中私有数据成员或私有函数,则一定要声明为友元。不要让operaor成为类的成员函数,必要时作友元。要让函数式左边对象做类型转换,就不能做成员函数。例子classcomplexcomplexop

43、erator*(complexrhs)const;private:floatx,y;complexa(1,2),b(1.5,4);a=a*b;/正确a=a*2;/可以a=2*a;/出错只能声明为非成员函数constcomplexoperator*(constcomplex&lhs,constcomplex&rhs);20避免将数据成员设置为公有数据避免将数据成员设置为公有数据让公有成员都是函数,可以保持一致性。将数据成员声明为私有成员或保护成员,可以确保数据的安全。21尽可能使用尽可能使用const使用const可以让编译器知道某值不能改变,编译器会确保这个条件不会被改变。constchar*

44、p;/指针,指向常值字符串char*constp;/常指针,指向固定地址,地址内字符串不一定是常量constchar*constp;/常指针,指向固定地址,内置常字符串constchr*p;charconst*p;/意义相同函数中函数中const 可以修饰传回值,参数,成员可以修饰传回值,参数,成员函数时甚至可以修饰整个函数。函数时甚至可以修饰整个函数。函数返回值用 const, 可以改善函数的安全性,和效率。T&A:operator(intindex);/传回数组的一个元素,可读,可写Aa(8);couta2;a2=b;/正确constT&A:operator(intindex);/传回数组

45、的一个元素,可读,不可写Aa(8);couta2;/正确a2=b;/错误constcomplexoperator*(constcomplex&lhs,constcomplex&rhs);complexa,b,c;(a*b)=c;/不允许参数用const可以保证参数值不变,让编译器作检查。const成员函数保证this指针不变。classApublic:intlength()const;private:intsize;;intA:length()constif(size0)return0;/错误不能改变任何数据成员returnsize;新C+标准新增保留字mutableclassApublic:

46、intlength()const;private:mutableintsize;/可以在任何地点被改动,/即使在const成员函数中;intA:length()constif(size0)return0;/正确returnsize;22尽量使用引用参数传址尽量使用引用参数传址pass by reference拷贝构造函数用来传值passbyvalue,为函数的参数传值,为函数的返回值传值。传值要占用许多资源。classPersonpublic:Person();Person();private:stringname,address;classstudent:publicPersonpublic

47、:student();student();private:stringschoolname,schoolAddress;studentreturnstudent(students)returns;studentplato;returnstudent(plato);函数调用中copy构造函数被调用两次,将plato传给参数s,再将函数值返回,析构函数调用两次,析构s,析构函数返回值。更有甚者,基类Person的构造函数也要调用两次。student对象中两个string数据对象要构造,基类Person中两个string数据对象也要构造,plato给s构造四次,返回传值构造四次总共调用12次构造函数

48、,当然还有12次析构函数要调用。免除这些不当成本,改用引用参数传址byreferenceconststudent&returnstudent(conststudent&s)returns;引用参数传址byreference不调用任何构造函数析构函数。虚函数的引用参数是基类时,实际传入派生类对象时可以调用派生类的函数。传值参数没有这样的功能。引用参数要注意别名(aliasing)问题。23当你必须传回当你必须传回objebct(传值传值)时不要传时不要传址址(引用)引用)尽可能让事情简单,但不要过于简单。A.Einstein尽可能让事情有效率,但不要过于有效率。C+函数必须传回一个对象,就不要传

49、址不要返回引用。不能传回一个不存在的地址,不能传回函数中产生的局部对象的地址。const complex operator*(const complex& lhs,constcomplex&rhs)complextemp(lhs.x*rhs.x-lhs.y*rhs.y,lhs.x*rhs.y+lhs.y*rhs.x);returntemp;&错误。返回值地址指向局部对象,与局部对象同名,运算执行完毕,局部对象被析构,返回值指向一个不存在的地址。const complex& operator*(const complex&lhs,constcomplex&rhs)complex*temp(lhs

50、.x*rhs.x-lhs.y*rhs.y,lhs.x*rhs.y+lhs.y*rhs.x);return*temp;指针temp被析构,内存已丢失。constcomplex&operator*(constcomplex&lhs,constcomplex&rhs)complex*temp=newcomplex(lhs.x*rhs.x-lhs.y*rhs.y,lhs.x*rhs.y+lhs.y*rhs.x);return*temp;指针temp没有析构,将来谁来析构呢。内存可能丢失。complexone(1),two(2),three(3),four(4);complexproduct;produ

51、ct=one*two*three*four;如何析构这几个operator*中间产生的temp指针呢?24函数重载和参数缺省之间,谨慎函数重载和参数缺省之间,谨慎抉择抉择函数重载和参数缺省之间容易引起混淆。如果可以选择一个合理的默认值,并且只需要一种算法,最好使用缺省参数。否则使用重载函数。例:求五个整数的最大值#includeintmax(inta,intb=std:numeric_limits:min(),intc=std:numeric_limits:min(),intd=std:numeric_limits:min(),inte=std:numeric_limits:min(),)in

52、ttemp=ab?a:b;inttemp=tempc?temp:c;inttemp=tempd?temp:dinttemp=tempe?temp:e;使用max函数对两个参数,三个参数,直至五个参数都有效。但是,计算平均数就找不到合适的默认值,只好重载。一般,构造函数和拷贝构造函数的算法不同,需要重载。25避免对指针类型和数值类型进行重载避免对指针类型和数值类型进行重载voidf(intx);voidf(string*ps);f(0);/调用那一个?调用f(int)void*constNULL=0;/无类型指针f(NULL);/错误类型不符#defineNULL0f(NULL);/调用f(in

53、t)#defineNULL(void*)0)f(NULL);/错误类型不符classNULLClass/类型名可以隐去public:templateoperatorT*()return0;/为任意类型T传回一个NULL指针NULL;f(string*ps);f(NULL);/NULL被转换为string*调用f(string*ps)尽可能避免对指针类型和数值类型进行重载26防备隐性二义性状态防备隐性二义性状态classB;classApublic:A(constB&);/由B可以造出A来;classBpublic:operatorA()const;/B可以转换成A;voidf(constA&)

54、;Bb;f(b);/错误模棱两可两种方法哪种更好?voidf(int);voidf(char);doubled=6.02;f(d);/模棱两可模棱两可可以潜伏很久,直到爆发。多继承最容易引发模棱两可。classBpublic:Bdoit();classCpublic:Cdoit();/放在私有成员中同样不行;classDerived:publicB,publicC;Derivedd;d.doit();/模棱两可d.B:doit();/正确d.C;doit();/正确27如果不想使用编译器暗自产生的成员如果不想使用编译器暗自产生的成员函数,明确地拒绝函数,明确地拒绝不允许一个函数存在,只要不把它

55、放进class中。但赋值函数,拷贝构造函数例外,系统会自行产生一个这种函数。不许对象调用某个函数,把它放在私有成员中。但公有函数,友元可以调用。声明一个函数,而不定义它,调用它编译器会指出错误。28尝试切割尝试切割global namespace (全局命名空间全局命名空间)标识符重名会引起混乱。同类名词冠以同一词头会使名字太长。建议使用namespace名字空间namespacesdmconstintBOOK_VERSION=2.0;classHandle;HandlegetHandle();有三种方法取用namespace内的名字。voidf1()usingnamespacesdm;/汇入

56、所有名字coutBOOK_VERSION;Handleh=getHandle();voidf2()usingsdm:BOOK_VERSION;/汇入单个名字coutBOOK_VERSION;/正确Handleh=getHandle();/错误voidf3()coutsdm:BOOK_VERSION;/没问题只用一次doubled=BOOK_VERSION;/错误Handleh=getHandle();/错误两个namespace中有相同的名字,只要标明namespace域名即可区分。usingnamespacestm;usingnamespacesdm;stm:;BOOK_VERSION;sd

57、m:BOOK_VERSION;类与函数的实现类与函数的实现 29避免传回内部数据的避免传回内部数据的handlesclassStringpublic:String(constchar*value);String();operatorchar*()const;private:char*data; inlineString:operatorchar*()constreturndata;/潜伏着危险constStringB(“Iloveyou!”);char*str=B;/str与B.data指向同一个地址strcpy(str,“Iloveyou?”);/改变str,也就改变了B.data字符串常量

58、B被改变。去掉operatorchar*()const;中const,可以令常量B不能调用operatorchar*();但String便不能转换成char*.安全的做法是:inlineString:operatorchar*()constchar*copy=newcharstrlen(data)+1;strcpy(copy,data);returncopy;这个函数比较慢,可能产生内存丢失。classStringpublic:String(constchar*value);String();operatorconstchar*()const;private:char*data; inline

59、String:operatorconstchar*()constreturndata;/又快又安全传回一个常量指针,不可改变。引用也可能发生传回内部数据的handle问题。classStringpublic:char&operator(intindex)constreturndataindex;private:char*data;Strings=“Iamnotconst”;s0=x;constStringcs=“Iamconst”;cs0=x;/改变了常字符串,编译器没发现问题出在const函数返回值是引用。即使不是即使不是const 函数,函数,“传回传回handles”也是有问题的。也是有

60、问题的。/随机选择一个作家的名字StringsomeFamousAuthor()switch(rand()%3)/随机函数case0:return“MargaretMitchell”;/飘的作者case1:return“StephenKing”;/著名通俗小说家case3:return“ScottMeyers”/本书作者return“”;这个函数的返回值是一个局部指针handle,函数用毕被销毁因此返回值将是无定义的内存(danglingpointer)。尽可能避免让一个函数传回danglinghandles,可以提高程序的可靠性。30.避免写出成员函数返回一个避免写出成员函数返回一个non-

61、const pointer或或reference(引用引用)并以之指向较低存取层级的成员并以之指向较低存取层级的成员classAddress;classPersonpublic:address&personAddress()returnaddress;private:Addressaddress;/私有数据成员;Personscott();Address&addr=scott.personAddress();/全局对象addr与scott.address同一地址私有成员公开化,可以从外部改变私有数据的值。变成指针也有同样的问题。31千万不要返回千万不要返回“函数内局部对象的函数内局部对象的re

62、ference”或或“函数内以函数内以new获得的指针所指的对获得的指针所指的对象象”“传回一个引用(reference),指向局部对象”,函数用毕返回时,局部对象被析构,变成引用一个不存在的对象。constcomplex&operator*(constcomplex&lhs,constcomplex&rhs)complextemp(lhs.x*rhs.x-lhs.y*rhs.y,lhs.x*rhs.y+lhs.y*rhs.x);returntemp;错误。返回值地址指向局部对象,与局部对象同名,运算执行完毕,局部对象被析构,返回值指向一个不存在的地址。classAddress;classPe

63、rsonpublic:address&personAddress()returnaddress;private:Addressaddress;/私有数据成员;Personscott();Address&addrPtr=scott.personAddress();/指针addr指向scott.address同一地址只要把函数返回值改成const类型即可避免外部修改。classAddress;classPersonpublic:address*personAddress()returnaddress;private:Addressaddress;/私有数据成员;Personscott();Addr

64、ess*addrPtr=scott.personAddress();/指针addr指向scott.address同一地址同样问题只要把函数返回值改成const类型即可避免外部修改。32. 尽可能延缓变量定义式的出现尽可能延缓变量定义式的出现/这个函数太早定义变量encryptedstringencryptPassward(conststring&passward)stringencrypted;if(passward.length()MINIMUM_PASSWARD_LENGTH)throwlogic_error(“Passwardistooshort”);returnencrypted;一旦

65、有异常抛出,encrypted定义就是多余的。尽量在变量定义时初始化。33 明智地运用明智地运用inlineinline内联函数提高效率,编译时实现最佳化。但是增加object目标代码,加大内存开销。太多的inline函数,会减低取出指令的速度(instructionfetch)编译器会因某些理由自动拒绝inline函数,将它当成非inline函数。这时系统发出一个警告。比如太长的函数,virtual函数,不适合inline的函数。构造函数看起来不长,有时继承派生类中的构造函数,比看起来的要长。比如基类的构造函数,new的使用,都使inline失效。inline函数不会自动升级,程序一旦被改动

66、就要重新编译。inline函数应限制用于一些非常平凡的,占有重要效率地位的函数。慎重使用inline函数,便于日后除错。34 将文件之间的编译关系降到最低将文件之间的编译关系降到最低一个class在文件file中定义并实现。程序用到这个类的对象就要连接文件file#includefile文件就与程序发生依赖关系。修改file文件,会引起全程序重编译。C+一个classB中,用到另一classA的对象,A必须先完全定义并实现。如果A写在文件file里,A的任何改变都会引起整个程序重新编译。改进的方法是A中尽量使用指向B类对象的指针或引用。这样,B只要先声明,A的编译不依赖于B.file改变时,A

67、不需要重新编译。A称为Handleclass另一种方法是用抽象类做基类,成为Protocolclass。Protocolclass没有任何实现。其作用只是一个接口。如果引用或指针能够完成任务,就不用对象。如果可能,以class的声明,代替class的实现。尽可能只把class的声明放在头文件里,其余实现放在由客户完成的文件里,不参与编译。头文件尽量不include别的头文件,除非不联不行。继承关系与面向对象设计继承关系与面向对象设计继承体系是C+与C的更本区别。如果需要一群class,拥有许多共享性质,那就要考虑用基类还是模板。如果classA是根据classB实现,考虑A中应该有一个B类对象

68、还是A继承B.如果需要设计一个安全类型,通用,而C+标准库未定义,那么应该使用模板还是以泛型void*指针来实现?“说出你的意思,并了解你所说的每一句话。”35公有继承,公有继承, “isa”的关系的关系请牢记:公有继承publicinheritance是一种isa的关系。如果classD公有继承classB,则D是B的subclass子类。D的对象是B的对象,反之不成立。B比D更一般化,D比B更特殊化。可以用B对象的地方,D的对象也可以用。要用D对象的地方,B对象无法效劳。每一匹白马都是马,每一匹马不一定是白马。公孙策“白马非马”,白马是马的真子集,而不相等。马是基类,白马是派生类。C+的继

69、承关系,不同于日常生活,也不同于数学。鸟bird会飞。企鹅penguin是鸟,可企鹅不会飞。如果鸟class中有fly,则企鹅不是鸟的派生类。长方形正方形?还是正方形长方形?两者都不对。另外定义一个基类,长方形和正方形都是它的派生类。36区分接口继承(区分接口继承(interface inheritance)和实)和实现继承(现继承(implementation inheritance)成员函数的接口总是会被继承声明纯虚函数是为了让导出类只继承其接口。纯虚函数也可以定义,即可以提供其实现代码。只有一种方法调用就是写明class域名.声明一般虚函数(非纯)是为了让导出类只继承其接口和缺省行为。声

70、明非虚拟函数是为了让导出类只继其接口及其实现。继承类中行为不变的函数不应声名为虚函数。37. 绝对不要重新定义继承而来的非虚函数绝对不要重新定义继承而来的非虚函数如果基类中定义了一个非虚函数,派生类同时继承其接口和实现。派生类的对象可直接调用基类的这个函数,行为不变。如果派生类中重新定义继承而来的非虚函数,基类中的函数将被覆盖。函数调用将发生变化。这就是说本应该定以为虚函数。如果不希望函数行为有变化,则不应该重定义非虚函数。38绝对不要重新定义继承而来的缺省参绝对不要重新定义继承而来的缺省参数值数值虚函数的调用由实际引入的参数对象确定。动态绑定,后联编。但如果函数有缺省值,缺省值则是静态绑定。

71、调用一个有缺省值的虚函数,非缺省参数的派生类型实参确定调用哪一个函数,但缺省参数依然用基类的值。重新定义的缺省参数并不起任何作用,反而引起混淆。要么不定义虚函数参数的缺省值,有了缺省值就不可以重定义。39避免在继承中做向下转型避免在继承中做向下转型(castdown)动作)动作classPerson;classBankAccountBankAccount(constPerson*primaryOwner,constPerson*jointOwner);virtualBankAccount();virtualvoidmakeDepoisit(doubleamount)=0;/存款virtualv

72、oidmakeWithdrawal(doubleamount)=0;/取款virtualvoidbalance()const=0;/余额;classSavingAccount:publicBankAccoutpublic:SavingAccount(constPerson*primaryOwner,constPerson*jointOwner);SavingAccount();voidcreditInterest();/利息加入账户;listallAccount;/所有账户列表,for(list:iterator p=allAccount.begin( ); p!=allAccount.end

73、( ); +p) /p相当于BankAccount*类型(*p)-creditInterest();/错误!*p是BankAccount*指针,BankAccount类中没有creditInterest()函数。尽管BankAccount是纯虚基类,没有实现,SavingAccount是唯一可实现的类。编译器仍然读不懂(*p)-creditInterest();可以改为:for(list:iterator p=allAccount.begin( ); p!=allAccount.end( ); +p)static_cast(*p)-creditInterest();/强制转型,向下转型!从基类

74、向下到派生类的转型downcast会导致维护上的梦魇。/增加支票账户类classCheckingAccount:publicBankAccountpublic:voidCreditInterest();/将利息存入账户;for(list:iterator p=allAccount.begin( ); p!=allAccount.end( ); +p)if(*ppointstoaSavingAccount)static_cast(*p)-creditInterest();/强制向下转型!if(*ppointstoacheckingAcount)static_cast(*p)-creditInte

75、rest();/强制向下转型这是不符合C+精神的做法。C+中以对象类型决定不同行为,应该使用虚函数。classBankAccount;/如前classinterestBearingAccount:publicBankAccountpublic:virtualvoidcreditInterest()=0;;classSavingAccount:publicInteresttBearingAccount;/如前classcheckingAccount:publicInteresttBearingAccount;/如前listallIBAccount;/银行中所有“须付利息的账户”for(list:

76、iteratorp=allIBAccount.begin();p!=allIBAccount.end();+p)(*p)-creditInterest();/正确,也适用于将来downcast向下转型,可以用虚函数的方法消除。向下转型容易出错,难以理解,难以维护。如果非向下转型不可,可以采用“安全向下转型动作”(safedowncasting)dynamic_cast将一个指针指向的动态对象转变成要求的类型,如果失败,传回null指针。classBankAccount;/如前classSavingAccount:publicBAnkAccount;/如前classcheckingAccount

77、:publicBankAccount;/如前listallAccount;/银行中所有账户voiderror(conststring&msg);for(list:iteratorp=allAccount.begin();p!=allAccount.end();+p)if(SavingAccount*psa=dynamic_cast(*p)psa-creditInterest();/安全转型,向下转型!elseif(checkingAcount*pca=dynamic_cast(*p)pca-creditInterest();/安全向下转型!elseerror(“unknownaccountty

78、pe!”);向下转型失败时可以侦察得到。但是,如果有人新加入一个账户类型,又忘记更新以上代码,上面这个程序中所有downcast都会失败。downcast会导致程序难以维护。比起虚函数相差太远。非万不得已,不要出此下策。40通过通过layering技术来模塑技术来模塑has-a或或is-implemented-in-terms-of的关系的关系layering技术:用另一个类的对象做本类数据成员。称layeringclass(外层),layeredclass(内层)classAddress;classPhoneNumber;classPersonpublic:private:stringnam

79、e;/layeredobjectAddressaddress;/layeredobjectPhoneNumbervoicNumber;/layeredobjectPhoneNumberfaxNumber;/layeredobject;称Person类铺陈(layered)在string,Address,PhoneNumber之上。layering=composition,containment,embedding;layering意味着有一个has-a或根据某物实现(is-implemented-in-termsof).如何区分isa和has-a?C+标准库有一个set模板类,要求set的元素

80、类型T是整型,可以编号排序。如果需要做一个set其元素类型T不能排序,怎么办?可以用list来实现set?list是T的链表类型。templateclassSet:publiclist;实际上完全错误。因为链表中元素可以重复出现,而集合不允许。正确的方法是,用链表做成员,重新设计一个集合类型即可。templateclassSetpublic:boolmember(constT&item)const;voidinsert(constT&item);voidremove(constT&item);intcardinality()const;/集合中元素个数private:listrep;/链表作成

81、员;templateboolset:member(constT&item)constreturnfind(rep.begin(),rep.end(),item)!=rep.end;)templatevoidset:insert(constT&item)if(!member(item)rep.push_back(item);templatevoidset:remove(constT&item)list:iteratorit=find(rep.begin(),rep.end(),item);if(it!=rep.end()rep.erase(it);templateintset:cardinali

82、ty()constreturnrep.size();注意:采用layering技术使两个class有了编译依赖关系,最好采用34条方法加以改进。41区分继承和模板区分继承和模板两个例子:信息系学生学习C+,想自行设计stack,intstack,stringstack等等。一个猫迷想设计一些catclasses表现不同品钟的猫。必须先问自己一个问题,不同的类型T会不会影响class的行为。如有影响,必须用虚函数,从而要用继承机制。如不影响可以用模板。不同类型的stack都有相同的行为。push,pop,peek,empty等等,用template再简单不过了。而不同的猫有不同的特性,不同的吃睡

83、本领。用继承最好。42明智地运用私有继承明智地运用私有继承classPerson;classStudent:privatePerson/私有继承;voiddance(constPerson&p);/任何人都会跳舞voidstudy(constStudent&s);/只有学生才在学校学习只有学生才在学校学习Personp;Students;dance(p);dance(s);/错误!student不是Person对象私有继承,基类中的公有,和保护成员在派生类中都变成私有成员。采用私有继承,并不希望isa,即导出类对象不再是基类对象。基类中私有数据成员在导出类中不可见。公有和保护成员函数的实现,可

84、以被导出类成员函数采用。尽量不用私有继承,除非必要。模板类的每一个具体类型化,都会产生一份代码。十个类型便得到十份完整代码。“因template导致程序代码膨胀”。可以用泛型指针来避免代码膨胀。把模板stack改为非模板stackclass.classGenericStackprotected:GenericStack();GenericStack();voidpush(void*object);void*pop();boolempty()const;private:structStackNodevoid*data;StackNode*next;StackNode(void*newData,S

85、tackNode,*NextNode):data(newData),next(nextNode);StackNode*top;GenericStack(constGenericStack&rhs);GenericStack&operator=(constGenericStack&rhs);GenericStack类不能使用,把protected改成public可以用,但太容易出错了。泛型指针对任意指针都不加区别。可以用私有继承完成代码:classIntStack:privateGenericStackpublic:voidpush(int*intptr)GenerickStack:push(i

86、ntptr);int*pop()returnstatic_cast(GenerickStack:pop();boolempty()constreturnGenericStack:empty();;classCatStack:privateGenericStackpublic:voidpush(Cat*icatptr)GenerickStack:push(icatptr);Cat*pop()returnstatic_cast(GenerickStack:pop();boolempty()constreturnGenericStack:empty();;IntStackints;/没问题CatSt

87、ackcs;/没问题与layering技术一样,私有继承实现的代码,可以避免重复。还可以用模板私有继承改进,进一步避免太多的书写。templateclassStack:privateGenericStackpublic:voidpush(T*objectptr)GenerickStack:push(objectptr);int*pop()returnstatic_cast(GenerickStack:pop();boolempty()constreturnGenericStack:empty();;这是一个通用Stack,可以具体实现任意类的Stack.如果你写错字,编译器会自动指出错误。由于

88、GenericStack使用泛型指针,这段代码只付出一份拷贝的代价。因此,这个程序既安全又高效,很难做得更好。43明智地运用多继承明智地运用多继承多继承产生歧义(ambiguity),两个基类中有同名成员,派生类中必须指明其基类。由一个类派A生出两个B,C,再由B,C多继承派生出D,所谓钻石形,出现模棱两可歧义。用虚基类可以避免数据成员的模棱两可。但要避免将构造函数的参数传给虚基类。最好的办法是虚基类中不要有任何数据成员。Java就有这样的规定。但怎样处理虚函数呢?虚函数经由不同的路径产生模棱两可。因此要避免钻石形继承。但非钻石形多继承是有意义的。两个基类一个是抽象类继承其接口,另一个继承其实

89、现,有时有奇妙的作用。一个公有继承,一个私有继承。classPersonpublic:virtualPerson();virtualstringname()const=0;virtualstringbirthDate()const=0;virtualstringaddress()const=0;virtualstringnationality()const=0;classDatabaseID;classPersonInfopublic:PersonInfo(DatabaseIDpid);virtualPersonInfo();virtualconstchar*thename()const;vi

90、rtualconstchar*theBirthDate()const;virtualconstchar*theAddress()const;virtualconstchar*theNationality()const;virtualconstchar*valumeDelimOpen()const;virtualconstchar*valumeDelimClose()const;;classMyPerson:publicPerson,privatePersonInfopublic:MyPerson(DatabaseIDpid):PersonInfo(pid)constchar*valumeDel

91、imOpen()constreturn“”;constchar*valumeDelimClose()constreturn“”;stringname()constreturnPersonInfo:theName();stringbirthDate()constreturnPersonInfo:theBirthDate();stringaddress()constreturnPersonInfo:address();stringnationality()constreturnPersonInfo:theNationality();43说出你的意思并了解你所说的每一句话说出你的意思并了解你所说的每

92、一句话杂项讨论杂项讨论45清楚知道清楚知道C+编译器为我们完成和调用哪些编译器为我们完成和调用哪些函数函数46宁愿编译和连接时出错,也不要执行时出错宁愿编译和连接时出错,也不要执行时出错47使用使用non-local static objects之前确定它已有之前确定它已有 初值初值48不要对编译器的警告信息视而不见不要对编译器的警告信息视而不见49尽量让自己熟息尽量让自己熟息C+标准程序库标准程序库50加强自己对加强自己对C+的了解的了解classCartoonCharacter;/卡通形象classInsert:publicCartoonCharacter/昆虫public:virtual

93、voiddance();virtualvoidsing();protected:virtualvoiddanceCustomization1()=0;virtualvoiddanceCustomization2()=0;virtualvoidsingCustomization()=0;classGrasshoper:publicInsert/蚱蜢protected:virtualvoiddanceCustomization1()=0;virtualvoiddanceCustomization2()=0;virtualvoidsingCustomization()=0;classCricket:

94、publicInsert/蟋蟀protected:virtualvoiddanceCustomization1()=0;virtualvoiddanceCustomization2()=0;virtualvoidsingCustomization()=0;44说出你的意思并了解你所说的每一句话说出你的意思并了解你所说的每一句话共同的基类意味着共同的特性公有继承是一种isa私有继承意味着根据某物实现(is-implemented-in-terms-of)layering意味着has-a或is-implemented-in-terms-of公有继承中纯虚函数意味着只继承函数的接口非纯虚函数意味着函

95、数接口和缺省参数会被继承非虚函数意味着函数的接口和实现都被继承杂项讨论杂项讨论45清楚知道清楚知道C+编译器为我们完成和调用编译器为我们完成和调用哪些函数哪些函数一个空类,编译器处理过后,会为你声明缺省构造函数,拷贝构造函数,析构函数,赋值运算,取址运算如果要禁止某个函数,要明确说明,采取措施。46宁愿编译和连接时出错,也不要执行宁愿编译和连接时出错,也不要执行时出错时出错尽可能将检查错误的工作交给编译器来做。数组边界检查,数据有效性检查。连接期检验检查一个用到的函数只被定义一次。执行期错误要靠自己运行检验,设各种可能的计边界条件,出界条件来检验。47使用使用non-local static

96、objects之前确定之前确定它已有初值它已有初值non-local static objects指指全局或namespace中的静态对象某个类内声明的对象某个文件内定义的对象Singletonpatten技术:将一个non-local static objects放到一个函数里,让函数返回一个引用,指向这个non-local static objects。这时编译器会检查是否初始化。48不要对编译器的警告信息视而不见不要对编译器的警告信息视而不见弄清每一个警告的含义避免出错。49尽量让自己熟息尽量让自己熟息C+标准程序库标准程序库旧C+头文件,如继续存在,但不在namespacestd内新的

97、C+头文件,如对应旧的头文件,放在namespacestd内C头文件,如继续存在,但不在namespacestd内新的C+头文件,如对C的头文件,放在namespacestd内标准程序库都被template化不要自己声明标准程序库中已有的任何东西,只要包含适当头文件就可以。iostreams内容增加,原有都有效string效率更高STLStandardTemplateLibrary可扩充containers包括vectors(动态扩充数组)、lists(双向串行)、queues、stacks、deques、maps、sets、bitsetsiteratorAlgorithms(算法)functiontemplates大部分适用于所有containers国际化支持(facetslocales)国际字符对应映射,时间,日期,数值,货币表示法。数值处理诊断功能出错处理50加强自己对加强自己对C+的了解的了解

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 建筑/环境 > 施工组织

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号