




已阅读5页,还剩167页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
改善程序设计技术的50个有效做法第二版2002 3ScottMeyers侯捷译 如何完成较好的设计如何避免常见的问题如何提高效率的一些准则不是放之四海而皆准的唯一真理 C 新标准新的类型bool 有两个值true false typedefintbool constboolfalse 0 constbooltrue 1 新的转型动作 static cast expression 将表达式expression转为type类型const cast expression 将常数类型expression转为非常数类型dynamic cast expression 安全向下转型见39reinterpret cast expression 函数指针类型转换不常用 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 尽量以取代 scanfprintf函数不能扩充用来输入输出自定义类型的变量 cin i x cout i x 可以扩展 方便得多 改变旧有的C习惯 shiftingfromCtoC 尽量以const和inline取代 define define是一个宏 只能被预处理 而不被编译 用它定义的常量甚至不被编译器看见 因此不能发现使用中的错误 用 define定义一个简单函数 必须为每一个参数加上一个括号 容易造成错误 用内联函数高效准确 3 尽量以new和delete取代malloc和free malloc和free不能调用构造函数 析构函数new和delete则可 不能混用newdeletemallocfree必要用C库函数时检查是否用到malloc重新用new和delete改过 4 尽量使用C 风格的注释形式 要保证成对出现 不小心错一大片 好看好读可以混合使用当心 definelight speed3e8 m sec inavacum 内存管理 memorymanagement new隐式调用构造函数 delete隐式调用析构函数 可以重载operatornew和operatordelete 不小心运用new和delete会导致各种错误 5 使用相同形式的new和delete string a newstring 10 deletea 出错delete a 正确string b newstring delete b 出错deleteb 正确 typedefstringaddresslines 4 string a newaddresslines deletea 出错delete a 正确不要对数组类型用typedef 不容易记住用哪一种delete 6 记得在析构函数中以delete对付指针成员 如果类中有指针数据成员 在每个构造函数中为指针成员配置内存 否则将它初始化为0 NULL指针 若构造函数中用new配置了内存 一定要在析构函数中用delete释放在赋值运算重载时要将原有指针的内存删除 重新分配内存 要在析构函数中删除这个指针 不要用delete删除一个未完成初始化的指针 不要删除一个未分配内存的指针 不要delete从一个类外面传来的指针 7 为内存不足的状况预作准备 不能认为 检查内存是否分配成功 是多此一举 否则会出现严重后果 必须建立一个错误处理策略 当operatornew无法满足需求时 在抛出异常之前 会调用一个内存不足处理函数newhandler 这个函数由头文件提供 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 调用一次特定处理方法 用毕恢复 应用 voidnomoreMemory X set new handler nomoreMemory X px1 newX 如果内存分配失败 调用nomoreMemory string ps newstring 如果内存分配失败 调用globalHandlerX set new handler 0 X px2 newX 如果内存分配失败 立即抛出异常 可以做一个混合风格基类允许 设定class专属new handler templateclassNewHandlerSupport public staticnew handlerset new handler new handlerp staticvoid operatornew size tsiz private staticnew handlercurrentHandler template new handlerNewHandlerSupport set new handler new handlerp new handleroldHandler currentHandler 保留当前new handlercurrentHandler p 再设置当前new handlerreturnoldHandler template void NewHandlerSupport operatornew size tsize newHandlerglobalHandler std set new handler currentHandler 配置新new handler保存globalHandlervoid memory try memory opratornew size 试分配内存 catch std bad alloc new handlerNewHandlerSupport currentHandler 初始化为0 classX publicNewHandlerSupport 不必声明set new handler和operatornew 类X不必改动原有的程序代码 就可以继续运作 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函数的行为应该与系统原有的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函数 重写operatordelete voidoperatordelete 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 为了节省内存需要定制内存管理 定制内存管理 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 operatornew BLOCK SIZE sizeof Airplane for inti 1 i BLOCKSIZE 1 i 保留第一块newBlock i next 只有当 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 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 Hello World a b data data 由于没有自定义的赋值函数 只能用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 voidManyDataMbs init a b c d e f g h 1 i j k l m 0 ManyDataMbs ManyDataMbs init ManyDataMbs ManyDataMbs constManyDataMbs 静态数据成员staticclassmember不应该在构造函数中初始化 静态数据成员只能初始化一次 不能初始化多次 12 数据成员初始化的次序应该和类内声明的次序相同 templateclassArray 有上下界的数组 public Array intlowBound inthighBound 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 这样数组中 究竟有多少个元素无法确定 基类成员总是比导出类先初始化 多重继承时初始化的先后次序要十分小心 14 总是让基类拥有虚析构函数 一个军事应用软件classEnemyTarget public EnemyTarget numTargets EnemyTarget constEnemyTarget 静态成员初始化为0 放在类外 classEnemyTank publicEnemyTarget public EnemyTank numTanks EnemyTank constEnemyTank 敌方坦克计数器 EnemyTarget targetPtr newEnemyTank deletetargetPtr 未定义 计数出错 影响战斗胜败解决办法 把EnemyTarget类中的析构函数定义为virtual即可 几乎所有的基类都有虚函数 只要有一个虚函数 就要把析构函数定义为虚函数 没有虚函数的类 有继承派生类对象析构 也要定义虚析构函数 但虚函数会增加内存开销 完全不必要时不要用虚析构函数 声明一个抽象类 可以加一个纯虚析构函数 15 让operator 返回 this的引用reference C语言中operator 的原型Cz operator 的返回值是y operator 的实参 他们应该有相同的类型 但不要让operator 返回void类型 const类型 Strin 16 在operator 中为所有的数据成员赋值 基类中这不成问题 在派生类中要小心 正确的赋值运算Derived Derived 拷贝构造函数中要调用基类构造函数 用第一种方法 在operator 中检查是否 自己赋值给自己 classX Xa X 自己赋值给自己合法在赋值函数中要特别谨慎的处理自己的别名赋值给自己的问题 提高效率先做检查 一发现自己赋值给自己立即返回 导出类的赋值运算重载中一定要先检查 可以节省许多工作确保正确性赋值运算通常要先将左边对象的资源释放 再行赋值 如果有自己赋值给自己的现象 这个资源可能丢失 不可挽回了 如何判断两个对象是同一个对象 不是对象的内容相同 而是看他们的地址是否相同 X aliasing问题不限于赋值运算内 只要用到指针或引用 就可能出现 这时我们就要当心 不要误删了有用的资源 类和函数的设计和申明 设计一个高效率的类型 class型别 必须先回答下列问题对象如何产生和销毁 确定构造函数和析构函数的设计 对象的初始化和赋值有什么不同 决定构造函数和赋值函数的设计 对象如何传值决定拷贝构造函数的设计 确定合法的范围成员数据的定义域确定做什么检查 何时抛出异常判断是否能从已有的类继承如果能继承 注意受基类哪些约束 哪些要用虚函数 允许那种类型转换构造函数可以用作隐式类型转换 显式类型转换要自定义 新类型需要哪些运算和函数确定class的接口 哪些运算和函数必须禁用放到private成员中 新类型的对象可调用哪些函数确定公有成员函数 保护成员函数 私有成员函数 是否通用类型确定是否要用类模板 18 努力让接口完满 complete 且最小化 客户端接口 clientinterface 指公有成员 一般只有公有函数 不要有公有数据 完满接口允许客户做合理要求的任意事情 最小化接口尽量让函数个数最少 不能有功能重叠的函数 太多函数不容易被理解 不易维护 浪费资源 如果增加一个函数 使新类型更方便使用 就可以增加 T 传回数组的一个元素 可读 不可写 19 区分成员函数 非成员函数和友元函数 成员函数可以动态绑定 可以用virtual非成员函数不能用virtual 非成员函数能不做友元尽量不做友元函数 非成员函数要调用类中私有数据成员或私有函数 则一定要声明为友元 不要让operaor 成为类的成员函数 必要时作友元 要让函数式左边对象做类型转换 就不能做成员函数 例子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 20 避免将数据成员设置为公有数据 让公有成员都是函数 可以保持一致性 将数据成员声明为私有成员或保护成员 可以确保数据的安全 21 尽可能使用const 使用const可以让编译器知道某值不能改变 编译器会确保这个条件不会被改变 constchar p 指针 指向常值字符串char constp 常指针 指向固定地址 地址内字符串不一定是常量constchar constp 常指针 指向固定地址 内置常字符串constchr p charconst p 意义相同 函数中const可以修饰传回值 参数 成员函数时甚至可以修饰整个函数 函数返回值用const 可以改善函数的安全性 和效率 Ta 2 b 正确 constT 正确a 2 b 错误 constcomplexoperator constcomplex 不允许 参数用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次析构函数要调用 免除这些不当成本 改用引用参数传址byreferenceconststudent 引用参数传址byreference不调用任何构造函数析构函数 虚函数的引用参数是基类时 实际传入派生类对象时可以调用派生类的函数 传值参数没有这样的功能 引用参数要注意别名 aliasing 问题 23 当你必须传回objebct 传值 时不要传址 引用 尽可能让事情简单 但不要过于简单 A Einstein尽可能让事情有效率 但不要过于有效率 C 函数必须传回一个对象 就不要传址不要返回引用 不能传回一个不存在的地址 不能传回函数中产生的局部对象的地址 constcomplexoperator constcomplex 错误 返回值地址指向局部对象 与局部对象同名 运算执行完毕 局部对象被析构 返回值指向一个不存在的地址 constcomplex 指针temp被析构 内存已丢失 constcomplex如何析构这几个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 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 templateoperatorT return0 为任意类型T传回一个NULL指针 NULL f string ps f NULL NULL被转换为string 调用f string ps 尽可能避免对指针类型和数值类型进行重载 26 防备隐性二义性状态 classB classA public A constB 错误模棱两可两种方法哪种更好 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 避免传回内部数据的handles classString 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 改变了常字符串 编译器没发现问题出在const函数返回值是引用 即使不是const函数 传回handles 也是有问题的 随机选择一个作家的名字StringsomeFamousAuthor switch rand 3 随机函数case0 return MargaretMitchell 飘 的作者case1 return StephenKing 著名通俗小说家case3 return ScottMeyers 本书作者 return 这个函数的返回值是一个局部指针handle 函数用毕被销毁因此返回值将是无定义的内存 danglingpointer 尽可能避免让一个函数传回danglinghandles 可以提高程序的可靠性 避免写出成员函数返回一个non constpointer或reference 引用 并以之指向较低存取层级的成员 classAddress classPerson public address 私有数据成员 Personscott Address 全局对象addr与scott address同一地址私有成员公开化 可以从外部改变私有数据的值 变成指针也有同样的问题 31 千万不要返回 函数内局部对象的reference 或 函数内以new获得的指针所指的对象 传回一个引用 reference 指向局部对象 函数用毕返回时 局部对象被析构 变成引用一个不存在的对象 constcomplex 错误 返回值地址指向局部对象 与局部对象同名 运算执行完毕 局部对象被析构 返回值指向一个不存在的地址 classAddress classPerson public address 指针addr指向scott address同一地址只要把函数返回值改成const类型即可避免外部修改 classAddress classPerson public address personAddress returnaddress private Addressaddress 私有数据成员 Personscott Address addrPtr scott personAddress 指针addr指向scott address同一地址同样问题只要把函数返回值改成const类型即可避免外部修改 32 尽可能延缓变量定义式的出现 这个函数太早定义变量encryptedstringencryptPassward conststring 一旦有异常抛出 encrypted定义就是多余的 尽量在变量定义时初始化 33 明智地运用inline inline内联函数提高效率 编译时实现最佳化 但是增加object目标代码 加大内存开销 太多的inline函数 会减低取出指令的速度 instructionfetch 编译器会因某些理由自动拒绝inline函数 将它当成非inline函数 这时系统发出一个警告 比如太长的函数 virtual函数 不适合inline的函数 构造函数看起来不长 有时继承派生类中的构造函数 比看起来的要长 比如基类的构造函数 new的使用 都使inline失效 inline函数不会自动升级 程序一旦被改动就要重新编译 inline函数应限制用于一些非常平凡的 占有重要效率地位的函数 慎重使用inline函数 便于日后除错 34 将文件之间的编译关系降到最低 一个class在文件file中定义并实现 程序用到这个类的对象就要连接文件file includefile文件就与程序发生依赖关系 修改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域名 声明一般虚函数 非纯 是为了让导出类只继承其接口和缺省行为 声明非虚拟函数是为了让导出类只继其接口及其实现 继承类中行为不变的函数不应声名为虚函数 37 绝对不要重新定义继承而来的非虚函数 如果基类中定义了一个非虚函数 派生类同时继承其接口和实现 派生类的对象可直接调用基类的这个函数 行为不变 如果派生类中重新定义继承而来的非虚函数 基类中的函数将被覆盖 函数调用将发生变化 这就是说本应该定以为虚函数 如果不希望函数行为有变化 则不应该重定义非虚函数 38 绝对不要重新定义继承而来的缺省参数值 虚函数的调用由实际引入的参数对象确定 动态绑定 后联编 但如果函数有缺省值 缺省值则是静态绑定 调用一个有缺省值的虚函数 非缺省参数的派生类型实参确定调用哪一个函数 但缺省参数依然用基类的值 重新定义的缺省参数并不起任何作用 反而引起混淆 要么不定义虚函数参数的缺省值 有了缺省值就不可以重定义 39 避免在继承中做向下转型 castdown 动作 classPerson classBankAccount BankAccount constPerson primaryOwner constPerson jointOwner virtual BankAccount virtualvoidmakeDepoisit doubleamount 0 存款virtualvoidmakeWithdrawal doubleamount 0 取款virtualvoidbalance const 0 余额 classSavingAccount publicBankAccout public SavingAccount constPerson primaryOwner constPerson jointOwner SavingAcc
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年环保知识竞赛试题及完整答案
- 2025年公务员面试试卷及答案
- 2025年公文写作基础知识竞赛试题库及答案
- 供热运营专业知识培训课件
- 2025年强振加速度仪项目合作计划书
- 2025年抗狂犬病血清合作协议书
- 2025年中石化:石油脑项目建议书
- 甲状腺外科年终考核试题(附答案)
- 2025年四氟丙烯项目合作计划书
- 2025年胃癌患者护理查房模板
- 2025年匹克球裁判试题及答案
- 2025规范家居装修协议
- 2025年广西继续教育公需科目考试试题及答案贯彻创新驱动发展战略打造
- “安全生产责任制”培训试题及答案
- 地调考试试题及答案2025
- 诊断学血管检查
- 2025年兵团职工考试试题及答案
- 管理学原理英文版版教学课件第10章
- 石油天然气建设公司HSE费用财务管理实施细则及会计核算办法
- MAU控制逻辑检讨
- AB股有限公司章程律师版
评论
0/150
提交评论