C++程序设计:I/O流
输入/输出(I/O)用于完成数据传输。C++语言支持两种I/O,一种是C语言中的I/O函数,另一种是面向对象的I/O流类库。本章将针对C++中I/O流类库及其使用进行详细讲解。
一、I/O流类库
I/O流类库是C++标准库的重要组成部分,它主要包括ios类库和streambuf类库。其中,ios类库提供流的高级I/O操作,streambuf类库主要负责缓冲区的处理,下面将分别介绍ios类库和streambuf类库。
3.1、ios类库
ios类库以ios类为基类,ios类是一个抽象类,提供了输入/输出所需的公共接口,如设置数据流格式、错误状态恢复、设置文件的输入/输出模式等。ios类库的层次结构如图下所示。
由图可知,抽象基类ios类派生了2个类,分别是istream类和ostream类,其中istream类是输入流类,ostream类是输出流类,它们定义了输入流和输出流的基本特性。istream类和ostream类又派生了多个类,具体介绍如下。
- ifstream类:文件输入流类,支持文件的读操作。
- ofstream类:文件输出流类,支持文件的写操作。
- istringstream类:字符串输入流类,支持字符串的输入操作。
- ostringstream类:字符串输出流类,支持字符串的输出操作。
- fstream类:文件输入/输出流类,支持文件的读写操作。
- stringstream类:字符串输入/输出流类,支持字符串的输入和输出操作。
3.2、streambuf类库
streambuf类库以streambuf类为基类,streambuf类是一个抽象类,提供了缓冲区操作接口,如设置缓冲区、从缓冲区提取字节、向缓冲区插入字节等。streambuf类库的层次结构如下图所示。
由图可知,streambuf类派生了3个类,分别是stdiobuf类、filebuf类、stringstreambuf类。其中,stdiobuf类用于标准I/O缓冲区管理,filebuf类用于文件缓冲区管理,stringstreambuf类用于内存缓冲区管理。
二、标准I/O流
标准输入流和标准输出流的一系列操作方法都是由istream和ostream两个类提供的,这两个类继承自抽象基类ios,它们预定义了标准输入流对象和标准输出流对象,并且提供了多种输入/输出方法。本节就针对标准输入流和标准输出流进行详细的讲解。
2.1、预定义流对象
C++提供了四个预定义流对象,包括cin、cout、cerr和clog。cin是istream类的对象,用于处理标准输入(键盘输入)。cout、cerr和clog是ostream类的对象,其中,cout用于处理标准输出(屏幕输出),cerr和clog用于处理标准错误信息。
cerr与clog用法相同,默认设备都是显示器。clog有缓冲区,而cerr没有缓冲,意味着cerr输出的信息会直接发送给屏幕,不会等到缓冲区填满或遇到换行符才输出错误信息。
关于四个预定义流对象的信息如下表所示。
2.2、标准输出流
ostream类重载了“<<”运算符,输出流对象与“<<”运算符结合使用,可以输出各种类型的数据。此外,ostream类还提供了成员函数用于输出数据,比较常用的两个成员函数为put()函数和write()函数,下面分别进行讲解。
2.2.1、put()函数
put()函数用于输出单个字符。put()函数将字符插入输出流对象,通过输出流对象将字符输出到指定位置。其函数声明如下所示:
1 |
|
上述函数声明中,参数ch表示要输出的字符,函数返回值为ostream类对象引用。由于put()函数返回的是输出流对象,因此put()函数与输出运算符“<<”一样,可以连续调用。
下面调用put()函数输出单个字符,示例代码如下所示:
1 |
|
2.2.2、write()函数
2.3、标准输入流
istream类预定义了输入流对象cin,并且重载了“>>”运算符,输入流对象与“>>”运算符结合使用,可以输入各种类型的数据。此外,istream类还提供了成员函数用于输入数据,如get()函数、getline()函数、read()函数等,下面分别介绍这些函数。
2.3.1、get()函数
2.3.2、getline()函数
2.3.3、read()函数
三、文件流
文件流,是指以磁盘中的文件作为输入、输出对象的数据流。输出文件流,是将数据从内存输出到文件中,这个过程通常称为写文件;输入文件流,是将数据从磁盘中的文件读入内存,这个过程通常称为读文件。本节将针对文件流进行详细讲解。
3.1、文件流对象的创建
在C++中,要进行文件的读写操作,首先必须建立一个文件流对象,然后把文件流对象与文件关联起来(打开文件)。文件流对象与文件关联之后,程序就可以调用文件流类的各种方法对文件进行操作了。
C++提供了三个类支持文件流的输入、输出,这三个类都包含在fstream头文件中。
- ifstream:输入文件流类,用于实现文件的输入。
- ofstream:输出文件流类,用于实现文件的输出。
- fstream:输入/输出文件流类,可同时实现文件的输入和输出。
文件流不像标准I/O流那样,预定义了输入流对象和输出流对象,使用文件流时,需要创建文件流对象。创建文件流对象时,可以调用文件流类的无参构造函数,也可以调用文件流类的有参构造函数,具体如下所示。
(1)调用无参构造函数创建文件流对象。ifstream类、ofstream类和fstream类都提供了无参构造函数,可以创建不带参数的文件流对象,示例代码如下所示:
1 |
|
(2)调用有参构造函数创建文件流对象。ifstream类、ofstream类和fstream类也提供了有参构造函数,在创建文件流对象时可以指定文件名和文件打开模式,示例代码如下所示:
1 |
|
ifstream类默认文件打开模式为ios::in,ofstream类默认文件打开模式为ios::out,fstream类默认文件打开模式为ios::in|ios::out。
3.2、文件的打开与关闭
文件最基本的操作就是打开和关闭,在对文件进行读写之前,需要先打开文件;读写结束之后,要及时关闭文件。下面将针对文件的打开与关闭进行讲解。
3.2.1、打开文件
C++提供了两种打开文件的方式:第一种方式是调用文件流类的构造函数;第二种方式是调用文件流类的成员函数open()。其中,第一种文件打开方式(调用文件流类的构造函数)就是在创建文件流对象时传入文件名和文件打开模式,这种方式在上节已经讲解过了。下面主要讲解第二种文件打开方式(调用open()函数)。
fstream类、ostream类和fstream类都提供了成员函数open()用于打开文件,open()函数声明如下所示:
1 |
|
在上述函数声明中,参数filename表示要打开的文件;参数mode表示文件打开模式。文件打开模式,是指以什么方式打开文件,如只读模式、只写模式等。C++常用的文件打开模式及含义如下表所示。
此外,文件打开模式可以通过位或运算符“|”组合使用,示例代码如下所示:
1 |
|
如果文件打开失败,则文件流对象的值为0。
3.2.2、关闭文件
文件使用完毕之后,要及时关闭。关闭文件就是解除文件与文件流对象的关联,释放缓冲区和其他资源的过程。
ifstream类、ostream类和fstream类都提供了成员函数close()用于关闭文件,close()函数声明如下所示:
1 |
|
close()函数没有参数和返回值,用法也很简单,直接通过文件流对象调用close()函数就可以关闭文件。
3.2.3、案例
下面通过案例演示,文件的打开与关闭。首先在项目根目录下创建文本文件hello.txt,然后编写代码,调用open()函数打开hello.txt文件,再调用close()函数关闭hello.txt文件,代码如下所示。
1 |
|
上述代码,第8行代码创建ifstream类对象ifs。第10行代码通过对象ifs调用open()函数打开hello.txt文件。第11~14行代码判断文件打开是否成功,如果文件打开失败,就输出“文件打开失败”;如果文件打开成功,就输出“文件打开成功”。第16行代码通过对象ifs调用close()函数关闭文件。
3.3、文本文件的读写
对文本文件的读写,C++提供了两种方式,第一种方式是使用提取运算符“>>”和插入运算符“<<”,第二种方式是调用文件流类的成员函数。下面分别介绍这两种读写方式。
3.3.1、使用提取运算符“>>”和插入运算符“<<”读写文件
istream类重载了“>>”运算符,ifstream类继承了istream类,也继承了“>>”运算符;ostream类重载了“<<”运算符,ofstream继承了ostream类,也继承了“>>”运算符。istream类和ostream类共同派生了iostream类,而fstream类又继承自iostream类,因此fstream类同时继承了“>>”运算符和“<<”运算符。
由上述继承关系可知,文件流对象也可以使用“>>”运算符和“<<”运算符传输数据,实现文本文件的读写。
下面通过案例演示,使用“>>”运算符和“<<”运算符读写文本文件,示例如下。
1 |
|
上述代码,第8行代码创建了ofstream类对象ofs,并以只写模式打开项目根目录下的hello.txt文件。第15~18行代码定义字符数组str,并通过预定义流对象cin从键盘输入数据并存储到str数组中。第20行代码使用对象ofs和“<<”运算符将str数组中的数据写入文件中。第23行代码调用close()函数关闭文件。
第26行代码创建了ifstream类对象ifs,并以只读模式打开hello.txt文件。第33行代码定义了字符数组buf。第35行代码使用对象ifs和“>>”运算符将文件中的数据读取到buf数组中。第38~40行代码输出buf数组中的数据,并关闭文件。
3.3.2、调用文件流类的成员函数读写文件
文件流类继承了istream类和ostream类的成员函数get()、getline()和put(),通过调用这些成员函数,文件流对象也可以完成文件的读写。
下面就通过案例演示,调用文件流类的成员函数实现文件的读写,示例如下。
1 |
|
上述代码,第8行代码创建了ofstream类对象ofs,并以只写模式打开项目根目录下的hello.txt文件。第16~2-行代码通过for循环调用put()函数将26个小写字母写入文件。第23行代码调用close()函数关闭文件。
第25行代码创建了ifstream类对象ifs,并以只读模式打开hello.txt文件。第36~41行代码通过while循环调用get()函数读取文件中的数据。第43行代码调用close()函数关闭文件。
注:上述代码,调用get()函数从文件中读取数据,也可以调用get()函数的其他重载形式,或调用getline()函数读取文件中的数据。
3.4、二进制文件的读写
文件流类从istream类和ostream类分别继承了write()函数和read()函数,这两个函数可以用来读写二进制文件。下面通过案例演示,二进制文件的读写,示例如下。
1 |
|
上述代码,第5~13行代码定义了学生结构体Student,该结构体包含姓名、年龄、性别3个成员。第18~24行代码定义了Student结构体数组stus,大小为3,并通过键盘输入3个学生信息。第26~40行代码创建ofstream类对象ofs,以ios::out|ios::binary
模式打开项目根目录下的student.dat文件。在for循环中通过调用write()函数将stus数组中3个学生的信息写入文件,并关闭文件。
第41~58行代码创建ifstream类对象ifs,以ios::in|ios::binary
模式打开student.dat文件。在for循环中通过调用read()函数将文件中数据读取到学生结构体数组stus1中,并关闭文件。
需要注意的是,上述代码,每次读写一个结构体变量,由于read()函数和write()函数的第一个参数为char*
类型,因此在传递参数时,需要调用reinterpret_cast<>
运算符将Student结构体变量的地址转换成char*
类型。
3.5、文件随机读写
在C语言中实现文件的随机读写要依靠文件位置指针,在C++中文件的随机读写也是通过移动文件位置指针完成的。
C++文件流类提供了设置文件位置指针的函数。ifstream类提供了tellg()函数、seekg()函数,这两种函数声明分别如下所示:
1 |
|
在上述函数声明中,tellg()函数用于返回文件位置指针的位置。seekg()函数用于设置文件位置指针的位置,即移动文件位置指针,它有两种重载形式。第一种重载形式有一个参数streampos,表示文件位置指针从文件开头移动streampos长度的距离;第二种重载形式有两个参数,第一个参数streamoff表示文件位置指针的移动距离,第二个参数ios::seek_dir
表示参照位置,其取值有以下三个。
- ios::beg=0,表示从文件开头处开始,移动文件位置指针。
- ios::cur=1,表示从当前位置开始,移动文件位置指针。
- ios::end=2,表示从文件结尾处开始,移动文件位置指针。
tellg()函数与seekg()函数的用法示例代码如下:
1 |
|
ofstream类提供了tellp()函数、seekp()函数用于移动文件位置指针,这两种函数声明分别如下所示:
1 |
|
tellp()函数与tellg()函数的含义与用法相同,seekp()函数与seekg()函数的含义与用法相同。fstream类则拥有上述所有函数,即拥有tellg()函数、seekg()函数、tellp()函数和seekp()函数。
下面通过案例演示,文件的随机读写,示例如下。
1 |
|
上述代码,第8行代码创建了输入文件流对象ofs,并以ios::out|ios::binary
模式打开文件random.dat。第15行代码调用tellp()函数获取文件位置指针的位置。第19~23行代码定义字符数组buf,将从键盘输入的数据存储到buf中,并调用write()函数将buf中的数据写入文件中。第24行代码调用tellp()函数再次输出文件位置指针的位置。第27~28行代码调用seekp()函数,将文件位置指针从文件末尾向前移动10个字节,然后再次调用tellg()函数输出文件位置指针的位置。
上述代码,刚打开文件时,文件位置指针的位置为0,即文件位置指针在文件开头;向文件写入30个字符之后,文件位置指针的位置为30,表明文件位置指针指向文件末尾;调用seekp()函数移动文件位置指针之后,文件位置指针的位置为20(相对于文件开头),表明seekp()函数调用成功。
四、字符串流
字符串流是以string对象为输入/输出对象的数据流,这些数据流的传输在内存中完成,因此字符串流也称为内存流。C++提供了istringstream、ostringstream和stringstream这三个类支持string对象的输入/输出。这三个类都由istream类和ostream类派生而来,因此它们都可以使用“>>”运算符和“<<”运算符,以及istream类和ostream类的成员函数。下面简单介绍这三个类。
提示:在C++98标准之前,C++使用istrstream、ostrstream和strstream三个类完成string对象的输入/输出,但从C++98标准开始,这三个类被弃用了,取而代之的是istringstream类、ostringstream类和stringstream类。
4.1、istringstream类
istringstream是输入字符串流类,用于实现字符串对象的输入。istringstream类的构造函数有三个,具体如下所示:
1 |
|
第一个构造函数带有一个默认参数openmode,表示流的打开方式,默认值为ios_base::in
。第二个构造函数带有两个参数,第一个参数str为string对象的常引用;第二个参数openmode是流的打开模式,默认值为ios_base::in
。第三个构造函数为移动构造函数。
istringstream类的一个典型用法就是将一个数字字符串转换成对应的数值。相比于C语言的数字和字符串转换函数,istringstream类具有模板亲和性,转换效率更高而且更安全。
下面通过案例演示,如何将数字字符串转换成对应的数值,示例如下。
1 |
|
上述代码,第6~17行代码定义了模板函数swapString(),用于将字符串转换为对应的数值。第22~23行代码,调用swapString()函数将字符串“10”转换为int类型数据10,并输出。第25~26行代码,调用swapString()函数将字符串“3.14”转换为double类型数据,并输出。第28~29行代码,调用swapString()函数将字符串“abc”转换为float类型数据,并输出。
需要注意的是,istringstream类、ostringstream类和stringstream类都包含在sstream头文件中,使用这三个类时要包含sstream头文件。
4.2、ostringstream类
ostringstream是输出字符串流类,用于实现字符串对象的输出。ostringstream类也提供了三个构造函数,分别如下所示:
1 |
|
ostringstream类构造函数的参数含义与istringstream类构造函数的参数含义相同。除了构造函数,ostringstream类还提供了一个比较常用的成员函数str()。str()函数用于获取ostringstream流缓冲区中的内容副本,函数声明如下所示:
1 |
|
str()函数获取ostringstream流缓冲区的数据后,并不对数据进行修改,获取的数据可以存储到一个string对象中。
下面通过案例演示,ostringstream类和str()函数用法,示例如下。
1 |
|
上述代码,第8行代码创建了ostringstream类对象ostr。第10行代码创建了string类对象str,第14行代码从键盘输入数据存储到str中。第16行代码使用插入运算符“<<”将str中的数据插入类对象ostr中。第18~19行代码,通过对象ostr调用str()函数获取对象ostr的缓冲区内容并输出。
4.3、stringstream类
stringstream类是输入/输出字符串流类,可同时实现字符串对象的输入/输出。stringstream类也提供了三种形式的构造函数,分别如下所示:
1 |
|
stringstream类构造函数的参数含义与istringstream类构造函数的参数含义相同。stringstream类包括了istringstream类与ostringstream类的功能。
五、参考
[《C++程序设计教程》](《C++程序设计教程(第2版)》电子书在线阅读-黑马程序员 编著-得到APP (dedao.cn))