C++(十二)文件操作.md
文件操作:打开–>读写–>关闭
文件流类
C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类,这 3 个类分别为:
- ifstream:专用于从文件中读取数据;
- ofstream:专用于向文件中写入数据;
- fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。
值得一提的是,这 3 个文件流类都位于 头文件中,因此在使用它们之前,程序中应先引入此头文件。
成员方法名 | 适用类对象 | 功 能 |
---|---|---|
open() | fstream | |
ifstream | ||
ofstream | 打开指定文件,使其与文件流对象相关联。 | |
is_open() | 检查指定文件是否已打开。 | |
close() | 关闭文件,切断和文件流对象的关联。 | |
swap() | 交换2个文件流对象。 | |
operator>> | fstream | |
ifstream | 重载>>运算符,用于从指定文件中读取数据。 | |
gcount() | 返回上次从文件流提取出的字符个数。该函数常和get()、getline()、ignore()、peek()、read()、readsome()、putback()和unget()联用。 | |
get() | 从文件流中读取一个字符,同时该字符会从输入流中消失。 | |
getline(str,n,ch) | 从文件流中接收n-1个字符给str变量,当遇到指定ch字符时会停止读取,默认情况下ch为’\0’。 | |
ignore(n,ch) | 从文件流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出n个字符,或者当前读取的字符为ch。 | |
peek() | 返回文件流中的第一个字符,但并不是提取该字符。 | |
putback(c) | 将字符c置入文件流(缓冲区)。 | |
operator<< | fstream | |
ofstream | 重载<<运算符,用于向文件中写入指定数据。 | |
put() | 向指定文件流中写入单个字符。 | |
write() | 向指定文件中写入字符串。 | |
tellp() | 用于获取当前文件输出流指针的位置。 | |
seekp() | 设置输出文件输出流指针的位置。 | |
flush() | 刷新文件输出流缓冲区。 | |
good() | fstream | |
ofstream | ||
ifstream | 操作成功,没有发生任何错误。 | |
eof() | 到达输入末尾或文件尾。 |
仅列举的了部分常用的成员方法,更详细的介绍,读者可查看 C++标准库手册。
文件的打开
打开文件可以通过以下两种方式进行:
- 调用流对象的 open 成员函数打开文件。
- 定义文件流对象时,通过构造函数打开文件。
使用 open 函数打开文件
以 ifstream 类为例,该类有一个 open 成员函数,其他两个文件流类也有同样的 open 成员函数:
1 | void open(const char* szFileName, int mode) |
第一个参数是指向文件名的指针,第二个参数是文件的打开模式标记。
文件的打开模式标记代表了文件的使用方式,这些标记可以单独使用,也可以组合使用。表 1 列出了各种模式标记单独使用时的作用,以及常见的两种模式标记组合的作用。
模式标记 | 适用对象 | 作用 |
---|---|---|
ios::in | ifstream | |
fstream | 打开文件用于读取数据。如果文件不存在,则打开出错。 | |
ios::out | ofstream | |
fstream | 打开文件用于写入数据。如果文件不存在,则新建该文件;如果文件原来就存在,则打开时清除原来的内容。 | |
ios::app | ofstream | |
fstream | 打开文件,用于在其尾部添加数据。如果文件不存在,则新建该文件。 | |
ios::ate | ifstream | 打开一个已有的文件,并将文件读指针指向文件末尾(读写指的概念后面解释)。如果文件不存在,则打开出错。 |
ios::trunc | ofstream | 打开文件时会清空内部存储的所有数据,单独使用时与ios::out相同。 |
ios::binary | ifstream | |
ofstream | ||
fstream | 以二进制方式打开文件。若不指定此模式,则以文本模式打开。 | |
ios::in|ios::out | fstream | 打开已存在的文件,既可读取其内容,也可向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。 |
ios::in|ios::out | ofstream | 打开已存在的文件,可以向其写入数据。文件刚打开时,原有内容保持不变。如果文件不存在,则打开出错。 |
ios::in|ios::out|ios::trunc | fstream | 打开文件,既可读取其内容,也可向其写入数据。如果文件本来就存在,则打开时清除原来的内容;如果文件不存在,则新建该文件。 |
ios::binary 可以和其他模式标记组合使用,例如:
- ios::in | ios::binary表示用二进制模式,以读取的方式打开文件。
- ios::out | ios::binary表示用二进制模式,以写入的方式打开文件。
在流对象上执行 open 成员函数,给出文件名和打开模式,就可以打开文件。判断文件打开是否成功,可以看“对象名”这个表达式的值是否为 true,如果为 true,则表示文件打开成功。
下面的程序演示了如何打开文件:
1 |
|
调用 open 成员函数时,给出的文件名可以是全路径的,如第 7 行的c:\\tmp\\test.txt
, 指明文件在 c 盘的 tmp 文件夹中;也可以只给出文件名,如第 13 行的test1.txt
,这种情况下程序会在当前文件夹(也就是可执行程序所在的文件夹)中寻找要打开的文件。
第 18 行的tmp\\test2.txt
给出的是相对路径,说明 test2.txt 位于当前文件夹的 tmp 子文件夹中。第 24 行的..\\test3.txt
也是相对路径,代表上一层文件夹,此时要到当前文件夹的上一层文件夹中查找 test3.txt。此外,..\\..\\test4.txt
、..\\tmp\\test4.txt
等都是合法的带相对路径的文件名。
使用流类的构造函数打开文件
定义流对象时,在构造函数中给出文件名和打开模式也可以打开文件。以 ifstream 类为例,它有如下构造函数:
1 | ifstream::ifstream (const char* szFileName, int mode = ios::in, int); |
第一个参数是指向文件名的指针;第二个参数是打开文件的模式标记,默认值为ios::in; 第三个参数是整型的,也有默认值,一般极少使用。
在定义流对象时打开文件的示例程序如下(用流类的构造函数打开文件):
1 | #include <iostream> |
注意,当不再对打开的文件进行任何操作时,应及时调用 close() 成员方法关闭文件。有关该方法的用法,后续会做详细讲解。
文件的关闭
close() 方法的用法很简单,其语法格式如下:
1 | void close( ) |
该方法既不需要传递任何参数,也没有返回值。
当文件流对象的生命周期结束时,会自行调用析构函数,此函数内部会先调用 close() 方法切断文件流对象与任何文件的关联,最后才销毁它。
那么,既然文件流对象自行销毁时会隐式调用 close() 方法,是不是就不用显式调用 close() 方法了呢?
当然不是。在实际进行文件操作的过程中,对于打开的文件,要及时调用 close() 方法将其关闭,否则很可能会导致读写文件失败。
flush()刷新缓冲区
1 | void flush(); |
C++ 中使用 open() 打开的文件,在读写操作执行完毕后,应及时调用 close() 方法关闭文件,或者对文件执行写操作后及时调用 flush() 方法刷新输出流缓冲区。
文件读写
C++ 标准库中,提供了 2 套读写文件的方法组合,分别是:
- 使用 >> 和 << 读写文件:适用于以文本形式读写文件;
- 使用 read() 和 write() 成员方法读写文件:适用于以二进制形式读写文件。
>>和<<读写文本文件
stream 或者 ifstream 类负责实现对文件的读取,它们内部都对 >> 输出流运算符做了重载;同样,fstream 和 ofstream 类负责实现对文件的写入,它们的内部也都对 << 输出流运算符做了重载。
当 fstream 或者 ifstream 类对象打开文件(通常以 ios::in 作为打开模式)之后,就可以直接借助 >> 输入流运算符,读取文件中存储的字符(或字符串);当 fstream 或者 ofstream 类对象打开文件(通常以 ios::out 作为打开模式)后,可以直接借助 << 输出流运算符向文件中写入字符(或字符串)。
1 |
|
read()和write()读写二进制文件
ofstream 和 fstream 的 write() 成员方法实际上继承自 ostream 类,其功能是将内存中 buffer 指向的 count 个字节的内容写入文件,基本格式如下:
1 | ostream & write(char* buffer, int count); |
其中,buffer 用于指定要写入文件的二进制数据的起始位置;count 用于指定写入字节的个数。
ifstream 和 fstream 的 read() 方法实际上继承自 istream 类,其功能正好和 write() 方法相反,即从文件中读取 count 个字节的数据。该方法的语法格式如下
1 | istream & read(char* buffer, int count); |
其中,buffer 用于指定读取字节的起始位置,count 指定读取字节的个数。同样,该方法也会返回一个调用该方法的对象的引用。
get()和put()字符读写文件
在某些特殊的场景中,我们可能需要逐个读取文件中存储的字符,或者逐个将字符存储到文件中。这种情况下,就可以调用 get() 和 put() 成员方法实现。
当 fstream 和 ofstream 文件流对象调用 put() 方法时,该方法的功能就变成了向指定文件中写入单个字符。put() 方法的语法格式如下:
1 | ostream& put (char c); |
其中,c 用于指定要写入文件的字符。该方法会返回一个调用该方法的对象的引用形式。例如,obj.put() 方法会返回 obj 这个对象的引用。
当 fstream 和 ifstream 文件流对象调用 get() 方法时,其功能就变成了从指定文件中读取单个字符(还可以读取指定长度的字符串)。值得一提的是,get() 方法的语法格式有很多(请猛击这里了解详情),这里仅介绍最常用的 2 种:
1 | int get(); |
其中,第一种语法格式的返回值就是读取到的字符,只不过返回的是它的 ASCII 码,如果碰到输入的末尾,则返回值为 EOF。第二种语法格式需要传递一个字符变量,get() 方法会自行将读取到的字符赋值给这个变量。
getline():从文件中读取一行字符串
当文件流对象调用 getline() 方法时,该方法的功能就变成了从指定文件中读取一行字符串。该方法有以下 2 种语法格式:
1 | istream & getline(char* buf, int bufSize); |
其中,第一种语法格式用于从文件输入流缓冲区中读取 bufSize-1 个字符到 buf,或遇到 \n 为止(哪个条件先满足就按哪个执行),该方法会自动在 buf 中读入数据的结尾添加 ‘\0’。
移动和获取文件读写指针(seekp、seekg、tellg、tellp)
在读写文件时,有时希望直接跳到文件中的某处开始读写,这就需要先将文件的读写指针指向该处,然后再进行读写。
- ifstream 类和 fstream 类有 seekg 成员函数,可以设置文件读指针的位置;
- ofstream 类和 fstream 类有 seekp 成员函数,可以设置文件写指针的位置。
所谓“位置”,就是指距离文件开头有多少个字节。文件开头的位置是 0。
这两个函数的原型如下:
1 | ostream & seekp (int offset, int mode); |
mode 代表文件读写指针的设置模式,有以下三种选项:
- ios::beg:让文件读指针(或写指针)指向从文件开始向后的 offset 字节处。offset 等于 0 即代表文件开头。在此情况下,offset 只能是非负数。
- ios::cur:在此情况下,offset 为负数则表示将读指针(或写指针)从当前位置朝文件开头方向移动 offset 字节,为正数则表示将读指针(或写指针)从当前位置朝文件尾部移动 offset字节,为 0 则不移动。
- ios::end:让文件读指针(或写指针)指向从文件结尾往前的 |offset|(offset 的绝对值)字节处。在此情况下,offset 只能是 0 或者负数。
此外,我们还可以得到当前读写指针的具体位置:
- ifstream 类和 fstream 类还有 tellg 成员函数,能够返回文件读指针的位置;
- ofstream 类和 fstream 类还有 tellp 成员函数,能够返回文件写指针的位置。
这两个成员函数的原型如下:
1 | int tellg(); |
要获取文件长度,可以用 seekg 函数将文件读指针定位到文件尾部,再用 tellg 函数获取文件读指针的位置,此位置即为文件长度。