高质量C++编程

  1. 头文件

    • 一定要用ifndef/define/endif防止被重复引用
    • 工程的头文件数目较多时(比如超过10个),可以考虑头文件和源文件保存在不同的目录,便于维护,如头文件位于include,源文件位于source
    • 有些头文件时私有的,不会被用户直接调用,没有必要公开,可以与源文件放在一个目录

    头文件搜索路径

    • #include “headfile.h”搜索顺序
      1. 搜索当前工作目录
      2. 搜索-I指定的目录
      3. 搜索gcc环境变量CPLUS_INCLUDE_PATH(C程序使用的是C_INCLUDE_PATH
      4. 搜索gcc内定目录:/usr/include/usr/local/include/usr/lib/gcc/x86_64-redhat-linux/4.1.1/include,各目录存在相同文件时,先找到哪个使用哪个
    • #include <headfile.h>搜索顺序
      1. 搜索-I指定目录
      2. 搜索gcc环境变量CPLUS_INCLUDE_PATHC程序使用的是C_INCLUDE_PATH
      3. 搜索gcc内定目录:/usr/include/usr/local/include/usr/lib/gcc/x86_64-redhat-linux/4.1.1/include,各目录存在相同文件时,先找到哪个使用哪个

    库文件搜索路径

    • 编译时
      1. 搜索-L指定的路径
      2. 搜索gcc的环境变量LIBRARY_PATH
      3. 搜索内定目录/lib /usr/lib /usr/local/lib
    • 运行时
      1. 编译时指定的动态库搜索路径
      2. 搜索环境变量LD_LIBRARY_PATH
      3. 搜索配置文件/etc/ld.so.conf指定的动态库路径
      4. 默认的动态库搜索路径/lib /usr/lib
  2. 类的格式

    • 以行为为中心:将private的数据放在前面,public的函数放在后面
    • 以数据为中心:将public的函数放在前面,private的数据放在后面
    • 建议以行为为中心,因为通常用户最关心的是接口
  3. 浮点数与数值比较

    • float和double类型都有精度限制,不要直接用==或!=与数值比较

    • // 变量x与数值a判断相等
      if (x >= a - EPSINON || x <= a + EPSINON)
      
  4. 循环语句效率

    • 多重循环中,将最长的循环放在内层,最短的循环放在外层,以减少CPU切换循环的次数
      • 书中举的例子是不对的,实验证明其实长循环在外面时执行时间更短,因为数组是连续存储(行优先),按短循环在外的写法每次访问数据可能会缺页中断,与程序局部性原理冲突。(但该说法本身没有错误)
    • 若循环体内存在逻辑判断,且循环次数很大,应将逻辑判断放到循环体外,这样编译器可以更好的进行优化处理,但是缺点是是程序看起来不够简洁
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HlBvF2aL-1608561492341)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201110221427006.png)]
  5. 常量

    • 使用const,不要使用#define,const可以进行类型安全检查
    • 需要对外公开的常量放头文件中,不对外公开的放源文件头部(可把不同模块常量集中放在一个公共头文件中,便于管理)
    • 类的const常量是在对象初始化完成的,每个对象的值可能不一样;如果要建立整个类中都恒定的常量,可以使用枚举,不会占用对象的存储空间,缺点是只能表示整数,且最大值有限
  6. 函数

    • 输入参数如果是传值方式,最好使用const &方式,省去临时对象的构造和析构过程

    • 有时函数不需要返回值,但为了增加灵活性如支持链式表达式,可以附加返回值,如strcpy:

    • char str[20];
      int length = strlen(strcpy(str, “Hello World”));
      
    • 函数返回一个对象时,最好使用“引用传递”提高效率,但有些场合只能“值传递”,否则出错(比如返回栈内对象)。

    • 提高函数质量:

      • 输入参数都要进行有效性检查;
      • 检查其他途径进入函数体内的变量有效性(全局变量,文件句柄等)
      • return语句的正确性,不要返回栈内存的指针和引用(函数结束后内存销毁,返回的指针/引用指向未知内存)
      • return语句效率性进行检查,返回值对象时考虑效率:return string(s1 + s2);string temp(s1 + s2); return temp效率高(内置数据类型效率差不多),省去了temp的构造和析构的开销
  7. 内存管理

    • 分配内存后立刻检查是否分配成功

    • 释放内存后立即设为NULL,防止野指针

    • 指针变量消亡了,不代表所指的内存会自动释放

    • 内存被释放了,不代表指针变量会消亡或成了NULL指针(delete/free只是释放了内存,对指针变量没有影响,所以释放后最好手动把指针变量置为NULL)

    • 野指针:

      1. 指针变量没有初始化,指针变量创建时不是NULL,缺省值是随机的,所以指针变量创建要初始化,或置为NULL
      2. delete或free后的指针不是NULL,指针变量的值没有变,最好手动设为NULL
    • 函数参数是一个指针时,不要指望该指针区申请动态内存

      • 编译器总是要为函数的每个参数制作临时副本,指针参数 p 的副本是_p,编译器使_p = p。如果函数体内的程序修改了_p的内容,就导致参数 p 的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p 申请了新的内存,只是把 _p 所指的内存地址改变了,但是 p 丝毫未变。所以函数 GetMemory 并不能输出任何东西。事实上,每执行一次GetMemory 就会泄露一块内存,因为没有用 free 释放内存

        void GetMemory(char *p, int num)
        {
            p = (char *)malloc(sizeof(char) * num);
        }
        int main(void)
        {
            char *str = NULL;
            GetMemory(str, 100);    // str 仍然为 NULL 
            strcpy(str, "hello");   // 运行错误
        }
        
    • 为什么free函数不需要大小类型?

      • 编译器知道要free的指针的大小(申请时时间上会多4个字节存放size)
    • new内置了sizeof,类型转换和类型安全检查,对于非内部数据类型。new创建对象时完了初始化,如果有多个构造函数,new的语句也可以有多种形式,但如果是创建对象数组,只能用无参构造函数。如:

      Obj *objects = new Obj[10](5,3);是错误写法

  8. 函数

    • 相比C语言函数,C++增加了重载(overloaded)、inline、const、virtual四种新机制,其中const和virtual仅用于类成员函数,重载和inline可以用于全局函数或类成员函数

    • 参数的缺省值只能出现在声明中,不能出现在定义体中;有多个参数时,只能从后向前缺省

    • 编译器会根据参数为每个重载函数产生不同的符号,如果函数已经被C编译器编译了,则需要用extern C来包含调用的函数,让C++编译器知道函数生成符号是按C方式来的

    • 重载、覆盖、隐藏区别

      • 重载:相同范围(同一个类中),函数名相同,参数不同,virtual可有可无

      • 覆盖:不同范围(子类覆盖父类),函数名相同,参数相同,父类函数必须有virtual关键字

      • 隐藏:子类函数屏蔽(隐藏)了父类的同名函数

        1. 子类函数与父类函数同名,参数不同,无论父类函数有无virtual,都被隐藏
        2. 子类函数与父类函数同名,参数相同,父类函数无virtual被隐藏,否则是覆盖

        隐藏不是将父类函数变没了,实际可以通过父类名::函数名方式访问,只是一个子类对象调用函数时是调用的子类的函数,调用不了父类的同名函数了

    • 内联函数具备宏代码的效率,而且相比宏增加了安全性,可以操作类的数据成员,所以C++中用内联函数替代宏。编译器会为内联函数生成符号

      inline是实现关键字,必须和函数体定义放一起才能使函数内联,若inline仅放在声明,不起作用

      定义在类声明的成员函数自动成为内联,但良好的编程风格是类中声明,类外定义加上inline

  9. 类的构造、析构、赋值函数

    • 如果不编写类的构造析构函数,编译器会自动为类生成默认的无参构造函数,拷贝构造函数,赋值函数,析构函数。默认的拷贝构造和赋值函数是浅拷贝,不是深拷贝
    • 初始化列表在参数表之后,函数体之前。初始化列表的工作是在函数体代码之前执行的
      • -如果类存在继承关系,子类必须在初始化列表中调用父类构造函数
      • 类的const常量成员只能在初始化列表进行初始化
      • 类的数据成员初始化有函数体内赋值和初始化列表两种方式,数据类型为内置类型时效率差不多,非内置类型时初始化列表方式效率更高(函数体赋值实际上会调用无参构造函数和赋值函数)
    • 构造&析构次序
      • 构造顺序首先调用基类的构造函数,然后是成员对象的构造函数,成员对象的构造顺序有类声明的次序决定,与初始化列表的顺序无关
      • 析构的次序与构造的顺序相反
    • 拷贝构造函数是对象创建时使用,赋值函数是对象已经存在了,不要混淆。String a(“hello”); String c = a;调用的是拷贝构造,因为对象c是创建,最好写成String c(a);语义上更清晰
    • 父类的构造、析构、赋值都不能被子类继承,如果存在继承关系要注意:
      • 子类的构造函数要在初始化列表中调用父类构造函数
      • 子类和父类的析构函数要加virtual关键字,否则可能内存泄漏
      • 子类的赋值函数,不要忘记对父类的数据成员重新赋值
  10. 类的继承和组合

    • 逻辑上A是B的一种(a kind of),且A的所有功能和属性对B有意义,则允许B继承A
    • 逻辑上A是B的一部分(a part of),则不允许B继承A,是用A和其它东西组合出B
  11. 编程经验

    • 尽量使用const,提高函数健壮性
      • 修饰参数:输出参数不要加const,否则失去输出功能;如果是值传递参数,没必要加const(编译器会制作副本,加不加const都不会改变),对于非内部数据最好使用const &传递参数确保参数不被修改(效率高)
      • 修饰返回值:返回是值传递方式时加不加const没有价值(会复制到外部临时存储单元中);函数返回引用的情况不多,一般只出现在类的赋值函数中(实现链式表达)
      • const成员函数:任何不修改数据成员的函数都应声明为const类型,如果编写const函数时,不慎修改数据成员,或调用非const成员函数,编译器将报错。
    • 先优化算法和数据结构,再优化执行代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
目 录 前 言6 第1 章 文件结构 1.1 版权和版本的声明. 1.2 头文件的结构. 1.3 定义文件的结构. 1.4 头文件的作用. 1.5 目录结构. 第2 章 程序的版式 2.1 空行. 2.2 代码行. 2.3 代码行内的空格. 2.4 对齐. 2.5 长行拆分. 2.6 修饰符的位置. 2.7 注释. 2.8 类的版式. 第3 章 命名规则 3.1 共性规则. 3.2 简单的WINDOWS 应用程序命名规则. 3.3 简单的UNIX 应用程序命名规则 第4 章 表达式和基本语句 4.1 运算符的优先级. 4.2 复合表达式. 4.3 IF 语句 4.4 循环语句的效率. 4.5 FOR 语句的循环控制变量. 4.6 SWITCH 语句. 4.7 GOTO 语句. 第5 章 常量 5.1 为什么需要常量. 5.2 CONST 与 #DEFINE 的比较. 5.3 常量定义规则. 5.4 类中的常量. 第6 章 函数设计 质量C++/C 编程指南,v 1.0 2001 Page 4 of 101 6.1 参数的规则. 6.2 返回值的规则. 6.3 函数内部实现的规则. 6.4 其它建议. 6.5 使用断言. 6.6 引用与指针的比较. 第7 章 内存管理 7.1 内存分配方式 7.2 常见的内存错误及其对策 7.3 指针与数组的对比 7.4 指针参数是如何传递内存的? 7.5 FREE 和DELETE 把指针怎么啦? 7.6 动态内存会被自动释放吗?. 7.7 杜绝“野指针”. 7.8 有了MALLOC/FREE 为什么还要NEW/DELETE ?. 7.9 内存耗尽怎么办?. 7.10 MALLOC/FREE 的使用要点 7.11 NEW/DELETE 的使用要点. 7.12 一些心得体会 第8 章 C++函数的级特性 8.1 函数重载的概念. 8.2 成员函数的重载、覆盖与隐藏. 8.3 参数的缺省值. 8.4 运算符重载. 8.5 函数内联. 8.6 一些心得体会. 第9 章 类的构造函数、析构函数与赋值函数 9.1 构造函数与析构函数的起源. 9.2 构造函数的初始化表. 9.3 构造和析构的次序. 9.4 示例:类STRING 的构造函数与析构函数 9.5 不要轻视拷贝构造函数与赋值函数. 9.6 示例:类STRING 的拷贝构造函数与赋值函数 9.7 偷懒的办法处理拷贝构造函数与赋值函数. 9.8 如何在派生类中实现类的基本函数. 9.9 一些心得体会. 第10 章 类的继承与组合. 质量C++/C 编程指南,v 1.0 2001 Page 5 of 101 10.1 继承 10.2 组合 第11 章 其它编程经验. 11.1 使用CONST 提函数的健壮性 11.2 提程序的效率 11.3 一些有益的建议 参考文献 附录A :C++/C 代码审查表. 附录B :C++/C 试题. 附录C :C++/C 试题的答案与评分标准.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值