第二部分 标准C++库
标准C++除了包含所有的标准C库外(其中有为支持类型安全而进行的少许增加与更改),还加进了自己特有的库。这些库比起标准C中的库,功能更加强大。可以说,它们的影响力几乎就等同于由C到C++的转变。
本教材的这一部分将对标准C++库的重点部分进行深入的介绍。
有关整个C++库的最全面,也是最令人费解的参考读物就是C++标准本身。Bjarne Stroustrup编写的《The C++Programming Language》[1](第3版,Addison Wesley,2000)对C++语言和库来说仍然是值得信赖的参考读物。最著名的专门论述C++库的参考读物是Nicolai Josuttis的《The C++Standard Library:A Tutorial and Reference》(Addison Wesley,1999)。本部分中各章的编写目的是:给读者提供丰富的描述说明与范例,使读者在解决与标准库的使用相关的任何问题时都有一个好的起点。但是,这里没有涉及某些不常用的技术和主题。如果在这些章里没有找到所需的内容,请参考上述两本书。编写这本教材的目的不是替代上述两本书,而是提供一些必要的补充。希望读者学习完接下来的内容后能够更轻松地理解那两本书。
读者会发现,这些章里并不包含标准C++库中的所有函数和类的详细文档,本教材把这些描述工作留给了别人,特别是P.J.Plauger的《Dinkumware C/C++Library Reference》,在http://www.dinkumware.com可以得到这些文档。它是用超文本标记语言(HypertextMarkupLanguage, HTML)格式写成的,是标准库文档联机资源中的精品。读者可守在电脑旁,当需要查找某些内容时,使用一下网络浏览器即可查到。可以联机查阅,也可以购买这些文档放在本机上,以便随时查阅。它包括C和C++库的全部参考页(referencepage)。(所以,它能够很好地帮助读者解决有关标准C/C++编程的问题。)电子文档是十分有效的,因为它不但随时可用,而且还能进行电子查找。
以上这些资料可满足程序员在奋力编程时的参考需要(如果本书对某些内容讲述不清,也可以参考上述这些资料) 。附录A也列举了一些其他的参考资料。
这部分的首章介绍了标准C++string类。它是一个功能非常强大的工具,可以简化在文本处理中可能遇到的大部分“琐事”。很可能在C语言中需要使用多行代码才能完成的字符串处理操作,用string类中的一个成员函数调用就可以完成。
第4章介绍的内容是iostreams库,它的内容包含与文件、字符串对象(string target)和系统控制台(system console)输入输出操作相关的类。
虽然第5章“深入理解模板”不是完全针对库的一章,但它对后面两章的内容介绍做了必要的准备工作。第6章将研究标准C++库提供的通用算法。由于这些算法都是用模板实现的,因此可以将它们应用于任意对象序列(sequence)。第7章介绍了标准容器及它们关联的迭代器(associated iterator)。首先介绍算法的原因是,只使用数组和vector容器(从第1卷开始就一直在使用)就可对其进行全面的研究。很自然地,本教材会在与容器相关的部分使用标准算法,因此在研究容器前熟悉算法是很有好处的。
第3章 深入理解字符串
在C语言中,对字符型数组进行字符串处理是最费时的工作之一。字符型数组要求程序员了解静态引用串(static quoted string)与在堆和堆栈中生成的数组之间的差别,实际上有时用类型“char*”就能达到要求,而有时则必须拷贝整个数组。
尤其是,由于字符串操作的普遍性,字符型数组可能造成许多混淆与错误。尽管如此,多年来创建字符串类仍是初级C++程序员通常的练习题。标准C++库中的string类一劳永逸地解决了字符型数组的处理问题,它监控内存在空间分配和拷贝构造时的情况,程序设计人员根本就不用为此劳神。
本章[2]研究标准C++中的string类,先简要介绍C++字符串的构成要素,然后阐释C++版本的字符串类与传统C语言字符型数组有哪些不同。读者将会了解使用string对象时的各种操作方法,还会看到C++string类在处理不同字符集和字符串数据转换时的神来之笔。
文本处理是编程语言最古老的应用之一,因此C++string类吸取了大量曾经被C及其他编程语言长时间使用的编程思想和术语。当开始介绍C++string类时,应该再次明确这个事实。不论采用哪一种编程方法,有3个操作是我们希望string类能够做到的:
·创建或修改string中存放的字符序列。
·检测string中元素的存在性。
·能够在多种描述string字符的方案之间进行转换。
读者将会看到C++string对象是怎样完成这些工作的。
3.1 字符串的内部是什么
在C语言中,字符串基本上就是字符型数组,并且总是以二进制零(通常被称为空结束符(null terminator))作为其最末元素。C++string与它们在C语言中的前身截然不同。首先,也是最重要的不同点,C++string隐藏了它所包含的字符序列的物理表示。程序设计人员不必关心数组的维数或空结束符方面的问题。string也包含关于其数据容量及存储地址的“内务处理”信息。具体地说,C++string对象知道自己在内存中的开始位置、包含的内容、包含的字符长度(length in characters)以及在必需重新调整内部数据缓冲区的大小之前自己可以增长到的最大字符长度。C++字符串极大地减少了C语言编程中3种最常见且最具破坏性的错误:超越数组边界,通过未被初始化或被赋以错误值的指针来访问数组元素,以及在释放了某一数组原先所分配的存储单元后仍保留了“悬挂”指针。
C++标准没有定义字符串类内存布局(memory layout)的确切实现。采用这种体系结构是为了获得足够的灵活性,从而允许不同的编译器厂商能够提供不同的实现,并且向用户保证提供可预测的行为。特别是,C++标准没有定义在哪种确切的情况下应该为字符串对象分配存储单元来保存数据。字符串分配规则明确规定:允许但不要求引用计数实现(reference-counted implementation),但无论其实现是否采用引用计数(reference counting),其语义都必须一致。这种表示稍有不同,在C语言中,每个char型数组都占据各自的物理存储区。在C++中,独立的几个string对象可以占据也可以不占据各自特定的物理存储区,但是,如果采用引用计数避免了保存同一数据的拷贝副本,那么各个独立的对象(在处理上)必须看起来并表现得就像独占地拥有各自的存储区一样。例如:
只有当字符串被修改的时候才创建各自的拷贝,这种实现方式称为写时复制(copy-on-write)策略。当字符串只是作为值参数(value parameter)或在其他只读情形下使用,这种方法能够节省时间和空间。
不论一个库的实现是不是采用引用计数,它对string类的使用者来说都应该是透明的。遗憾的是,情况并不总是这样。在多线程程[3]序中,几乎不可能安全地使用引用计数来实现。
[1]本书已由机械工业出版社引进出版。—编辑注
[2]本章的某些材料来源于Nancy Nicolaisen。
[3]很难在保证线程安全的前提下实现引用计数(参阅Herb Sutter的《More Exceptional C++》第104~114页)。详见第11章关于多线程编程的部分。