改善程序设计技术的50个有效做法电子教案_第1页
改善程序设计技术的50个有效做法电子教案_第2页
改善程序设计技术的50个有效做法电子教案_第3页
改善程序设计技术的50个有效做法电子教案_第4页
改善程序设计技术的50个有效做法电子教案_第5页
已阅读5页,还剩167页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

改善程序设计技术的

50个有效做法

如何完成较好的设计如何避免常见的问题如何提高效率的一些准则不是放之四海而皆准的唯一真理C++新标准新的类型bool有两个值true,false.typedefintbool;constboolfalse=0;constbooltrue=1;1.尽量以const和inline取代#define#define

是一个宏,只能被预处理,而不被编译,用它定义的常量甚至不被编译器看见,因此不能发现使用中的错误。用#define定义一个简单函数,必须为每一个参数加上一个括号,容易造成错误。用内联函数高效准确。#defineratio1.653

//编译器看不见ratio,只看见1.653

//一旦出错,不会报告constdoubleratio=1.653;constchar*constname=“ScottMeyers”;//字符串常量InClass常量用静态变量类内声明,类外定义。classEngineerConstants{

private:

staticconstdoubleFactor;……};constdoubleEngineerConstants::Factor=1.35;2.尽量以<iostream>取代<stdio.h>scanfprintf函数不能扩充用来输入输出自定义类型的变量。cin>>i>>x;cout<<i<<x;可以扩展,方便得多改变旧有的C习惯(shiftingfromCtoC++)尽量以const和inline取代#define#define是一个宏,只能被预处理,而不被编译,用它定义的常量甚至不被编译器看见,因此不能发现使用中的错误。用#define定义一个简单函数,必须为每一个参数加上一个括号,容易造成错误。用内联函数高效准确。3.尽量以new和delete取代malloc和freemalloc和free不能调用构造函数,析构函数

new和delete则可。不能混用newdeletemallocfree必要用C库函数时检查是否用到malloc重新用new和delete改过。4.尽量使用C++风格的注释形式/*……*/要保证成对出现,不小心错一大片。

//好看好读可以混合使用当心!#definelight_speed3e8//m/sec(inavacum)内存管理(memorymanagement)

new隐式调用构造函数,delete隐式调用析构函数,

可以重载operatornew和operatordelete.不小心运用new和delete会导致各种错误。5.使用相同形式的new和deletestring*a=newstring[10];……deletea;//出错delete[]a;//正确string*b=newstring;……delete[]b;//出错deleteb;//正确typedefstringaddresslines[4];string*a=newaddresslines;……deletea;//出错delete[]a;//正确不要对数组类型用typedef,不容易记住用哪一种delete6.记得在析构函数中以delete对付指针成员如果类中有指针数据成员,在每个构造函数中为指针成员配置内存,否则将它初始化为0(NULL指针)。若构造函数中用new配置了内存,一定要在析构函数中用delete释放在赋值运算重载时要将原有指针的内存删除,重新分配内存。要在析构函数中删除这个指针。不要用delete删除一个未完成初始化的指针,不要删除一个未分配内存的指针。不要delete从一个类外面传来的指针。7.为内存不足的状况预作准备

不能认为“检查内存是否分配成功”是多此一举。否则会出现严重后果。必须建立一个错误处理策略。当operatornew无法满足需求时,在抛出异常之前,会调用一个内存不足处理函数newhandler,这个函数由头文件<new>提供。

typedefvoid(*new_handle)();

new_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<<“Unabletosatisfyformemeory\n”abort();//exit}intmain(){set_new_handler(nomoreMemory);int*pBigDataArray=newint[100000000];……}当operatornew无法配置10000000个整数空间时,系统调用nomoreMemory,然后结束。设计new_handler函数,令其完成如下任务:--让更多的内存可用。预留一块内存,

new_handler第一次被调用时释放,同时发出警告。安装新的new_handler以取代自己。new_handler中调用C++标准库函数set_new_handler即可。卸载这个new_handler,返回NULL指针,并抛出bad_alloc(或其继承)类型的异常。直接调用abort或exit终止程序。C++不支持class中专用的new_handler,但仍可以在一个class中重载operatornew,和set_new_handler函数,让它调用特定的new_handler函数,而不用系统给出的全局new_handler。classX{public:

staticnew_handlerset_new_handler(new_handlerp);staticvoid*operatornew(size_tsiz);private:staticnew_handlercurrentHandler;};new_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保存globalHandlervoid*memory;try{memory=::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=newstring;

//如果内存分配失败,调用globalHandlerX::set_new_handler(0);X*px2=newX;

//如果内存分配失败,立即抛出异常可以做一个混合风格基类

允许“设定class专属new_handler”template<classT>classNewHandlerSupport{public:

staticnew_handlerset_new_handler(new_handlerp);staticvoid*operatornew(size_tsiz);private:staticnew_handlercurrentHandler;};template<classT>new_handlerNewHandlerSupport<T>::set_new_handler(new_handlerp){new_handleroldHandler=currentHandler;//保留当前new_handlercurrentHandler=p;//再设置当前new_handlerreturnoldHandler;}template<classT>void*NewHandlerSupport<T>::operatornew(size_tsize)

{newHandlerglobalHandler=std::set_new_handler(currentHandler);

//配置新new_handler保存globalHandlervoid*memory;try{memory=::opratornew(size);//试分配内存}catch(std::bad_alloc&){std::set_new_handler(globalHandler);

//恢复原有处理方法

throw;//传播异常

}std::set_new_handler(globalHandler):

//恢复原有处理方法

returnmemory;}new_handlerNewHandlerSupport<T>

::currentHandler;//初始化为0classX:publicNewHandlerSupport<X>{……

//不必声明set_new_handler和operatornew}类X不必改动原有的程序代码,就可以继续运作。1993年前C++要求operatornew在无法满足内存需求时返回0,新标准则是抛出一个bad_alloc类型异常。失败便转为0的传统被保留为“nothrow”不抛出异常。<new>头文件中定义了一个nothrow对象

classWidget{……};Widget*pw1=newWidget;//如果失败抛出std::bad_alloc异常

if(pw1==0)……//无效

widget*wp2=new(nothrow)Widget;

//如果失败,返回0if(wp2==0)……//有效8.撰写operatornew和operatordelete时

应遵守的公约

当你有必要重载operatornew时,你的new函数的行为应该与系统原有的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_new_handler(0)

//利用NULL,找出目前的错误处理函数

set_new_handler(globalHandler);

//重新设定为原本的函数

if(globalHandler)(*globalHandler)()elsethrowstd::bad_alloc();}}无穷循环可以让更多的内存可用,

或安装一个不同的new_handler,

或卸载new_handler,

或抛出一个异常,

或直接结束程序。

operatornew可以被继承,

但要小心,否则会导致问题classBase{public:staticvoid*opratornew(size_tsize);……};classDerived:publicBase{……};//导出类中没有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保持一致

DeallocatethememorypointedtobyrawMemory;return;}//member版classBase{public:staticvoid*operatornew(size_tsize);

staticvoidoperatordelete(void*rawMemory,size_tsize);……}voidBase::operatordelete(void*rawMemory,size_tsize);{if(rawMemory==0)return;if(size!=sizeof(Base)//如果大小错误

{::operatordelete(rawMemory);

//用标准版delete处理

return;}deallocatethememorypointedtobyrawmeMemory;return;}9.避免覆盖new的正规形式解决办法

(1)再写的一个专用的operatornew函数,让它支持正规的newclassX{public: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::operatornew(size_tsize);

(2)为operatornew的每一个参数提供默认值

(缺省值)10.如果写了一个operatornew

不要忘记写一个operatordelete需要动态分配大量小额内存空间的应用程序,有时需要重载operatornew。classAirplaneRep{……};classAirplane{public:……private:AirplaneRep*rep;//唯一数据成员是指针};Airplane*p=newAirplane;//要求内存不大分配的内存比实际所需要的内存要大,这是为了delete这块内存时,系统能知道其大小。pa

纪录内存大小的数据Airplane对象所需的内存为了节省内存需要定制内存管理。定制内存管理。classAirplane{public:staticvoid*operatornew(size_tsize);

staticvoidoperatordelete(void*deadObject,size_tsize);……private:union{AirplaneRep*rep;Airplane*next;};//两个指针公用一个内存

staticconstintBLOCK_SIZE;staticAirplane*headOfFreeList;

//用链表配置一片内存,整个类只须一个链};void*Airplane::operatornew(size_tsize);{if(size!=sizeof(Airplane))return::operatornew(size);Airplane*p=headOfFreeList;

//p指向链表头

if(p)headOfFreeList=p->next;//表头后移,p可用

else{Airplane*newBlock=static_cast<Airplane*>(::operatornew(BLOCK_SIZE*sizeof(Airplane)));for(inti=1;i<BLOCKSIZE-1;++i)//保留第一块

newBlock[i].next=&newBlock[i+1];newBlock[BLOCK_SIZE-1].next=0;//置表尾

p=newBlok;//p可用

headOfFreeList=&newBlock[1];}returnp;}只有当::operatornew失败时,这里的operatornew才失败。这时::operatornew会调用new_handler直到抛出异常,因此我们不需要再写一次new_handler处理具体实现文件中要先对静态成员初始化,Airplane*Airplane::headOfFreeList;

//headOfFreeList置0constintAirplane::BLOCK_SIZE=512;这个版本的operatornew可以运作良好,速度快过两个数量级。还要在Airplane类中写一个operatordeletevoidAirplane::operatordelete(void*deadObject,size_tsize){if(deadObject==0)return;if(size!=sizeof(Airplane)){::operatordelete(deadObject);

//与operatornew处理保持一致

return;}Airplane*carcass=static_cast<Airplane>(deadObject);carcass->next=headOfFreeList;HeadOfFreeList=carcass;}如果没有定义相应的delete函数,而使用了原有的delete,结果会出现意想不到的错误,有时是严重的错误。如果用member版本不要忘记定义virtual析构函数。这里的delete函数没有memoryleak问题。

这是因为用了memorypool一次分配一块内存,逐步使用逐步释放,不必再专门释放memorypool.定义一个memorypool类,使每一个pool对象都是一个内存配置器。classPool{public:Pool(size_tn);void*alloc(size_tn);//为一个对象配置足够

//的内存遵循operatornew的规矩

voidfree(void*p,size_tn);//将p的内存送回

//pool遵循operatordelete的规矩

~pool();//释放pool中所有内存};用Pool对象来配置内存,当被销毁时,配置的内存自动被释放。于是memoryleak就可以避免。classAirplane{public:staticvoid*operatornew(size_tsize);staticvoidoperatordelete(void*p,size_tsize);……private:AirplaneRep*rep;staticPoolmemPool;//Airplane的memorypool};inlinevoidAirline::operatornew(size_tsize){returnmemPool.alloc(size);}inlinevoidAirline::operatordelete(void*p,size_tsize){memPool.free(p,size);}为Airplane的memPool初始化,要放在Airplane类实现的文件里PoolAirplane::memPool(sizeof(Airplane));构造函数、析构函数和赋值运算符

构造函数、析构函数和赋值运算用来产生一个新对象并初始化,撤销一个对象并收回占有的内存,为已有的对象赋一个新值。不能有错,必须将他们彻底搞清楚。

11.class内有成员指针并动态配置内存时,一定要有拷贝构造函数,赋值运算符重载classString{public:String(constchar*value);~String();……//没有拷贝构造函数,

//也没有赋值运算符重载

private:char*data;};String::String(constchar*value){if(value){data=newchar[strlen(value)+1];strcopy(data,value);}else{data=newchar[1];*data=“\0”;}}inlineString::~String(){delete[]data;}Stringa(“Hello”);Stringb(“World”);b=a;HelloWorldabdatadata由于没有自定义的赋值函数,只能用C++产生的默认赋值函数,它简单地将b的成员指针data指向a.data,引起字符串“World”占有的内存遗失。而且a.data与b.data指向同一个内存,其中一个被析构时另一个就丢失了。拷贝构造函数用来传值,voiddonothing(Stringla){}Strings=“thetruthisoutofthere”;donothing(s);当函数donothing完成任务后,参数s所含的指针被析构,la被删除。即便la不再使用,将来又一次析构la会造成问题。解决的办法就是自己定义拷贝构造函数,赋值函数重载。如果确信永不使用这些函数,把他们定义为私有函数,而且不实现。一旦出错,编译器会给出错误提示。12构造函数中尽量以初始化代替赋值

一个类中的const成员数据和reference引用数据只能被初始化,不能被赋值。即便没有const成员数据和reference引用数据,初始化也比赋值效率高。构造函数分两个阶段实现:

1.数据成员初始化。

2.调用构造函数。数据成员赋值要调用构造函数,再调用赋值函数,做两次调用影响效率。初始化也容易维护,修改。有一种例外:一个类内有大量数据成员时,赋值比初始化效率高。classManyDataMbs{public:ManyDataMbs()

ManyDataMbs(constManyDataMbs&x);private:inta,b,c,d,e,f,g,h;doublei,j,k,l,m;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.数据成员初始化的次序应该和类内声明的次序相同template<classT>classArray//有上下界的数组{public:Array(intlowBound,inthighBound);……private:vector<T>data;//数组数据存储于一个vector对象data中

size_tsize;//数组中元素的个数

intlBound,hBound;//上下界};template<classT>Array<T>::Array(intlowBound,inthighBound):size(highBound-lowBound+1),lBound(lowBound),hBound(highBound),data(size){}实际初始化中,data先被初始化,然后size,lBound,hBound.这样数组中,究竟有多少个元素无法确定。基类成员总是比导出类先初始化。多重继承时初始化的先后次序要十分小心。14.总是让基类拥有虚析构函数一个军事应用软件classEnemyTarget{public:EnemyTarget(){++numTargets;}EnemyTarget(constEnemyTarget&){++numTargets;}~EnemyTarget(){--numTargets;}staticsize_tnumberOfTargets(){returnnumTargets;}virtualbooldestroy();//摧毁敌方目标是否成功

private:staticsize_tnumTargets;//对象计数器};size_tEnemyTarget::numTargets;//静态成员初始化为0,放在类外classEnemyTank:publicEnemyTarget{public:EnemyTank(){++numTanks;}EnemyTank(constEnemyTank&){++numTanks;}~EnemyTank(){--numTanks;}staticsize_tnumberOfTanks(){returnnumTanks;}virtualbooldestroy();//摧毁敌方坦克是否成功

private:staticsize_tnumTanks;//敌方坦克计数器};EnemyTarget*targetPtr=newEnemyTank;……deletetargetPtr;//未定义,计数出错,影响战斗胜败解决办法,把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.operator=的返回值是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应该是一个变量。结论:必须返回*this;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<Base&>(*this)=rhs;//*this强制转换成基类的引用赋值基类成员

data=rhs.data;return*this;}

拷贝构造函数中要调用基类构造函数。用第一种方法在operator=中检查是否“自己赋值给自己”

classX{……};Xa;X&b=a;//b是a的别名(aliasing)a=b;//自己赋值给自己合法

在赋值函数中要特别谨慎的处理自己的别名赋值给自己的问题。提高效率

先做检查,一发现自己赋值给自己立即返回。导出类的赋值运算重载中一定要先检查,可以节省许多工作确保正确性

赋值运算通常要先将左边对象的资源释放,再行赋值。如果有自己赋值给自己的现象,这个资源可能丢失,不可挽回了。如何判断两个对象是同一个对象?不是对象的内容相同,而是看他们的地址是否相同。

X&X::operator=(constX&rhs){if(this==&rhs)return*this;……}

aliasing问题不限于赋值运算内,只要用到指针或引用,就可能出现。这时我们就要当心,不要误删了有用的资源。类和函数的设计和申明设计一个高效率的类型(class型别),必须先回答下列问题对象如何产生和销毁?确定构造函数和析构函数的设计。对象的初始化和赋值有什么不同?决定构造函数和赋值函数的设计。对象如何传值决定拷贝构造函数的设计确定合法的范围成员数据的定义域确定做什么检查,何时抛出异常判断是否能从已有的类继承如果能继承,注意受基类哪些约束,哪些要用虚函数。允许那种类型转换构造函数可以用作隐式类型转换,显式类型转换要自定义。新类型需要哪些运算和函数确定class的接口。哪些运算和函数必须禁用放到private成员中。新类型的对象可调用哪些函数确定公有成员函数,保护成员函数,私有成员函数。是否通用类型确定是否要用类模板18.努力让接口完满(complete)且最小化客户端接口(clientinterface)指公有成员,一般只有公有函数,不要有公有数据。完满接口允许客户做合理要求的任意事情。最小化接口尽量让函数个数最少。不能有功能重叠的函数。太多函数不容易被理解,不易维护,浪费资源。如果增加一个函数,使新类型更方便使用,就可以增加。T&operator[](intindex);//传回数组的一个元素,可读,可写constT&operator[](intindex)const;//传回数组的一个元素,可读,不可写19.区分成员函数、非成员函数

和友元函数

成员函数可以动态绑定,可以用virtual非成员函数不能用virtual,非成员函数能不做友元尽量不做友元函数。非成员函数要调用类中私有数据成员或私有函数,则一定要声明为友元。不要让operaor<<和operator>>成为类的成员函数,必要时作友元。要让函数式左边对象做类型转换,就不能做成员函数。例子classcomplex{……complexoperator*(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*p;//指针,指向常值字符串char*constp;//常指针,指向固定地址,地址内字符串不一定是常量constchar*constp;//常指针,指向固定地址,内置常字符串constchr*p;charconst*p;//意义相同函数中const可以修饰传回值,参数,成员函数时甚至可以修饰整个函数。函数返回值用const,可以改善函数的安全性,和效率。T&A<T>::operator[](intindex);//传回数组的一个元素,可读,可写Aa(8);cout<<a[2];a[2]=’b’;//正确constT&A<T>::operator[](intindex);//传回数组的一个元素,可读,不可写

Aa(8);cout<<a[2];//正确

a[2]=’b’;//错误constcomplexoperator*(constcomplex&lhs,constcomplex&rhs);complexa,b,c;(a*b)=c;//不允许参数用const可以保证参数值不变,让编译器作检查。const成员函数保证this指针不变。classA{public:……intlength()const;private:intsize;};intA::length()const{if(size<0)return0;//错误不能改变任何数据成员

returnsize;}新C++标准新增保留字mutableclassA{public:……intlength()const;private:

mutableintsize;//可以在任何地点被改动,

//即使在const成员函数中};intA::length()const{if(size<0)return0;//正确

returnsize;}22.尽量使用引用参数传址

passbyreference

拷贝构造函数用来传值passbyvalue,为函数的参数传值,为函数的返回值传值。传值要占用许多资源。classPerson{public:Person();~Person();……private:stringname,address;};

classstudent:publicPerson{public:student();~student();private:stringschoolname,schoolAddress;};

studentreturnstudent(students){returns;}

studentplato;returnstudent(plato);

函数调用中copy构造函数被调用两次,将plato传给参数s,再将函数值返回,析构函数调用两次,析构s,析构函数返回值。更有甚者,基类Person的构造函数也要调用两次。student对象中两个string数据对象要构造,基类Person中两个string数据对象也要构造,plato给s构造四次,返回传值构造四次总共调用12次构造函数,当然还有12次析构函数要调用。免除这些不当成本,改用引用参数传址byreference

conststudent&returnstudent(conststudent&s){returns;}引用参数传址byreference不调用任何构造函数析构函数。虚函数的引用参数是基类时,实际传入派生类对象时可以调用派生类的函数。传值参数没有这样的功能。

引用参数要注意别名(aliasing)问题。23.当你必须传回objebct(传值)时不要传址(引用)

尽可能让事情简单,但不要过于简单。

A.Einstein尽可能让事情有效率,但不要过于有效率。

C++函数必须传回一个对象,就不要传址不要返回引用。不能传回一个不存在的地址,不能传回函数中产生的局部对象的地址。constcomplex

operator*(constcomplex&lhs,constcomplex&rhs){complextemp(lhs.x*rhs.x-lhs.y*rhs.y,lhs.x*rhs.y+lhs.y*rhs.x);returntemp;}

&错误。返回值地址指向局部对象,与局部对象同名,运算执行完毕,局部对象被析构,返回值指向一个不存在的地址。constcomplex&operator*(constcomplex&lhs,constcomplex&rhs){complex*temp(lhs.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;product=one*two*three*four;如何析构这几个operator*中间产生的temp指针呢?24.函数重载和参数缺省之间,谨慎抉择函数重载和参数缺省之间容易引起混淆。如果可以选择一个合理的默认值,并且只需要一种算法,最好使用缺省参数。否则使用重载函数。例:求五个整数的最大值#include<limits>intmax(inta,intb=std::numeric_limits<int>::min(),intc=std::numeric_limits<int>::min(),intd=std::numeric_limits<int>::min(),inte=std::numeric_limits<int>::min(),){inttemp=a>b?a:b;inttemp=temp>c?temp:c;inttemp=temp>d?temp:dinttemp=temp>e?temp:e;}使用max函数对两个参数,三个参数,直至五个参数都有效。但是,计算平均数就找不到合适的默认值,只好重载。一般,构造函数和拷贝构造函数的算法不同,需要重载。25.避免对指针类型和数值类型进行重载

voidf(intx);voidf(string*ps);f(0);//调用那一个?调用f(int)void*constNULL=0;//无类型指针f(NULL);//错误类型不符#defineNULL0f(NULL);//调用f(int)#defineNULL((void*)0))f(NULL);//错误类型不符classNULLClass//类型名可以隐去{public:template<classT>operatorT*(){return0;}//为任意类型T传回一个NULL指针}NULL;f(string*ps);f(NULL);//NULL被转换为string*调用f(string*ps)尽可能避免对指针类型和数值类型进行重载26.防备隐性二义性状态classB;classA{public:A(constB&);//由B可以造出A来};classB{public:operatorA()const;//B可以转换成A};voidf(constA&);Bb;f(b);//错误模棱两可两种方法哪种更好?voidf(int);voidf(char);doubled=6.02;f(d);//模棱两可模棱两可可以潜伏很久,直到爆发。多继承最容易引发模棱两可。classB{public:Bdoit();};classC{public:Cdoit();//放在私有成员中同样不行};classDerived:publicB,publicC{……};Derivedd;d.doit();//模棱两可d.B::doit();//正确d.C;;doit();//正确27.如果不想使用编译器暗自产生的成员函数,明确地拒绝

不允许一个函数存在,只要不把它放进class中。但赋值函数,拷贝构造函数例外,系统会自行产生一个这种函数。不许对象调用某个函数,把它放在私有成员中。但公有函数,友元可以调用。声明一个函数,而不定义它,调用它编译器会指出错误。28.尝试切割globalnamespace

(全局命名空间)标识符重名会引起混乱。同类名词冠以同一词头会使名字太长。建议使用namespace名字空间namespacesdm{constintBOOK_VERSION=2.0;classHandle{……};HandlegetHandle();}有三种方法取用namespace内的名字。voidf1(){usingnamespacesdm;//汇入所有名字

cout<<BOOK_VERSION;Handleh=getHandle();……}voidf2(){usingsdm::BOOK_VERSION;//汇入单个名字

cout<<BOOK_VERSION;//正确

Handleh=getHandle();//错误

……}voidf3(){cout<<sdm::BOOK_VERSION;//没问题只用一次

doubled=BOOK_VERSION;//错误

Handleh=getHandle();//错误

……}两个namespace中有相同的名字,只要标明namespace域名即可区分。

usingnamespacestm;usingnamespacesdm;

stm:;BOOK_VERSION;

sdm::BOOK_VERSION;类与函数的实现

29.避免传回内部数据的handlesclassString{public:String(constchar*value);~String();operatorchar*()const;private:char*data;};

inlineString::operatorchar*()const{returndata;}//潜伏着危险constStringB(“Iloveyou!”);char*str=B;//str与B.data指向同一个地址strcpy(str,“Iloveyou?”);//改变str,也就改变了B.data字符串常量B被改变。去掉operatorchar*()const;中const,可以令常量B不能调用operatorchar*();但String便不能转换成char*.安全的做法是:inlineString::operatorchar*()const{char*copy=newchar[strlen(data)+1];strcpy(copy,data);returncopy;}这个函数比较慢,可能产生内存丢失。classString{public:String(constchar*value);~String();operatorconstchar*()const;private:char*data;};

inlineString::operatorconstchar*()const{returndata;}//又快又安全传回一个常量指针,不可改变。引用也可能发生传回内部数据的handle问题。classString{public:……char&operator[](intindex)const{returndata[index];}private:char*data;};Strings=“Iamnotconst”;s[0]=‘x’;constStringcs=“Iamconst”;cs[0]=‘x’;//改变了常字符串,编译器没发现问题出在const函数返回值是引用。即使不是const函数,“传回handles”也是有问题的。//随机选择一个作家的名字StringsomeFamousAuthor(){switch(rand()%3){//随机函数<stdlib.h>case0:return“MargaretMitchell”;//《飘》的作者

case1:return“StephenKing”;//著名通俗小说家

case3:return“ScottMeyers”//本书作者

}return“”;}这个函数的返回值是一个局部指针handle,函数用毕被销毁因此返回值将是无定义的内存(danglingpointer)。

尽可能避免让一个函数传回danglinghandles,可以提高程序的可靠性。避免写出成员函数返回一个

non-constpointer或reference(引用)

并以之指向较低存取层级的成员classAddress{……};classPerson{public:address&personAddress(){returnaddress;}……private:Addressaddress;//私有数据成员……};Personscott(……);Address&addr=scott.personAddress();//全局对象addr与scott.address同一地址私有成员公开化,可以从外部改变私有数据的值。变成指针也有同样的问题。31.千万不要返回“函数内局部对象的reference”或“函数内以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{……};classPerson{public:address&personAddress(){returnaddress;}……private:Addressaddress;//私有数据成员……};Personscott(……);Address&addrPtr=scott.personAddress();//指针addr指向scott.address同一地址只要把函数返回值改成const类型即可避免外部修改。classAddress{……};classPerson{public:address*personAddress(){returnaddress;}……private:Addressaddress;//私有数据成员……};Personscott(……);Address*addrPtr=scott.personAddress();//指针addr指向scott.address同一地址同样问题只要把函数返回值改成const类型即可避免外部修改。32.尽可能延缓变量定义式的出现//这个函数太早定义变量encryptedstringencryptPassward(conststring&passward){stringencrypted;if(passward.length()<MINIMUM_PASSWARD_LENGTH){throwlogic_error(“Passwardistooshort”);……returnencrypted;}一旦有异常抛出,encrypted定义就是多余的。尽量在变量定义时初始化。33.明智地运用inlineinline内联函数提高效率,编译时实现最佳化。但是增加object目标代码,加大内存开销。太多的inline函数,会减低取出指令的速度(instructionfetch)编译器会因某些理由自动拒绝inline函数,将它当成非inline函数。这时系统发出一个警告。比如太长的函数,virtual函数,不适合inline的函数。构造函数看起来不长,有时继承派生类中的构造函数,比看起来的要长。比如基类的构造函数,new的使用,都使inline失效。inline函数不会自动升级,程序一旦被改动就要重新编译。inline函数应限制用于一些非常平凡的,占有重要效率地位的函数。慎重使用inline函数,便于日后除错。34.将文件之间的编译关系降到最低

一个class在文件file中定义并实现。程序用到这个类的对象就要连接文件file#include<file.h>file文件就与程序发生依赖关系。修改file文件,会引起全程序重编译。C++一个classB中,用到另一classA的对象,A必须先完全定义并实现。如果A写在文件file里,A的任何改变都会引起整个程序重新编译。改进的方法是A中尽量使用指向B类对象的指针或引用。这样,B只要先声明,A的编译不依赖于B.file改变时,A不需要重新编译。A称为Handleclass另一种方法是用抽象类做基类,成为Protocolclass。Protocolclass没有任何实现。其作用只是一个接口。如果引用或指针能够完成任务,就不用对象。如果可能,以class的声明,代替class的实现。尽可能只把class的声明放在头文件里,其余实现放在由客户完成的文件里,不参与编译。头文件尽量不include别的头文件,除非不联不行。继承关系与面向对象设计继承体系是C++与C的更本区别。如果需要一群class,拥有许多共享性质,那就要考虑用基类还是模板。如果classA是根据classB实现,考虑A中应该有一个B类对象还是A继承B.

如果需要设计一个安全类型,通用,而C++标准库未定义,那么应该使用模板还是以泛型void*指针来实现?“说出你的意思,并了解你所说的每一句话。”35.公有继承,“isa”的关系

请牢记:公有继承publicinheritance是一种isa的关系。如果classD公有继承classB,则D是B的subclass子类。D的对象是B的对象,反之不成立。B比D更一般化,D比B更特殊化。可以用B对象的地方,D的对象也可以用。要用D对象的地方,B对象无法效劳。每一匹白马都是马,每一匹马不一定是白马。公孙策“白马非马”,白马是马的真子集,而不相等。马是基类,白马是派生类。C++的继承关系,不同于日常生活,也不同于数学。鸟bird会飞。企鹅penguin是鸟,可企鹅不会飞。如果鸟class中有fly,则企鹅不是鸟的派生类。长方形

正方形?还是正方形

长方形?两者都不对。另外定义一个基类,长方形和正方形都是它的派生类。36.区分接口继承(interfaceinheritance)和实现继承(implementationinheritance)成员函数的接口总是会被继承声明纯虚函数是为了让导出类只继承其接口。纯虚函数也可以定义,即可以提供其实现代码。只有一种方法调用就是写明class域名.声明一般虚函数(非纯)是为了让导出类只继承其接口和

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论