《高质量程序设计指南–C/C++语言》学习笔记

《高质量程序设计指南–C/C++语言》学习笔记

  • 高质量软件开发之道
  • 程序设计入门
    • 内部名称
    • 连接规范
    • 变量及其初始化
    • 浮点变量与零值比较
  • C++/C常量
    • 正确定义符号常量
    • 类中的常量
    • 实际应用中如何定义常量
      • 在C程序中定义多个编译单元或模块公用的常量
      • 在C程序中定义仅供一个编译单元使用的常量
      • 在C++程序中定义多个编译单元或模块公用的常量
      • 在C++程序中定义仅供一个编译单元使用的常量
  • C++/C函数设计基础
    • 连接类型
    • 使用断言
  • C++/C指针、数组和字符串
    • 字符数组、字符指针和字符串
    • 函数指针
  • C++/C高级数据类型
    • 成员对齐
    • Union
    • 枚举 Enum
  • C++/C编译预处理
    • 文件包含
    • 头文件包含的合理顺序
    • 宏定义
    • 条件编译
    • #error
    • #pragam
    • 预定义符号常量
  • C++/C文件结构和程序版式
    • 程序文件的目录结构
    • 文件的结构
      • 头文件的用途和结构
    • 源文件结构
  • C++/C应用程序命名规则
  • C++面向对象程序设计方法概述
    • 对象的内存映像
  • 对象的初始化、拷贝和析构
  • C++函数的高级特性
    • 函数重载
    • 摆脱隐藏
    • 参数的默认值
    • 运算符重载
    • 内联函数
    • 类型转换函数
    • const成员函数
  • C++异常处理机制和RTTI
    • C++异常处理
    • 异常类型和异常对象
    • 异常的类型匹配规则
    • 异常说明及其冲突
    • 当异常抛出时局部对象如何释放
    • 如何使用好异常处理技术
    • C++的标准异常
  • RTTI
    • typeid运算符
    • dynamic_cast运算符
  • 内存管理
    • 有了malloc/free为什么还要new/delete
    • new有3种使用方式
      • plain new/delete
      • nothrow new/delete
      • palcement new/delete
  • 学习和使用STL
    • 容器设计原理
    • 迭代器
    • 适配器
    • 泛型算法
    • STL使用心得
  • 经典C/C++试题

高质量软件开发之道

一般地,软件设计应该将“设备相关程序”与“设备无关程序”分开,将“功能模块”与“用户界面”分开,这样可以提高可移植性。

尽可能复用你所能复用的东西。

修改错误代码时的注意事项:

  • 发现错误时不要急于修改,先思考一下修改此代码会不会引发其他问题。
  • 考虑是否还有同类型的其他错误。
  • 不论原有程序是否绝对正确,只要对此程序做过改动(哪怕是微不足道的),都要进行回归测试。

对于以源代码形式提供的库,必须使用当前的编译器对其重新编译;如果是二进制级的库,除非它的开发商保证该库的实现与IDE的缺省库是二进制兼容的,否则不能使用。

开发环境泛指支持软件开发的一切工具,例如操作系统、代码编辑器、编译器、连接器、调试器等等。**集成开发环境(IDE)**则是把编辑器、编译器、连接器及调试器等各种工具集成到了一个工作空间中。如果没有IDE,就得手动编辑编译连接的命令行或者makefile,手工编辑它们的参数设置。

设计上应该追求简单低耦合

程序设计入门

内部名称

在C语言中,所有函数不是局部于编译单元(文件作用域)的函数,就是具有连接类型和作用域的全局函数,因此除了两个分别位于不同编译单元的函数可以同名外,全局函数是不能同名的;全局变量也是同样的道理。其原因是C语言采用了一种极其简单的函数名称区分规则:仅在所有函数名的前面添加前缀”_”,从唯一识别函数的作用上来说,与不添加前缀没有什么不同。

但是C++语言允许用户在不同的作用域中定义同名的函数、类型和变量等,这些作用域不仅仅限于编译单元,还包括、、、等;甚至在同一个作用域中也可以定义同名的函数,即重载函数。为了避免连接二义性,会对这些函数进行重命名,在C++中,称为**”Name-Mangling”(名字修饰或名字改编)**。例如在它们的前面分别添加所属各级作用域的名称(、等)及重载函数的经过编码的参数信息(参数类型和个数等)作为前缀或后缀。另外,C++标注的不同实现会采取不同的Name-Mangling方案(标准没有强制规定)。

连接规范

在使用不同编程语言进行联合软件开发的时候,需要统一全局函数、全局变量、全局常量、数据类型等的链接规范,特别是在不同模块之间共享的接口定义部分。因为连接规范关系到编译器采用什么样的Name-Mangling方案来重命名这些标识符的名称,而如果同一个标识符在不同的编译单元或模块中具有不一致的连接规范,就会产生不一致的内部名称,这肯定会导致程序连接失败。

同样道理,在开发程序库的时候,明确连接规范也是必须要遵循的一条规则。通用的连接规范则属C连接规范:。

变量及其初始化

初始化和赋值的不同:前者发生在对象(变量)创建的同时,而后者是在对象创建后进行的。

注意:在一个编译单元中定义的全局变量的初始值不要依赖定义于另一个编译单元中的全局变量的初始值。这是因为:虽然编译器和连接器可以决定同一个编译单元中定义的全局变量的初始化顺序保持与它们定义的先后顺序一致,但是却无法决定当两个编译单元连接在一起时哪一个的全局变量的初始化先于另一个编译单元的全局变量的初始化。也就是说,这一次编译连接和下一次编译连接很可能使不同编译单元之间的全局变量的初始化顺序发生改变。例如下面的做法是不当的:

如果初始化被排在的前面,那么就会被初始化为110;但是如果反过来,那么的初始值就无法预料了。

浮点变量与零值比较

计算机表示浮点数(float和double类型)都有一个精度限制。对于超出了精度限制的浮点数,计算机会把它们的精度之外的小数部分截断。因此本来不相等的两个浮点数在计算机中可能就变成相等的了。

如果两个同符号浮点数之差的绝对值小于或等于一个可接受的误差(即精度),就认为它们是相等的,否则就是不相等的。精度根据具体应用要求而定,不要直接用或对两个浮点数进行比较,虽然C/C++语言支持直接对浮点数进行和的比较操作,但是由于它们采用的精度往往比我们实际应用中要求的精度高,所以可能导致不符合实际需求的结果甚至错误。

C++/C常量

常用的常量可以分为:字面常量、符号常量、契约性常量、布尔常量和枚举常量等。

由于字面常量只能引用,不能修改,所以语言实现一般把它保存在程序的符号表里面而不是一般的数据区中。

存在两种符号常量:用定义的宏常量和用定义的常量。由于是预编译伪指令,它定义的宏常量在进入编译阶段前就已经被替换为所代表的字面常量了,因此宏常量在本质上是字面常量。

在标准C语言中,符号常量默认是外连接的(分配存储),也就是说你不能在两个(或两个以上)编译单元总同时定义一个同名的符号常量(重复定义错误),或者把一个符号常量定义放在一个头文件中而在多个编译单元中同时包含该头文件。但是在标准C++中,符号常量默认是内连接的,因此可以定义在头文件中。当在不同的编译单元中同时包含该头文件时,编译器认为它们是不同的符号常量,因此每个编译单元独立编译时会分别为它们分配存储空间,而在连接时进行常量合并。

正确定义符号常量

在C++需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为便于管理,可以把不同模块的常量集中存放在一个公用的头文件中。

尽量使用而不是来定义符号常量,包括字符串常量。

类中的常量

非静态数据成员是属于每一个对象的成员,只在某个对象的生存期限内是常量,而对于整个类来说它是可变的,除非是。

不能在类声明中初始化非静态数据成员。例如下面的代码是错误的,因为在类的对象被创建前,编译器无法知道的值是多少。

想要建立在整个类中都恒定的常量,需要通过类中的枚举常量或者来完成。

实际应用中如何定义常量

在C程序中,符号常量定义的默认连接类型是的,即外连接(extern linkage),就像全局变量一样。因此,如果要在头文件中定义,必须使用关键字,这样每一个包含该头文件的编译单元就会分别拥有该常量的一份独立定义实体(如同直接在每一个源文件中分别定义一次),否则会导致“redefinition”的编译器诊断信息;如果在源文件中定义,除非明确改变它的连接类型为(实际上是存储类型为,连接类型为内连接)的,否则其他编译单元就可以通过声明来访问它。

但是在C++程序中,符号常量定义的默认连接类型却是的,即内连接,就像class的定义一样,这就是在头文件中定义而不需要关键字的原因。

在C程序中定义多个编译单元或模块公用的常量

方法一:
在某个公用头文件中将符号常量定义为并初始化,例如:

然后每一个使用它的编译单元该头文件即可;

方法二:
在某个公用的头文件中将符号常量声明为的,例如:

并且在某个源文件中定义一次:

然后每个使用它的编译单元该头文件即可.

方法三
如果是整型常量,在某个公用头文件中定义类型,然后每一个使用它的编译单元该头文件即可。

在C程序中定义仅供一个编译单元使用的常量

直接于该编译单元(源文件)开头位置将符号常量定义为并初始化,例如:

在C++程序中定义多个编译单元或模块公用的常量

方法一
在某个公用的头文件中直接在某个名字空间中或者全局名字空间中定义符号常量并初始化(有无无所谓),例如:

然后每一个使用它的编译单元该头文件即可。

方法二

在某个公用的头文件中并且在某个名字空间中或者全局名字空间中将符号常量声明为的,例如:

并且在某个源文件中定义一次并初始化:

然后每个使用它的编译单元该头文件即可。

方法三
如果是整形常量,在某个公用头文件中定义类型,然后每一个使用它的编译单元该头文件即可。

方法四
定义为某一个公用类的数据成员并初始化,或者定义为类内的枚举类型,例如:

每一个使用它的编译单元该类的定义即可。

方法二和方法四的优点:

  • 节省存储,每一个编译单元访问的都是这个唯一的定义
  • 修改初值后只需重新编译定义所在编译单元即可,影响面很小

方法二和方法四的缺点:

  • 如果要改变初值,需修改源文件

方法一和方法三的优点:

  • 维护方便

方法一和方法三的缺点:

  • 如果修改常量初值,则将影响多个编译单元,所有受影响的编译单元必须重新编译;
  • 每一个符号常量在每一个包含它们的编译单元内都存在一份独立的拷贝内容,每个编译单元访问的就是各自的拷贝内容,因此浪费存储空间

在C++程序中定义仅供一个编译单元使用的常量

直接于该编译单元(源文件)开头位置将符号常量定义为常量并初始化(有无无所谓),例如:

C++/C函数设计基础

不论是函数的原型还是定义,都要明确写出每个参数的类型和名字,不要贪图省事只写参数的类型而忽略参数名字。如果函数没有参数,那么使用而不要空着,这是因为标准C把空的参数列表解释为可以接收任何类型和个数的参数;而标准C++则把空的参数列表解释为不可以接收任何参数。在移植C++/C程序时尤其要注意这方面的不同。

不要将正常值和错误标志混在一起返回。建议正常值用输出参数获得,而错误标志用语句返回。

函数的功能要单一,即一个函数只完成一件事情,不要设计多用途的函数。函数规模尽量控制在50行代码以内。

不仅要检查输入参数的有效性(例如通过),还要检查通过其他途径进入函数体内的变量的有效性,例如全局变量、文件句柄等。

连接类型

连接类型分为外连接内连接无连接 3种。连接类型表明了一个标识符的可见性,容易与作用域混淆。

如果一个标识符能够在其他编译单元中或者在定义它的编译单元中的其他范围内被调用,那么它就是外连接的。外连接的标识符需要分配运行时的存储空间。

如果一个标识符能在定义它的编译单元中的其他范围内被调用,但是不能在其他编译单元中被调用,那么它就是内连接的。

static void f2() {...} //f2为内连接的union  //匿名联合的成员是内连接的{	long count;	char *p;}class Me{...}; //Me是内连接的const int MAX_LENGTH = 1024;

来源:南方以北
                                                        

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年5月11日
下一篇 2020年5月11日

相关推荐