《C++编程风格(修订版)》——2.3 物理状态的一致性

2.3 物理状态的一致性

C++编程风格(修订版)
在 string 类的第三个构造函数中将对函数的字符串参数进行复制。在这个构造函数中对 s 进 行了初始化,并将 string 对象置于明确定义的状态,但它对 len 的处理方式与前两个构造函数是 不一致的。在前两个构造函数中,len 是所分配的字符数组的长度。而在第三个构造函数中,len 却是字符串的长度——也就是字符数组的长度减 1。那么 len 到底应该是动态分配数组的长度还 是字符串的长度呢? len 的这两种含义都是有意义的,但在所有的构造函数及其他的成员函数中, 我们必须只能使用一种含义。除非 len 的含义是唯一的,否则成员函数将无法以一致的方式来解 释 string 对象的状态。

我们可以在前两个构造函数中,或者在第三个构造函数中来改正这个问题。通过观察在最后 一个构造函数(拷贝构造函数)以及成员函数 assign() 和 concat() 中对 len 的使用方式,我们可 以知道 len 的含义究竟是数组的长度还是字符串的长度。现在,我们来依次观察这三个函数。首先, 在拷贝构造函数中,len 的含义是数组的大小:

从拷贝构造函数对 len 的使用方式中,我们可以看到问题是在于第三个构造函数,也就是带 有字符指针参数的构造函数。其次,在成员函数 assign() 中,len 是被设置为字符串的长度,这却表明问题是在于前两个构造函数:

最后,在成员函数 concat() 中,情况将变得更糟糕,在这个函数中 len 的使用方式与其他成 员函数中的使用方式都不相同。

     ![image](https://yqfile.alicdn.com/74d11dddc4ac03c394aad0bdb21e57295e83fbf9.png)

一方面,如果 len 是数组的长度,那么在 concat() 中所分配数组的长度就会比所需数组的长 度大 1。另一方面,如果 len 是字符串的长度,那么在 concat() 中所分配数组的长度就会比所需 数组的长度小 1。因此,无论 len 是以上哪种含义,在函数 concat() 中分配的数组都存在着多 1 或者少 1 的错误。对于这种情况,程序员可能无法决定该如何来使用 len,因此也就无法解决这 个问题。在最初的程序中,string 对象的状态对于数据成员 len 的含义并没有一致的定义。

时间: 2024-09-23 11:45:14

《C++编程风格(修订版)》——2.3 物理状态的一致性的相关文章

《C++编程风格(修订版)》——导读

前言 C++编程风格(修订版) 本书采用一种统一的方法来给出所要学习的内容.通过研究示例程序--"编程风格示例"--来引入每个学习主题,这些示例程序通常在某些重要的方面存在着缺陷.在分析程序时,我们采取了与做代码交叉审查时一样的思路:在审查同事的代码时,我们要找出哪些问题是最需要改正的,以及对程序的哪些部分进行修改才能最大程度提升程序的整体性能.在本书中,我们将对每个示例程序做详尽的阅读和分析.读者在阅读书中对示例程序的分析之前,可以首先从自己的角度去分析程序中的问题,然后试着给出自己

《C++编程风格(修订版)》——2 一致性

2 一致性 C++编程风格(修订版)对于任何一个类,都可以从两个主要方面来进行观察:类的接口和类的实现.类的接口也 就是类的公有成员集合,它决定了这个类创建的对象能够为程序其他部分中的客户代码提供什么 样的服务.而类的实现则是完成了这些服务的功能,它们通常是作为类的私有成员被封装起来, 客户代码无法进行访问.在设计一个类时,程序员通常需要从这两个方面来进行考虑.接口必须 能够代表一致的抽象,而实现则必须使对象在行为上与这个抽象保持一致.本章将从接口和实现 这两个角度来讨论一致性的问题. 在任何时

《C++编程风格(修订版)》——2.5 动态内存的一致性

2.5 动态内存的一致性 C++编程风格(修订版) 在程序清单 2.2 的 string 类中仍然存在着一些问题和不一致的地方.其中,在动态内存管理 上的不一致性与我们在前面所看到的不一致性是一样的,都是严重的问题.对于所有动态分配的 内存,我们都需要回答两个问题:首先,动态内存是不是足够大以容纳将要存储的信息?其次, 是不是所有的动态内存都是可回收的? 在默认构造函数中分配的字符数组肯定可以容纳空字符串: 这个构造函数所基于的假设是:在创建对象时将会为字符串分配内存,并且这个内存足以 容纳在对

《C++编程风格(修订版)》——2.2 明确定义的状态

2.2 明确定义的状态 C++编程风格(修订版) 在 string 类的前两个构造函数中存在着同样的问题. 如果在创建 string 对象时调用了上述两个构造函数之一,那么这个 string 对象的初始状态将 是未定义的.在下面的代码中将输出两个字符串,其中在创建 string 对象时分别调用了上面两个 构造函数. 对 x 和 y 来说,调用函数 print() 的结果是未定义的,因为由 x.s 和 y.s 指向的字符数组中的 内容是未定义的.在这两个构造函数中,都为字符数组分配了内存,但却没有

《C++编程风格(修订版)》——2.7 编程风格示例:第二种方法

2.7 编程风格示例:第二种方法 C++编程风格(修订版) 我们暂时先不去考虑去解决 string 类中的其他问题,而是将注意力转移到另一个不同的字符 串类.在这个类中,我们避免了大多数的上述问题.我们来分析程序清单 2.3 中的 SimpleString 类. 虽然 SimpleString 相对于 string 进行了改进,但仍然存在着一些缺陷. 程序清单 2.3 最初的 SimpleString 类 与 string 类一样,SimpleString 通过一个字符类型的指针 _string

《C++编程风格(修订版)》——3.4 封装

3.4 封装 C++编程风格(修订版) 新的堆栈抽象更简单,程序的代码量也更小,并且也使用了更少的内存.然而,在StackIndex及其派生类的关系中还存在着尚未暴露出来的问题.在本章的前面部分,我们已经注意到,派生类的成员函数push()和pop()隐藏了它们从基类继承而来的同名成员函数.不过,我们也可以在IntStack和CharStack对象上来调用这些基类函数,这就需要使用函数的完全解析名字:StackIndex::push()和StackIndex::pop().事实上,在它们各自的派

《C++编程风格(修订版)》——3.6 模板

3.6 模板 C++编程风格(修订版) IntStack和CharStack的共同属性可以用另一种不同的方式来表达,即C++的模板机制.模板也被称之为参数化类型,在程序清单3.5中给出了堆栈的模板. 程序清单3.5 Stack模板 Stack模板定义了一组类.在使用Stack模板来声明一个对象时,必须同时提供一个类型来替换模板声明中的类型T.例如, 在上面的语句中,声明了一个对象stackOfChar,这个对象是一个存储10个char类型值的堆栈,而在下面的语句中: 声明了一个对象stackOf

《C++编程风格(修订版)》——3.2 继承作用域准则

3.2 继承作用域准则 C++编程风格(修订版)基类Stack的公有接口如下所示: 在派生类中,我们可以看到和基类成员函数有着同样名字的成员函数.注意,其中的Stack::pop()和Stack::push()都不是虚函数.同时,我们还注意到,派生类成员函数中的参数类型与相应的基类成员函数中的参数类型并不匹配.例如,Stack::push()不带参数,而IntStack::push()的参数是一个整数.对于这样的函数,根据C++作用域准则,这就意味着派生类的成员函数将隐藏基类的成员函数,因为在派

《C++编程风格(修订版)》——3.5 接口与实现

3.5 接口与实现 C++编程风格(修订版)为什么要使用继承?如果对继承关系作进一步的分析,我们会发现程序中的继承其实可以完全去掉.因此,第二种解决方案就是使用成员对象而不是继承. 在第2章中已经讨论过,一个C++类有着两个重要的方面:用于描述类行为的公有接口,以及行为的私有实现.大多数继承所采用的都是公有继承的形式:派生类同时继承了基类的接口和实现.不过,我们还可以有选择性地进行继承,即派生类可以只继承接口或者只继承实现.在私有基类中,派生类继承了所有的实现,但没有继承任何接口.而在继承公有的