目 录
第一章 认识C++的对象 3
1.1 初识C++的函数和对象 3
1.2 认识C++语言面向过程编程的特色 4
1.3 程序的编辑、编译和运行的基本概念 5
第二章 从结构到类的演变 6
2.1 结构的演化 6
2.2 从结构演变一个简单的类 6
2.3 面向过程与面向对象 6
2.4 C++面向对象程序设计的特色 6
2.5 使用类和对象 7
2.6 string对象数组与泛型算法 7
第3章 函数和函数模板 7
3.1 函数的参数及其传递方式 7
3.2 深刻讨论函数返回值 8
3.3 内联函数 9
3.4 函数重载和默认参数 9
3.5 函数模板 9
第4章 类和对象 10
4.1 类及其实例化 10
4.2 构造函数 11
4.3 析构函数 12
4.4 调用复制构造函数的综合实例 13
4.5 成员函数重载及默认参数 13
4.6 this指针 13
4.7 一个类的对象做为另外一个类的成员 13
4.8 类和对象的性质 13
4.9 面向对象的标记图 14
4.10 面向对象编程的文件规范 15
第五章 特殊函数和成员 16
5.1 对象成员的初始化 16
5.2 静态成员 17
5.3 友元函数 17
5.4 const对象 18
5.5 数组和类 19
5.6 指向类成员函数的指针 19
5.7 求解一元二次方程 20
第六章 继承和派生 20
6.1 继承和派生的基本概念 20
6.2 单一继承 20
6.3 多重继承 22
6.4 二义性及其支配规则 22
第七章 类模板与向量 22
7.1 类模板 22
7.2 向量与泛型算法 23
7.3 出圈游戏 25
第八章 多态性和虚函数 25
8.1 多态性 25
8.2 虚函数 25
8.3 多重继承与虚函数 27
8.4 类成员函数的指针与多态性 27
第9章 运算符重载及流类库 27
9.1 运算符重载 27
9.2 流类库 28
9.3 文件流 31
第10章 面向对象设计实例 32
10.1 过程抽象和数据抽象 32
10.2 发现对象并创建对象层 33
10.3 定义数据成员和成员函数 33
10.4 如何发现基类和派生类结构 34
10.5 接口继承与实现继承 34
10.6 设计实例 35ios
第一章认识C++的对象c++
//c++ hello world 示例 #include <iostream> using namespace std; int main() { cout << "Hello, World!" << endl; return 0; }
1.1 初识C++的函数和对象
通常称现实世界中客观存在的事物为对象。
1.混合型语言
C++程序以.cpp做为文件扩展名,而且必须有一个且只能有一个名为mian(不是C++的关键字)的主函数。真正的面向对象的语言没有主函数,C++保留了这个面向过程的主函数,因此称之为混合型语言。
2.灵活的注释方式程序员
/* c++注释方式 */ //我是注释 #if 0 我是注释内容,但考试不会考到:) #endif
3.使用输出和输入对象
C++将数据从一个对象流向另外一个对象的流动的抽象称为“流”。从流中获取数据的操做称为提取操做。向流中添加数据的操做称为插入操做。
cin用来处理标准输入,即键盘输入。cin通常和流提取运算符>>结合使用。
cout用来处理标准输出,即屏幕输出。cout通常和流插入运算符<<结合使用。算法
char something[50]; char something2[50]; cin >> something; cout << "你输入的是:" << something << endl; //endl 用于在行末添加一个换行符 cin >> something >> something2; /* 至关于 cin >> something; cin >> something2; */ cout << "你输入的第一件事:" << something << endl; cout << "你输入的第二件事:" << something2 << endl;
4.使用命名空间
所谓命名空间(namespace)是一种将程序库名称封装起来的方法,它提升了程序的性能和可靠性。
C++新标准就是将标准类库中的头文件与通常的头文件(须要使用扩展名“.h”)区分开来。固然,也能够本身定义符合标准库的头文件,使用这种头文件时,也须要同时使用命名空间语句。
若是仍然使用C库中的头文件,则须要使用扩展名“.h”形式,例如<math.h>和<stdio.h>。若是使用C++提供的头文件,则不须要使用扩展名“.h”,例如,<string>。注意C++库中替代C库中的头文件的正确名称,例如可使用<cmath>替代<math.h>。编程
5.对象的定义及初始化
定义对象包括为它命名并赋予它数据类型。数组
int num; char something[50]; int main() { // 一些代码; return 0; }
6.函数原型及其返回值
函数都须要有类型说明。int main() 指出main是整数类型,返回值由return后面的表达式决定,且表达式的值必须与声明函数的类型一致。
C++使用变量的基本规则是:必须先声明,后使用,对函数调用也是如此。
7.const修饰符和预处理程序
C语言通常使用"#define"定义常量,在C++中,建议使用const代替宏定义。const是放在语句定义以前的,所以能够进行类型判别。
用关键字const修饰的标识符是一类特殊的常量,称为符号常量,或const变量。使用const容许编译器对其进行类型检查并可改善程序的可读性。
C++语言可使用宏定义。无参数的宏做为常量,而参数的宏则能够提供比函数更高的效率。但预处理只是进行简单的文本代替而不进行语法检查,因此会存在一些问题。
由于被const修饰的变量的值在程序中不能被改变,因此在声明符号常量是,必须对符号常量进行初始化,除非这个变量使用extern修饰的外部变量。安全
#include <iostream> using namespace std; //不管是define仍是const定义的常量名称,使用大写对与普通标识符作区分是很好的编程实践 #define LENGTH 10 //注意const定义常量时的不一样写法,包括类型、等号、语句后的分号等。 const int WIDTH = 5; #define NEWLINE '\n' int main() { int area; area = LENGTH * WIDTH; cout << area; cout << NEWLINE; return 0; }
C++语言预处理程序不是C++编译程序的一部分,它负责分析处理几种特殊的语句,这些语句被称为预处理语句。顾名思义,预处理程序对这几种特殊语句的分析处理是在编译程序的其余部分以前进行的。为了与通常的C++程序语句相区别,全部预处理语句都以位于行首的符号“#”开始。
预处理语句有3种,分别是宏定义、文件包含和条件编译。
预处理程序把全部出现的、被定义的名字所有替换成对应的“字符序列”。#define中的名字与C++中的标识符有相同的形式,为了区别,每每用大写字母来表示(标识符用小写字母),这也适合const语句。
文件引用使用双引号仍是尖括号,其含义并不同。采用尖括号引用系统提供的包含文件,C++编译系统将首先在C++系统设定的目录中寻找包含文件,若是没有找到,就到指定的目录中去寻找。采用双引号引用本身定义的包含文件(通常都放在本身定义的指定目录中),这将通知C++编译器在用户当前的目录下或指定的目录下寻找包含文件。指定的目录没必要在同一个逻辑盘中。数据结构
//标准规定,包含C++提供的标准头文件或系统头文件时应使用尖括号,包含自定义头文件时可以使用双引号。 #include <system_lib> #inclue "my_lib"
8.程序运行结果
9.程序书写格式
1.2 认识C++语言面向过程编程的特色
C++语言的标准模板库(Standard Templete Library,STL)提供了与要操做的元素类型无关的算法,不只使许多复杂问题迎刃而解,并且也将许多面向对象的程序设计问题转化成基于对象的面向过程编程。
1.2.1 使用函数重载
C++容许为一个函数定义几个版本,从而使一个函数名具备多种功能,这称为函数重载。
1.2.2 新的基本数据类型及其注意事项
void是无类型标识符,只能声明函数的返回值类型,不能声明变量。C++语言还比C语言多了bool(布尔)型。C++标准只限定int和short至少要有16位,而long至少32位,short不得长于int,int不得长于long。
地址运算符“&”用来取对象存储的首地址。
C++语言中的整数常量有4种类型:十进制常量、长整型常量、八进制常量和十六进制常量,并用前缀和后缀进行分类标识。app
//c++中整数常量 85 // 十进制 0213 // 八进制 0x4b // 十六进制 30 // 整数 30u // 无符号整数 30l // 长整数 30ul // 无符号长整数
1.2.3 动态分配内存
在使用指针时,若是不使用对象地址初始化指针,能够本身给它分配地址。对于值存储一个基本类型数据的指针,申请的方式以下:
new 类型名[size] //申请能够存储size个该数据类型的对象
再也不使用时,简单地使用“delete指针名”便可释放已经申请的存储空间。
示例:框架
#include <iostream> using namespace std; int main () { double* pvalue = NULL; // 初始化为 null 的指针 pvalue = new double; // 为变量请求内存 *pvalue = 29494.99; // 在分配的地址存储值 cout << "Value of pvalue : " << *pvalue << endl; delete pvalue; // 释放内存 return 0; }
1.2.4 引用
别名的地址就是原来对象的地址,选定命名时使用“引用”运算符“&”,再选用数据类型与之配合。引用的声明方式以下:
数据类型& 别名=对象名;
所谓“引用”就是将一个新标识符和一块已经存在的存储区域相关联。所以,使用引用时没有分配新的存储区域,它自己并非新的数据类型。
引用一般用于函数的参数表中或者做为函数的返回值。对引用实质性的理解应抓住以下两点:
(1)引用实际上就是变量的别名,使用引用就如同直接使用变量同样。引用与变量名在使用的形式上是彻底同样的,引用只是做为一种标识对象的手段,不能直接声明对数组的引用,也不能声明引用的引用。
(2)引用的做用与指针有类似之处,它会对内存地址上存在的变量进行修改,但它不占用新的地址,从而节省开销。
#include <iostream> using namespace std; int main () { // 声明简单的变量 int i; double d; // 声明引用变量 int& r = i; double& s = d; i = 5; cout << "Value of i : " << i << endl; cout << "Value of i reference : " << r << endl; d = 11.7; cout << "Value of d : " << d << endl; cout << "Value of d reference : " << s << endl; return 0; }
1.2.5 对指针使用const限定符
能够用const限定符强制改变访问权限。
1.左值和右值
左值是指某个对象的表达式。
2.指向常量的指针
指向常量的指针是在很是量指针声明前面使用const,例如:
const int *p;
它告诉编译器,“p”是常量,不能将“p”做为左值进行操做,即限定了“*p=”的操做,因此称为指向常量的指针。
3.常量指针
把const限定符放在*号的右边,是使指针自己称为一个const指针。
int x=5;
int * const p=&x;
不能改变p的指向,但能够经过间接引用运算符“”改变其值,例如语句“p=56;”将上面的x的值改变为56。
4.指向常量的常量指针
也能够声明指针和指向的对象都不能改动的“指向常量的常量指针”,这时必需要初始化指针。例如:
int x=2;
const int * const p=&x;
告诉编译时,*p和p都是常量,都不能做为左值。
1.2.6 泛型算法应用于普通数组
所谓泛型算法,就是提供的操做与元素的类型无关。
1.2.7 数据的简单输入输出格式
C++提供了两种格式控制方式:一种是使用ios _base类提供的接口;另外一种是使用一种称为操控符的特殊函数,它的特色是可直接包含在输出和输入的表达式中,所以更为方便,不带形式参数的操控符定义在头文件<iostream>中,带形式参数的操控符定义在头文件<iomanip>中。使用它们时,一是要正确包含它们,二是只有与符号“<<”或“>>”链接时才起做用,三是无参数的操控符函数不能带有“()”号。
#include <iomanip> #include <iostream> using namespace std; int main () { //iomanip对输入输出流添加更好的控制 cout << setw(5) << 255 << endl; cout << setw( 3 ) << 1 << setw( 3 ) << 10 << setw( 3 ) << 100 << endl; } /*输出结果: * 255 * 1 10100 */
1.3 程序的编辑、编译和运行的基本概念
用C++语言写成的程序称为源程序,源程序必须通过C++编译程序翻译成机器语言才能执行。要获得一个用C++语言设计的、名为myapp.exe的可执行文件,其过程可分为以下几步:
(1)先使用编辑器编辑一个C++程序mycpp.cpp,又称其为C++的源程序。
(2)而后使用C++编译器对这个C++程序进行编译,产生文件mycpp.obj
(3)再使用链接程序(又称Link),将mycpp.obj变成mycpp.exe
所谓集成环境,就是将C++语言编辑、编译、链接和运行程序都集成到一个综合环境中。
第二章 从结构到类的演变
2.1 结构的演化
类是从结构演变而来,开始称为“带类的C”。这种演变就是从让结构含有函数开始的。
2.1.1 结构发生质的演变
1.函数与数据共享
2.封装性
2.1.2 使用构造函数初始化结构对象
2.2 从结构演变一个简单的类
2.3 面向过程与面向对象
所谓“面向过程”,就是没必要了解计算机的内部逻辑,而把精力主要集中在对如何求解问题的算法和过程的描述上,经过编写程序把解决问题的步骤告诉计算机。
所谓函数,就是模块的基本单位,是对处理问题的一种抽象。
结构化程序设计使用的是功能抽象,面向对象程序设计不只能进行功能抽象,并且能进行数据抽象。“对象”其实是功能抽象和数据抽象的统一。(有没有感受有点是在学马克思,类和对象的抽象其实是通常和特殊的关系,在马克思里面也有相似的概念。计算机的发展实际上也是借鉴了不少当代的先进理论。)
面向对象的程序设计方法不是以函数过程和数据结构为中心,而是以对象表明求解问题的中心环节。它追求的是现实问题空间与软件系统解空间的近似和直接模拟(或者说软件世界对现实世界的抽象)。
软件开发是对给定问题求解的过程。从认识论的角度看,能够归为两项主要的活动:认识与描述。
软件开发者将被开发的整个业务范围称做“问题域”(problem domain),“认识”就是在所要处理的问题域范围内,经过人的思惟,对该问题域客观存在的事物以及对所要解决的问题产生正确的认识和理解,包括弄清事物的属性,行为及其彼此之间的关系并找出解决问题的方法。
“描述”是指用一种语言把人们对问题域中事物的认识、对问题及其解决方法的认识描述出来。最终的描述必须使用一种可以被机器读得懂的语言,即编程语言。
2.4 C++面向对象程序设计的特色
和传统的程序设计方法相比,面向对象的程序设计具备抽象、封装、继承和多态性等关键要素。
2.4.1 对象
C++可以使用对象名、属性和操做三要素来描述对象。
2.4.2 抽象和类
抽象是一种从通常的观点看待事物的方法,即集中于事物的本质特征,而不是具体细节或具体实现。
类的概念来自于人们认识天然、认识社会的过程。在这一过程当中,人们主要使用由特殊到通常的概括法和由通常到特殊的演绎法。在概括的过程当中,是从一个个具体的事物中把共同的特征抽取出来,造成一个通常的概念,这就是“归类”;在演绎的过程当中,把同类事物,根据不一样的特征分红不一样的小类,这就是“分类”。对于一个具体的类,它有许多具体的个体,这些个体叫作“对象”。
类的做用是定义对象。
所谓“一个类的全部对象具备相同的属性”,是指属性的个数、名称、数据类型相同,各个对象的属性值则能够互不相同,而且随着程序的执行而变化。
2.4.3 封装
将类封装起来,也是为了保护类的安全。所谓安全,就是限制使用类的属性和操做。
对象内部数据结构这种不可访问性称为信息(数据)隐藏。
封装就是把对象的属性和操做结合成一个独立的系统单位,并尽量隐蔽对象的内部细节。
在类中,封装是经过存取权限实现的。
2.4.4 继承
继承是一个类能够得到另外一个类的特性的机制,继承支持层次概念。
经过继承,低层的类只须定义特定于它的特征,而共享高层的类中的特征。
2.4.5 多态性
不一样的对象能够调用相同名称的函数,但可致使彻底不一样的行为的现象称为多态性。
2.5 使用类和对象
2.5.1 使用string对象
所谓方法,书就是供这类对象使用的成员函数。对象使用本身的成员函数的方法是经过“.”运算符,格式以下:
对象名.成员函数
2.5.2 使用string类的典型成员函数实例
2.5.3 使用complex对象
C++标准程序库提供complex类定义复数对象。
2.5.4 使用对象小结
注意:类是抽象出一类物质的共同特征,模板则是概括出不一样类型事物的共同操做。
2.6 string对象数组与泛型算法
第3章 函数和函数模板
3.1 函数的参数及其传递方式
C语言函数参数的传递方式只有传值一种,传值又分为传变量值和传变量地址值两种状况。比较复杂的是结构变量,结构变量的值是指结构域中全部的变量值。在C++中,像int、float、double、char、bool等简单数据类型的变量,也是对象。类对象通常都包括数据成员和成员函数,若是在C++沿用C语言的说法,则对象的值就是对象全部数据成员的值,约定参数传递中传递“对象值”是指对象的数据成员值,传递“对象地址值”是指对象的首地址值。C++函数还可使用传递对象的“引用”方式,即它的函数参数有两种传递方式:传值和传引用。传引用其实就是传对象的地址,全部也称传地址方式。
参数传递中不要混淆传地址值和传地址的区别。传地址值传递的是值,不是地址;传地址传的是地址不是地址值。传递对象地址值是使用对象指针作为参数;传递地址时使用对象引用做为参数。
3.1.1 对象做为函数参数
将对象做为函数参数,是将实参对象的值传递给形参对象,这种传递是单向的。形参拥有实参的备份,当在函数中改变形参的值时,改变的是这个备份中的值,不会影响原来实参的值。
3.1.2 对象指针做为函数参数
将指向对象的指针做为函数参数,形参是对象指针(指针能够指向对象的地址),实参是对象的地址值。
注意:不要非要在主程序里产生指针,而后再用指针做为参数。函数原型参数的类型是指针,能够指针让它指向对象地址,即:
string *s1=&str1;
是彻底正确的,使用&str1标识的是取对象str1的地址值,因此&str1直接做为参数便可。
3.1.3 引用做为函数参数
可使用“引用”做为函数的参数(引用形参)。这时函数并无对形参对象初始化,即没有指定形参对象是哪一个对象的别名。在函数调用是,实参对象名传给形参对象名,形参对象名就成为实参对象名的别名。实参对象和形参对象表明同一个对象,因此改变形参对象的值就是改变实参对象的值。
实际上,在虚实结合时是把实参对象的地址传给形参对象,使形参对象的地址取实参对象的地址,从而使形参对象和实参对象共享同一个单元。这就是地址传递方式。
经过使用引用参数,一个函数能够修改另一个函数内的变量。由于引用对象不是一个独立的对象,不单独占用内存单元,而对象指针要另外开辟内存单元(其内容是地址),因此传引用比传指针更好。
注意:虽然系统向形参传递的是实参的地址而不是实参的值,但实参必须使用对象名。
3.14 默认参数
默认参数就是不要求程序员设定该参数,而由编译器在须要时给该参数赋默认值。当程序员须要传递特殊值时,必须显式地指明。默认参数是在函数原型中说明的,默认参数能够多于1个,但必须放在参数序列的后部。
若是一个默认参数须要指明一个特定值,则在其以前全部的参数都必须赋值。
3.1.5 使用const保护数据
用const修饰传递参数,意思是通知函数,它只能使用参数而无权修改它。这主要是为了提升系统的自身安全。C++中广泛采用这种方法。
3.2 深刻讨论函数返回值
C++函数的返回值类型能够是除数组之外的任何类型。非void类型的函数必须向调用者返回一个值。数组只能返回地址。当函数返回值是指针或引用对象时,须要特别注意:函数返回所指的对象必须继续存在,所以不能将函数内部的局部对象做为函数的返回值。
3.2.1 返回引用的函数
函数能够返回一个引用,将函数说明为返回一个引用的主要目的是为了将该函数用在赋值运算符的左边。函数原型的表示方法以下:
数据类型 &函数名(参数列表);
3.2.2 返回指针的函数
函数的返回值能够是存储某种类型数据的内存地址,称这种函数为指针函数。它们的通常定义形式以下:
类型标识符 *函数名(参数列表);
在C++中,除了内存分配失败以外,new不会返回空指针,而且没有任何对象的地址为零。指针所指向的对象的生存期不该低于该指针的生存期。
3.2.3 返回对象的函数
3.2.4 函数返回值做为函数的参数
若是用函数返回值做为另外一个函数的参数,这个返回值必须与参数类型一致。
3.3 内联函数
使用关键字inline说明的函数称内联函数。在C++中,除具备循环语句、switch语句的函数不能说明为内联函数外,其余函数均可以说明为内联函数。使用内联函数能加快程序执行速度,但若是函数体语句多,则会增长程序代码的大小。使用小的内联函数在代码速度和大小上能够取得折衷,其余状况下取决于程序员是追求代码速度,仍是追求代码的规模。
因为编译器必须知道内联函数的函数体,才能进行内联替换,所以,内联函数必须在程序中第一次调用此函数的语句出现以前定义。
3.4 函数重载和默认参数
函数重载可使一个函数名具备多种功能,即具备“多种形态”,这种特性称为多态性。
C++的多态性又被直观地称为“一个名字,多个函数”。源代码只指明函数调用,而不说明具体调用哪一个函数。编译器的这种链接方式称为动态联编或迟后联编。在动态联编中,直到程序运行才能肯定调用哪一个函数(动态联编须要虚函数的支持)。若是编译器在编译时,能根据源代码调用固定的函数标识符,并用物理地址代替它们,这就称为静态联编或先期联编。静态联编是在程序被编译时进行的。
使用默认参数,就不能对少于参数个数的函数进行重载。另外,仅有函数返回值不一样也是区分不了重载函数的。当使用默认参数设计类的构造函数时,要特别注意这一问题。
3.5 函数模板
1.引入函数模版
因为函数在设计时没有使用实际的类型,而是使用虚拟的类型参数,故其灵活性获得增强。当用实际的类型来实例化这种函数时,就好像按照模版来制造新的函数同样,因此称这种函数为函数模板。将函数模版与某个具体数据类型连用,就产生了模板函数,又称这个过程为函数模板实例化,这种形式就是类型参数化。
2.函数模板的参数
对于一个默认调用,能从函数参数推断出模板参数的能力是其中最关键的一环。要想省去显式调用的麻烦,条件是由这个调用的函数参数表可以唯一地标识出模板参数的一个集合。
3.使用显式规则和关键字typename
C++专门定义一个仅仅用在模板中的关键字typename,它的用途之一是代替template参数列表中的关键字class。
template<typename T> int compare(const T& left, const T& right) { if (left < right) { return -1; } if (right < left) { return 1; } return 0; } compare<int>(1, 2); //使用模板函数
第4章 类和对象
4.1 类及其实例化
对象就是一类物体的实例,将一组对象的共同特征抽象出来,从而造成“类”的概念。
4.1.1 定义类
像C语言构造结构同样,类也是一种用户本身构造的数据类型并遵循C++的规定。
类要先声明后使用,无论声明内容是否相同,声明同一个名字的两个类是错误的,类是具备唯一标识符的实体;在类中声明的任何成员不能使用extern、auto和register关键字进行修饰;类中声明的变量属于该类,在某些状况下,变量也能够被该类的不一样实例所共享。
类和其余数据类型不一样的是,组成这种类型的不只能够有数据,并且能够有对数据进行操做的函数,他们分别叫作类的数据成员和类的成员函数,并且不能在类声明中对数据成员使用表达式进行初始化。
1.类声明
类声明以关键字class开始,其后跟类名。类所声明的内容用花括号括起来,右花括号后的分号做为类关键字声明语句的结束标志。这一对花括号之间的内容称为类体;
访问权限用于控制对象的某个成员在程序中的可访问性,若是没有使用关键字,则全部成员默认声明为private权限。
2.定义成员函数
类中声明的成员函数用来对数据成员进行操做,还必须在程序中实现这些成员函数。
定义成员函数的通常形式以下:
返回类型 类名::成员函数名(参数列表)
{
成员函数的函数体//内部实现
}
其中“::”是做用域运算符,“类名”是成员函数所属类的名字,“::”用于表名其后的成员函数是属于这个特定的类。换言之,“类名::成员函数名”的意思就是对属于“类名”的成员函数进行定义,而“返回类型”则是这个成员函数返回值的类型。
也可使用关键字inline将成员函数定义为内联函数。
若是在声明类的同时,在类体内给出成员函数的定义,则默认为内联函数。
3.数据成员的赋值
不能在类体内给数据成员赋值。在类体外就更不容许了。
数据成员的具体值是用来描述对象的属性的。只有产生了一个具体的对象,这些数据值才有意义。若是在产生对象时就使对象的数据成员具备指定值,则称为对象的初始化。
4.1.2 使用类的对象
对象和引用都使用运算符“.”访问对象的成员,指针则使用“- >”运算符。
暂不涉及尚未介绍的保护成员,能够概括出以下规律:
(1)类的成员函数能够直接使用本身类的私有成员(数据成员和成员函数)
(2)类外面的函数不能直接访问类的私有成员(数据成员和成员函数)
(3)类外面的函数只能经过类的对象使用该类的公有成员函数。
在程序运行时,经过为对象分配内存来建立对象。在建立对象时,使用类做为样板,故称对象为类的实例。
定义类对象指针的语法以下:
类名* 对象指针名;
对象指针名=对象的地址;
也能够直接进行初始化。
类名* 对象指针名=对象的地址;
类对象的指针能够经过“->”运算符访问对象的成员,即:
对象指针名->对象成员名
4.1.3 数据封装
面向对象的程序设计是经过为数据和代码创建分块的内存区域,以便提供对程序进行模块化的一种程序设计方法,这些模块能够被用作样板,在须要时在创建其副本。根据这个定义,对象是计算机内存中的一块区域,经过将内存分块,每一个模块(即对象)在功能上保持相对独立。
对象被视为能作出动做的实体,对象使用这些动做完成相互之间的做用。换句话说,对象就像在宿主计算机上拥有数据和代码并能相互通讯的具备特定功能的一台较小的计算机。
4.2 构造函数
C++有称为构造函数的特殊成员函数,它可自动进行对象的初始化。
初始化和赋值是不一样的操做,当C++语言预先定义的初始化和赋值操做不能知足程序的要求时,程序员能够定义本身的初始化和赋值操做。
4.2.1 默认构造函数
当没有为一个类定义任何构造函数的状况下,C++编译器总要自动创建一个不带参数的构造函数。
4.2.2 定义构造函数
1.构造函数的定义和使用方法
构造函数的名字应与类名同名。并在定义构造函数时不能指定返回类型,即便void类型也不能够。
当声明一个外部对象时,外部对象只是引用在其余地方声明的对象,程序并不为外部对象说明调用构造函数。若是是全局对象,在main函数执行以前要调用它们的构造函数。
2.自动调用构造函数
程序员不能在程序中显式地调用构造函数,构造函数是自动调用的。例如构造一个Point类的对象a,不能写成“Point a.Point(x,y) ;”,只能写成“Point a(x,y) ;”。编译系统会自动调用Point(x,y)产生对象a并使用x和y将其正确地初始化。
能够设计多个构造函数,编译系统根据对象产生的方法调用相应的构造函数,构造函数是在产生对象的同时初始化对象的。
4.2.3 构造函数和预算符new
运算符new用于创建生存期可控的对象,new返回这个对象的指针。因为类名被视为一个类型名,所以,使用new创建动态对象的语法和创建动态变量的语法相似,其不一样点是new和构造函数一块儿使用。
使用new创建的动态对象只能用delete删除,以便释放所占空间。应养成及时释放再也不使用的内存空间的习惯。
4.2.4 构造函数的默认参数
若是程序定义本身的有参数构造函数,又想使用无参数形式的构造函数,解决的方法时间相应的构造函数所有使用默认的参数设计。
4.2.5 复制构造函数
引用在类中一个很重要的用途是用在复制构造函数中。一是一类特殊并且重要的函数,一般用于使用已有的对象来创建一个新对象。
在一般状况下,编译器创建一个默认复制构造函数,默认复制构造函数采用拷贝方式使用已有的对象来创建新对象,因此又直译为拷贝构造函数。程序员能够本身定义复制构造函数,对类A而言,复制构造函数的原型以下:
A::A(A&)
从这个函数原型来看,首先它是一个构造函数,由于这毕竟是在创造一个新对象。其次,他的参数有些特别,是引用类本身的对象,即用一个已有的对象来创建新对象。使用引用是从程序的执行效率角度考虑的。为了避免改变原有对象,更普通的形式是像下面这样使用const限定:
A::A(const A &)
像调用构造函数同样,若是自定义了复制构造函数,编译器只调用程序员为它设计的赋值构造函数。
在C++中,在一个类中定义的成员函数能够访问该类任何对象的私有成员。
4.3 析构函数
在对象消失时,应使用析构函数释放由构造函数分配的内存。构造函数、赋值构造函数和析构函数是构造型成员函数的基本成员,应深入理解他们的做用并熟练掌握其设计方法。
4.3.1 定义析构函数
由于调用析构函数也是由编译器来完成的,因此编译器必须总能知道应调用哪一个函数。最容易、也最符合逻辑的方法是指定这个函数的名称与类名同样。为了与析构函数区分,在析构函数的前面加上一个“~”号(仍然称析构函数与类同名)。在定义析构函数时,不能指定任何返回类型,即便指定void类型返回类型也不行。析构函数也不能指定参数,可是能够显示地说明参数为void,即形如A::~A(void)。从函数重载的角度分析,一个类也只能定义一个析构函数且不能指明参数,以便编译系统自动调用。
析构函数在对象的生存期结束时被自动调用。当对象的生存期结束时,程序为这个对象调用析构函数,而后回收这个对象占用的内存。全局对象和静态对象的析构函数在程序运行结束以前调用。
类的对象数组的每一个元素调用一次析构函数。全局对象数组的析构函数在程序结束以前被调用。
4.3.2 析构函数与运算符delete
运算符delete与析构函数一块儿工做。当使用运算符delete删除一个动态对象时,他首先为这个动态对象调用析构函数,而后再释放这个动态对象占用的内存,这和使用new创建动态对象的过程正好相反。
当使用delete释放动态对象数组时,必须告诉这个动态对象数组有几个元素对象,C++使用“[ ]”来实现。即语句
delete[ ] ptr ; //注意不要写错为delete ptr[]
当程序前后建立几个对象时,系统按后建先析构的原则析构对象。当使用delete调用析构函数时,则按delete的顺序析构。
4.3.3 默认析构函数
若是在定义类时没有定义析构函数,C++编译器也为它产生一个函数体为空的默认析构函数。
4.4 调用复制构造函数的综合实例
4.5 成员函数重载及默认参数
4.6 this指针
使用this指针,保证了每一个对象能够拥有本身的数据成员,但处理这些数据成员的代码能够被全部的对象共享。
C++规定,当一个成员函数被调用是,系统自动向它传递一个隐含的参数,该参数是一个指向调用该函数的对象的指针,从而使成员函数知道该对哪一个对象进行操做。在程序中,可使用关键字this来引用该指针。this指针是C++实现封装的一种机制,它将对象和该对象调用的成员函数链接在一块儿,在外部看来,每个对象都拥有本身的成员函数。
除非有特殊须要,通常状况下都省略符号“this ->”,而让系统进行默认设置。
4.7 一个类的对象做为另外一个类的成员
4.8 类和对象的性质
4.8.1 对象的性质
(1)同一个类的对象之间能够相互赋值。
(2)可以使用对象数组。
(3)可以使用指向对象的指针,使用取地址运算符&将一个对象的地址置于该指针中。
注意,指向对象的指针的算术运算规则与C语言的同样,但指向对象的指针不能取数据成员的地址,也不能去成员函数的地址。
(4)对象能够用做函数参数。
(5)对象做为函数参数时,可使用对象、对象引用和对象指针。
(6)一个对象能够作为另外一个类的成员。
4.8.2 类的性质
1.使用类的权限
为了简单具体,讨论数据成员为私有,成员函数为公有的状况。
(1)类自己的成员函数可使用类的全部成员(私有和公有成员)。
(2)类的对象只能访问公有成员函数。
(3)其余函数不能使用类的私有成员,也不能使用公有成员函数,它们只能经过类的对象使用类的公有成员函数。
(4)虽然一个能够包含另一个类的对象,但这个类也只能经过被包含类的对象使用那个类的成员函数,经过成员函数使用数据成员。
2.不彻底的类声明
类不是内存中的物理实体,只有当使用类产生对象时,才进行内存分配,这种对象创建的过程称为实例化。
应当注意的是:类必须在其成员使用以前先进行声明。
class MembersOnly; //不彻底的类声明
MenbersOnly *club; //定义一个全局变量类指针
第一条语句称为不彻底类声明,它用于在类没有彻底定义以前就引用该类的状况。
不彻底声明的类不能实例化,不然会出现编译错误;不彻底声明仅用于类和结构,企图存取没有彻底声明的类成员,也会引发编译错误。
3.空类
尽管类的目的是封装代码和数据,它也能够不包括任何声明。
class Empty{};
4.类做用域
声明类时所使用的一对花括号造成所谓的类的做用域。在类做用域中声明的标识符只在类中可见。
若是该成员函数的实现是在类定义以外给出的,则类做用域也包含类中成员函数的做用域。
类中的一个成员名可使用类名和做用域运算符来显式地指定,这称为成员名限定。
4.9 面向对象的标记图
4.9.1 类和对象的UML标记图
4.9.2 对象的结构与链接
只有定义和描述了对象之间的关系,各个对象才能构成一个总体的、有机的系统模型,这就是对象的结构和连结关系。对象结构是指对象之间的分类(继承)关系和组成(聚合)关系,统称为关联关系。对象之间的静态关系是经过对象属性之间的链接反映的,称为实例链接。对象行为之间的动态关系是经过对象行为(信息)之间的依赖关系表现的,称之为消息链接,实例链接和消息链接统称为链接。
1.分类关系及其表示
C++中的分类结构是继承(基类/派生类)结构,UML使用一个空三角形表示继承关系,三角形指向基类。
2.对象组成关系及其表示
组成关系说明的结构是总体与部分关系。C++中最简单的是包含关系。C++语言中的“聚合”隐含了两种实现方式,第一种方式是独立地定义,能够属于多个总体对象,并具备不一样生存期。这种所属关系是能够动态变化的,称之为汇集。使用空心菱形表示它们之间的关系。第二种方式是用一个类的对象做为一种广义的数据类型来定义总体对象的一个属性,构成一个嵌套对象。在这种状况下,这个类的对象只能隶属于唯一的总体对象并与它同生同灭,称这种状况为“组合”,它们之间的关联关系比第一种强,具备管理组成部分的责任,使用实心菱形表示。
3.实例链接及其表示
实例链接反映对象之间的静态关系,例如车和驾驶员的关系,这种双边关系在实现中能够经过对象(实例)的属性表达出来。实例链接有一对1、一对多、多对多3种链接方式。
4.消息链接及其表示
消息链接描述对象之间的动态关系。即若一个对象在执行本身的操做时,须要经过消息请求另外一个对象为它完成某种服务,则说第一个对象与第二个对象之间存在着消息链接。消息链接是有方向的,使用一条带箭头的实线表示,从消息的发送者指向消息的接收者。
4.9.3 使用实例
4.9.4 对象、类和消息
对象的属性是指描述对象的数据成员。数据成员能够是系统或程序员定义的数据类型。对象属性的集合称为对象的状态。
对象的行为是定义在对象属性上的一组操做的集合。操做(函数成员)是响应消息而完成的算法,表示对象内部实现的细节。对象的操做集合体现了对象的行为能力。
对象的属性和行为是对象定义的组成要素,分别表明了对象的静态和动态特征。由以上分析的例子可见,不管对象是简单的或是负责的,通常具备如下特征:
(1)有一个状态,由与其相关的属性集合所表征。
(2)有唯一标识名,能够区别于其余对象。
(3)有一组操做方法,每一个操做决定对象的一种行为。
(4)对象的状态只能被本身的行为所改变。
(5)对象的操做包括自身操做(施加于自身)和施加于其余对象的操做。
(6)对象之间以消息传递的方式进行通讯。
(7)一个对象的成员仍能够是一个对象。
4.10 面向对象编程的文件规范
通常要求将类的声明放在头文件中,很是简单的成员函数能够在声明中定义(默认内联函数形式),实现放在.cpp文件中。在.cpp文件中,将头文件包含进去。主程序单独使用一个文件,这就是多文件编程规范。
4.10.1 编译指令
C++的源程序可包含各类编译指令,以指示编译器对源代码进行编译以前先对其进行预处理。全部的编译指令都以#开始,每条编译指令单独占用一行,同一行不能有其余编译指令和C++语句(注释例外)。编译指令不是C++的一部分,但扩展了C++编程环境的使用范围,从而改善程序的组织和管理。
1.嵌入指令
嵌入指令#include指示编译器将一个源文件嵌入到带有#include指令的源文件中该指令所在的位置处。尖括号或双引号中的文件名可包含路径信息。例如:
注意:因为编译指令不是C++的一部分,所以,在这里表示反斜杠时只使用一个反斜杠。
2.宏定义
“宏名”必须是一个有效的C++标识符,“替换正文”可为任意字符组成的字符序列。“宏名”和“替换正文”之间至少有一个空格。注意,宏定义由新行结束,而不以分号结束。若是给出了分号,则它也被视做为替换正文的一部分。当替换正文要书写在多行上时,除最后一行外,每行的行尾要加上一个反斜线,表示宏定义继续到下一行。
因宏定义有许多不安全因素,对须要使用无参数宏的场合,应该尽可能使用const代替宏定义。
在程序的一个地方定义的宏名,若是不想使其影响到程序的其余地方,能够在再也不使用时用#undef删除。
3.条件编译指令
条件编译指令是#if、#else、#elif和#endif,它们构成相似于C++的if选择结构,其中#endif表示一条指令结束。
编译指令#if用于控制编译器对源程序的某部分有选择地进行编译。该部分从#if开始,到#endif结束。若是#if后的常量表达式为真,则编译这部分,不然就不编译该部分,这时,这部分代码至关于被从源文件中删除。
编译指令#else在#if测试失效的状况下创建另一种选择。能够在#else分支中使用编译指令#error输出出错信息。#error使用的形式以下:
“出错信息”是一个字符序列。当遇到#error指令时,编译器显示其后面的“出错信息”,并停止对程序的编译。
编译指令可嵌套,嵌套规则和编译器对其的处理方式与C++的if预计嵌套状况相似。
4.define操做符
关键字defined不是指令,而是一个预处理操做符,用于判断一个标识符是否已经被#define定义。若是标识符identifier已被#define定义,则defined(identifier)为真,不然为假。
条件编译指令#ifdef和#ifndef用于测试其后的标识符是否被#define定义,若是已经被定义,则#ifdef测试为真,#ifndef测试为假;若是没有被定义,则#ifdef测试为假,#ifndef测试为真。
4.10.2 在头文件中使用条件编译
第五章 特殊函数和成员
5.1 对象成员的初始化
能够在一个类中说明具备某个类的类型的数据成员,这些成员成为对象成员。在类A中说明对象成员的通常形式以下:
class A
{
类名1 成员名1;
类名2 成员名2;
……
类名n 成员名n;
};
说明对象成员是在类名以后给出对象成员的名字。为初始化对象成员,A类的构造函数要调用这些对象成员所在类的构造函数,A类的构造函数的定义形式以下:
A::A(参数表0):成员1(参数表1),成员2(参数表2),成员n(参数表n)
{//其余操做}
冒号“:”后由逗号隔开的项目组成员初始化列表,其中的参数表给出了为调用相应成员所在类的构造函数时应提供的参数。参数列表中的参数都来自“参数表0”,可使用任意复杂的表达式,其中能够有函数调用。若是初始化列表某项的参数表为空,则列表中相应的项能够省略。
对象成员构造函数的顺序取决于这些对象成员在类中说明的顺序,与他们在成员初始化列表中给出的顺序无关。析构函数的调用顺序与构造函数正好相反。
5.2 静态成员
简单成员函数是指声明中不含const、volatile、static关键字的函数。若是类的数据成员或成员函数使用关键字static进行修饰,这样的成员称为静态数据成员或静态成员函数,统称为静态成员。
静态数据成员只能说明一次,若是在类中仅对静态数据成员进行声明,则必须在文件做用域的某个地方进行定义。在进行初始化以前,必须进行成员名限定。
注意:因为static不是函数中的一部分,因此在类声明以外定义静态成员函数时,不使用static。在类中定义的静态成员函数是内联的。
类中的任何成员函数均可以访问静态成员。由于静态成员函数没有this指针,因此静态成员函数只能经过对象名(或指向对象的指针)访问该对象的非静态成员。
静态成员函数与通常函数有以下不一样:
(1)能够不指向某个具体的对象,只与类名连用。
(2)在没有创建对象以前,静态成员就已经存在。
(3)静态成员是类的成员,不是对象的成员。
(4)静态成员为该类的全部对象共享,它们被存储于一个公用的内存中。
(5)没有this指针,因此除非显式地把指针传给它们,不然不能存取类的数据成员。
(6)静态成员函数不能说明为虚函数。
(7)静态成员函数不能直接访问非静态函数。
静态对象具备以下性质:
(1)构造函数在代码执行过程当中,第一次遇到它的时候变量定义时被调用,但直到整个程序结束以前仅调用一次。
(2)析构函数在整个程序退出以前被调用,一样也只调用一次。
5.3 友元函数
有时两个概念上相近的类要求其中一个类能够无限制地存取另外一个类的成员。
友元函数解决了这类难题。友元函数能够存取私有成员、公有成员和保护成员。其实,友元函数能够是一个类或函数,还没有定义的类也能够做为友元引用。
1.类自己的友元函数
为本类声明一个友元函数,这时,虽然在类中说明它,但它并非类的成员函数,因此能够在类外面像普通函数那样定义这个函数。
虽然友元函数是在类中说明的,但函数的定义通常在类以外,做用域的开始点在它的说明点,结束点和类的做用域相同。
友元说明能够出现于类的私有或公有部分。由于友元说明也必须出现于类中,因此应将友元看做类的接口的一部分。使用它的主要目的是提升程序效率。友元函数能够直接访问类对象的私有程序,于是省去调用类成员函数的开销。它的另外一个优势是:类的设计者没必要在考虑号该类的各类可能使用状况以后再设计这个类,而能够根据须要,经过使用友元增长类的接口。但使用友元的主要问题是:它容许友元函数访问对象的私有成员,这破坏了封装和数据隐藏,致使程序的可维护性变差,所以在使用友元时必须权衡得失。
注意:友元函数能够在类中声明时定义。若是在类外定义,不能再使用friend关键字。
2.将成员函数用做友元
一个类的成员函数(包括构造函数和析构函数)能够经过使用friend说明为另外一个类的友元。
3.将一个类说明为另外一个类的友元
能够将一个类说明为另外一个类的友元。这时,整个类的成员函数均具备友元函数的性能。声明友元关系简化为“friend class 类名;”
须要注意的是,友元关系是不传递的,即当说明类A是类B的友元,类B又是类C的友元时,类A却不是类C的友元。这种友元关系也不具备交换性,即当说明类A是类B的友元时,类B不必定是类A的友元。
当一个类要和另外一个类协同工做时,使一个类称为另外一个类的友元是颇有用的。
友元声明与访问控制无关。友元声明在私有区域进行或在公有区域进行是没有太大区别的。虽然友元函数的声明能够置于任何部分,但一般置于能强调其功能的地方以使其直观。对友元函数声明的唯一限制是该函数必须出如今类声明内的某一部分。
5.4 const对象
能够在类中使用const关键字定义数据成员和成员函数或修饰一个对象。一个const对象只能访问const成员函数,不然将产生编译错误。
1.常量成员
常量成员包括常量数据成员、静态常数据成员和常引用。静态常数据成员仍保留静态成员特征,须要在类外初始化。常数据成员和常引用只能经过初始化列表来得到初值。
2.常引用做为函数参数
使用引用做为参数,传送的是地址。但有时仅但愿将参数的值提供给函数使用,并不容许函数改变对象的值,这时可使用常引用做为参数。
3.常对象
在对象名前使用const声明常量对象,但声明时必须同时进行初始化,并且不能被更新。定义的语法以下:
类名 const 对象名(参数表);
4.常成员函数
能够声明一个成员函数为const函数。一个const对象能够调用const函数,但不能调用非const成员函数。const放在函数声明以前意味着返回值是常量,但这不符合语法。必须将关键字const放在参数列表以后,才能说明该函数是一个const成员函数。声明常成员函数的格式以下:
类型标识符 函数名(参数列表) const;
为了保证不只声明const成员函数,并且确实也定义为const函数,程序员在定义函数时必须重申const声明。也就是说,const已经成为这种成员函数识别符的一部分,编译器和链接程序都要检查const。定义格式以下:
类型标识符 类名::函数名(参数列表)const{//函数体}
const位于函数参数表以后,函数体以前。也能够在类中用内联函数定义const函数。
类型标识符 函数名(参数列表)const{//函数体}
在定义成员函数时,函数体以前加上const能够防止覆盖函数改变数据成员的值。
在常成员函数里,不能更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数。若是将一个对象说明为常对象,则经过该对象只能调用它的const成员函数,不能调用其余成员函数。
注意:用const声明static成员函数没有什么做用。在C++中声明构造函数和析构函数时使用const关键字均是非法的,但有些C++的编译程序并不给出出错信息。volatile关键字的使用方法与const相似,但因其不多用,此处不介绍。
5.5 数组和类
编译器调用适当的构造函数创建数组的每个份量。若是找不到合适的构造函数,则产生错误信息。
5.6 指向类成员函数的指针
对象是一个完整的实体。为了支持这一封装,C++包含了指向类成员的指针。能够用普通指针访问内存中给定类型的任何对象,指向类成员的指针则用来访问某个特定类的对象中给定类型的任何成员。
C++既包含指向类数据成员函数的指针,又包含指向成员函数的指针。他提供一种特殊的指针类型,指针指向类的成员,而不是指向该类的一个对象中该成员的一个实例,这种指针称为指向类成员的指针。不过,指向类数据成员的指针要求数据成员是公有的,用途不大,因此不予介绍,仅简单介绍指向类成员函数的指针。
类并非对象,但有时可将其视为对象来使用。能够声明并使用指向对象成员函数的指针。指向对象的指针是比较传统的指针,假设类A的成员函数为“void fa(void);”,如要创建一个指针pafn,它能够指向任何无参数和无返回值的类A的成员函数:
void(A::* pafn)(void);
虽然“::”是做用域分辨符,但在这种状况下,“A::”最好读成“A的成员函数”,这时,从内往外看,此声明可读做:pafn是一个指针,指向类A的成员函数,此成员函数既无参数,也无返回值。下面的例子说明了pafn如何被赋值并用以调用函数fa:
pafn=A::fa; //指向类A的成员函数fa的指针pafn
a x; //类A的对象x
A *px=&x; //指向类A对象x的指针px
(x.*pafn)(); //调用类A的对象x的成员函数fa
(px->pafn)(); //调用类A的对象x的指针px指向的成员函数fa
指向类A中参数类型列表为list,返回类型为type的成员函数的指针声明形式以下:
type(A::* pointer)(list);
若是类A的成员函数fun的原型与pointer所指向的函数的原型同样,则语句
pointer=A::fun;
将该函数的地址(这个地址不是真实地址,而是在A类中全部对象的便宜)置给了pointer。在使用指向类成员函数的指针访问对象的某个成员函数时,必须指定一个对象。用对象名或引用调用pointer所指向的函数时,使用运算符“.”,使用指向对象的指针调用pointer所指向的成员函数时,使用运算符“->”。5.7 求解一元二次方程第六章 继承和派生6.1 继承和派生的基本概念这种经过特殊化已有的类来创建新类的过程,叫作“类的派生”,原来的类叫作“基类”,新创建的类则叫作“派生类”。另外一方面,从类的成员角度看,派生类自动地将基类的全部成员做为本身的成员,这叫作“继承”。基类和派生类又能够分别叫作“父类”和“子类”,有时也称为“通常类”和“特殊类”。从一个或多个之前定义的类(基类)产生新类的过程称为派生,这个新类称为派生类。派生的新类同时也能够增长或者从新定义数据和操做,这就产生了类的层次性。类的继承是指派生类继承基类的数据成员和成员函数。继承经常使用来表示类属关系,不能将继承理解为构成关系。当从现有的类中派生出新类时,派生类能够有如下几种变化:(1)增长新的成员(数据成员或成员函数)(2)从新定义已有的成员函数(3)改变基类成员的访问权限C++派生类使用两种基本的面向对象技术:第一种称为性质约束,即对基类的性质加以限制;第二种称为性质扩展,即增长派生类的性质。C++中有两种继承:单一继承和多重继承。对于单一继承,派生类只能有一个基类;对于多重继承,派生类能够有多个基类。6.2 单一继承6.2.1 单一继承的通常形式在C++,声明单一继承的通常形式以下:class 派生类名:访问控制 基类名{private:成员声明列表protected:成员声明列表public:成员声明列表};这里和通常的类声明同样,用关键字class声明一个新的类。冒号后面的部分指示这个新类是哪一个基类的派生类。所谓“访问控制”是指如何控制基类成员在派生类中的访问属性。它是3个关键字public、private、protected中的一个。一对大括号“{}”是用来声明派生类本身的成员的。6.2.2 派生类的构造函数和析构函数定义派生类的构造函数的通常形式以下:派生类名::派生类名(参数表0) : 基类名(参数表){...//函数体}冒号后“基类名(参数表)”称为成员初始化列表,参数表给出所调用的基类构造函数所须要的实参。实参的值能够来自“参数表0”,或由表达式给出。构造函数(包括析构函数)是不被继承的,因此一个派生类只能调用它的直接基类的构造函数。当定义派生类的一个对象时,首先调用基类的构造函数,对基类成员进行初始化,而后执行派生类的构造函数,若是某个基类还是一个派生类,则这个过程递归执行。该对象消失时,析构函数的执行顺序和执行构造函数的顺序正好相反。6.2.3 类的保护成员C++语言规定,使用公有方式产生的派生类成员函数能够直接访问基类中定义的或从另外一个基类继承来的公有成员,但不能访问基类的私有成员。在类的声明中,关键字protected以后声明的是类的保护成员。保护成员具备私有成员和公有成员的双重角色;对派生类的成员函数而言,它是公有成员,能够被访问;而对其余函数而言则还是私有成员,不能被访问。6.2.4 访问权限和赋值兼容规则1.公有派生和赋值兼容规则在公有派生的状况下,基类成员的访问权限在派生类中保持不变。这就意味着:(1)基类的公有成员在派生类中依然是公有的。(2)基类的保护成员在派生类中依然是保护的。(3)基类的不可访问的和私有的成员在派生类中也仍然时不可访问的。当但愿类的某些成员可以被派生类所访问,而又不能被其余的外界函数访问的时候,就应当把它们定义为保护的。所谓赋值兼容规则是指在公有派生状况下,一个派生类的对象能够做为基类的对象来使用的状况。注意:静态成员能够被继承,这时基类对象和派生类的对象共享该静态成员。2.“isa”和“has-a”的区别类与类之间的关系有两大类:一是继承和派生问题,二是一个类使用另外一个类的问题。后者的简单用途是把另外一个类的对象做为本身的数据成员或者成员函数的参数。对于继承,首先要掌握公有继承的赋值兼容规则,理解公有继承“就是一个(isa)”的含义。分层也能够叫作包含、嵌入或者聚合。公有继承的意思是“isa”。与此相反,分层的意思是指“has-a(有一个)”。3.公有继承存取权限表注意:静态成员能够被继承,这时,基类对象和派生类的对象共享该静态成员。4.私有派生经过私有派生,基类的私有和不可访问成员在派生类中是不可访问的,而公有和保护成员这时就成了派生类的私有成员,派生类的对象不能访问继承的基类成员,必须定义公有的成员函数做为接口。更重要的是,虽然派生类的成员函数可经过定义自定义的函数访问基类的成员,但将派生类做为基类在继续派生时,这时即便使用公有派生,原基类公有成员在新的派生类中也将是不可访问的。5.保护派生派生也可使用protected。这种派生使原来的权限都降一级使用,即private变为不可访问;protected变为private;public变为protected。由于限制了数据成员和成员函数的访问权限,因此用的比较少。它与private继承的主要区别在于下一级的派生中。6.3 多重继承一个类从多个基类派生的通常形式以下:class 类名1:访问控制 类名2,访问控制 类名3,...,访问控制 类名n{...//定义派生类本身的成员};6.4 二义性及其支配规则对基类成员的访问必须是无二义性的,如使用一个表达式的含义能解释为可访问多个基类中的成员,则这种对基类成员的访问就是不肯定的,称这种访问具备二义性。6.4.1 做用域分辨符和成员名限定从类中派生其余类可能致使几个类使用同一个成员函数名或数据成员名。程序必须确切地告诉编译器使用哪一个版本的数据成员或成员函数。若是基类中的名字在派生类中再次声明,则派生类中的名字隐藏了基类中的相应名字。C++能够迫使编译器“看到”当前做用域的外层部分,存取那些被隐藏的名字,这是由做用域分辨运算符“::”实现的(简称做用域运算符)。这一过程叫作做用域分辨。做用域分辨操做的通常形式以下:类名::标识符“类名”能够是任意基类或派生类名,“类标识符”是该类中声明的任一成员名。6.4.2 派生类支配基类的同名函数基类的成员和派生类新增的成员都具备类做用域,基类在外层,派生类在内层。若是这时派生类定义了一个和基类成员函数同名的新成员函数(由于参数不一样属于重载,因此这里是指具备相同参数表的成员函数),派生类的新成员函数就覆盖了外层的同名成员函数。在这种状况下,直接使用成员函数名只能访问派生类的成员函数,只有使用做用域分辨,才能访问基类的同名成员函数。派生类D中的名字N支配基类B中同名的名字N,称为名字支配规则。第七章 类模板与向量7.1 类模板若是将类看做包含某些数据类型的框架,而后将这些数据类型从类中分离出来造成一个通用的数据类型T,为这个数据类型T设计一个操做集,而且容许原来那些数据类型的类都能使用这个操做集,这将避免由于类的数据类型不一样而产生的重复性设计,其实,类型T并非类,而是对类的描述,常称之为类模板。在编译时,由编译器将类模板与某种特定数据类型联系起来,就产生一个特定的类(模板类)。利用类模板能大大简化程序设计。7.1.1 类模板基础知识1.类模板的成分及语法关键字class在这里的含义是“任意内部类型或用户定义类型”,但T也多是结构或类。对于函数模版及类模板来讲,模板层次结构的大部份内容都是同样的,然而在模板声明以后,对类而言便显示出了根本性的差别。为了窗机类模板,在类模板参数表以后应该有类声明。在类中能够像使用其余类型(如int或double)那样使用模板参数。类模板声明的通常方法以下:template <类模板参数> class 类名{//类体};2.类模板的对象类模板也称为参数化类。初始化类模板时,只要传给它指定的数据类型,编译器就用指定类型替代模板参数产生相应的模板类。用类模板定义对象的通常格式以下:类名<模板实例化参数类型> 对象名(构造函数实参列表);类名<模板实例化参数类型> 对象名;//默认或者无参数构造函数使用模板类时,当给模板实例化参数类型一个指定的数据类型时,编译器自动用这个指定数据类型替代模板参数。在类体外面定义成员函数时,必须用template重写类模板声明。通常格式以下:template <模板参数>返回类型 类名<模板类型参数>::成员函数名(函数参数列表){//函数体}<模板类型参数> 是指template的“< >”内使用class(或typename)声明的类型参数,构造函数和析构函数没有返回类型。模板实例化参数类型包括数据类型和值。编译器不能从构造函数参数列表推断出模板实例化参数类型,因此必须显式地给出对象的参数类型。7.1.2 类模板的派生与继承类模板也能够继承,继承的方法与普通的类同样。声明模板类继承以前,必须从新声明类模板。模板类的基类和派生类均可以是模板(或非模板)类。类模板的派生与继承很复杂,本书仅简单介绍模板类继承非模板类和从模板类派生一个类模板两种状况。能够用一个非模板类为一组模板提供一种共同的实现方法。7.2 向量与泛型算法在数组生存期内,数组的大小是不会改变的。向量是一维数组的类版本,它与数组类似,其中的元素项老是连续存储的,但它和数组不一样的是:向量中存储元素的多少能够在运行中根据须要动态地增加或缩小。向量是类模板,具备成员函数。7.2.1 定义向量列表向量(vector)类模板定义在头文件vector中,它提供4种构造函数,用来定义由各元素组成的列表。用length表示长度,数据类型用type表示,对象名为name。向量定义的赋值运算符“=”,容许同类型的向量列表相互赋值,而无论它们的长度如何。向量能够改变赋值目标的大小,使它的元素数目与赋值源的元素数目相同。向量的第一个元素也是从0开始。不能使用列表初始化向量,但能够先初始化一个数组,而后把数组的内容复制给向量。7.2.2 泛型指针“与操做对象的数据类型相互独立”的算法称为泛型算法。也就是说,泛型算法提供了许多可用于向量的操做行为,而这些算法和想要操做的元素类型无关。这是借助一对泛型指针来实现的。向量具备指示第一元素的标记begin和指示结束的标记end,也就是标识要进行操做的元素空间。若是begin不等于end,算法便会首先做用于begin所指元素,并将begin前进一个位置,而后做用于当前begin所指元素,如此继续进行,直到begin等于end为止。由于end是最后一个元素的下一个位置,因此元素存在的范围是半开区间[begin,end)。在向量中,泛型指针是在底层指针的行为之上提供一层抽象化机制,取代程序原来的“指针直接操做方式”。假设用T表示向量的参数化数据类型,iterator在STL里面是一种通用指针,它在向量中的做用至关于T*。用iterator声明向量的正向泛型指针的通常形式以下:vector<type> :: iterator泛型指针名;对向量的访问能够是双向的,图中的rbegin和rend是提供给逆向泛型指针的开始和结束标志。逆向泛型指针的加操做是使它向rend方向移动,减操做向rbegin移动。声明逆向泛型指针使用reverse_iterator。声明的方法以下:vector<数据类型> :: reverse_iterator 指针名;7.2.3 向量的数据类型向量除了可使用基本数据类型以外,还可使用构造类型,只要符合构成法则便可。7.2.4 向量最基本的操做方法1.访问向量容量信息的方法(1)size():返回当前向量中已经存放的对象的个数(2)max_size():返回向量能够容纳最多对象的个数,通常是操做系统的寻址空间所需容纳的对象的个数。这个参数不是用户指定的,它取决于硬件结构。(3)capacity():返回无需再次分配内存就能容纳的对象个数。它的初始值为程序员最初申请的元素个数。当存放空间已满,又增长一个元素时,它在原来的基础上自动翻倍扩充空间,以便存放更多的元素。通俗地讲,也就是已申请的空间。这三者的关系以下。(4)empty():当前向量为空时,返回true。2.访问向量中对象的方法(1)front():返回向量中的第一个对象。(2)back():返回向量中的最后一个对象。(3)operator[](size_type,n):返回向量中的第n+1个对象(下标为n的向量元素)。3.在向量中插入对象的方法(1)push_back(const T&):向向量尾部插入一个对象。(2)insert(iterator it,const T&):向it所指向的向量位置前插入一个对象。(3)insert(iterator it,size_type n,const T&X):向it所指向量位置前插入n个值为X的对象。4.在向量中删除对象的方法(1)pop_back(const T&):删除向量中最后一个对象。(2)erase(iterator it):删除it所指向的容器对象。(3)clear():删除向量中的全部对象,empty()返回true。7.3 出圈游戏第八章 多态性和虚函数8.1 多态性静态联编所支持的多态性称为编译时的多态性。当调用重载函数时,编译器能够根据调用时使用的实参在编译时就肯定下来应调用哪一个函数。动态联编所支持的多态性称为运行时的多态性,这由虚函数来支持。虚函数相似于重载函数,但与重载函数的实现策略不一样,即对虚函数的调用使用动态联编。8.1.1 静态联编中的赋值兼容性及名字支配规律对象的内存地址空间中只包含数据成员,并不存储有关成员函数的信息。这些成员函数的地址翻译过程与其对象的内存地址无关。声明的基类指针只能指向基类,派生类指针只能指向派生。它们的原始类型决定它们只能调用各类的同名函数area。8.1.2 动态联编的多态性当编译系统编译含有虚函数的类时,将为它创建一个虚函数表,表中的每个元素都指向一个虚函数的地址。此外,编译器也为类增长一个数据成员,这个数据成员是一个指向该虚函数表的指针,一般称为vptr。虚函数的地址翻译取决于对象的内存地址。编译器为含有虚函数类的对象首先创建一个入口地址,这个地址用来存放指向虚函数表的指针vptr,而后按照类中虚函数的声明次序,一一填入函数指针。当调用虚函数时,先经过vptr找到虚函数表,而后再找出虚函数的真正地址。派生类能继承基类的虚函数表,并且只要是和基类同名(参数也相同)的成员函数,不管是否使用virtual声明,它们都自动称为虚函数。若是派生类没有改写继承基类的虚函数,则函数指针调用基类的虚函数。若是派生类改写了基类的虚函数,编译器将从新为派生类的虚函数创建地址,函数指针会调用改写过的虚函数。虚函数的调用规则是:根据当前对象,优先调用对象自己的成员函数。这和名字支配规律相似,不过虚函数是动态联编的,是在执行期“间接”调用实际上欲联编的函数。8.2 虚函数一旦基类定义了虚函数,该基类的派生类中的同名函数也自动称为虚函数。8.2.1 虚函数的定义虚函数只能是类中的一个成员函数,但不能是静态成员,关键字virtual用于类中该函数的声明中。当在派生类中定义了一个同名的成员函数时,只要该成员函数的参数个数和相应类型以及它的返回类型与基类中同名的虚函数彻底同样,则不管是否为该成员使用virtual,它都将成为一个虚函数。8.2.2 虚函数实现动态性的条件关键字virtual指示C++编译器对调用虚函数进行动态联编。这种多态性是程序运行到须要的语句处才动态肯定的,因此称为运行时的多态性。不过,使用虚函数并不必定产生多态性,也不必定使用动态联编。例如,在调用中对虚函数使用成员名限定,能够强制C++对该函数的调用使用静态联编。产生运行时的多态性有以下3个前提:(1)类之间的继承关系知足赋值兼容性规则。(2)改写了同名函数。(3)根据赋值兼容性规则使用指针(或引用)。因为动态联编是在运行时进行的,相对于静态联编,它的运行效率比较低,但它可使程序员对程序进行高度抽象,设计出可扩充性好的程序。8.2.3 构造函数和析构函数调用虚函数在构造函数和析构函数中调用虚函数采用静态联编,即他们所调用的虚函数是本身的类或基类中定义的函数,但不是任何在派生类中重定义的虚函数。目前推荐的C++标准不支持虚构造函数。因为析构函数不容许有参数,所以一个类只能有一个虚析构函数。虚析构函数使用virtual说明。只要基类的析构函数被说明为虚函数,则派生类的析构函数,不管是否使用virtual进行说明,都自动地成为虚函数。delete运算符和析构函数一块儿工做(new和构造函数一块儿工做),当使用delete删除一个对象时,delete隐含着对析构函数的一次调用,若是析构函数为虚函数,则这个调用采用动态联编。通常来讲,若是一个类中定义了虚函数,析构函数也应说明为虚函数,尤为是在析构函数要完成一些有意义的任务时,例如,释放内存。若是基类的析构函数为虚函数,则在派生类为定义析构函数时,编译器所生成的析构函数也为虚函数。8.2.4 纯虚函数与抽象类在许多状况下,不能再基类中为虚函数给出一个有意义的定义,这时能够将它说明为纯虚函数,将其定义留给派生类去作。说明纯虚函数的通常形式以下:class 类名{virtual 函数类型 函数名(参数列表)=0;};一个类能够说明多个纯虚函数,包含有纯虚函数的类称为抽象类。一个抽象类只能做为基类来派生新类,不能说明抽象类的对象。但能够说明指向抽象类对象的指针(或引用)。从一个抽象类派生的类必须提供纯虚函数的实现代码,或在该派生类中仍将它说明为纯虚函数,不然编译器将给出错误信息。这说明了纯虚函数的派生类还是抽象类。若是派生类给了某类全部纯虚函数的实现,则该派生类再也不是抽象类。若是经过同一个基类派生一系列的类,则将这些类总称为类族。抽象类的这一特色保证了进度类族的每一个类都具备(提供)纯虚函数所要求的行为,进而保证了围绕这个类族所创建起来的软件能正常运行,避免了这个类族的用户因为偶然失误而影响系统正常运行。抽象类至少含有一个虚函数,并且至少有一个虚函数是纯虚函数,以便将它与空的虚函数区分开来。下面是两种不一样的表示方法:virtual void area()=0;virtual void area(){}在成员函数内能够调用纯虚函数。由于没有为纯虚函数定义代码,因此在构造函数或虚构函数内调用一个纯虚函数将致使程序运行错误。8.3 多重继承与虚函数8.4 类成员函数的指针与多态性在派生类中,当一个指向基类成员函数的指针指向一个虚函数,而且经过指向对象的基类指针(或引用)访问这个虚函数时,仍发生多态性。第9章 运算符重载及流类库9.1 运算符重载9.1.1 重载对象的赋值运算符编译器在默认状况下为每一个类生成一个默认的赋值操做,用于同类的两个对象之间相互赋值。默认的含义是逐个为成员赋值,即将一个对象的成员的值赋给另外一个对象相应的成员,这种赋值方式对于有些类多是不正确的。C++的关键字“operator”和运算符一块儿使用就表示一个运算符函数。例如“operator +”表示重载“+”运算符。9.1.2 运算符重载的实质C++是由函数组成的,在C++内部,任何运算都是经过函数来实现的。由于任何运算都是经过函数来实现的,因此运算符重载其实就是函数重载,要重载某个运算符,只要重载相应的函数就能够了。与以往稍有不一样的是,须要使用新的关键字“operator”,它和C++的一个运算符连用,构成一个运算符函数名,例如“operator+”.经过这种构成方法就能够像重载普通函数那样重载运算符函数operator+()。因为C++已经为各类基本数据类型定义了该运算函数,因此只须要为本身定义的类型重载operator+()就能够了。通常地,为用户定义的类型重载运算符都要求可以访问这个类型的私有成员,因此只有两条路可走:要么将运算符重载为这个类型的成员函数,要么将运算符重载为这个类型的友元。C++的运算符大部分均可以重载,不能重载的只有. 、:: 、* 和 ?: 。前面三个是由于在C++中都有特定的含义,不许重载以免没必要要的麻烦;“?:”则是由于不值得重载。另外,“sizeof”和“#”不是运算符,于是不能重载,而=、()、[ ] 、->这4个运算符只能用类运算符来重载。9.1.3 <<、>>和++运算符重载实例其实,插入符“<<”和提取符“>>”的重载也与其余运算符重载同样,但操做符的左边是流对象的别名而不是被操做的对象,运算符跟在流对象的后面,它们要直接访问类的私有数据,并且流是标准类库,用户只能继承不能修改,更不能是流库的成员,因此它们必须做为类的友元重载。插入符函数的通常形式以下:ostream &operator<<(ostream & output,类名 &对象名){return output;}output是类ostream对象的引用,它是cout的别名,即ostream&output=cout。调用参数时,output引用cout(即cout的别名)。显然,插入符函数的第2个参数使用引用方式比直接使用对象名的可读性要好一些。提取符函数的通常形式以下:istram &operator>>(istream & input,类名&对象名){return input;}input是类istream对象的引用。它是cin的别名,即istream&input=cin。调用参数时,input引用cin(即cin的别名)。另外,提取符函数须要返回新的对象值,因此应该使用引用,即“类名&对象名”,不能使用“类名 对象名”。插入符函数不改变对象的值,因此两种方法均可以。显然,运算符“<<”重载函数有两个参数,第1个是ostream类的一个引用,第2个是自定义类型的一个对象。这个重载方式是友元重载。这个函数的返回类型是一个ostream类型的引用,在函数中实际返回的是该函数的第一个参数,这样作是为了使得“<<”可以连续使用。有些C++编译器不区分前缀或后缀运算符,这时只能经过对运算符函数进行重载来反映其为前缀或后缀运算符。注意不能本身定义新的运算符,只能是把C++原有的运算符用到本身设计的类上面去。同时,通过重载,运算符并不改变原有的优先级,也不改变它所需的操做数目。当不涉及到定义的类对象时,它仍然执行系统预约义的运算,只有用到本身定义的对象上,才执行新定义的操做。应该根据须要进行运算符重载。不排除在某些特殊状况下会有一些特殊的须要,但大多数状况下不会将运算符“+”重载为两个复数相减的运算(尽管有能力这么作)。通常老是要求运算符重载合乎习惯。9.1.4 类运算符和友元运算符的区别若是运算符所需的操做数(尤为是第一个操做数)但愿进行隐式类型转换,则运算符应经过友元来重载。另外一方面,若是一个运算符的操做须要修改类对象的状态,则应当使用类运算符,这样更符合数据封装的要求。但参数是引用仍是对象,则要根据运算符在使用中可能出现的状况来决定。若是对象做为重载运算符函数的参数,则可使用构造函数将常量转换成该类型的对象。若是使用引用做为参数,由于这些常量不能做为对象名使用,因此编译系统就要报错。9.1.5 下标运算符“[ ]”的重载运算符“[ ]”只能用类运算符来重载。9.2 流类库C++的流类库由几个进行I/O操做的基础类和几个支持特定种类的源和目标的I/O操做的类组成。9.2.1 流类库的基础类在C++中,输入输出时同流来完成的。C++的输出操做将一个对象的状态转换成一个字符序列,输出到某个地方。输入操做也是从某个地方接收到一个字符序列,而后将其转换成一个对象的状态所要求的格式。这看起来很像数据在流动,因而把接收输出数据的地方叫作目标,把输入数据来自的地方叫作源。而输入和输出操做能够当作字符序列在源、目标以及对象之间的流动。C++将与输入和输出有关的操做定义为一个类体系,放在一个系统库里,以备用户调用。这个执行输入和输出操做的类体系就叫作流类,提供这个流类实现的系统库就叫作流类库。C++的流类库由几个进行I/O操做的基础类和几个支持特定种类源和目标的I/O操做类组成。在C++中,若是在多条继承路径上有一个汇合处,则称这个汇合处的基类为公共基类(ios符合条件)。由于能够经过不一样的访问路径访问这个基类,从而使公共的基类会产生多个实例,这样会引发二义性。若是想使这个公共的基类只产生一个实例,则能够将这个基类说明为虚基类。ios类就是isrream类和ostream类的虚基类,用来提供对流进行格式化I/O操做和错误处理的成员函数。用关键字virtual可将公共基类说明为虚基类,虚基类的定义很难处理,这就是为何最初的C++语言没有能支持多重继承的缘由。从ios类公有派生的istream和ostream两个类分别提供对流进行提取操做和插入操做的成员函数,而iostream类经过组合istream类和ostream类来支持对一个流进行双向(也就是输入和输出)操做,它并无提供新的成员函数。C++流类库预约义了4个流,它们是cin、cout、cerr、clog。事实上,能够将cin视为类istream的一个对象,而将cout视为类ostream的对象。流是一个抽象概念,当实际进行I/O操做时,必须将流和一种具体的物理设备(好比键盘)联接起来。C++的流类库预约义的4个流所联接起来的具体设备为:cin 与标准输入设备相联接cout 与标准输出设备相联接cerr 与标准错误输出设备相联接(非缓冲方式)clog 与标准错误输出设备相联接(缓冲方式)9.2.2 默认输入输出的格式控制关于数值数据,默认方式可以自动识别浮点数并用最短的格式输出,还能够将定点数分红整数和小数部分。特别要注意字符的读入规则。对单字符来说,它将舍去空格,直到读到字符为止。对于单字符对象a,b和c,“cin>>a>>b>>c;”能将连续的3个字符分别正确地赋给相应对象。对字符串来说,它从读到第一个字符开始,到空格符结束。对于字符数组,使用数组名来总体读入。但对于字符指针,尽管为它动态分配了地址,也只能采起逐个赋值的方法,它不只不以空格结束,反而舍弃空格(读到字符才计数)。由于字符串没有结束位,因此将字符串做为总体输出时,有效字符串后面将出现乱码。不过,能够手工增长表示字符串的结束符“0”来消除乱码。当用键盘同时给一个单字符对象和一个字符串对象赋值时,不要先给字符串赋值。若是先给它赋值,应该强行使用结束符。Bool(布尔型)在VC6.0中把输入的0识别为false,其余的值均识别为1。输出时,只有0和1两个值。若是默认输入输出格式不能知足本身的要求,就必须重载它们。9.2.3 使用ios_base类1.ios_base类简介ios_base类派生ios类,ios类又是istream类和ostream类的虚基类。表9.1 常量名及含义常量名 含义skipws 跳过输入中的空白left 输出数据按输出域左边对齐输出right 输出数据按输出域右边对齐输出intermal 在指定任何引导标志或基以后填充字符dec 转换基数为十进制形式oct 转换基数为八进制形式hex 转换基数为十六进制形式showbase 输出带有一个表示制式的字符showpoint 浮点输出时必须带有一个小数点和尾部的0uppercase 十六进制数值输出使用大写A~F,科学计数显示使用大写字母Eshowpos 在正数前添加一个“+”号scientific 使用科学计数法表示浮点数fixed 使用定点形式表示浮点数untibuf 每次插入以后,ostream::osfx刷新该流的缓冲区。默认缓冲单元为cerrboolalpha 把逻辑值输出为true和false(不然输出为1和0)adjustfield 对齐方式域(与left、right、internal配合使用,例如ios_base::adjustfield)basefield 数字方式域(与dex、oct、hex配合使用,例如ios_base::basefield)floatfield 浮点方式域(与fix、scientific配合使用,例如ios_base::floatfield)表9.2 处理标志的成员函数及其做用成员函数 做 用long flags(long) 容许程序员设置标志字的值,并返回之前所设置的标志字long flags() 仅返回当前的标志字long setf(long,long) 用于设置标志字的某一位,第2个参数指定所要操做的位,第1个参数指定为该参数所设置的值long setf(long) 用来设置参数指定的标志位long unsetf(long) 清除参数指定的标志位int width(int) 返回之前设置显示数据的域宽int width() 只返回当前域宽(默认宽度为0)char fill(char) 设置填充字符,设置的宽度小时,空余的位置用填充字符来填充,默认条件下是空格。这个函数返回之前设置的填充字符char fill() 得到当前的填充字符int precision(int) 返回之前设置的精度(小数点后的小数位数)int precision 返回当前设置的精度2.直接使用格式控制表9.1中的名字能够直接用在系统提供的输入输出流中,而且有些是成对的。加no前缀标识取消原操做。用width(int)设置宽度的效果只对一次输入或输出有效,在完成一次输入或输出以后,宽度设置自动恢复为0(表示按实际数据宽度输入输出)。在设置输入时,实际输入的字符串最大长度为n-1(宽度n计入结束符)。setw(int)只对紧跟其后的输出有效。3.使用成员函数在使用成员函数进行格式控制的时候,setf用来设置,unsetf用来恢复默认设置。它们被流对象调用,使用表9.1的常量设置。9.3 文件流在C++里,文件操做是经过流来完成的。C++总共有输入文件流、输出文件流和输入输出文件流3种,并已将它们标准化。要打开一个输入文件流,须要定义一个ifstream类型的对象;要打开一个输出文件流,须要定义一个ofstream类型的对象;若是要打开输入输出文件流,则要定义一个fstream类型的对象。这3种类型都定义在头文件<fstream>里。9.3.1 使用文件流流类具备支持文件的能力,在创建和使用文件时,就像使用cin和cout同样方便。能够总结出对文件进行操做的方法以下:(1)打开一个相应的文件流.(2)把这个流和相应的文件关联起来。由于ifstream、ofstream和fstream这3个类都具备自动打开文件的构造函数,而这个构造函数就具备open()的功能。所以,事实上能够用一条语句:ofstream myStream("myText.txt");来完成上述两步。若是指定文件路径,路径中的“”号必须使用转义字符表示。(3)操做文件流。综上所述,流是I/O流类的中心概念。流是一种抽象,它负责在数据的生产者和消费者之间创建联系并管理数据的流动,程序将流对象看作是文件对象的化身。一个输出流对象是信息流动的目标,ofstream是最重要的输出流。一个输入流对象是数据流动的源头,ifstream是最重要的输入流。一个iostream对象能够是数据流动的源或目的,fstream就是从它派生的。其实,文件流的对象是字节流,并且文本文件和二进制文件均是字节流。将文件与流关联起来,就是使用“>>”和“<<”对文件进行读写。但不能使用空格,这是由“>>”和“<<”原来的性质所决定的。9.3.2 几个典型流成员函数1.输出流的open函数open函数的原型以下:void open(char const *,int filemode,int =filebuf::openprot);它有3个参数,第一个是要打开的文件名,第2个是文件的打开方式,第3个是文件的保护方式,通常都使用默认值。第2个参数能够取以下所示的值:ios_base::in 打开文件进行读操做,这种方式可避免删除现存文件的内容ios_base::out 打开文件进行写操做,这是默认模式ios_base::ate 打开一个已有的输入或输出文件并查找到文件尾ios_base::app 打开文件以便在文件尾部添加数据ios_base::binary 指定文件以二进制方式打开,默认为文本方式ios_base::trunc 如文件存在,将其长度截断为零并清除原有内容除ios_base::app方式以外,在其余几种方式下,文件刚刚打开是,指示当前读写位置的文件指针都定位于文件的开始位置,而ios_base::app使文件当前的写指针定位于文件尾。这几种方式也能够经过“或”运算符“|”同时使用。2.输入流类的open函数使用默认构造函数创建对象,而后调用open成员函数打开文件。输入流的打开模式以下所示:ios_base::in 打开文件用于输入(默认)ios_base::binary 指定文件以二进制方式打开,默认为文本方式3.close成员函数close成员函数用来关闭与一个文件流相关联的磁盘文件。若是没有关闭该文件,由于文件流是对象,因此在文件流对象的生存期结束时,将自动关闭该文件。不过,应养成及时关闭再也不使用的文件的习惯,以免误操做或者干扰而产生的错误。4.错误处理函数在对一个流对象进行I/O操做时,可能会产生错误。当错误发生时,可使用文件流的错误处理成员函数进行错误类型判别。这些成员函数的做用见表9.3。可使用这些函数检查当前流的状态,例如:if(cin.good()) cin>>data;函数clear更多地是用于在已知流发生错误的状况下清除流的错误状态,也能够用于设置流的错误状态。除非发生致命错误(hardfail),不然可使用函数clear清除流的错误状态。“!”运算符已通过了重载,它与fail函数执行相同的功能,所以表达式if(!cout)等价于if(cout.fail()),if(cout)等价于if(!cout.fail())。表9.3 成员函数及其功能函 数 功 能bad() 若是进行非法操做,返回true,不然返回falseclear() 设置内部错误状态,若是用缺省参量调用则清除全部错误位eof() 若是提取操做已经到达文件尾,则返回true,不然返回falsegood() 若是没有错误条件和没有设置文件结束标志,返回true,不然返回falsefail() 与good相反,操做失败返回false,不然返回trueis_open() 断定流对象是否成功地与文件关联,如果,返回true,不然返回false9.3.3 文件存取综合实例 第10章 面向对象设计实例10.1 过程抽象和数据抽象抽象是造成概念的必要手段,它是从许多事物中舍弃个别的、非本质性的特征,抽取共同及本质性的特征的过程。对于分析而言,抽象原则具备两方面的意义:(1)尽管问题域中的事物很复杂,但分析员并不须要了解和描述其所有特征,只须要分析研究与系统目标有关的事物及其本质特征。对于那些与系统目标无关的特征和许多具体的细节,即便有所了解,也应该舍弃。(2)经过舍弃个体事物在细节上的差别,抽取其共同特征能够获得一批事物的抽象概念。过程抽象是指任何一个完成肯定功能的操做序列,其使用者均可以把它看作一个单一的实体,尽管实际上它多是由一系列更低级的操做完成的。运用过程抽象,软件开发者能够把一个复杂的功能分解为一些子功能,过程抽象对于在对象范围内组织对象的成员函数是颇有用的。数据抽象是指根据施加于数据之上的操做来定义数据类型,并限定数据的值只能由这些操做来修改和观察。10.2 发现对象并创建对象层软件开发者将被开发的整个业务范围称做“问题域”,能够按以下步骤考虑发现对象并创建对象层。1.将问题域和系统责任做为出发点2.正确运用抽象原则在OOA中正确地运用抽象原则,首先要舍弃那些与系统责任无关的事物,只注意与系统责任有关的事物。其次,对于与系统责任有关的事物,也不是把他们的任何特征都在相应的对象中表达出来,而是要舍弃那些与系统责任无关的特征。判断事物是否与系统责任有关的关键问题,一是该事物是否为系统提供了一些有用的信息(是否须要系统为它保存和管理某些信息);二是它是否向系统提供了某些服务(是否须要系统描述它的某些行为)。3.寻找候选对象的基本方法寻找候选对象的基本方法的主要策略是从问题域、系统边界和系统责任三方面找出可能有的候选对象。4.审查和筛选对象5.异常状况的检查和调整通常认为下述状况都算异常状况,则须要进行调整。(1)类的数据成员或成员函数不适合该类的所有对象。(2)不一样类的数据成员或成员函数相同或类似。(3)对同一事物的重复描述。10.3 定义数据成员和成员函数为了发现对象的数据成员,首先应考虑借鉴以往的OOA结果,看看相同或类似的问题域是否有已开发的OOA模型,尽量复用其中同类对象数据成员的定义。而后重点研究当前问题域和系统责任,针对本系统应该设置的每一类对象,按照问题域的实际状况,以系统责任为目标进行正确地抽象,从而找出每一类对象应有的数据成员。1.寻找数据成员的通常方法2.审查与筛选数据成员对于初步发现的数据成员,要进行审查和筛选。即对每一个数据成员提出如下问题:(1)这个数据成员是否体现了以系统责任为目标的抽象。(2)这个数据成员是否描述了这个对象自己的特征。(3)该属性是否符合人们平常的思惟习惯。(4)这个数据成员是否是能够经过继承获得。(5)是否能够从其余数据成员直接导出的数据成员。3.定义成员函数使用中要特别注意区分红员函数、非成员函数和友元函数三者。10.4 如何发现基类和派生类结构1.学习当前领域的分类学知识分析员应该学习一些与当前问题域有关的分类学知识,,由于问题域现行的分类方法(若是有),每每比较正确地反映了事物的特征、类别以及各类概念的通常性与特殊性。2.按照常识考虑事物的分类从不一样的角度考虑问题域中事物的分类,能够造成一些创建基类与派生类结构的初步设想,从而启发本身发现一些确实须要的基类与派生类结构。3.构建基类与派生类按照基类与派生类结构的两种定义,可引导咱们从两种不一样的思路去发现基类与派生类结构。一种思路是把每个类看作是一个对象集合,分析这些集合之间的包含关系。另外一种思路是看一个类是否是具备另外一个类的所有特征。这包括两种状况:一是创建这些类时已经计划让某个类继承另外一个类的所有成员,此时应创建基类与派生类结构来落实这种继承;另外一种状况是起初只是孤立地创建每个类,如今发现一个类中定义的成员(数据成员和成员函数)所有在另外一个类中从新出现了,此时应该考虑创建基类与派生类结构,把后者做为前者的派生类,以简化定义。4.考察类的成员对系统中的每一个类,从如下两个方面考察它们的成员;一是看一个类的成员是否适合这个类的所有对象。若是某些数据成员和成员函数只能适合该类的一部分对象,说明应该从这个类中划分出一些派生类,创建基类与派生类关系。另外一方面检查是否有两个(或更多的)类含有一些共同的数据成员和成员函数。若是有,则考虑若把这些共同的数据成员和成员函数提取出来后可否构成一个在概念上是包含原来那些类的基类,组成一个基类与派生类结构。还要对发现的结构进行审查、调整和简化,处理异常状况,才能创建合适的结构。10.5 接口继承与实现继承如今假设设计一个能够供其余类继承的基类,派生类使用公有继承方式。公有继承其实是由两个不一样部分组成的,即函数接口的继承和函数实现的继承。可归纳成以下两点:(1)继承的老是成员函数的接口。对于基类是正确的任何事情,对于它的派生类必须也是正确的。(2)声明纯虚函数的目的是使派生类仅仅继承函数接口,而纯虚函数的实现则由派生类去完成。(3)声明虚函数的目的是使派生类既能继承基类对此虚函数的实现,又能继承虚函数提供的接口。(4)声明实函数的目的是使派生类既能继承基类对此实函数的实现,又能继承实函数提供的接口。1.纯虚函数纯虚函数最显著的两个特征是:(1)它们必须由继承它的非抽象类从新说明(2)它们在抽象类中没有定义2.虚函数虚函数的状况不一样于纯虚函数,派生类继承虚函数的接口,虚函数通常只提供一个可供派生类选择的实现。之因此声明虚函数,目的在于使派生类既继承函数接口,也继承虚函数的实现。3.实函数一个实成员函数指定它本身在派生类中保持不变。所以,声明实函数的目的是使派生类既继承这个函数的实现,也继承其函数接口。4.避免从新定义继承的实函数虽然在派生类中能够重定义基类的同名实函数,可是从使用安全角度来看,为了提升程序质量,在实际应用中应避免这样作。10.6 设计实例