文件基本概念和文件流类
文件的概念
- 从不同的角度来看待文件就可以得到不同的文件分类。C++根据文件数据的编码方式不同分为文本文件和二进制文件。根据存取方式不同分为顺序存取文件和随机存取文件。
- 所谓
文本文件
和二进制文件
是从文件格式的角度进行分类,是约定俗成的、从计算机用户角度出发进行的分类。 - 所谓的
顺序存取文件
和随机存取文件
是根据访问文件中数据的方式来划分的。顺序存取文件就是按照文件中数据存储次序进行顺序操作,为访问第i
个数据,就首先要访问第i-1
个数据,在整个文件操作过程中,将移动位置指针的工作交给系统自动完成。磁带文件就是一个典型的顺序存取文件。随机访问文件是根据应用的需要,通过命令移动位置指针直接定位到文件内需要的位置并进行数据操作。 - 对文件的基本操作分为读文件和写文件。所谓
读文件
就是将文件中的数据读入内存之中,也称为输入
。所谓写文件
就是将内容中的数据存入文件之中,也称为输出
。
C++文件流类
C++标准类库中有3个流类可以用于文件操作,这3个类同城为文件流类,分别如下:
-
ifstream
:用于从文件中读取数据 -
ofstream
:用于向文件中写入数据 -
fstream
:即可用于从文件中读取数据,又可用于向文件中写入数据
使用这2个流类时,程序中需要包含fstream
头文件。
类ifstream
和类fstream
都是从类istream
派生而来的,因此类ifstream
拥有类istream
的全部成员函数。同样,类ofstream
和类fstream
也拥有类ostream
的全部成员函数。这3个类中有一些十分熟悉的成员函数可以使用,如operator<<
、operator>>
、peek()
、ignore()
、getline()
、get()
等。
在程序中,要使用一个文件,必须包含3个基本步骤:打开(open)文件----操作文件----关闭(close)文件。操作文件就是对文件进行读/写。
C++文件流类有相应的成员函数来实现打开、读、写、关闭等文件操作。
打开和关闭文件
打开文件
打开文件的方式有以下两种:
-
先建立流对象,然后调用
open()
函数连接外部文件。格式如下:流类名 对象名; 对象名.open(文件名, 模式);
-
调用流类带参数的构造函数,在建立流对象的同时连接外部文件。格式如下:
流类名 对象名(文件名, 模式);
其中的“流类”是C++流类库定义的文件流类
ifstream
、ofstream
或fstream
。若要以读方式打开文件则应使用类ifstream
,若以写方式打开文件则应使用类ofstream
,若以读/写方式打开文件则应使用类fstream
。
模式标记 | 适用对象 | 作用 |
---|---|---|
ios::in | ifstream fstream |
以读方式打开文件。 如果文件不存在,则打开出错 |
ios::out | ofstream ftream |
以写方式打开文件。 如果文件不存在,则新建该文件; 如果文件已经存在,则打开清除原来的内容 |
ios::app | ofstream | 以追加方式打开文件,用于在文件尾部添加数据。 如果文件不存在,则新建该文件 |
ios::ate | ofstream | 打开一个已有的文件,并将文件读指针指向文件末尾。 如果文件不存在,则打开出错 |
ios::trunc | ofstream | 删除文件现有内容。单独使用时与ios::out 相同 |
ios::binary | ifstream ofstream fstream |
以二进制方式打开文件。 若不指定此模式,则以默认的文本模式打开文件 |
ios::in | ios::out | fstream | 打开已存在的文件,既可读取其内容,也可向其写入数据。 文件刚打开时,原有内容保持不变。 如果文件不存在,则打开出错 |
ios::in | ios::out | ofstream | 打开已存在的文件,可向其写入数据。 文件刚打开时,原有内容保持不变。 如果文件不存在,则打开出错 |
例如,要从当前文件夹中名为data.txt
的文件中读取数据,可以使用如下语句打开文件。
ifstream inFile;//建立输入文件流对象
inFile.open("data.txt", ios::in);//连接文件,指定打开模式
也可以使用第二种方式打开,语句如下:
ifstream inFile("data.txt", ios::in);
调用ifstream
类带参数的构造函数,在建立流对象的同时,用参数形式连接外部文件并在指定打开模式。
要以读方式打开文本文件,还可以使用如下语句:
ifstream inFile;//建立输入文件流对象
inFile.open("data.txt");//没有指定打开模式,默认以in方式打开文本文件
再比如,要在c盘的c2020文件夹中打开(创建)一个名为newfile
的二进制文件,用于保存程序产生的数据,可以使用如下语句打开文件:
ofstream outFile;//建立输入文件流对象
outFile.open("c:\\c2020\\newfile", ios::out | ios::binary);//连接文件,指定打开方式
也可以使用如下语句打开文件:
ofstream outFile("c:\\c2020\\newfile", ios::out | ios::binary);
关闭文件
使用fstream
中的成员函数close()
关闭文件。
#include
#include
using namespace std;
int main() {
//声明对象inFile并调用构造函数
ifstream inFile("c:\\temp\\test.txt", ios::in);
if (inFile) {
cout << "成功打开文件:c:\\temp\\test.txt" << endl;
inFile.close();
} else {
cout << "打开文件失败:c:\\temp\\test.txt" << endl;
}
//声明对象outFile并调用构造函数
ofstream outFile("test1.txt", ios::out);
if (!outFile) {
cout << "error" << endl;
} else {
cout << "成功打开文件:test1.txt" << endl;
outFile.close();
}
//声明对象outFile2并调用构造函数
fstream outFile2("temp\\test2.txt", ios::in | ios::out);
if (outFile2) {
cout << "成功打开文件:temp\\test2.txt" << endl;
outFile2.close();
} else {
cout << "error2" << endl;
}
return 0;
}
文件读写操作
读写文本文件
示例程序:从键盘输入学生的学号(假设不超过10个字节)、姓名(假设不超过20个字节)和成绩(整型),将它们存入文件score.txt
中。可以使用文本文件保存数据,文件中每一行保存一名学生的成绩信息,学生成绩信息的数据项之间通过空格符分隔。
#include
#include
using namespace std;
int main() {
char id[11], name[21];
int score;
ofstream outFile;
//以写的方式打开文本文件
outFile.open("score.txt", ios::out);
if (!outFile) {
cout << "create file failed" << endl;
return 0;
}
cout << "请输入:学号 姓名 成绩(以`ctrl+z`结束输入)" << endl;
while (cin >> id >> name >> score) {
//向流中插入数据
outFile << id << " " << name << " " << score << endl;
outFile.close();
}
return 0;
}
#include
#include
#include
using namespace std;
int main() {
char id[11], name[21];
int score;
ifstream inFile;
//以读方式打开文本文件
inFile.open("score.txt", ios::in);
if (!inFile) {
cout << "open file failed" << endl;
return 0;
}
cout << "学号 姓名 成绩" << endl;
while (inFile >> id >> name >> score) {//读入文件
//向流中插入数据
cout << left << setw(10) << id << " " << setw(20) << name << " " << setw(3) << score << endl;
inFile.close();
}
return 0;
}
#include
#include
#include
using namespace std;
int main() {
char ch, filename[20];
int count = 0;//行号计数器
bool newline = true;//开始一个新的标志
cout >> filename;
//以读的方式打开文本文件
ifstream inFile(filename, ios::in);
if (!inFile) {
cout << "open file failed" << endl;
return 0;
}
//从流inFile中读入一个字符并判断
while ((ch = inFile.get()) != EOF) {
if (newline) {//若是新行开始,则显示行号
cout << setw(4) << ++count << ":";
newline = false;//清除新行标志
}
if (ch == "\n") {//若读入字符为“\n”,则表示将开始一个新行
newfile = true;//设置新行标志
cout << ch;
}
}
inFile.close();
return 0;
}
读写二进制文件
对二进制文件进行读写不能使用前面提到的类似于cin
、cout
从流中读写数据的方法。C++用binary
方式打开二进制文件,调用ifstream
或fstream
的read()
成员函数从文件中读取数据,调用ofstream
或fstream
的wirite()
成员函数向文件中写入数据。
1.用ostream::write()
成员函数写文件
ofstream
或fstream
的wirite()
成员函数继承自ostream
类,原型如下:
ostream & write(char *buffer, int nCount);
该成员函数将内存中buffer
所指向的nCount
个字节的内容写入文件,返回值是对函数所作用的对象的引用,如obj.write(...)
的返回值就是对obj
的引用。该函数是非格式化操作,将buffer
所指的数据按字节序列直接存入文件中。
在使用write()
与read()
进行数据读写时,不必在数据之间再额外“插入”分隔符,这是因为它们都要求提供第2个参数来指定读写长度。
#include
#include
using namespace std;
class CStudent {
public:
char id[11];
char name[21];
int score;
}
int main() {
CStudent stu;
//以二进制写方式打开文本文件
ofsteam outFile("student.dat", ios::out | ios::binary);
if (!outFile) {
cout << "创建文件失败" << endl;
return 0;
}
cout << "请输入:学号 姓名 成绩(以`ctrl + z`结束输入)" << endl;
while (cin >> stu.id >> stu.name >> stu.score) {
outFile.write((char *)&stu, sizeof(stu));//向文件中写入数据
outFile.close();
}
return 0;
}
2.用istream::read()
成员函数读文件
ifstream
或fstream
的read()
成员函数实际上继承自类istream
,原型如下:
istream &read(char *buffer, int nCount);
该成员函数从文件中读取nCount
个字节的内容,存放到buffer
所指向的内存缓冲区中,返回值是对函数所作用的对象的引用。该函数时非格式化操作,对读取的字节序列不进行处理,直接存入buffer
中,由程序的类型定义解释。
3.用ostream::gcount()
成员函数得到读取字节数
如果要知道每次读操作成功读取了多少个字节,可以在read()
函数执行后立即调用文件流对象的成员函数gcount()
,其返回值就是最近一次read()
函数执行时成功读取的字节数。gcount()
成员函数原型如下:
int gcount();
用成员函数put()
和get()
读写文件
函数get()
有3种主要形式:
int get();
不带参数的get()
函数从指定的输入流中提取一个字符(包含空白字符),函数的返回值即为该字符。当遇到文件结束符时,返回系统常量EOF
。istream &get(char &rch);
从指定输入流这种提取一个字符(包含空白字符),将该中字符只作为rch
引用的对象。当遇到文件结束符时,函数返回0;否则返回对istream
对象的引用。istream &get(char *pch, int nCount, char delim = '\n');
从流的当前字符开始,读取nCount - 1
个字符,或遇到指定的分隔符delim
结束。函数把读取的字符(不包括分隔符)写入数组pch
中,并在字符串后添加结束符\0
。
函数put()
的语法格式如下:
ostream &put(char ch);
函数的功能是向输出流中插入一个字节。
成员函数get()
和put()
常用于读写字符或文本文件,但他们不仅仅可用于对字符的处理,而且对于二进制值文件同样可以进行有效的处理。
文本文件与二进制文件的异同
- 在输入/输出过程中,系统要对内外存的数据格式进行相应转换,文本文件是以文本形式存储数据,其优点是具有较高的兼容性。缺点是存储一批纯数值信息时,要在数据之间人为的添加分隔符。另一个缺点是不便于对数据进行随机访问。
- 二进制文件是以二进制形式存储数据,其优点是便于对数据实行随机访问(相同数据类型的数据所占空间的大小均是相同的,不必在数据之间人为地添加分隔符)。在输入/输出过程中,系统不需要对数据进行任何转换。缺点是数据兼容性差。
- 通常将纯文本信息(如字符串)以文本文件形式存储,而将数值信息以二进制文件形式存储。
随机访问文件
- 如果一个文件只能进行顺序存取操作,则称为顺序文件。典型的顺序文件(设备)是键盘、显示器和保存在磁带上的文件。如果一个文件可以在文件的任意位置进行存取操作,则成为随机文件。磁盘文件就是典型的随机文件。
- 在访问文件的过程中,若严格按照数据保存的次序从头到尾访问文件,则称为顺序访问。
- 在访问文件的过程中,若不必按照数据的存储次序访问文件,而是要根据需要在文件的不同位置进行访问,则称为随机访问。
- 显然,对于顺序文件只能进行顺序访问;对于随机文件既可以进行顺序访问,也可以进行随机访问。
类istream
中与位置指针相关的函数如下:
-
移动读指针函数
istream &seekg(long pos);
该函数的功能是将渎职者指针设置为
pos
,即将读指针移动到文件的pos
字节处。istream &seekg(long offset, ios::seek_dir dir);
该函数的功能是将读指针按照
seek_dir
的指示(方向)移动offset
个字节,其中seek_dir
是在类ios
中定义的一个枚举类型。enum seek_dir { beg = 0, cur, end };
seek_dir
的常量值含义如下:-
ios::beg
:表示流的开始位置。此时,offset应为非负正整数。 -
ios::cur
:表示流的当前位置。此时,offset为正数则表示向后(文件尾)移动,为负数则表示向前(文件头)移动。 -
ios::end
:表示流的结束位置。此时,offset应为非正整数。
-
-
返回写指针当前位置的函数
long tellg();
函数返回值为流这种读指针的当前位置。
类ostream
中与位置指针相关的函数如下:
-
移动写指针函数
ostream &seekp(long pos);
改函数的功能是将写指针设置为
pos
,即将写指针移动到文件的pos
字节处。ostream &seekp(long offset, ios::seek_dir dir);
该函数的功能是将写指针按
seek_dir
指示的方向移动offset
个字节。 -
返回写指针当前位置的函数
long tellp();
函数的返回值为流中写指针的当前位置。
注意:在类
fstream
中既提供了操作读指针函数seekg()
和tellg()
,又提供了操作写指针的函数seekp()
和tellp()
,实际上在文件中这两个指针是同一个指针。