C++面向对象(七):I / O 流类库

C++面向对象:I / O 流类库

会有点长,不过读过就全学会喽!!!!!!
会有点长,不过读过就全学会喽!!!!!!
会有点长,不过读过就全学会喽!!!!!!

1. C++ 为何建立自己的输入输出系统

C++ 系统提供了一个用于输入输出( I/ O)操作的类体系, 这个类体系提供了对预定义类型进行输入输出操作的能力,程序员也可以利用这个类体系进行自定义类型的输入输出操作。
C 语言的输入输出系统是一个使用灵活、功能强大的系统。那么, 为什么 C + + 还要建立自己的输入输出系统呢 ?
这是因为在 C + + 中需要定义众多的用户自定义类型,面向对象方法的数据封装性就是通过用户所定义的类类型来体现的, 而继承性和多态性也是通过对用户定义的类对象的操作来体现的。但 C 语言的输入输出系统不支持用户自定义的对象。

struct my - struct{
int i;
float f;
char * str;
}s;

对此结构类型,在 C 语言中下面的语句是不能接受的:printf(”% my - struct”,s) ;
因为 printf( )函数只能识别系统预定义的类型, 而没有办法对新的数据类型进行扩充。使用 C + + 的输入输出系统,就可以通过重载“ < < ”和“ > > ”运算符来解决以上问题。C ++ 的类机制允许它建立一个可扩展的输入输出系统, 它可以通过修改和扩展来加入用户自定义类型及相应操作。

2. C++ 的流及流类库

2 .1 C + + 的流

输入输出是一种数据传递操作,它可以看作字符序列在主机与外部介质之间的流动。
流(stream)为从源( 或生产者)到目的( 或消费者) 的数据流的引用。每个流都是一种与设备相联系的对象。流具有方向性:与输入设备( 如键盘)相联系的流称为输入流; 与输出设备(如屏幕) 相联系的流称为输出流;与输入输出设备 ( 如磁盘 ) 相联系的流称为输入输出流。
C + + 中包含几个预定义的流,它们是标准输入流 cin、标准输出流 cout、非缓冲型的标准出错流 cerr 和缓冲型的标准出错流 clog。这四个流所关联的具体设备为:

  • cin 与标准输入设备相关联
  • cout 与标准输出设备相关联
  • cerr 与标准错误输出设备相关联(非缓冲方式)
  • clog 与标准错误输出设备相关联(缓冲方式)

在缺省情况下,指定的标准输出设备是显示终端, 标准输入设备是键盘。在任何情况下,指定的标准错误输出设备总是显示终端。
cerr 与 clog 均用来输出出错信息。cerr 和 clog 之间的区别是: cerr 没有被缓冲, 因而发送给它的任何内容都立即输出;相反, clog 被缓冲,只有当缓冲区满时才进行输出, 也可以通过刷新流的方式强迫刷新缓冲区。
假设要为用户计算平均价格,但用户的输入不恰当( 例如输入的除数为零) , 下面的代码能检测用户所输入的数值并显示出相应的错误信息。

cout << ”what was the total dollar amount of last month′s sales ?;
cin >> sales;
cout << ”How many units did you sell ?;
cin >> num;
if ( num == 0 )
{ cerr << ”The average cannot be computed . \n”; }
else
{
avgsales = sales/ num;
cout << ”The average selling price per unit was”;
cout << avgsales << ”\n”;
}

当然,在这个程序中, 也可以把错误信息送到 cout。但当用户把标准输出设备定向为其它设备时, cerr 仍然把信息送给显示终端, 以便用户能立即看到该信息。
clog 也可以用于显示错误信息,但我们主张使用 cerr, 因为 cerr 是非缓冲方式的。所谓非缓冲方式是指在一个错误信息发出后用户能立即看到,而不必送给一个缓冲区, 待缓冲区满后才送给显示终端。clog 是缓冲方式的, 有时缓冲区的使用会抑制错误信息的显示,而应用 cerr 就不会有这样的问题。

2 . 2 流类库

C + + 流类库是用继承方法建立起来的一个输入输出类库, 它具有两个平行的基类,即 streambuf 类和 ios 类, 所有其它的流类都是从它们直接或间接地派生出来的。
使用 C + + 的流类库, 程序中应包含有头文件 iostream .h。部分流函数可能还需要其它的一些头文件,例如 strstream .h、fstream .h、iomanip .h 等。

  1. streambuf 类
    streambuf 类提供物理设备的接口, 它提供缓冲或处理流的通用方法, 几乎不需要任何格式。缓冲区由一个字符序列和两个指针组成 ( 输入缓冲区指针和输出缓冲区指针 ) ,这两个指针指向字符要被插入或取出的位置。
    streambuf 提供对缓冲区的低级操作,如设置缓冲区、对缓冲区指针进行操作、从缓冲区取字符、向缓冲区存储字符等。streambuf 类主要用作流类库的其它部分使用的基类。streambuf 类可以派生出三个类,即 filebuf 类、strstreambuf 类和 conbuf 类。
    filebuf 类使用文件来保存缓冲区中的字符序列。当写文件时, 实际是将缓冲区的字符写到指定的文件中,之后刷新缓冲区; 当读文件时, 实际是将指定文件中的内容读到缓冲区中来。将 filebuf 同某个文件的描述字相联系就称打开这个文件。
    strstreambuf 类扩展了 streambuf 类的功能, 它提供了在内存中进行提取和插入操作的缓冲区管理。
    conbuf 类扩展了 streambuf 类的功能,用于处理输出。它提供了控制光标、设置颜色、定义活动窗口、清屏、清一行等功能,为输出操作提供缓冲区管理。
    在通常情况下,均使用这三个派生类, 很少直接使用 streambuf 类。
  2. ios 类
    ios 类及其派生类为用户提供使用流类的接口,它们均有一个指向 streambuf 的指针。ios 类(及其派生类) 使用 streambuf 及其派生类完成检查错误的格式化输入输出, 并支持对 streambuf 的缓冲区进行输入输出时的格式化或非格式化转换。
    ios 作为流类库中的一个基类, 可以派生出许多类, ios 类有四个直接派生类,即输入流类(istream) 、输出流类( ostream) 、文件流类( fstreambase )和串流类(strstreambase) , 这四种流作为流库中的基本流类。输入输出流类 iostream 是通过多重继承从输入流类 istream 和输出流类 ostream 派生而来的,即:
    class ios;
    class istream∶virtual public ios;
    class ostream∶virtual public ios;
    class iostream∶public istream, public ostream;

    输入输出流类 iostream 把 istream 和 ostream 结合到一起, 支持输入输出双向操作。
    以 istream、ostream、fstreambase 和 strstreambase 四个基本流类为基础还可以派生出多个实用的流类, 例如 fstream ( 输入输出文件流类 )、strstream ( 输入输出串流类 )、constream(屏幕输出流类 )、ifstream(输入文件流类 )、ofstream ( 输文件流类) 、istrstream ( 输入串流类)和 ostrstream(输出串流类)等。由多个基类生成的派生类可以继承基类的公有成员(包括数据成员和成员函数) ,例如 fstream 的对象可以继承和访问 iostream、istream、ostream、fstreambase 和 ios 的所有公有成员( 数据和函数)。
    在 istream 类、ostream 类和 iostream 类的基础上, 分别重载赋值运算符“ = ”, 就派生出 istream - withassign、ostream - withassign 和 iostream - withassign 类, 即:
    class istream - withassign∶public istream;
    class ostream - withassign∶public ostream;
    class iostream - withassign∶public iostream
    在编写 C + + 程序时, 经常用 cin 和 cout 来进行输入输出, 它们是标准的输入输出流。之所以能随时使用它们,是因为开始执行 C + + 程序时, C + + 会自动打开的几个预定义流,即 cin、cout、cerr 和 clog, 它们在 iostream .h 中说明为 - withassign 类的对象:
    extrean istream - withassign cin;
    extrean ostream - withassign cout ;
    extrean ostream - withassign cerr;
    extrean ostream - withassign clog;
    流是一个抽象的概念, 当实际进行 I/ O 操作时, 必须将流和一种具体的物理设备联系起来。例如,将流和键盘联系起来, 当从这个流中提取数据时,这些数据就来自键盘; 当将流和显示终端联系在一起,向这个流中插入数据时, 就使数据显示在屏幕上。以上四个预定义的流 cin、cout、cerr 和 clog 分别与标准输入设备、标准输出设备、标准错误输出设备(非缓冲方式) 和标准错误输出设备(缓冲方式) 相关联。
    用户也可以用 istream 和 ostream 等类声明自己的流对象,例如:
    istream is;
    ostream os;
    在此声明了 is 为输入流对象,声明了 os 为输出流对象。
    使用流类库, 程序既可以识别系统预定义的 I/ O 类型, 又可以重载运算符“ < < ”和“ > > ”, 使程序能够识别用户定义的类型,大大提高程序的可靠性和灵活性。

3. 输入输出的格式控制

在 C + + 中,仍然可以使用C 中的 printf( )和 scanf( )函数进行格式化。除此以外, C + + 还提供了两种进行格式控制的方法:一种是使用 ios 类中有关格式控制的成员函数; 另一种是使用称为操纵符的特殊类型的函数。

3 .1 用 ios 类的成员函数进行格式控制

ios 类中有几个成员函数可以用来对输入输出进行格式控制。进行格式控制主要是通过对格式状态字、域宽、填充字符以及输出精度的操作来完成的。

  1. 状态标志字
    C + + 可以给每个流对象的输入输出进行格式控制, 以满足用户对输入输出格式的需求。输入输出格式由一个 long int 类型的状态标志字确定。在 ios 类的 public 部分定义了一个枚举,它的每个成员可以分别定义状态标志字的一个位, 每一位都称为一个状态标志位。这个枚举的定义如下:
    enum{
    skipws = 0x0001, / / 跳过输入中的空白,可用于输入 f&
    left = 0x0002, / / 左对齐输出,可用于输出
    right = 0x0004, / / 右对齐输出,可用于输出
    internal = 0x0008, / / 在符号位和基指示符后填入字符,可用于输出
    dec = 0x0010, / / 转换基数为十进制,可用于输入或输出
    oct = 0x0020, / / 转换基数为八进制,可用于输入或输出
    hex = 0x0040, / / 转换基数为十六进制,可用于输入或输出
    showbase = 0x0080, / / 在输出时显示基指示符,可用于输入或输出
    showpoint = 0x0100, / / 在输出时显示小数点,可用于输出
    uppercase = 0x0200, / / 十六进制输出时,表示制式的和表示数值的字符
    / / 一律为大写,可用于输出
    showpos = 0x0400, / / 正整数前显示“ + ”符号,可用于输出
    scientific = 0x0800, / / 用科学表示法显示浮点数,可用于输出
    fixed = 0x1000 , / / 用定点形式显示浮点数,可用于输出
    unitbuf = 0x2000, / / 在输出操作后立即刷新所有流,可用于输出
    stdio = 0x4000, / / 在输出操作后刷新 stdout 和 stderr, 可用于输出
    } ;
    这些枚举元素的值有一个共同的特点,即分别使状态标志字二进制表示中的不同位为 1,如:
    skipws 0x0001 0000 0000 0000 0001 "
    left 0x0002 0000 0000 0000 0010
    right 0x0004 0000 0000 0000 0100
    . . .
    状态标志字是由各状态标志位组合而成的, 在 ios 类中状态标志位存放在数据成员long x - flags 中。若设定了某一项, 则 x - flags 中的某一位为“1”, 否则为“0”。例如, 若在状态标志字 中设定了 skipws 和 dec, 其 它均未设 定, 则 x - flags 的值 应为 0000 00000001 0001 ,即为十六进制的 0x0011, 十进制的 17。这些状态值之间是或的关系, 可以几个并存。
  2. ios 类中用于控制输入输出格式的成员函数
    在 ios 类中, 定义了几个用于控制输入输出格式的成员函数

控制输入输出格式的成员函数

函 数 原 型 功 能功 能
long ios∷setf(long flags) ;设置状态标志 flags
long ios∷unsetf(long flags) ;清除状态标志 ,并返回清除前标志
long ios∷flags( ) ;测试状态标志
long ios∷flags(long flags) ;设置标志 flags,并返回设置前标志
int ios∷width( ) ;返回当前的宽度设置值
int ios∷width( int w) ;设置域宽 w ,返回以前的设置
int ios∷precision(int p) ;设置小数位数 p ,返回以前的小数位数
char ios∷fill( ) ;返回当前的填充字符
char ios∷fill( char ch ) ;设置填充字符 ch,返回当前的填充字符

参数 flags 是状态控制字,例如当 flags 为0010 1100 0000 0001时,将把输入输出格式化为:
unitbuf
scientific
showpos
skipws
下面分别介绍这些成员函数的使用方法。
(1 ) 设置状态标志
设置状态标志,即是将某一状态标志位置“1”,可使用 setf( )函数,其一般格式为:**long ios∷setf(long flags)**使用时,其一般的调用格式为:
流对象 .setf(ios∷状态标志) ;
例如:

istream isobj;
ostream osobj;
isobj .setf(ios∷skipws) ; // 跳过输入中的空白
osobj .setf(ios∷left) ; // 设置输出左对齐

在此, isobj 为类 istream 的流对象,osobj 为类 ostream 的流对象。实际上, 在编程中用的最多的是 cin .setf(…) 或 cout .seft( …)。

#include<iostream>
using namespace std;
int main ()
{
    cout.setf(ios::showpos|ios::scientific) ;
    cout<< 567 << " "<< 567.89 << endl;
    return 0;
}

设置 showpos 使得每个正数前添加“ + ”号, 设置 scientific 使浮点数按科学表示法( 指数形式)进行显示。输出结果为:
+ 567 + 5 .6789e02
注意:要设置多项标志时, 中间用或运算符“ | ”分隔, 例如:

cout .seft(ios∷showpos| ios∷dec| ios∷scientific ) ;

(2 ) 清除状态标志
清除某一状态标志, 即是将某一状态标志位置“0”, 可使用 unseft ( )函数, 它的一般格式为:**long ios∷unsetf(long flags)**使用时的调用格式与 setf( )相同。
(3 ) 取状态标志
取一个状态标志,可使用 flags( )函数。flags( )函数有不带参数与带参数两种形式, 其一般格式为:
long ios∷flags( ) ;
long ios∷flags(long flag) ;
前者用于返回当前的状态标志字;后者将状态标志字设置为 flag, 并返回设置前的状态标志字。Flags( )函数与 setf( ) 函数的差别在于:setf( ) 函数是在原有的基础上追加设定的,而 flags( ) 函数是用新设定覆盖以前的状态标志字。
下面的例子说明了以上几个成员函数的使用方法。

# include < iostream .h >
void showflags(long f) / / 输出状态标志字函数
{
long i;
for(i = 0x8000; i ; i = i > > 1) / / 用右移方法使 i 中的值为“1”的位不断右移
if (i&f) cout < <1; / / 判断 f 中的某一位是否为“1else cout < <0;
cout < < endl;
}
main ( )
{
long f;
f = cout .flags( ) ; / / 取当前状态标志字
showflags(f) ; / / 显示状态标志字
cout .setf(ios∷showpos| ios∷scientific) ; / / 追加状态标志位
f = cout .flags( ) ;
showflags(f) ;
cout .unsetf(ios∷scientific ) ;/ / 从状态标志字中去掉 scientific
f = cout .flags( ) ;
showflags(f) ;
f = cout .flags( ios∷oct) ; / / 重新设置状态标志字
showflags(f) ; / / 显示设置前的状态标志字
f = cout .flags( ) ; / / 取设置后的状态标志字
showflags(f) ; / / 显示设置后的状态标志字
return 0 ;
}

程序运行结果为:
0010000000000001 ①
0010110000000001 ②
0010010000000001 ③
0000000000100000 ④
0000000000100000 ⑤
此程序中 showflags( ) 是一个输出状态标志字函数。它的算法是: 从最高位到最低位,逐位计算各位与 1(即 0X8000 依次右移)的位与, 并输出该位与的值。由于只有 1 和 1的位相与值才为 1,所以该函数每执行一次, 输出一个二进制的状态标志字。
结果①显示缺省状态下的状态标志,即 skipws 和 unitbuf 为真。
结果②显示执行 setf( )函数后的各状态标志位, 不改变原来的设定,只是增加设置。
结果③显示执行 unsetf( ) 函数后的各状态标志位。
结果④显示执行函数 flags(ios∷oct)后,其函数的返回值。此函数返回的是设置前的状态标志字,因此其结果与③相同。
结果⑤显示执行函数 flags( ios∷oct) 后, 各状态标志位的情况, 此时除去与 ios∷oct相应的位为“1”外, 其余均为“0”。
(4 ) 设置域宽
域宽主要用来控制输出,在 ios 类中域宽存放在数据成员 int x - width 中。设置域宽的成员函数有两个,其一般格式为:
int ios∷width( ) ;
int ios∷width( int w) ;

前者用来返回当前的域宽值,后者用来设置域宽, 并返回原来的域宽。
(5 ) 设置显示的精度
在 ios 类中用数据成员 int x - precision 来存放浮点数的输出显示精度。设置显示精度的成员函数的一般格式为:
int ios∷precision(int p) ;
此函数用来重新设置浮点数所需小数的位数,并返回设置前的小数点后的位数。
(6 ) 填充字符
填充字符的作用是:当输出值不满域宽时用填充字符来填充, 缺省情况下填充字符为空格。所以在使用填充字符函数时,必须与 width ( )函数相配合, 否则就没有意义。在 ios类中用数据成员 x - fill 来存放填充的字符。填充字符的成员函数有两个, 其一般形式为:
char ios∷fill( ) ;
char ios∷fill( char ch) ;

前者用来返回当前的填充字符,后者用 ch 重新设置填充字符,并返回设置前的填充字符。

# include < iostream .h >
main ( )
{
cout < < ”x - width =< < cout .width( ) < < endl;
cout < < ”x - fill =< < cout .fill( ) < < endl;
cout < < ”x - precision =< < cout .precision ( ) < < endl;
cout < < 123 < < ” ”< < 123 .45678 < < endl;
cout < <------------------------------------- \ n”;
cout < <* * * x - width = 10, x - fill = , x - precision = 4 * * * \ n”;
cout .width( 10 ) ;
cout .precision( 4) ;
cout < < 123 < < ” ”< < 123 .45678 < < ” ”< < 234 .567 < < endl;
cout < < ”x - width =< < cout .width( ) < < endl;
cout < < ”x - fill =< < cout .fill( ) < < endl;
cout < < ”x - precision =< < cout .precision ( ) < < endl;
cout < <------------------------------------- \ n”;
cout < <* * * x - width = 10, x - fill = & , x - precision = 4 * * * \ n”;
cout .fill(&) ;
cout .width( 10 ) ;
cout < < 123 < < ” ”< < 123 .45678 < < endl;
cout .set(ios∷left) ;
cout .width( 10 ) ;
cout < < 123 < < ” ”< < 123 .45678 < < endl;
cout < < ”x - width =< < cout .width( ) < < endl;
cout < < ”x - fill =< < cout .fill( ) < < endl;
cout < < ”x - precision =< < cout .precision ( ) < < endl
return 0;
}

程序运行结果如下:
x - width = 0
x - fill =
x - precision = 0
123 123 .45678


      • x - width = 10, x - fill = , x - precision = 4 * * *
        123 123 .4568 234 .567
        x - width = 0
        x - fill =
        x - precision = 4

      • x - width = 10, x - fill = & , x - precision = 4 * * *
        & & & & & & &123 123 .4568
        123& & & & & & & 123 .4568
        x - width = 0
        x - fill = &
        x - precision = 4
        分析以上程序和运行结果,可以看出:
        ① 在缺省情况下, x - width 取值为“0”, 这个“0”意味着一个特殊的意义———无域宽,即数据按自身的宽度打印; x - fill 取值为空格; x - precision 取值为“0”, 数据按实际的小数位数打印,例如 123 .45678, 其小数的位数就按其实际的 5 位打印。
        ② 当用 width( )函数设置了域宽后,只对紧跟着它的第一个输出有影响, 当第一个输出完成后, x - width 立即自动置为“0”。而调用 precision( )函数和 fill( )函数, 设置了 x - fill和 x - precision 后,在程序中一直有效, 除非它们被重新设置。
        ③ 当设置了 x - precision 后,若实际输出数值的精度与其不一致时, 最终的输出结果为:当实际输出的小数位数大于 x - precision,则以 x - precision 的位数按四舍五入输出; 当实际输出的小数位数小于 x - precision, 则按实际的小数位数输出。例如本例中当 x - precision 设置为 4 时, 123 .45678 被四舍五入为 123 .4568 输出, 而234 .567 仍按原值输出。
        ④ 当显示数据所需的宽度比使用 ios∷width ( )设置的宽度小时, 空余的位置用填充字符来填充, 缺省情况下的填充字符是空格。填充字符的填充位置由 ios∷left 和 ios∷right 规定。若设置 ios∷left , 则字符填充在数据右边 ( 输出数据左对齐 ) ; 若设置 ios∷right(缺省设置) ,则字符填充在数据左边( 输出数据右对齐)。

下面的程序表示如何利用控制输入输出格式的成员函数建立对齐的数字表。

# include < iostream .h >
# include < math .h >
main ( )
{
double x;
cout .precision(4 ) ;
cout < < ” x sqrt( x) x^2 \ n \ n”;
for ( x = 1 .0; x < = 20 .0; x + + )
{
cout .width( 8) ;
cout < < x < <′′;
cout .width( 8) ;
cout < < sqrt( x ) < <′′;
cout .width( 8) ;
cout
< < x * x < <′\ n′;
}
return 0 ;
}

这个程序建立了如下的表:

x 	sqrt( x) 	x^2 
1 		1 		 1 
2 		1.4142 	 4 
3 		1.7321 	 9 
4 		2 		 16 
5		2.2361   25 
6		2.4495   36 
7 		2.6458   49 
8 		2.8284   64 
9 		3 	     81 
10		3.1623   100 
11		3.3166	 121 
12		3.4641	 144 
13		3.6056	 169 
14		3.7417	 196 
15 		3.873 	 225 
16 		4  	 	 256 
17 		4.1231   289 
18 		4.2426 	 324 
19 		4.3598   361 
20 		4.4721   400
3 .2 使用操纵符进行输入输出格式控制

使用 ios 类中的成员函数进行输入输出格式控制时, 每个函数的调用需要写一条语句,而且不能将它们直接嵌入到输入输出语句中去, 显然使用起来不太方便。
在很多情况下, 使用操纵符进行格式化控制比用 ios 格式标志和成员函数要方便。

  1. C + + 预定义的操纵符
    C + + 预定义的操纵符是以一个流引用作为其参数,并返回同一流的引用,因此它可以嵌入到输入输出操作的链中。操纵符可用来改变域宽, 使整型数以八进制或十六进制形式输入或输出, 并设置填充字符等。许多操纵符的功能类似于上面介绍的 ios 类成员函数的功能。C + + 提供的预定义操纵符如下:
    (1 ) dec 以十进制形式输入或输出整型数, 可用于输入或输出。
    (2 ) hex 以十六进制形式输入或输出整型数, 可用于输入或输出。
    (3 ) oct 以八进制形式输入或输出整型数, 可用于输入或输出。
    (4 ) ws 用于在输入时跳过开头的空白符,仅用于输入。
    (5 ) endl 插入一个换行符并刷新输出流,仅用于输出。
    (6 ) ends 插入一个空字符, 通常用来结束一个字符串,仅用于输出。
    (7 ) flush 刷新一个输出流,仅用于输出。
    (8 ) setbase (int n) 把转换基数设置为 n ( n 的取值为 0 , 8, 10 或 16 ) , n 的缺省值为0, 即以十进制形式输出。
    (9 ) resetiosflags(long f) 关闭由参数 f 指定的格式标志, 可用于输入或输出。
    (10) setiosflags(long f) 设置由参数 f 指定的格式标志, 可用于输入或输出。
    (11) setfill( int c ) 设置 c 为填充字符,缺省时为空格, 可用于输入或输出。
    (12) setprecision(int n) 设置数据小数部分的位数, 缺省时小数的位数为 6, 可用于输入或输出
    (13) setw(int n) 设置域宽为 n,可用于输入或输出。

操纵符 setiosflags( ) 和 resetiosflags( ) 中所用的格式标志如表
操纵符 setiosflag( ) 和 resetiosflag( ) 所用的格式标志

格式标志名含 义
ios∷left输出数据按域宽左对齐输出
ios∷ right输出数据按域宽右对齐输出
ios∷scientific使用科学计数法表示浮点数
ios∷fixed使用定点形式表示浮点数
ios∷dec转换基数为十进制形式
ios∷hex转换基数为十六进制形式
ios∷oct转换基数为八进制形式
ios∷uppercase十六进制形式和科学计数法输出时 ,表示数值的字符一律为大写
ios∷showbase输出带有一个表示制式的字符 (如“X”表示十六进制“, O”表示八进制 )
ios∷showpos在正数前添加一个“ + ”号
ios∷showpoint浮点输出时必须带有一个小数点
  1. 操纵符的使用
    操纵符 分为带参 数的操纵 符和不带 参数的操 纵符。通常, 不带参 数的 操纵符 在iostream .h 文件中定义, 而带参数的操纵符在 iomanip .h 文件中定义。
    操纵符 setiosflags( ) 和 resetiosflags( ) 中所用的格式标志如表所示。
    在进行输入输出时,操纵符被嵌入到输入或输出链中, 用来控制输入输出的格式, 而不是执行输入或输出操作。为了使用这些操纵符,程序中必须含有下列预编译命令:#include < iomanip .h >

下面通过一个例子来介绍操纵符的使用。

# include < iostream .h >
# include < iomanip .h >
main ( )
{
cout < < setw(10) < < 123 < < 567 < < endl; / / ①
cout < < 123 < < setiosflags( ios∷scientific ) < < setw(20) / /< < 123 .456789 < < endl;
cout < < 123 < < setw( 10 ) < < hex < < 123 < < endl; / / ③
cout < < 123 < < setw( 10 ) < < oct < < 123 < < endl; / / ④
cout < < 123 < < setw( 10 ) < < setbase( 0) < < 123 < < endl; / / ⑤
cout < < resetiosflags(ios∷scientific) < < setprecision (4 ) / /< < 123 .456789 < < endl;
cout < < setiosflags(ios∷left) < < setfill(′# ′) < < setw( 8) / /< < 123 < < endl;
cout < < resetiosflags(ios∷left) < < setfill(′$′) < < setw( 8) / /< < 456 < < endl;
return 0;
}

程序运行结果为:


       123567123       1.234567e+02123       7b 				③
7b       173173       123123.4568123 # # # # #               ⑦
$$$$$456

操纵符可直接嵌入到语句中。下面分析每条语句和输出结果。
第一条 cout 语句 首先设置域宽为 10, 之后输出 123 和 567, 123 和 567 被连到了一起, 所以得到结果①。表明操纵符 setw 只对最靠近它的输出起作用, 也就是说, 它的作用是“一次性”的。
第二条 cout 语句 首先按缺省方式输出 123, 之后按照浮点数的科学表示法及域宽为 20 输出 123 .456789,由于缺省时小数位数为 6 ,所以得到结果②。
第三条 cout 语句 首先按缺省方式输出 123, 之后按照域宽为 10, 以十六进制输出123, 得到结果③。
第四条 cout 语句 由于上一条语句中使用了操纵符 hex, 其作用仍然保持, 所以先输出 123 的十六进制数, 之后按照域宽为 10, 重新设置进制为八进制, 输出 123 得到结果④。结果表明:使用 dec、oct、hex 等操作符后,其作用一直保持, 直到重新设置为止。
第五条 cout 语句 由于上一条语句的操纵符 oct 的作用仍然保持, 所以先输出 123的八进制数,之后按照域宽为 10,用操纵符 setbase( 0)恢复进制为十进制后, 输出结果⑤。
第六条 cout 语句 取消 浮点 数的科学 示法输 出后, 设 置小 数 位数 为 4, 输 出123 .456789 ,从而得到结果⑥。结果表明用 setprecision 操纵符设置小数位数后, 输出时作四舍五入处理。
第七条 cout 语句 按域宽为 8, 填充字符为“ # ”,按左对齐输出 123, 得到结果⑦。
第八条 cout 语句 按域宽为 8,填充字符为“$”, 取消左对齐输出 (缺省对齐方式为右对齐)后, 输出 456,得到结果⑧。
下面是显示数 1~20 的二次方表与二次方根表的程序的另一个版本。这一版本用输入输出操纵符代替 ios 类的成员函数。

# include < iostream .h >
# include < iomanip .h >
# include < math .h >
main ( )
{
double x;
cout << setprecision(4 ) ;
cout << ” x sqrt( x) x^2 \n \n”;
for ( x = 1 .0; x < = 20 .0; x + + )
{
cout << setw( 8) << x <<′′;
cout<< setw( 8)<< sqrt( x )<< ′′;
cout << setw( 8) << x * x <<′\n′;
}
return 0 ;
}
  1. 用户自定义的操纵符

C + + 除了提供系统预定义的操纵符外, 也允许用户自定义操纵符,合并程序中频繁使用的输入输出操作,使输入输出密集的程序变得更加清晰高效, 并可避免意外的错误。
下面介绍建立自定义操纵符的方法。

若为输出流定义操纵符函数,则定义形式如下:
ostream &manip - name (ostream &stream)
{
/ / 自定义代码
return stream;
}
若为输入流定义操纵符函数,则定义形式如下:
istream &manip - name (istream &stream)
{
/ / 自定义代码
return stream;
}

以上的定义形式中, manip - name 是操纵符函数的名字, 其它单词可照原样写上。在此,操纵符返回 stream (也可用其它标识符)是一个关键, 否则操纵符就不能用在流的输入输出操作序列中。

# include < iostream .h >
# include < iomanip .h >
ostream &output 1 (ostream &stream)
{
stream .setf(ios∷left) ;
stream < < setw(10) < < hex < < setfill(&) ;
return stream;
}
int main ( )
{
cout < < 123 < < endl;
cout < < output 1 < < 123 < < endl;
return 0 ;
}

程序运行结果如下:
123
7b& & & & & & & &
该程序建立了一个操纵符函数 output 1, 其功能为: 设置左对齐格式标志, 把域宽置为 10, 整数按 16 进 制输出, 填空 字符为“ & ”。在 main ( ) 函数 中引 用该函 数时, 只 写“output1”即可。其调用方法与预定义操纵符,如 dec、endl 等完全一样。

# include < iostream .h >
# include < iomanip .h >
istream &input1(istream &in)
{
in > > hex;
cout < < ”Enter number using hex format :;
return in;
}
int main ( )
{
int i;
cin > > input1 > > i;
cout < < i < < endl;
return 0 ;
}

以上程序中定义了一个操纵符函数 input1, 该函数要求输入一个十六进制数。程序运行后,屏幕上显示:
Enter number using hex format :
提示用户输入一个十六进制数。

4. 用户自定义类型的输入输出

对于用户自定义的类类型数据的输入或输出,在 C + + 中可以通过重载运算符“ > > ”和“ < <”来实现。

4 .1 重载输出运算符“<< ”

输出运算符“ << ”, 也称插入运算符。通过重载运算符“ << ”可以实现用户自定义类型的输出。
定义输出运算符“ < < ”重载函数的一般格式如下:

ostream &operator < < ( ostream &stream, class - name obj)
{
// 操作代码
return stream;
}

函数中第一 个参数是 对 ostream 对象的 引用。这意味着 stream 必须是输 出流 ( 注意:ostream是在 ios 内部定义的) , 它可以是其它任何合法的标识符, 但必须与 return 后面的标识符相同。第二个参数接收将被输出的对象, 其中 class - name 是类名, obj 为该类的对象名。

#include<iostream>
using namespace std;
class coord
{
public :
    int x, y;
    coord()
    {
        x = 0 ;
        y = 0;
    }
    coord(int i, int j)
    {
        x = i;
        y = j;
    }
};
ostream &operator << ( ostream &stream, coord ob )
{
    stream << ob.x << ","<< ob.y << endl;
    return stream;
}
int main ()
{
    coord a (5, 6 ), b( 10, 34) ;
    cout << a << b;
    return 0 ;
}

程序运行结果如下:
5, 6
10 ,34
在此输出运算符“ < < ”的重载函数中有两个参数, 第一个参数 (即 strean) 是对流的引用,定义时出现在“ < < ”运算符的左边,第二个参数是类 coord 的对象,出现在运算符“ < <”的右边。此函数输出类 coord 对象的两个值。
一般情况下,重载输出运算符函数及后面要介绍的重载输入运算符函数都不能是类的成员。因为如果一个运算符函数是类的成员, 则其左运算数就应当是调用运算符函数的类的对象, 而且这一点是无法改变的。但重载输出运算符时, 其左边的参数是流, 而右边参数是类的对象。因此,重载输出运算符必须是非成员函数。
在上面的例子中,由于把变量 x 和 y 定义为类的公有成员, 因此重载输出运算符函数即使不是类 coord 的成员, 仍可以访问这些变量。但是, 把数据定义为公有成员将破坏数据的封装特性,如果重载输出运算符函数不能作为类的成员函数, 那么, 它怎样访问类的私有成员呢 ? 为了解决这个问题,应该把重载输出运算符函数定义为类的友元函数, 这样就可以访问类的私有成员。前面的例子修改为下例。

class coord{
int x, y;
public :
coord( )
{ x = 0 ; y = 0; }
coord(int i, int j)
{ x = i; y = j; }
friend ostream &operator < < ( ostream &stream, coord ob) ;
} ;
ostream &operator < < ( ostream &stream, coord ob )
{
stream < < ob .x < <,< < ob .y < < endl;
return stream;
}
· 221 ·
main ( )
{
coord a (5, 6 ) , b( 10, 34) ;
cout < < a < < b;
return 0 ;
}

程序运行结果如下:
5, 6
10 ,34
在修改后的程序中, x 和 y 是类 coord 的私有成员, 但由于重载输出运算符函数被定义为友元函数,因此仍能直接访问它们。

4 .2 重载输入运算符“>> ”

输入运算符“ > > ”, 也称为提取运算符。定义重载输入运算符函数与重载输出运算符函数的格式基本相同,只是要把 ostream 换成 istream,把“ < < ”用“ > > ”代替; 此外, 定义输入运算符函数时,其第二个参数是一个引用。完整的格式如下:

istream &operator > > (istream &stream, class - name &ob)
{
/ / 操作代码
return stream;
}

与重载输出运算符函数一样,重载输入运算符函数也不能是所操作的类的成员函数,但可以是该类的友元函数或独立函数。

#include<iostream>
using namespace std;
class threed
{
    int x, y, z;
public :
    threed(int a, int b, int c)
    {
        x = a;
        y = b;
        z = c;
    }
    friend ostream &operator << ( ostream &output, threed ob) ;
    friend istream &operator >> (istream &itput, threed &ob) ;
} ;
ostream &operator << ( ostream &output, threed ob)
{
    output << ob.x <<",";
    output << ob.y <<",";
    output << ob.z << endl;
    return output ;
}
istream &operator >> (istream &input, threed &ob)
{
    cout << "Enter x, y, z value:";
    input >> ob.x;
    input >> ob.y;
    input >> ob.z;
    return input ;
}
int main ()
{
    threed obj(10, 20,30) ;  // 定义类 three - d 的对象 obj
    cout << obj; // 输出对象 obj 的成员值
    cin >> obj; // 输入对象 obj 的各成员值,将原值覆盖
    cout << obj; // 输出对象 obj 的成员值(新值 )
    return 0 ;
}

程序运行结果如下:
10 ,20, 30
Enter x, y, z value∶40 50 60
40 ,50, 60
在这个程序中,定义了重载输出运算符和输入运算符,它们都是类 three - d 的友元函数,分别完成对该类对象的输出和输入操作。在定义重载输入运算符时, 第二个参数 obj前面的 & 不能省略,否则将得到错误的结果。

5 .文件的输入输出

C + + 把文件看作字符序列, 即文件是由一个一个字符数据顺序组成的。根据数据的组织形式,文件可分为文本文件和二进制文件。
文本文件又称 ASCII 文件, 它的每个字节存放一个 ASCII 代码, 代表一个字符。
二进制文件则是把内存中的数据, 按其在内存中的存储形式原样写到磁盘上存放。
假定有一个整数 10000 , 在内存中占两个字节, 如果按文本形式输出到磁盘上,则需占 5 个字节, 而如果按二进制形式输出, 则在磁盘上只占两个字节。用文本形式输出时, 一个字节对应一个字符, 因而便于对字符进行逐个处理,也便于输出字符, 缺点是占存储空间较多。用二进制形式输出数据, 可以节省存储空间和转换时间,但一个字节不能对应一个字符, 不能直接以字符形式输出。对于需要暂时保存在外存上,以后又需要输入到内存的中间结果数据, 通常用二进制形式保存。
C + + 文件是一个字符流或二进制流, 它把数据看作是一连串的字符, 而不考虑记录的界限, 它对文件的存取以字符为单位进行。我们把这种文件称为流式文件。C + + 允许以一个字符为单位进行存取,这增加了处理的灵活性。
在 C + + 中, 要进行文件的输入输出, 必须首先创建一个流, 然后将这个流与文件相关联,即打开文件, 此时才能进行读写操作, 完成后再关闭这个文件。这是 C + + 中进行文件输入输出的基本过程。

5 .1 文件的打开与关闭
  1. 文件的打开
    为了对一个文件进行读写操作,应先“打开”该文件; 在使用结束后,则应“关闭”文件。在 C + + 中,打开一个文件, 就是将这个文件与一个流建立关联;关闭一个文件, 就是取消这种关联。
    用于文件输入输出的三个流类
名 称基 类功 能
ofstreamostream用于文件的输出
ifstreamistream用于文件输入
fstreamiostream用于文件输入或输出

ofstream 是 ostream 的派生类, ifstream 是 istream 的派生类, fstream 是 iostream 的派生类,它们同属于 ios 类,因此可以访问在 ios 类中定义的所有操作。
与上述三个类相对应, C + + 为文件的输入输出提供了三种类型的流, 即输入流、输出流以及输入输出流。建立流的过程就是定义流类的对象,例如:
ifstream in;
ofstream out;
fstream both ;
它们分别定义了输入流对象 in, 输出流对象 out ,输入输出流对象 both。
建立了流以后,就可以用函数 open ( ) 把某一文件与上面的某一流连在一起, 即打开文件。open( )函数是上述三个流类的成员函数, 其原型为:

void open ( const unsigned char * , int mode, int access = filebuf∷openprot) ;

其中第一个参数是用来传递文件名的;第二个参数 mode 的值决定文件将如何被打开, 它必须取下面的值中的一个:
ios∷app 使输出追加到文件尾部
ios∷ate 查找文件尾
ios∷in 打开一个文件进行读操作
ios∷nocreate 文件不存在时, 导致 open( )失败
ios∷noreplace 若文件存在, 则 open( )失败
ios∷out 打开一个文件进行写操作
ios∷trunc 使同名文件被删除
ios∷binary 文件以二进制方式打开, 缺省时为文本文件

下面对这些值作进一步的说明:
(1 ) 如果希望向文件尾部添加数据, 则应当用“ios∷app”方式打开文件, 但此时文件必须存在。打开时,文件位置指针移到文件尾部。用这种方式打开的文件只能用于输出。
(2 ) 用“ios∷ ate”方式打 开一个已存在的文件 时, 文 件位置指针自动 移到文件 的尾部。
(3 ) 用“ios∷in”方式打开的文件只能用于输入数据, 而且该文件必须已经存在。如果用类 ifstream 来产生一个流,则隐含为输入流, 不必再说明使用方式。用“ios∷out”方式打开文件,表示可以向该文件输出数据。如果用类 ofstream 来产生一个流,则隐含为输出流,不必再说明使用方式。
(4 ) 通常, 当用 open( ) 函数打开文件时,如果文件存在, 则打开该文件,否则建立该文件。但当用“ios∷nocreate”方式打开文件时, 表示不建立新文件,在这种情况下, 如果要打开的文件不存在, 则函数 open ( ) 调用失败。相反, 如果使用“ios∷noreplace”方式打开文件,则表示不修改原来文件, 而是要建立新文件。因此, 如果文件已经存在, 则 open ( ) 函数调用失败。
(5 ) 当使用“ios∷trunc”方式打开文件时, 如果文件已存在,则清除该文件的内容, 文件长度被压缩为零。实际上, 如果指定“ios∷out”方式, 且未指定“ios∷ate”方式或“ios∷app”方式,则隐含为“ios∷trunc”方式。
(6 ) 如果使用“ios∷binary”方式,则以二进制方式打开文件, 缺省时,所有的文件以文本方式打开。**在用文本文件向计算机输入时,把回车和换行两个字符转换为一个换行符,而在输出时把换行符转换为回车和换行两个字符。**对于二进制文件则不进行这种转换,在内存中的数据形式与输出到外部文件中的数据形式完全一致,一一对应。
access 的值决定文件的访问方式。文件的访问方式指的是文件类别。access 的缺省值是 filebuf∷openprot(filebuf 是流类的父类 )。该值对于 UNIX 环境是 0X644, 意味着普通文件。在 DOS/ Windows 环境中, access 值通常对应于 DOS/ Windows 的文件属性代码,它们是:
0 普通文件
1 只读文件
2 隐含文件
4 系统文件
8 备份文件

了解了文件的使用方式后,可以通过以下步骤打开文件:
(1 ) 定义一个流类的对象, 例如:

ofstream out;

定义了类 ofstream 的对象 out ,它是一个输出流。
(2 ) 使用 open ( )函数打开文件, 也就是使某一文件与上面定义的流相联系。例如:

out .open(”test”, ios∷out , 0) ;

将打开一个普通的输出文件 test。
以上是打开文件的一般操作步骤。实际上, 由于文件的 mode 参数 ( 使用方式 ) 和access参数(访问方式) 都有缺省值,对于类 ifstream, mode 的缺省值为 ios∷in, access 的缺省值为“0”(普通文件 ) ; 而对于类 ofstream, mode 的缺省值为 ios∷out, access 的缺省值也为“0”。因此, 上述语句通常可写成:

out .open(”test”) ;

当一个文件需要用两种或多种方式打开时,可以用“或”操作符( 即“ | ”) 把几种方式连接在一起。例如,为了打开一个能用于输入和输出的流, 必须把使用方式设置为 ios∷in和 ios∷out( 对于这种情况,不能提供 mode 的缺省值) , 打开方法如下:

fstream mystream;
mystream .open(”test”, ios∷in | ios∷out) ;

只有在打开文件后,才能对文件进行读写操作。如果由于某些原因, 执行函数 open( ) 失败,流变量的值将为零。因此, 在使用文件之前,必须进行检测, 以确认打开一个文件是否成功。可以使用类似下面的方法进行检测:

if ( ! mystream)
{
cout < < ”Cannot open file ! \ n”;
/ / 错误处理代码
}

虽然用 open( )函数打开文件是完全正确的, 但是在大多数情况下不必如此。因为类ifstream、ofstream 与 fstream 都能自动打开文件的构造函数, 这些构造函数的参数及缺省值与 open( )函数的完全相同。因此,在实际编程时, 打开一个文件的最常见的形式为:

ofstream out(”test”) ;

它相当于语句:

ofstream out;
out .open(”test”) ;

如同前面所述,如果出于某些原因, 文件不能打开, 与文件相联系的流变量的值将是零。因此,无论是使用构造函数来打开文件, 还是直接调用 open( )来打开文件,通常都要判断打开文件是否成功。
2. 文件的关闭
在使用完一个文件后,应该把它关闭。所谓关闭, 实际上就是使打开的文件与流“脱钩”。关闭文件可使用 close ( ) 函数完成, close ( ) 函数也是流类中的成员函数, 它不带参数,没有返回值, 例如:

out .close ( ) ;

将关闭与流 out 相连接的文件。
在进行文件操作时,应养成将已完成操作的文件关闭的习惯。如果不关闭文件, 则有可能丢失数据。

5 . 2 文件的读写

在含有文件操作的程序中, 必须包含头文件 fstream .h, 即必须有如下的编译预处理命令:

#include < fstream .h >

在 Borland C + + 中,文件 iostream .h 自动包含于 fstream .h 文件中, 因此使用了上面的编译预处理命令后,就不必再包含 iostream .h(如果使用也可以) 。
当文件打开以后,即文件与流建立了联系后, 就可以进行读写操作了。

  1. 文本文件的读写
    一旦文件打开了,从文件中读取文本数据与向文件中写入文本数据都十分容易, 只需使用运算符“ < < ”与“ > > ”就可以了,只是必须用与文件相连接的流代替 cin 和 cout。
# include < fstream .h >
int main ( )
{
ofstream fout(”test”) ;
if ( ! fout)
{cout < < ”Cannot open output file \ n,;
return 1;
}
fout < < 10 < < ””< < 123 .456 < < ”\ ”This is a text file . \ ”\ n”;
fout .close ( ) ;
return 0 ;
}

程序运行后,屏幕上不显示任何信息, 因为输出的内容存入文件 test 中。在 DOS 下用 type test 命令可以看到该文件的内容:
c > type test
10 123 .456 ”This is a test file”
先建立一个输出文件, 向它写入数据, 然后关闭文件, 再按输入模式打开它,并读取信息。

# include < fstream .h >
int main ( )
{
ofstream fout(”test2”) ;
if ( ! fout)
{ cout < < ”Cannot open output file . \ n”;
return 1;
}
fout < <”Hello ! \ n”;
fout < < 100 < <′′< < hex < < 100 < < endl;
fout .close ( ) ;
ifstream fin(”test2”) ;
if ( ! fin)
{
cout < < ”Cannot open input file . \ n”;
return 1;
}
char str[80] ;
int i;
fin > > str > > i;
cout < < str < <””< < i< < endl;
fin .close( ) ;
return 0 ;
}

程序运行后, 首先建立一个输出文件 test2, 并向它写入数据。若在 DOS 下用“typetest2”命令,就可以显示出该文件的内容:
Hello !
100 64
完成写入数据后,关闭输出文件 test2。之后, 再将文件 test2 按输入模式打开, 并将字符串“Hello ! \ n”赋给字符数组 str,将整数 100 赋给整型变量 i。最后在屏幕上显示出str 和 i 的值, 如下所示:
Hello !
100
从键盘读入字符串, 并将它们写进磁盘。当用户输入空白字符时, 程序停止。

# include < iostream .h >
# include < fstream .h >
# include < stdio .h >
main (int argc, char * argv[ ] )
{
if ( argc ! = 2 )
{
cout < < ”Usage : write < filename > \ n”;
return 1;
}
ofstream outf( argv[1 ] ) ;
if( !outf)
{
cout < < ”Cannot open output file . \ n”;
return 1;
}
char str[60] ;
cout < < ”Write strings to disk, RETURN to stop \ n”;
do
{
cout < <:;
gets(str) ;
outf < < str < < endl;
} while ( * str) ;
outf .close ( ) ;
return 0 ;
}

在这个例子中采用了命令行参数的形式, 执行时需传递一个参数, 即输出文件名, 加上本程序 的 文 件 名, argc 应 等 于 2。 若 此 文 件 名 为 e7-14 .CPP, 经 编 译、连 接 后 得e7-14 .EXE。在 DOS 下可输入以下命令行:

c > e7 - 14 file

即在键入可执行文件名后,再输入参数 file,赋给 argv[1 ] , 此时 argc 的值为 2 (因为命令行有 2 个参数) , 而 argv[ 0]的内容为 e7-14。程序运行情况如下:
Write strings to disk, RETURN to stop
∶abcdefg↙ ( 从键盘输入一个字符串)
∶↙ ( 输入空行,程序停止)
程序运行后, 在磁盘上生成一个名为“file”的文件, 其内容与屏幕显示的内容相同。可以在 DOS 下用 type 命令显示该文件的内容:
c > type file↙
abcdefg

  1. 二进制文件的读写
    任何文件,无论它包含格式化的文本还是包含原始数据, 都能以文本方式或二进制方式打开。文本文件是字符流,而二进制文件是字节流。
    在缺省情况下, 文件用文本方式打开。这就是说, 在输入时, 回车和换行两个字符要转换为字符“ \ n”; 在输出时, 字符“ \ n”转换为回车和换行两个字符。这些转换在二进制方式下是不进行的。这是文本方式和二进制方式主要的区别。
    对二进制文件进行读写有两种方式,其中一种使用的是 get( )和 put( ) , 另一种使用的是 read( )和 write ( ) 。这四种函数也可以用于文本文件的读写。在此主要介绍对二进制文件的读写。除字符转换方面略有差别外,文本文件的处理过程与二进制文件的处理过程基本相同。
    (1 ) 用 get( ) 函数和 put( ) 函数读写二进制文件
    get( )是输入流类 istream 中定义的成员函数, 它可以从与流对象连接的文件中读出数据,每次读出一个字节( 字符)。put( )是输出流类 ostream 中的成员函数, 它可以向与流对象连接的文件中写入数据,每次写入一个字节( 字符)。
    get( )函数有许多格式, 其最一般的使用版本的原型如下:
    istream &get( unsigned char & ch) ;
    get( )函数从相关流中只读一个字符, 并把该值放入引用 ch& 中。
    put( )函数的原型如下:
    ostream &put( char ch) ;
    put( )函数将字符 ch 写入流中。
    实现任意类型的文件拷贝。
# include < iostream .h >
# include < fstream .h >
main (int argc, char * argv[ ] )
{
char ch;
if ( argc ! = 3 )
{
cout < < ”Bad command ! \ n”;
return 0;
}
ifstream inf( argv[ 1] ) ;
if ( !inf)
{
cout < < ”Cannot open source file .;
return 1;
}
ofstream outf( argv[2 ] ) ;
if ( !outf)
{
cout < < ”Cannot open target file .;
return 1;
}
while( inf)
{
inf .get( ch) ;
outf .put( ch) ;
}
inf .close( ) ;
outf .close ( ) ;
return 0 ;
}

说明: 以上例子中没有指定访问方式 ios∷binary。指定 ios∷binary 只是为了防止任何字符转换的出现。因为例子中字符转换不成为问题,所以不必一定需要 ios∷binary。

  1. 用 read( ) 函数和 write( )函数读写二进制文件
    有时需要读写一组数据(如一个结构变量的值 ) , 为此 C + + 提供了两个函数 read( )和 write ( ) , 用来读写一个数据块,这两个函数最常用的形式如下:
    istream & read( unsigned char * buf, int num ) ;
    ostream & write( const unsigned char * buf, int num) ;

    read ( )是类 istream 中的成员函数, 其功能为:从相应的流中读取 num 个字节(字符 ) ,并把它们放入指针 buf 所指的缓冲区中。该函数有两个参数: 第一个参数 buf 是一个指针,它是读入数据的存放地址( 起始地址) ; 第二个参数 num 是一个整数值,它是要读入的数据的字节(字符) 数。其调用格式为:
    read(缓冲区首址,读入的字节数 ) ;
    注意“: 缓冲区首址”的数据类型为 unsigned char * ,当输入其它类型数据时, 必须进行类型转换,例如:
    int array[ ] = {50, 60, 70} ;
    read( ( unsigned char * ) & array,sizeof ( array) ) ;

    该例定义了一个整型数组 array, 为了读入它的全部数据, 必须在 read ( )函数中给出它的首地址,并把它转换为 unsigned char * 类型。由 sizeof( ) 函数确定要读入的字节数。
    write ( )是流类 ostream 的成员函数, 利用该函数,可以从 buf 所指的缓冲区把 num 个字节写到相应的流上。参数的含义及调用注意事项与 read( )函数类似。
    如果在 num 个字节( 字符) 被读出之前就达到了文件尾, 则 read ( ) 只是停止执行, 此时缓冲区包含所有可能的字符。我们可以用另一个成员函数 gcount( )统计出有多少字符被读出。gcount( )的原型如下:
    int gcount( ) ;
    它返回所读取的字节数。

用 write( )函数向文件 test 中写入双精度数与字符串。

# include < iostream .h >
# include < fstream .h >
# include < string .h >
main ( )
{
ofstream out(”test”) ;
if ( !out)
{
cout < < ”Cannot open output file . \ n”;
return 1;
}
double num = 100 .45 ;
char str[ ] = ”This is a test”;
out .write( ( char * ) & num,sizeof( double ) ) ;
out .write(str,strlen (str) ) ;
out .close( ) ;
return 0 ;
}

程序执行后,屏幕上不显示任何信息, 但程序已将双精度数 100 .45 和字符串“This isa test”以二进制形式写入文件 test 中。用下面的程序可以读取文件 test 中的数据, 并在屏幕上显示出来,以验证前面程序的操作。

用 read( )函数读取例 7 .17 中程序所建立的文件。

# include < iostream .h >
# include < fstream .h >
# include < string .h >
main( )
{
ifstream in(”test”) ;
if ( ! in)
{
cout < < ”Cannot open input file . \ n”;
return 1;
}
double num;
char str[80] ;
in .read( ( char * ) &num,sizeof( double ) ) ;
in .read(str, 14 ) ;
cout < < num < <′′< < str;
in .close( ) ;
return 0 ;
}

程序运行结果为:
100 .45 This is a test
3. 文件的随机读写
前面介绍的文件操作都是按一定顺序进行读写的, 因此称为顺序文件。对顺序文件而言,只能按实际排列的顺序, 一个一个地访问文件中的各个元素。为了增加对文件访问的灵活性, C + + 在类 istream 及类 ostream 中定义了几个与在输入或输出流中随机移动文件指针相关的成员函数,则可以在流内随机移动文件指针, 从而可以对文件的任意数据进行随机读写。
在 C + + 的输入输出系统中, 可以用函数 seekg( ) 和 seekp( ) 进行随机访问。它们最常用的形式如下所示:
istream &seekg(streamoff offset ,seek - dir origin) ;
ostream &seekp(streamoff offset ,seek - dir origin) ;

其中,参数 origin 表示文件指针的起始位置, offset 表示相对于这个起始位置的位移量。
seek - dir 是一个系统定义的枚举名, origin 是枚举变量。origin 的取值有以下三种情况:
① ios∷beg 从文件头开始, 把文件指针移动由 offset 指定的距离。
② ios∷cur 从当前位置开始, 把文件指针移动由 offset 指定的距离。
③ ios∷end 从文件尾开始,把文件指针移动由 offset 指定的距离。
当 origin 为 ios∷beg 时, offset 的值为正数; 当 origin 为 ios∷end 时, offset 的值为负数;而当 origin 为 iso∷cur 时, offset 的值可以为正数, 也可以为负数。正数时从前向后移动文件指针,负数时从后向前移动文件指针。
位移量 offset 的类型是 streamoff,这种类型在头文件 iostream .h 中定义如下:
typedef streamoff long;
可以看出,它实际上是 long 型。
函数 seekg( )用于输入文件,将相应文件的读指针从 origin 说明的位置移动 offset 个字节; 函数 seekp ( )用于输出文件, 将相应文件的写指针从 origin 说明的位置移动 offset 个字节。
进行文件的随机读写时,可以用以下函数确定每一个文件指针的当前位置:
streampos tellg( ) ;
streampos tellp( ) ;
其中 tellg( ) 函数用于输入文件, tellp ( ) 函数用于输出文件。streampos 是在 iostream .h 中定义的类型,它实际上是 long 型的。

此例中演示了 seekp( ) 函数。它允许改变文件中的指定字符。在命令行指定文件名,其后紧跟文件中想修改的字符的位置, 然后再跟新的字符。

# include < fstream .h >
# include < stdlib .h >
int main (int argc, char * argv[ ] )
{
if ( argc != 4)
{
cout < < ”Usage : CHANGE < filename > < byte > < char > \ n”return 1;
}
fstream out( argv[ 1] , ios∷in | ios∷out) ;
if ( !out)
{
cout < < ”Cannon open file”< < argv[ 1] < <”\ n”return 1;
}
out .seekp( atoi( argv[2 ] ) , ios∷beg) ;
out .put( * argv[ 3] ) ;
out .close( ) ;
return 0 ;
}

假 定 上 述 程 序 的文 件 名 为 E7 - 19 .cpp, 经 编 译、连 接 生 成 的 可 执 行 文 件 名 为E7 - 19 .exe。需要修改的文件名为 file, 在 DOS 下用 type 命令显示文件 file 的内容:
c > type file
AAAAAAAAAA1234567890
执行下述命令:
c > E7 - 19 file 5 #
该程序先用 seekp( ) 函数把文件指针移到指定的位置, 然后将这个位置上的字符修改为“ # ”。程序运行结果为:
AAAAA # AAAA1234567890
使用 seekg( ) 函数,它从指定的位置开始显示文件内容。

# include < fstream .h >
# include < stdlib .h >
main(int argc, char * argv[ ] )
{
char ch;
if ( argc ! = 3 )
{
cout
< < ”Usage : LOCATE < filename > < loc > \ n”;
return 1;
}
ifstream inf( argv[ 1] ) ;
if ( ! inf)
{
cout < < ”Cannot open input file . \ n”;
return 1;
}
inf .seekg( atoi( argv[ 2] ) , ios∷beg) ;
while ( ! inf .eof( ) )
{
inf .get( ch) ;
cout < < ch;
}
inf .close( ) ;
return 0 ;
}

假定本程序的执行文件名为 E7 - 20 .exe ,要显示的文件名为 file1, 希望从第 5 个位置开始显示,则执行的命令行为:
c > E7 - 20 file1 5

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值