第九章 运算符重载及流类库

第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类公有派生的istreamostream两个类分别提供对流进行提取操作和插入操作的成员函数,而iostream类通过组合istream类和ostream类来支持对一个流进行双向(也就是输入和输出)操作,它并没有提供新的成员函数。

C++流类库预定义了4个流,它们是cincoutcerrclog。事实上,可以将cin视为类istream的一个对象,而将cout视为类ostream的对象。

流是一个抽象概念,当实际进行I/O操作时,必须将流和一种具体的物理设备(比如键盘)联接起来。C++的流类库预定义的4个流所联接起来的具体设备为:

cin 与标准输入设备相联接

cout 与标准输出设备相联接

cerr 与标准错误输出设备相联接(非缓冲方式)

clog 与标准错误输出设备相联接(缓冲方式)

9.2.2 默认输入输出的格式控制

关于数值数据,默认方式能够自动识别浮点数并用最短的格式输出,还可以将定点数分成整数和小数部分。

特别要注意字符的读入规则。对单字符来讲,它将舍去空格,直到读到字符为止。对于单字符对象abc,“cin>>a>>b>>c;”能将连续的3个字符分别正确地赋给相应对象。

对字符串来讲,它从读到第一个字符开始,到空格符结束。对于字符数组,使用数组名来整体读入。但对于字符指针,尽管为它动态分配了地址,也只能采取逐个赋值的方法,它不仅不以空格结束,反而舍弃空格(读到字符才计数)。因为字符串没有结束位,所以将字符串作为整体输出时,有效字符串后面将出现乱码。不过,可以手工增加表示字符串的结束符“\0”来消除乱码。

当用键盘同时给一个单字符对象和一个字符串对象赋值时,不要先给字符串赋值。如果先给它赋值,应该强行使用结束符。

Bool(布尔型)VC6.0中把输入的0识别为false,其他的值均识别为1。输出时,只有01两个值。如果默认输入输出格式不能满足自己的要求,就必须重载它们。

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

 浮点输出时必须带有一个小数点和尾部的0

 uppercase

 十六进制数值输出使用大写A~F,科学计数显示使用大写字母E

 showpos

 在正数前添加一个“+”号

 scientific

 使用科学计数法表示浮点数

 fixed

 使用定点形式表示浮点数

 untibuf

 每次插入之后,ostream::osfx刷新该流的缓冲区。默认缓冲单元为cerr

 boolalpha

 把逻辑值输出为truefalse(否则输出为10

 adjustfield

 对齐方式域(与leftrightinternal配合使用,例如ios_base::adjustfield

 basefield

 数字方式域(与dexocthex配合使用,例如ios_base::basefield

 floatfield

 浮点方式域(与fixscientific配合使用,例如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 使用文件流

流类具有支持文件的能力,在建立和使用文件时,就像使用cincout一样方便。

可以总结出对文件进行操作的方法如下:

(1)打开一个相应的文件流.

(2)把这个流和相应的文件关联起来。

因为ifstreamofstreamfstream3个类都具有自动打开文件的构造函数,而这个构造函数就具有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,否则返回false

 clear()

 设置内部错误状态,如果用缺省参量调用则清除所有错误位

 eof()

 如果提取操作已经到达文件尾,则返回true,否则返回false

 good()

 如果没有错误条件和没有设置文件结束标志,返回true,否则返回false

 fail()

 与good相反,操作失败返回false,否则返回true

 is_open()

 判定流对象是否成功地与文件关联,若是,返回true,否则返回false

9.3.3 文件存取综合实例 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值