C++输入/输出流类库

转载地址:http://www.weixueyuan.net/cpp/rumen_9/

在C++中,数据从一个对象到另一个对象的传送被抽象为“流”,由它负责在数据的产生者和使用者之间建立联系,并管理数据的流动。

在现代操作系统中,一切输入/输出设备,包括键盘、显示器、打印机、网卡、磁盘、声卡等,都被视为广义的文件。在C++中,与这些文件的交互,即数据的输入/输出,是通过包含在C++标准库里的输入/输出(I/O)流类库来实现的。

本章重点介绍怎样把数据保存到磁盘文件中。
本章内容:
C++的基本流类体系
C++输入输出的格式控制
C++标准设备的输入/输出(cin,cout,cerr,clog,>>
C++文件的打开与关闭
C++文件的读/写(文本文件和二进制文件)
C++文件的随机访问
C++字符串流
C++文件与对象

C++的基本流类体系

流类体系

整个流类体系是一个派生类体系,如下图所示。


图 输入/输出流类派生体系

按ANSI C++标准,类ios是抽象类,它的析构函数是虚函数,它的构造函数为保护的,作为所有基本流类的基类。VC++中有一个构造函数ios (streambuf*)为公有,与ANSI C++不同。

在流类库中,最重要的两部分功能为标准输入/输出(standard input/output)和文件处理。

在C++的流类库中定义了四个全局流对象:cin,cout,cerr和clog。可以完成人机交互的功能。
  • cin:标准输入流对象,键盘为其对应的标准设备。带缓冲区的,缓冲区由streambuf类对象来管理。
  • cout:标准输出流对象,显示器为标准设备。带缓冲区的,缓冲区由streambuf类对象来管理。
  • cerr和clog:标准错误输出流,输出设备是显示器。为非缓冲区流,一旦错误发生立即显示。

要使用这四个功能,必须包含 <iostream.h> 文件。

提取运算符“>>”(stream_extraction operator)和插入运算符“<<”(stream_insertion operator),执行输入/输出操作。
  • “提取”的含义是指输入操作,可看作从流中提取一个字符序列。
  • “插入”的含义是指输出操作,可看作向流中插入一个字符序列。

文件处理完成永久保存的功能。在VC++的MFC编程中采用了序列化(Serialization)。


C++输入输出的格式控制

C++在类ios中提供格式化输入输出。

格式控制符

使用格式控制符,可以进行格式化输入输出。这些格式对所有文本方式的输入输出流均适用。

格式控制符定义为公有的无名的枚举类型:
enum{
       skipws=0x0001, //跳过输入中的空白字符
       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
}

该枚举量说明中的每一个枚举量,实际对应两字节数据(16位)中的一个位,所以可以同时采用几个格式控制,只要把对应位置1即可,这样既方便又节约内存。

取多种控制时,用或“|”运算符来合成,合成为一个长整型数,在ios中为:
protected:
      long x_flags;

配合使用的格式控制标志

protected:
       int x_precision; //标志浮点数精度,默认为6位
       int x_width; //输出域宽,默认域宽为0,
              //重设域宽只对其后第一输出项有效,如域宽不足,则不受限制
       char x_fill; //标志域宽有富余时填入的字符


【例9.1】整型数输出。( 查看源码

【例9.2】浮点数输出。( 查看源码

流操作子(setiosflags stream manipulator)

流格式控制成员函数的使用比较麻烦,可改用 流操作子 (Setiosflags Stream Manipulator)。例如setw()等,可代替流类中的格式控制成员函数。

注意,绝大多数流操作子仅适用于新的C++标准流类库(头文件不带.h),常用流操作子如下表所示。

cin,cout和clog都是缓冲流。对输出而言,仅当输出缓冲区满才将缓冲区中的信息输出,对输入而言,仅当输入一行结束,才开始从缓冲区中取数据,当希望把缓冲区中的信息立即输出,可用flush,加endl也有同样功能,回车并立即显示,不必等缓冲区满(endl清空缓冲区)。

【例9.2_1】采用流操作子的浮点数输出。(查看源码


C++标准设备的输入/输出(cin,cout,cerr,clog,>>,<<)

本节对 cin,cout,cerr,clog,>>和<< (提取和插入运算符)的使用细节作进一步讨论。

提高标准输入/输出的健壮性

◆  1、标准设备输入使用要点
  • cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据。不可能用刷新来清除缓冲区,所以不能输错,也不能多输!
  • 输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state(枚举类型io_state)中对应位置位(置1),程序继续。所以要提高健壮性,就必须在编程中加入对状态字state的判断。
  • 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格(ASCII码为32)无法用cin输入,字符串中也不能有空格。回车符也无法读入。
  • 输入数以后再输入字符或字符串:如果数后直接加回车,应该用cin.get()提取回车。如果还有空格,则要清空缓冲区。
◆  2、程序运行状态
状态字state为整型,其的各位在ios中说明:
enum ios_state
{
       goodbit=0x00, //流正常
       eofbit=0x01,    //输入流结束忽略后继提取操作;或文件结束已无数据可取
       failbit=0x02, //最近的I/O操作失败,流可恢复
       badbit=0x04, //最近的I/O操作非法,流可恢复
       hardfail=0x08     // I/O出现致命错误,流不可恢复,VC6.0++不支持
}

读取状态的有关操作如下:
inline int ios::rdstate() const //读取状态字
{return state;}

inline int ios:operator!() const //可用操作符!()代替fail()
{return state&(badbit|failbit);}

inline int ios::bad() //返回非法操作位
{ return state & badbit;}

inline void ios::clear(int _i) //人工设置状态,可用来清状态
{ lock();state=_i;unlock();}

inline int ios::eof() const //返回流(文件)结束位
{return state&eofbit;}

inline int ios::fail() const //返回操作非法和操作失败这两位
{return state&(badbit|failbit);}

inline int ios::good() const //正常返回1,否则返回0
{return state==0;}

◆ 3、举例:【例9.3】提高输入的健壮性。输入时需要故意输错,以测试健壮性。 ( 查看源码

标准输入/输出成员函数

◆  1、输入流成员函数声明
(1)字符输入:
    int istream::get();
    //提取一个字符,包括空格,制表,backspace和回车等,
    //与cin有所不同.注意返回为整型
    istream&istream::get(char &);
    istream&istream::get(unsigned char &);
提取一个字符,放在字符型变量中

(2)字符串输入:
    istream&istream::get(char *,int,char=’\n’);
    istream&istream::get(unsigned char *,int,char=’\n’);
    istream&istream::getline(char *,int,char=’\n’);
    istream&istream::getline(unsigned char *,int,char=’\n’);
提取的串放在第一个参数为开始地址的存储区(不查边界);第二个参数为至多提取的字符个数(指定为n,最多取n-1个,再加一个字符串结束符);第三个参数为结束字符,遇此字符则结束,默认为回车换行符。

get系列函数要求单独提取结束字符。getline提取字符串时如遇到指定结束符则提取该结束符,但不保存在串中。这两个函数都会在提取的一系列字符后加一个串结束符,返回值为对象本身(*this)。

(3)其他函数:
函数gcount()返回最后一次提取的字符数量,包括回车:
     int istream::gcount();

函数ignore()读空(指定一个大的数量)缓冲区:
     istream&istream::ignore(int=1,int=EOF);
第一个参数为要提取的字符数量,默认为1;第二个参数为结束字符,提取该结束字符,但对所提取的字符不保存不处理,作用是空读。第二个参数的默认值EOF为文件结束标志。

在iostream中EOF定义为-1,在int get()函数中,读入输入流结束标志Ctrl+Z(^Z)时,函数返回EOF,为了能表示EOF的“-1”值,返回类型为int。采用cin.eof()函数,当前所读为EOF则返回非零,注意函数自身未从流中读取。

【例9.4】 ignore()和gcount()函数使用。( 查看源码

◆  2、输出流成员函数声明
    ostream&ostream::put(char);
    //输出参数字符
    ostream&ostream::put(unsigned char);
    ostream&ostream::put(signed char);
    ostream&ostream::flush();
    //刷新一个输出流,用于cout和clog

重载插入和提取运算符

重载必须保留原来的使用特性。重载只能在用户定义类中,将重载的运算符的函数说明为该类的友元函数:
    friend istream&operator>>(istream&,className&);
    friend ostream&operator<<(ostream&,className&);
函数的返回值是对输入或输出流的引用,这是为了保证在cin和cout中可以连续使用“>>”或“<<”运算符,与所有“>>”和“<<”重载函数一致。第一个参数是输入或输出流的引用,作为“>>”或“<<”的左操作数;第二个参数为用户定义类的引用,作为右操作数,流用作函数参数,必须是引用调用,不能是传值调用。

【例9.5】改进【例6.10】,重载插入运算符“<<”。( 查看源码

【例9.6】用户定义的复数类Complex的输入与输出。( 查看源码



C++文件的打开与关闭

文件的基本概念

本节中文件指的是 磁盘文件

C++根据文件(file)内容的数据格式,可分为两类:
  • 文本文件:由字符序列组成,在文本文件中存取的最小信息单位为字符(character),也称ASCII码文件。
  • 二进制文件:存取的最小信息单位为字节(Byte)。

C++把每个文件都看成一个有序的字节流,每一个文件或者以文件结束符(end of file marker)结束,或者在特定的字节号处结束,如下图所示。



当打开一个文件时,该文件就和某个流关联起来了。对文件进行读写实际上受到一个 文件定位指针 (file position pointer)的控制。

输入流的指针 也称为 读指针 ,每一次提取操作将从读指针当前所指位置开始,每次提取操作自动将读指针向文件尾移动。 输出流指针 也称 写指针 ,每一次插入操作将从写指针当前位置开始,每次插入操作自动将写指针向文件尾移动。

文件的打开与关闭

文件使用的5步骤:
①说明一个文件流对象,这又被称为内部文件:
     ifstream ifile;//只输入用
   ofstream ofile;//只输出用
    fstream iofile;//既输入又输出用


②使用文件流对象的成员函数打开一个磁盘文件。这样在文件流对象和磁盘文件名之间建立联系。文件流中说明了三个打开文件的成员函数。
     void ifstream::open(const char*,int=ios::in,int=filebuf::openprot);
    voidofstream::open(const char*,int=ios::out,int=filebuf::openprot);
    void fstream::open(const char*,int,int=filebuf::openprot);

第一个参数为要打开的磁盘文件名。第二个参数为打开方式,有输入(in),输出(out)等,打开方式在ios基类中定义为枚举类型。第三个参数为指定打开文件的保护方式,一般取默认。所以第二步可如下进行:
     iofile.open(“myfile.txt”,ios::in|ios::out);

上面三个文件流类都重载了一个带默认参数的构造函数,功能与open函数一样:
     ifstream::ifstream(const char*,int=ios::in,int=filebuf::openprot);
    ofstream::ofstream(const char*,int=ios::out,int=filebuf::openprot);
    fstream::fstream(const char*,int,int=filebuf::operprot);

所以①和②两步可合成: fstream iofile(”myfile.txt”,ios::in|ios::out);

③打开文件也应该判断是否成功,若成功,文件流对象值为非零值,不成功为0(NULL),文件流对象值物理上就是指它的地址。因此打开一个文件完整的程序为:
fstream iofile(”myfile.txt”,ios::in|ios::out);
if(!iofile)
{ //“!”为重载的运算符
       cout<<”不能打开文件:”<<”myfile,txt”<<endl;
       return -1;
} //失败退回操作系统


④使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写,这在下一节中讨论。

⑤关闭文件。三个文件流类各有一个关闭文件的成员函数 :
     void ifstream::close();
    void ofstream::close();
    void fstream::close();

使用很方便,如:
     iofile.close();

关闭文件时,系统把该文件相关联的文件缓冲区中的数据写到文件中,保证文件的完整,收回与该文件相关的内存空间,可供再分配,把磁盘文件名与文件流对象之间的关联断开,可防止误操作修改了磁盘文件。如又要对文件操作必须重新打开。

关闭文件并没有取消文件流对象,该文件流对象又可与其他磁盘文件建立联系。文件流对象在程序结束时,或它的生命期结束时,由析构函数撤消。它同时释放内部分配的预留缓冲区。



C++文件的读/写(文本文件和二进制文件)

文本文件的读写

文本文件的顺序读写:顺序读写可用C++的提取运算符(>>)和插入运算符(<<)进行。

【例9.7】复制文件。( 查看源码

【例9.8】按行复制文本文件。( 查看源码

【例9.9】文本式数据文件的创建与读取数据。( 查看源码

资源获取是由构造函数实现,而资源释放是由析构函数完成。所以与内存动态分配一样,由文件重构对象放在构造函数中,把对象存入文件则放在析构函数中。参见后面章节。

二进制文件的读写

◆  1、对二进制文件进行读写的成员函数
    istream&istream::read(char *,int);
    //从二进制流提取
    istream&istream::read(unsigned char*,int);
    istream&istream::read(signed char *,int);
    //第一个参数指定存放有效输入的变量地址,第二个参数指定提取的字节数,
    //函数从输入流提供指定数量的字节送到指定地址开始的单元

    ostream&ostream::write(const char *,int);
    //向二进制流插入
    ostream&ostream::write(const unsigned char *,int);
    ostream&ostream::write(const signed char *,int);
    //函数从该地址开始将指定数量的字节插入输入输出流

◆  2、文件结束判断 :读函数并不能知道文件是否结束,可用状态函数int ios::eof()来判断文件是否结束。必须指出系统是根据当前操作的实际情况设置状态位,如需根据状态位来判断下一步的操作,必须在一次操作后立即去调取状态位,以判断本次操作是否有效。

◆ 3、举例:【例9.10】创建二进制数据文件,以及数据文件的读取。( 查看源码 )这两项操作设计为成员函数,给出与【例9.9】不同的读写方式。

◆  4、二进制文件优点 :可以控制字节长度,读写数据时不会出现二义性,可靠性高。同时不知格式是无法读取的,保密性好。文件结束后,系统不会再读(见eofbit的说明),但程序不会自动停下来,所以要判断文件中是否已没有数据。如写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。如关闭文件后重新打开,文件定位指针就在文件头。


C++文件的随机访问

在C++中可以由程序控制文件指针的移动,从而实现文件的随机访问,即可读写流中任意一段内容。

一般文本文件很难准确定位,所以随机访问多用于二进制文件。如【 例9.9 】中对象中两个字符串是按实际串长存放的,不是按数组元素来存放的,而【 例9.10 】中是按数组长度来存放的,每个对象数据长度固定,所以便于随机访问。

随机访问指针控制字

在ios类中说明了一个公有枚举类型:
enum seek_dir
{
       beg=0, //文件开头
       cur=1, //文件指针的当前位置
       end=2 //文件结尾
};

设置“输入流指针控制字”的成员函数

设置“输入流指针控制字”的成员函数:
    istream&istream::seekg(streampos); //指针直接定位
    istream&istream::seekg(streamoff, ios::seek_dir); //指针相对定位
    long istream::tellg(); //返回当前指针位置

流的指针位置类型streampos和流的指针偏移类型streamoff定义为长整型,也就是可访问文件的最大长度为4G。例:
    datafile.seekg(-20L,ios::cur);
    //表示将文件定位指针从当前位置向文件头部方向移20个字节。

    datafile.seekg(20L,ios::beg);
    //表示将文件定位指针从文件头向文件尾方向移20个字节。

    datafile.seekg(-20L,ios::end);
    //表示将文件定位指针从文件尾向文件头方向移20个字节。

    tellg()和seekg()往往配合使用。
    //指针不可移到文件头之前或文件尾之后。

设置“输出流指针控制字”的成员函数

设置“输出流指针控制字”的成员函数:
    ostream&ostream::seekp(streampos);
    ostream&ostream::seekp(streamoff,ios::seek_dir);
    long ostream::tellp();
为了便于记忆,函数名中g是get的缩写,而p是put的缩写。对输入输出文件定位指针只有一个但函数有两组,这两组函数功能完全一样。

举例

【例9.11】使用随机访问对【例9.10】进行改造。( 查看源码


C++字符串流

字符流概念:字符串(string)也可以看作字符流。可以用输入输出操作来完成串流的操作。串流与内存相关,所以也称内存流。

串流类包括 ostrstream istrstream strstream ,它们在 <strstrea.h> 中说明。串流类对象可以保存字符,也可以保存整数、浮点数。串流类对象采用文本方式。

其构造函数常用下面几个:
     istrstream::istrstream(const char * str);
    istrstream::istrstream(const char * str,int);
    ostrstream::ostrstream(char *,int,int=ios::out);
    strstream::strstream(char *,int,int);

其中第二个参数说明数组大小,第三参数为文件打开方式。

【例9.12】字符串流类的应用。
#include<strstream>
#include<iostream>
#include<cstring>
using namespace std;
int main(){
    int i;
    char str[36]="This is a book.";
    char ch;
    istrstream input(str,36);          //以串流为信息源
    ostrstream output(str,36);
    cout<<"字符串长度:"<<strlen(str)<<endl;
    for(i=0;i<36;i++){
        input>>ch;             //从输入设备(串)读入一个字符,所有空白字符全跳过
        cout<<ch;                     //输出字符
    }
    cout<<endl;
    int inum1=93,inum2;
    double fnum1=89.5,fnum2;
    output<<inum1<<' '<<fnum1<<'\0';  //加空格分隔数字
    cout<<"字符串长度:"<<strlen(str)<<endl;
    istrstream input1(str,0);      //第二参数为0时,表示连接到以串结束符终结的串
    input1>>inum2>>fnum2;
    cout<<"整数:"<<inum2<<'\t'<<"浮点数:"<<fnum2<<endl; //输出:整数:93 浮点数:89.5
    cout<<"字符串长度:"<<strlen(str)<<endl;
    return 0;
}


C++文件与对象

规范化操作:在面向对象的程序设计中,信息总是放在对象的数据成员里。这些信息最终应该保存到文件中。当程序开始运行时要由打开的文件重新创建对象。在运行过程中,放在对象的数据成员里的信息得到利用和修改。运行结束时必须把这些信息重新保存到文件中,然后关闭文件。

在面向对象的C++程序设计中,文件应该在构造函数中打开,并创建对象;而在析构函数中保存和关闭文件,并撤销对象。当撤销对象时,能自动释放资源。释放资源包括将对象中的信息再次存入磁盘文件。程序运行中,总要对保存在对象的数据成员里的信息进行操作,这时应该将信息适时保存到相应的磁盘文件中,以免数据意外丢失。这些都是常规操作,是面向对象的C++程序设计的固定框架。

【例9.13】将商店的货物,定义为一个货物数组类。数组对象动态建立,初始为2个元素,不够用时扩充一倍。用文本数据文件建立数组元素对象,要求放在构造函数中,而数据的保存和文件的关闭放在析构函数中。第一次运行时,建立空的数据文件,由键盘输入建立数组元素对象,并写入文件,程序退出时,关闭文件;下一次运行由该文件构造对象,恢复前一次做过的工作。( 查看源码

这是一个标准的面向对象的程序设计,也是对前面各章内容的小结。注意,本例使用了多重的插入运算符重载。 

猜你喜欢

转载自blog.csdn.net/wangjian530/article/details/80511540