前 言 0.1 一点历史 欢迎使用本书。笔者希望通过本书,能够极大地提高您学习C++语言的能力,同时为您提供未来几年都适用的方便参考文档。 本书在1993年6月第一次出版,1996年完成了第2版的修订。笔者很高兴许多学院以及大学把它作为C++课程的教材。从1996到现在,C++语言发生了很多的变化(包括1998年的标准化),因此这本书的最新版本出来得很迟。 很长时间以来,人们谈到这门语言的定义是“C++标准草案”,但是现在是“C++标准”。非常详细地描述了这门语言细节的技术文档是“ISO/IEC 14882程序设计语言——C++”。您可以直接从美国国家标准化组织(ANSI)购买副本,网址为: 如果您需要直接从ANSI获得帮助,请发送电子邮件到storemanager@ansi.org。 0.2 本书内容 本书通过示例方法介绍了C++程序设计语言。书中给出了400多个简短易懂的示例,本书可以作为您的课堂笔记、指南和参考。书中提供了详细而牢靠的技巧以及概念的进阶,其详细程度超过了大多数C++书籍。本书既可以独立使用,也可以在课堂上用作笔记、教材或者参考指南。 因为笔者相信KISS(Keep It Simple,Students)教学方法,所以提供了许多相对简短并易于理解的C++代码。笔者赞成代码段或者完整示例中的思想以及观点。当代码可编译、可执行时,在执行完毕后会给出输出结果。在每个重要的主题的结尾,笔者都做了简短的总结以强调基本的观点。 如果您坐在教室看这本书,您不需要做太多的笔记,尽管荧光笔可能有用。当笔者使用这种方法讲授C++的时候,感到非常的轻松,在过去的十多年中笔者都记不得已讲授C++课程多少次了。最重要的是理解文字以及示例,并能够辨别主要的观点。在课堂结束之后,可以更仔细地复习这些示例并阅读原文。某些老师认为学生通过记大量的笔记并复制尽可能多的代码会使学习变得更加主动,但笔者发现这种方法是错误的,并极大地背离了笔者试图建立的观点。当您埋头记录笔者所讲述的一切时,您很难注意笔者以及笔者所讲的内容。为了让您愉快地学习,并让您集中注意力,笔者经常在教室使用自己的笔记本电脑来编译并运行书上的程序,甚至会构想出任何人都可以编写并调试的新代码。 0.3 使用本书的先决条件 本书假定您具备C语言知识。也就是说,您必须熟悉C语言的关键字、基本类型、结构、指针、解引用、数组、函数调用和运算符等。另一些优秀的C++书籍在深入研究C++本身的语义之前,都先复习了这些概念,但是本书没有。而本书讲述C语言未涉及的问题。如果您需要讲述C语言的一本好书,有许多可供您选择,从中挑选一本适合您的几乎不是问题。然而,在讲述C++的各种主题时,不可能脱离C语言,因此通过本书您会对C语言有一个比较好的回顾,尤其是在第1章。 0.4 代码风格 如果把5个程序员放在一个房间,并询问代码风格,会得到至少9种不同的意见,笔者对此非常了解。然而,在本书中笔者使用了如下的代码风格: (1) 所有左、右花括号都垂直排列,所有包含的内容都缩进排列。笔者发现这种风格易于阅读以及调试,特别是在由于缺少或者多出花括号而出现大量错误的时候。惟一的例外是函数体或者类定义(例如,构造函数)是空的时候,在此情况下,笔者可能在一行内编写{}。另外,可能只用一行的代码来编写简单的枚举(例如,enum color{red,white,blue};)。 (2) 在for循环的结尾,计数变量使用前缀风格来进行自增或者自减。尽管大多数C或者C++书籍使用后缀,但与使用前缀的结果没有区别,准备好用户自定义的类型是一个好的习惯,在此情况下使用前缀更为有效。您看到过多少次使用如*ptr++来增加计数器的代码?当然这样的代码不会得到想要的结果,因为圆括号包括的*ptr强制执行(*ptr)++。没有圆括号,您首先对ptr解引用,然后对ptr加1,而不是对ptr所指的对象加1。然而,如果使用前缀符号,无论是否使用圆括号,都会得到正确的结果,也就是说,++*ptr以及++(*ptr)是等效的。 (3) 类在给定的示例中模块化,从而在类的头文件及其定义文件中有清晰的间隔。这样,在某些IDE(例如Borland或者Microsoft)中,代码可以很容易地分离并复制到独立的文件中。为了执行平面文件编译(头文件内嵌于定义文件中,作为一个编译单元),必须删除或者注释掉某些#include语句,如包含类的头文件的语句。 (4) 在涉及到引用或者指针的声明语句中,&或者*标记与被声明的名称(声明符)而不是与类型(声明限定符)相关联。例如:T *ptr,而不是T* ptr,也不是T*ptr。另外,两个T类型的指针需要被声明为T *ptr1,*ptr2,与单个指针的声明一致。声明函数时也使用了相同的规则。例如,在此有两个函数,都返回指向T的指针: T *func1(),*func2(); (5) 在声明限定符中出现了带有关键字const的语句,或者const作为返回类型的一部分时,const总是写在最后。例如,char const *ptr,而不是const char *ptr,int const size=10而不是const int size=10。当然,编译器并不关注采用哪种方法,因为这种修饰符作为声明限定符的一部分可以以任何顺序出现。当然从右向左读会好一点,也就是说“ptr是一个指向const (常量)char(字符)的指针”,“size是一个const(常量)int”。 (6) 在命名类和变量时,笔者一直试图遵循Java约定:(a)类名以大写字母开头,(b)变量名和函数名以小写字母开头,?在多个单词的名称中大写所有随后的单词。 0.5 符合C++标准 本书的代码已经在Comeau 4.2.45.2c编译器中以严格模式测试过,在Borland CBuilder6(编译器版本为5.6)中也作过测试。Comeau编译器实际上完全符合ANSI C++标准,笔者还没有在其中找到bug(如果您对编译器感兴趣,强烈推荐您访问获取更多的细节)。如果您使用其他的编译器来编译书中的代码(例如,Microsoft Visual C++ 6.0、.NET,或者GUN C++),并发现了编译错误、链接错误或者不同的运行时结果,如果原因不明,请与笔者联系。在这一点上,要知道某些C++编译器没有将其C语言头文件放在标准(std)命名空间中(详见第2章)。这样,为了编写在这些编译器中仍然可以运行的符合C++标准(规定所有C语言的头文件位于std命名空间中)的代码,您应该编写类似于下面的示例来检索字符串的长度: #include #include #ifndef MY_COMPILER // Whatever macro is valid for your compiler using std::strlen; using std::size_t; // Other using declarations as needed #endif void func(char const *str) { size_t length = strlen(str); } 如果您使用其他人的编译器(在此情况下,C语言的头文件实际上在std命名空间中),那么这两个using声明会将strlen以及size_t引入全局作用域,func()中的代码会正常编译。如果您使用自己的编译器(在此情况下,C语言的头文件仍然在全局命名空间中),那么这两个using声明会被忽略,func()中的代码仍然会成功编译。 本书没有指出各个编译器的不足,尽管笔者知道这些不足确实存在。这是因为:(a)笔者不知道什么时候这些缺点会修正,(b)如果笔者说有些编译器不好,而它运行得很好,可能会涉及到法律问题。对于这种情况,笔者会使用诸如“您的编译器现在还不支持该功能”的说法。