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

下载本文档

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

文档简介

改善程序设计技术的 50个有效做法 第二版2002.3 Scott Meyers 侯 捷 译,如何完成较好的设计 如何避免常见的问题 如何提高效率的一些准则 不是放之四海而皆准的唯一真理,C+新标准新的类型bool,有两个值true, false. typedef int bool; const bool false=0; const bool true=1;,新的转型动作,static_cast(expression) /将表达式expression转为type类型 const_cast(expression) /将常数类型expression转为非常数类型 dynamic_cast(expression) /安全向下转型 见39 reinterpret_cast(expression) /函数指针类型转换 不常用,1. 尽量以const和inline取代#define,#define 是一个宏,只能被预处理, 而不被编译,用它定义的常量甚至不被编 译器看见,因此不能发现使用中的错误。 用#define定义一个简单函数,必须为每一 个参数加上一个括号,容易造成错误。用 内联函数高效准确。,define ratio 1.653 /编译器看不见ratio,只看见1.653 /一旦出错,不会报告,const double ratio=1.653; const char* const name=“Scott Meyers”; /字符串常量,In Class 常量,用静态变量 类内声明,类外定义。 class EngineerConstants private: static const double Factor; ; const double EngineerConstants:Factor=1.35;,2. 尽量以取代,改变旧有的C习惯(shifting from C to C+) 尽量以const和inline取代#define #define 是一个宏,只能被预处理,而不被编译,用它定义的常量甚至不被编译器看见,因此不能发现使用中的错误。 用#define定义一个简单函数,必须为每一个参数加上一个括号,容易造成错误。用内联函数高效准确。,3. 尽量以new和delete取代malloc和free,malloc 和free 不能调用构造函数,析构函数 new 和delete则可。 不能混用new delete malloc free 必要用C库函数时检查是否用到malloc 重新用new和delete改过。,4. 尽量使用C+风格的注释形式,/* */ 要保证成对出现,不小心错一大片。 / 好看好读 可以混合使用 当心! define light_speed 3e8 /m/sec(in a vacum),内存管理(memory management),new 隐式调用构造函数, delete 隐式调用析构函数, 可以重载operator new和operator delete. 不小心运用new 和delete会导致各种错误。,5. 使用相同形式的new和delete,string *a=new string10; delete a;/出错 delete a;/正确 string *b=new string; delete b;/出错 delete b;/正确,typedef string addresslines4;,string *a=new addresslines; delete a;/出错 delete a;/正确 不要对数组类型用typedef, 不容易记住用哪一种delete,6. 记得在析构函数中以delete对付指针成员,如果类中有指针数据成员, 在每个构造函数中为指针成员配置内存,否则将它初始化为0(NULL指针)。 若构造函数中用new配置了内存,一定要 在析构函数中用delete释放 在赋值运算重载时要将原有指针的内存删除,重新分配内存。 要在析构函数中删除这个指针。,不要用delete删除一个未完成初始化的指针, 不要删除一个未分配内存的指针。 不要delete从一个类外面传来的指针。,7. 为内存不足的状况预作准备,不能认为“检查内存是否分配成功” 是多此一 举。否则会出现严重后果。 必须建立一个错误处理策略。当operator new 无 法满足需求时,在抛出异常之前,会调用一个内 存不足处理函数new handler,这个函数由头文件提供。,typedef void (*new_handle)( );,new_handler set_new_handler(new_handler p)throw( ); new_handler是一个函数指针,无参, 无返回值。 函数set_new_handler用来配置new_handler, 参数和返回值都是函数指针,new_handler类型。 它确定新的new_handler函数(参数),保留旧的new_handler函数(返回)。,可以自定义新的new_handler函数, 用set_new_handler确认。,void nomoreMemory( ) cerr “Unable to satisfy for memeoryn” abort( );/exit int main( ) set_new_handler(nomoreMemory); int *pBigDataArray = new int100000000; 当operator new 无法配置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中重载operator new, 和set_new_handler函数,让它调用特定的new_handler函数,而不用系统给出的全局new_handler。,class X public: static new_handler set_new_handler(new_handler p); static void* operator new(size_t siz); private: static new_handler currentHandler; ;,new_handler X:currentHandler;/初始化为0 new_handler X:set_new_handler(new_handler p) new_handler oldHandler = currentHandler; /保留当前new_handler currentHandler = p;/再设置当前new_handler return oldHandler; ,void *X:operator new(size_t size), newHandler globalHandler= std:set_new_handler(currentHandler); /配置新new_handler 保存globalHandler void *memory; try memory = :oprator new(size); /试分配内存 catch(std:bad_alloc /调用一次特定处理方法,用毕恢复,/应用,void nomoreMemory( ); X:set_new_handler(nomoreMemory); X *px1 = new X; /如果内存分配失败,调用nomoreMemory( ) string *ps = new string; /如果内存分配失败,调用globalHandler X: set_new_handler(0); X *px2 = new X; /如果内存分配失败,立即抛出异常,可以做一个混合风格基类 允许 “设定class专属new_handler”,template class NewHandlerSupport public: static new_handler set_ new_handler(new_handler p); static void* operator new(size_t siz); private: static new_handler currentHandler; ;,template,new_handler NewHandlerSupport: set_new_handler(new_handler p) new_handler oldHandler = currentHandler;/保留当前new_handler currentHandler = p;/再设置当前new_handler return oldHandler; ,template,void * NewHandlerSupport:operator new(size_t size) newHandler globalHandler= std:set_new_handler(currentHandler); /配置新new_handler 保存globalHandler void *memory; try memory = :oprator new(size); /试分配内存 catch(std:bad_alloc ,new_handler NewHandlerSupport :currentHandler; /初始化为0,class X : public NewHandlerSupport /不必声明set_new_handler和operator new 类X 不必改动原有的程序代码,就可以继续运作。,1993年前C+要求operator new在无法满足内存需 求时返回0,新标准则是抛出一个bad_alloc类型 异常。 失败便转为0的传统被保留为“nothrow”不抛出异 常。头文件中定义了一个nothrow对象 class Widget; Widget *pw1 = new Widget; /如果失败抛出std:bad_alloc异常 if(pw1=0)/无效 widget *wp2 = new(nothrow) Widget; /如果失败,返回0 if(wp2=0)/有效,8. 撰写operator new和operator delete时 应遵守的公约,当你有必要重载operator new时,你的new函数的行为应该与系统原有的new函数的行为保持一致。 应该有正确的返回值:返回一个指针指向分配的内存。如果内存不足,抛出一个bad_alloc类型的异常。 不能覆盖系统原有的new函数。,/new 函数的伪码,void * operator new(size_t size) if(size=0)size=1; /将0内存需求,看成1内存需求,避免与无内存混淆 while(true)/无穷循环 直到内存被分配 或抛出异常 attempt to allocate size bytes; if(the allocation was successful) return (a pointer to the memory); new_handle globalHandle = set_new_handler(0) /利用NULL,找出目前的错误处理函数 set_new_handler(globalHandler); /重新设定为原本的函数 if(globalHandler) (*globalHandler)( ) else throw std:bad_alloc( ); ,无穷循环可以让更多的内存可用, 或安装一个不同的new_handler, 或卸载new_handler, 或抛出一个异常, 或直接结束程序。,operator new 可以被继承, 但要小心,否则会导致问题,class Base public: static void*oprator new(size_t size); ; class Derived : public Base ;/导出类中没有operator new 函数 Derived *p = new Derived; /调用Base类中的operator new出错 这里导出类内存比基类要大。,改进的办法:,void *operator new(size_t size) if(size != sizeof (Base) return : oprator new(size); /回到标准operator new函数 ,重写operator delete,void operator delete(void*rawMemory) if(rawMemory = 0)return; /与C+标准delete保持一致 Deallocate the memory pointed to by rawMemory; return; ,/member版,class Base public: static void *operator new(size_t size); static void operator delete(void* rawMemory,size_t size); ,void Base:operator delete( void* rawMemory,size_t size); if(rawMemory =0)return; if(size!=sizeof(Base) /如果大小错误 : operator delete(rawMemory); /用标准版delete处理 return; deallocate the memory pointed to by rawmeMemory; return; ,9. 避免覆盖new的正规形式,解决办法 (1) 再写的一个专用的operator new函数,让它支 持正规的new class X public: void f( ); static void* operator new(size_t size, new_handler p); static void* operator new(sise_t size) return :operator new(size); ;,X *p1=new (specialErrorHandler) X; /调用X: operator new(size_t size, new_handler p); X *p2=new X; /调用X: operator new(size_t size); (2) 为operator new的每一个参数提供默认值 (缺省值),10. 如果写了一个operator new 不要忘记写一个operator delete,需要动态分配大量小额内存空间的应用程序, 有时需要重载operator new。 class AirplaneRep ; class Airplane public: private: AirplaneRep *rep; /唯一数据成员是指针 ;,Airplane *p=new Airplane; /要求内存不大,分配的内存比实际所需要的内存要大, 这是为了delete 这块内存时,系统能知道其大小。,pa,为了节省内存需要定制内存管理。,定制内存管理。,class Airplane public: static void* operator new( size_t size); static void operator delete(void*deadObject, size_t size); private: union AirplaneRep *rep; Airplane *next; ;/两个指针公用一个内存 static const int BLOCK_SIZE; static Airplane * headOfFreeList; /用链表配置一片内存,整个类只须一个链 ;,void* Airplane :operator new( size_t size); if(size != sizeof(Airplane) return :operator new(size); Airplane *p= headOfFreeList; /p指向链表头 if(p) headOfFreeList = p-next; /表头后移, p可用 else Airplane *newBlock = static_cast(:operator new(BLOCK_SIZE*sizeof(Airplane); for(int i=1; iBLOCKSIZE-1;+i) /保留第一块 newBlocki.next = ,只有当 :operator new失败时,这里的operator new 才失败。这时:operator new会调用new_handler直 到抛出异常,因此我们不需要再写一次new_handler处理 具体实现文件中要先对静态成员初始化, Airplane * Airplane:headOfFreeList; / headOfFreeList置0 const int Airplane:BLOCK_SIZE = 512; 这个版本的operator new 可以运作良好,速度快过两个数量级。,还要在Airplane类中写一个operator delete void Airplane:operator delete(void*deadObject, size_t size) if(deadObject=0) return; if(size !=sizeof(Airplane) :operator delete(deadObject); /与operator new处理保持一致 return; Airplane *carcass = static_cast(deadObject); carcass-next =headOfFreeList; HeadOfFreeList = carcass; ,如果没有定义相应的delete函数,而使用了原有的delete, 结果会出现意想不到的错误,有时是严重的错误。 如果用member版本不要忘记定义virtual 析构函数。 这里的delete函数没有memory leak 问题。 这是因为用了memory pool 一次分配一块内存,逐步使用逐步释放, 不必再专门释放memory pool.,定义一个memory pool 类,使每一个pool对象都是一个内存配置器。,class Pool public: Pool(size_t n); void* alloc( size_t n);/为一个对象配置足够 /的内存遵循operator new的规矩 void free(void* p, size_t n);/将p的内存送回 /pool遵循operator delete的规矩 pool( );/释放pool中所有内存 ;,用Pool 对象来配置内存,当被销毁时,配置的内存自动被释放。 于是memory leak 就可以避免。,class Airplane public: static void* operator new( size_t size); static void operator delete(void*p, size_t size); private: AirplaneRep *rep; static Pool memPool; /Airplane的memory pool ;,inline void Airline:operator new(size_t size) return memPool.alloc(size); inline void Airline:operator delete(void* p, size_t size) memPool.free(p, size); 为Airplane 的memPool初始化, 要放在Airplane 类实现的文件里 Pool Airplane:memPool(sizeof(Airplane);,构造函数、析构函数和赋值运算符,构造函数、析构函数和赋值运算用来产生一个新对象并初始化,撤销一个对象并收回占有的内存,为已有的对象赋一个新值。 不能有错,必须将他们彻底搞清楚。,11. class内有成员指针并动态配置内存时,一定要有拷贝构造函数,赋值运算符重载,class String public: String(const char*value); String( ); /没有拷贝构造函数, /也没有赋值运算符重载 private: char*data; ;,String:String(const char *value) if(value) data = new charstrlen(value)+1; strcopy(data, value); else data = new char1; *data = “0”; inline String:String( )delete data;,String a( “Hello” ); String b(“World” ); b = a;,Hello,World,a,b,data,data,由于没有自定义的赋值函数,只能用C+产生的默认赋值函数, 它简单地将b的成员指针data指向a.data,引起 字符串“World”占有的内存遗失。 而且a.data与b.data指向同一个内存,其中一个被析构时另一个就丢失了。,拷贝构造函数用来传值, void donothing(String la) String s= “the truth is out of there”; donothing(s); 当函数donothing完成任务后,参数s所含的指针被析构,la被删除。 即便la不再使用,将来又一次析构la会造成问题。,解决的办法就是自己定义拷贝构造函数,赋值函数重载。 如果确信永不使用这些函数,把他们定义为私有函数,而且不实现。 一旦出错,编译器会给出错误提示。,12构造函数中尽量以初始化代替赋值,一个类中的const成员数据和reference引用数据只能被初始化,不能被赋值。 即便没有const成员数据和reference引用数据,初始化也比赋值效率高。 构造函数分两个阶段实现: 1. 数据成员初始化。 2. 调用构造函数。 数据成员赋值要调用构造函数,再调用赋值函数,做两次调用影响效率。 初始化也容易维护,修改。,有一种例外: 一个类内有大量数据成员时,赋值比初始化效率高。,class ManyDataMbs public: ManyDataMbs() ManyDataMbs(const ManyDataMbs,void ManyDataMbs:init( ) a=b=c=d=e=f=g=h=1; i=j=k=l=m=0; ManyDataMbs:ManyDataMbs( ) init( ); ManyDataMbs:ManyDataMbs(const ManyDataMbs ,静态数据成员static class member不应该在构造函数中初始化。 静态数据成员只能初始化一次,不能初始化多次。,12. 数据成员初始化的次序应该和类内声明的次序相同,template class Array /有上下界的数组 public: Array(int lowBound, int highBound); private: vector data; /数组数据存储于一个vector对象data中 size_t size; /数组中元素的个数 int lBound, hBound; /上下界 ;,template Array:Array(int lowBound, int highBound) : size(highBound-lowBound+1), lBound(lowBound), hBound(highBound), data(size) 实际初始化中,data先被初始化,然后 size, lBound, hBound. 这样数组中,究竟有多少个元素无法确定。 基类成员总是比导出类先初始化。多重继承时初始化的先后次序要十分小心。,14. 总是让基类拥有虚析构函数,一个军事应用软件 class EnemyTarget public: EnemyTarget( )+ numTargets; EnemyTarget(const EnemyTarget /静态成员初始化为0,放在类外,class EnemyTank : public EnemyTarget public: EnemyTank ( )+ numTanks; EnemyTank (const EnemyTank /敌方坦克计数器 ;,EnemyTarget *targetPtr = new EnemyTank; delete targetPtr; /未定义,计数出错,影响战斗胜败 解决办法,把EnemyTarget类中的析构函数定义为virtual即可。 几乎所有的基类都有虚函数,只要有一个虚函数,就要把析构函数定义为虚函数。 没有虚函数的类,有继承派生类对象析构,也要定义虚析构函数。 但虚函数会增加内存开销。完全不必要时不要用虚析构函数。 声明一个抽象类,可以加一个纯虚析构函数。,15. 让operator=返回*this的引用reference,C语言中operator= 的原型 C z.operator=的返回值是y.operator=的实参。他们应该有相同的类型。 但不要让operator=返回void类型,const类型,Strin,16. 在operator=中为所有的数据成员赋值,基类中这不成问题,在派生类中要小心。 正确的赋值运算 Derived ,Derived 拷贝构造函数中要调用基类构造函数。 用第一种方法,在operator=中检查是否“自己赋值给自己”,class X; X a; X /自己赋值给自己 合法 在赋值函数中要特别谨慎的处理自己的别名赋值给自己的问题。,提高效率 先做检查,一发现自己赋值给自己立即返回。导出类的赋值运算重载中一定要先检查,可以节省许多工作 确保正确性 赋值运算通常要先将左边对象的资源释放,再行赋值。如果有自己赋值给自己的现象,这个资源可能丢失,不可挽回了。,如何判断两个对象是同一个对象? 不是对象的内容相同,而是看他们的地址是否相同。 X aliasing问题不限于赋值运算内,只要用到指针或引用,就可能出现。这时我们就要当心,不要误删了有用的资源。,类和函数的设计和申明,设计一个高效率的类型(class 型别), 必须先回答下列问题 对象如何产生和销毁? 确定构造函数和析构函数的设计。 对象的初始化和赋值有什么不同? 决定构造函数和赋值函数的设计。 对象如何传值 决定拷贝构造函数的设计,确定合法的范围 成员数据的定义域 确定做什么检查,何时抛出异常 判断是否能从已有的类继承 如果能继承,注意受基类哪些约束,哪些要用虚函数。 允许那种类型转换 构造函数可以用作隐式类型转换,显式类型转换要自定义。,新类型需要哪些运算和函数 确定class的接口。 哪些运算和函数必须禁用 放到private成员中。 新类型的对象可调用哪些函数 确定公有成员函数,保护成员函数, 私有成员函数。 是否通用类型 确定是否要用类模板,18努力让接口完满(complete)且最小化,客户端接口(client interface)指公有成员,一般只有公有函数,不要有公有数据。 完满接口 允许客户做合理要求的任意事情。 最小化接口 尽量让函数个数最少。不能有功能重叠的函数。太多函数不容易被理解,不易维护,浪费资源。,如果增加一个函数,使新类型更方便使用,就可以增加。 T /传回数组的一个元素,可读,不可写,19区分成员函数、非成员函数 和友元函数,成员函数可以动态绑定,可以用virtual 非成员函数不能用virtual, 非成员函数能不做友元尽量不做友元函数。 非成员函数要调用类中私有数据成员或私有函数,则一定要声明为友元。 不要让operaor成为类的成员函数, 必要时作友元。 要让函数式左边对象做类型转换,就不能做成员函数。,例子 class complex complex operator*(complex rhs)const; private: float x, y; ; complex a(1,2),b(1.5,4); a=a*b;/正确 a=a*2;/可以 a=2*a;/出错 只能声明为非成员函数 const complex operator*(const complex,20避免将数据成员设置为公有数据,让公有成员都是函数,可以保持一致性。 将数据成员声明为私有成员或保护成员,可以确保数据的安全。,21尽可能使用const,使用const可以让编译器知道某值不能改变,编译器会确保这个条件不会被改变。 const char* p; /指针,指向常值字符串 char* const p;/常指针,指向固定地址,地址内字符串不一定是常量 const char* const p;/ 常指针,指向固定地址,内置常字符串 const chr *p; char const *p; /意义相同,函数中const 可以修饰传回值,参数,成员函数时甚至可以修饰整个函数。,函数返回值用 const, 可以改善函数的安全性,和效率。 T a2=b;/正确,const T /正确 a2=b;/错误,const complex operator*(const complex /不允许,参数用const 可以保证参数值不变,让编译器作检查。 const成员函数保证this指针不变。 class A public: int length( )const; private: int size; ; int A:length( )const if(size0)return 0; /错误 不能改变任何数据成员 return size; ,新C+标准 新增保留字 mutable class A public: int length( )const; private: mutable int size;/可以在任何地点被改动, /即使在const成员函数中 ; int A:length( )const if(size0)return 0; /正确 return size; ,22尽量使用引用参数传址 pass by reference,拷贝构造函数用来传值pass by value, 为函数的参数传值, 为函数的返回值传值。 传值要占用许多资源。,class Person public: Person( ); Person( ); private: string name, address; ;,class student : public Person public: student( ); student( ); private: string schoolname,schoolAddress; ; student returnstudent(student s) return s; student plato; returnstudent(plato);,函数调用中copy构造函数被调用两次,将plato传给参数s, 再将函数值返回,析构函数调用两次,析构s,析构函数返回值。 更有甚者,基类Person 的构造函数也要调用两次。student对象中两个string数据对象要构造,基类Person中两个string数据对象也要构造,plato给s构造四次, 返回传值构造四次 总共调用12次构造函数,当然还有12次析构函数要调用。,免除这些不当成本,改用引用参数传址by reference const student 引用参数传址by reference 不调用任何构造函数析构函数。 虚函数的引用参数是基类时,实际传入派生类对象时可以调用派生类的函数。 传值参数没有这样的功能。 引用参数要注意别名(aliasing)问题。,23当你必须传回objebct(传值)时不要传址(引用),尽可能让事情简单,但不要过于简单。 A.Einstein 尽可能让事情有效率,但不要过于有效率。 C+ 函数必须传回一个对象,就不要传址不要返回引用。 不能传回一个不存在的地址,不能传回函数中产生的局部对象的地址。,const complex operator*(const complex ,& 错误。返回值地址指向局部对象,与局部对象同名,运算执行完毕,局部对象被析构,返回值指向一个不存在的地址。,const complex 指针temp被析构,内存已丢失。,const complex 如何析构这几个operator*中间产生的temp指针呢?,24函数重载和参数缺省之间,谨慎抉择,函数重载和参数缺省之间容易引起混淆。 如果可以选择一个合理的默认值,并且只需要一种算法,最好使用缺省参数。 否则使用重载函数。,例:求五个整数的最大值 #include int max(int a, int b=std:numeric_limits:min( ), int c=std:numeric_limits:min( ), int d=std:numeric_limits:min( ), int e=std:numeric_limits:min( ),) int temp=ab?a:b; int temp=tempc? temp:c; int temp= temp d? temp:d int temp= temp e? temp:e; ,使用max函数对两个参数,三个参数,直至五个参数都有效。 但是,计算平均数就找不到合适的默认值,只好重载。 一般,构造函数和拷贝构造函数的算法不同,需要重载。,25避免对指针类型和数值类型进行重载,void f(int x); void f(string *ps); f(0); /调用那一个?调用f(int) void * const NULL=0;/无类型指针 f(NULL);/错误 类型不符 #define NULL 0 f(NULL);/调用f(int) #define NULL(void*) 0) f(NULL);/错误 类型不符,class NULLClass/类型名可以隐去 public: template operator T*( ) return 0;/为任意类型T传回一个NULL指针 NULL; f(string*ps); f(NULL);/NULL被转换为string*调用f(string*ps) 尽可能避免对指针类型和数值类型进行重载,26防备隐性二义性状态,class B; classA public: A(const B/错误 模棱两可 两种方法哪种更好?,void f(int); void f(char); double d=6.02; f(d);/模棱两可 模棱两可可以潜伏很久,直到爆发。,多继承最容易引发模棱两可。 class B public: B doit( ); ; class C public: C doit( ); /放在私有成员中同样不行 ; class Derived :public B, public C ; Derived d; d.doit( );/模棱两可 d.B:doit( );/正确 d.C;doit( );/正确,27如果不想使用编译器暗自产生的成员函数,明确地拒绝,不允许一个函数存在,只要不把它放进 class中。但赋值函数,拷贝构造函数例外,系统会自行产生一个这种函数。 不许对象调用某个函数,把它放在私有成员中。但公有函数,友元可以调用。 声明一个函数,而不定义它,调用它编译器会指出错误。,28尝试切割global namespace (全局命名空间),标识符重名会引起混乱。同类名词冠以同一 词头会使名字太长。 建议使用namespace名字空间 namespace sdm const int BOOK_VERSION = 2.0; class Handle; Handle getHandle( ); ,有三种方法取用namespace内的名字。,void f1( ) using namespace sdm;/汇入所有名字 coutBOOK_VERSION; Handle h=getHandle( ); ,void f2( ) using sdm: BOOK_VERSION;/汇入单个名字 coutBOOK_VERSION;/正确 Handle h=getHandle( );/错误 void f3( ) cout sdm: BOOK_VERSION;/没问题只用一次 double d=BOOK_VERSION;/错误 Handle h=getHandle( );/错误 ,两个namespace中有相同的名字,只要标明namespace域名即可区分。 using namespace stm; using namespace sdm; stm:;BOOK_VERSION; sdm:BOOK_VERSION;,类与函数的实现 29避免传回内部数据的handles,class Stringpublic: String(const char *value); String( ); operator char *( ) const; private: char * data; inline String:operator char *( )const return data; /潜伏着危险 const String B(“I love you!”); char * str = B;/str 与 B.data指向同一个地址 strcpy(str, “I love you?”);/改变str, 也就改变了B.data 字符串常量B 被改变。,去掉operator char*( )const;中 const , 可以令常量B不能调用operator char *( ); 但String 便不能转换成char*. 安全的做法是: inline String:operator char *( )const char* copy = new charstrlen(data)+1; strcpy(copy, data); return copy; 这个函数比较慢,可能产生内存丢失。,class String public: String(const char *value); String( ); operator const char *( ) const; private: char * data; ; inline String:operator const char *( )const return data; /又快又安全 传回一个常量指针,不可改变。,引用也可能发生传回内部数据的handle问题。 class String public: char /改变了常字符串,编译器没发现 问题出在const 函数返回值是引用。,即使不是const 函数,“传回handles”也是有问题的。 /随机选择一个作家的名字 String someFamousAuthor( ) switch( rand( )%3) /随机函数 case 0: return “Margaret Mitchell”; /飘的作者 case 1: return “Stephen King”; /著名通俗小说家 case 3: return “Scott Meyers” /本书作者 return “ ”; ,这个函数的返回值是一个局部指针handle,函数用毕被销毁 因此返回值将是无定义的内存(dangling pointer)。 尽可能避免让一个函数传回dangling handles,可以提高程序的可靠性。,避免写出成员函数返回一个 non-const pointer或reference(引用) 并以之指向较低存取层级的成员,class Address; class Person public: address /私有数据成员 ;,Person scott(); Address /全局对象addr与scott.address 同一地址 私有成员公开化,可以从外部改变私有数据的值。 变成指针也有同样的问题。,31千万不要返回“函数内局部对象的reference”或“函数内以new获得的指针所指的对象”,“传回一个引用(reference),指向局部对象”,函数用毕返回时,局部对象被析构,变成引用一个不存在的对象。 const complex 错误。返回值地址指向局部对象,与局部对象同名,运算执行完毕,局部对象被析构,返回值指向一个不存在的地址。,class Address; class Person public: address /指针addr指向scott.address 同一地址 只要把函数返回值改成const类型即

温馨提示

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

评论

0/150

提交评论