9.5 磁盘文件的输入和输出
在前面的章节中,我们已经讨论了流的读写操作,主要是针对文本流的操作。本节将介绍文件流的操作。
文件流通常是指磁盘文件流。对磁盘文件流的操作通常是这样进行的:首先打开待操作的磁盘文件,打开后对文件进行读操作或写操作,文件读写操作所使用的读操作函数和写操作函数与前面讲的标准文件的读写函数相同。操作结束后,要关闭该文件。磁盘文件一般分为文本文件、二进制文件和随机文件。在进行随机文件操作时,还要进行文件中的读指针和写指针的定位操作。以上是磁盘文件的主要操作,这些就是本节讲述的主要内容。
9.5.1 磁盘文件的打开和关闭操作
磁盘文件的打开和关闭一般使用 fstream
类中所定义的成员函数 open()
和 close()
。
1. 打开文件
在打开文件前,先声明一个 fstream
类的对象,再使用成员函数 open()
打开指定的文件。文件被打开后,才可以对文件进行读写操作。例如,以输出方式打开一个文件的方法如下:
fstream outfile;
outfile.open("f1.txt", ios::out);
其中,outfile
是 fstream
类的一个对象。打开函数 open()
有两个参数,第一个参数是被打开的文件名,使用文件名时包含路径名和扩展名。第二个参数是文件的访问方式常量。文件访问方式包括读、写、读/写以及二进制数据模式等。下表(表9-3)给出了不同的文件访问方式常量。
表9-3 文件访问方式常量
方式 | 名称 | 用途 |
---|---|---|
in | 输入 | 以输入(读)方式打开文件 |
out | 输出 | 以输出(写)方式打开文件 |
app | 追加 | 以输出追加方式打开文件 |
ate | 尾 | 文件打开时,文件指针位于文件尾 |
trunc | 截断 | 如果文件存在,将其长度截断为0并清除原有内容 |
binary | 二进制 | 以二进制方式打开文件,默认时为文本方式 |
nocreate | 无创建 | 打开一个已有文件,如该文件不存在则打开失败 |
noreplace | 无替换 | 如果文件存在,除非设置 ios::ate 或 ios::app ,否则打开操作失败 |
`ios::in | ios::out` | 读/写方式打开文件 |
`ios::out | ios::binary` | 以二进制写方式打开文件 |
`ios::in | ios::binary` | 以二进制读方式打开文件 |
除了 ios::app
方式外,使用其他方式刚打开文件时,文件的读写位置指针位于文件头。而用 ios::app
方式打开文件时,文件读写位置指针位于文件尾。
在以 ios::out
方式打开文件,而未指定 ios::in
、ios::ate
、ios::app
方式时,则隐含为 ios::trunc
方式。
表中的几种方式可以通过“位或”操作结合起来,表示具有几种方式的操作。例如:
ios::in | ios::out | ios::binary
在未指定 binary
方式时,文件都以文本方式打开。若指定了 binary
方式,则文件以二进制方式打开,表示二进制的读写方式操作。
打开文件的另一种方法是把文件名、访问方式作为文件标识符说明的一部分,例如:
fstream outfile("f1.txt", ios::out);
ofstream ostream("f1.txt");
另外,还可以用下述方法表示打开某个写文件,例如:
ofstream ostrm;
ostrm.open("f1.txt");
可以用下述方法表示打开某个读文件,例如:
ifstream istrm("f2.txt");
或者
ifstream istrm;
istrm.open("f2.txt");
2. 关闭文件
当结束一个文件的操作后,要及时将该文件关闭。关闭文件时,调用成员函数 close()
。例如,关闭文件标识符为 outfile
的文件,使用下面格式:
outfile.close();
于是文件流 outfile
被关闭,由它所标识的文件被送入磁盘中。
例 9.18 分析下列程序输出结果
#include <fstream.h>
void main() {
ofstream ostrm;
ostrm.open("f1.dat");
ostrm << 120 << endl;
ostrm << 310.85 << endl;
ostrm.close();
ifstream istrm("f1.dat");
int n;
double d;
istrm >> n >> d;
cout << n << "," << d << endl;
istrm.close();
}
执行该程序输出如下结果:
120,310.85
通过以上程序可以看出,使用 fstream
类和相应的成员函数 open()
和 close()
可以方便地对磁盘文件进行读写操作,并且操作方式与对标准输入输出流的操作相似。这使得文件的处理变得更加灵活和方便。
9.5.2 文本文件的读写操作
对文本文件进行读写操作时,首先要打开文件,然后再对打开文件时设定的文件流进行操作。
将文本写入到指定的文件
以下是一个将文本写入到指定文件的示例:
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
void main() {
fstream outfile;
outfile.open("f2.dat", ios::out);
if (!outfile) {
cout << "f2.dat can't open.\n";
abort();
}
outfile << "this is a program.\n";
outfile.close();
}
执行该程序,将字符串 "this is a program.\n" 写入文件 f2.dat
中。在程序中打开文件 f2.dat
时,对建立的文件流 outfile
进行检查,看文件是否被成功打开。判断 outfile
是否为0,当文件没有打开时,其值为0;当文件打开时,其值非0。如果文件没有打开,则输出错误信息,并使用 abort()
函数退出程序。abort()
函数被包含在 stdlib.h
头文件中。
从文本文件中读出文本信息
下面是一个从文本文件中读取信息并显示在屏幕上的示例:
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
void main() {
fstream infile;
infile.open("f2.dat", ios::in);
if (!infile) {
cout << "f2.dat can't open.\n";
abort();
}
char s[80];
while (!infile.eof()) {
infile.getline(s, sizeof(s));
cout << s << endl;
}
infile.close();
}
执行该程序,将从 f2.dat
文件中读出如下信息并显示在屏幕上:
this is a program.
this is a program.
在该程序中,使用了成员函数 eof()
来判断文件是否结束。eof()
函数的功能是:当文件结束时返回非0值,文件没有结束时返回0值。程序中 getline()
函数的用法前面已经讲过,区别仅在于这里是对 infile
流进行读取,而前面讲的是对 cin
流进行读取。
使用 get() 和 put() 函数读写文本文件
对于单字符的输入和输出(即读和写)可以使用成员函数 get()
和 put()
。下面是一个使用 get()
和 put()
进行文件读写操作的示例:
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
#include <string.h>
void main() {
fstream outfile, infile;
outfile.open("f3.dat", ios::out);
if (!outfile) {
cout << "f3.dat can't open.\n";
abort();
}
char str[] = "this is a C++ program.";
for (int i = 0; i <= strlen(str); i++)
outfile.put(str[i]);
outfile.close();
infile.open("f3.dat", ios::in);
if (!infile) {
cout << "f3.dat can't open.\n";
abort();
}
char ch;
while (infile.get(ch))
cout << ch;
cout << endl;
infile.close();
}
执行该程序输出如下结果:
this is a C++ program.
程序将字符串逐个字符读出,并显示到屏幕上。
将一个文件的内容复制到另一个文件
以下是一个将一个文件的内容复制到另一个文件的示例:
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
void main() {
fstream infile, outfile;
infile.open("f2.dat", ios::in);
if (!infile) {
cout << "f2.dat can't open.\n";
abort();
}
outfile.open("f4.dat", ios::out);
if (!outfile) {
cout << "f4.dat can't open.\n";
abort();
}
char ch;
while (infile.get(ch))
outfile.put(ch);
infile.close();
outfile.close();
}
执行该程序,将 f2.dat
文件中的内容复制到 f4.dat
文件中,实现文件复制的操作。
9.5.3 二进制文件的读写操作
在打开二进制文件时,需要在 open
函数中加上 ios::binary
选项。该函数格式前面也介绍过。下面通过一个例子讲解如何使用这两个函数对二进制文件进行读写操作。
示例 9.23 对一个二进制文件进行读写操作
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
struct person {
char name[20];
double height;
unsigned int age;
};
struct person people[4] = {
{"Wang", 1.65, 25},
{"Zhang", 1.72, 24},
{"Li", 1.89, 21},
{"Hung", 1.70, 22}
};
void main() {
fstream infile, outfile;
outfile.open("f5.dat", ios::out | ios::binary);
if (!outfile) {
cout << "f5.dat can't open.\n";
abort();
}
for (int i = 0; i < 4; i++)
outfile.write((char *)&people[i], sizeof(people[i]));
outfile.close();
infile.open("f5.dat", ios::in | ios::binary);
if (!infile) {
cout << "f5.dat can't open.\n";
abort();
}
for (int i = 0; i < 4; i++) {
infile.read((char *)&people[i], sizeof(people[i]));
cout << people[i].name << "\t" << people[i].height << "\t" << people[i].age << endl;
}
infile.close();
}
执行该程序输出如下结果:
Wang 1.65 25
Zhang 1.72 24
Li 1.89 21
Hung 1.70 22
说明:该程序中先打开一个文件,打开方式如下所示:
ios::out | ios::binary
该方式表示打开的文件是可写的二进制文件。然后将 people
数组中的数据写入文件中,接着重新打开文件,以读方式读取数据并显示在屏幕上。
例 9.25 分析下列程序的输出结果
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
struct student {
char name[20];
long number;
double totalScore;
};
student stu[5] = {
{"Ma", 97001, 85.72},
{"Li", 97002, 92.62},
{"Hu", 97003, 89.25},
{"Yan", 97004, 90.84},
{"Lu", 97005, 80.92}
};
void main() {
fstream file;
student one;
file.open("f7.dat", ios::out | ios::in | ios::binary);
if (!file) {
cout << "f7.dat can't open.\n";
abort();
}
for (int i = 0; i < 5; i++)
file.write((char *)&stu[i], sizeof(student));
file.seekp(sizeof(student) * 4);
file.read((char *)&one, sizeof(student));
cout << one.name << "\t" << one.number << "\t" << one.totalScore << endl;
file.seekp(sizeof(student) * 1);
file.read((char *)&one, sizeof(student));
cout << one.name << "\t" << one.number << "\t" << one.totalScore << endl;
file.close();
}
执行该程序输出如下结果:
Lu 97005 80.92
Li 97002 92.62
说明:该程序将 stu[]
数组中的若干个记录写入到被打开的 f7.dat
文件中,写的方法是用 write()
函数。然后,使用 seekp()
函数定位到第4个记录,读出它并显示在屏幕上。再使用 seekp()
函数定位到第1个记录上,读出它并显示在屏幕上。
9.5.4 随机访问数据文件
C++语言的文件都是流文件,而系统总是用读或写文件指针记录着流的当前位置。istream
类提供三个成员函数来对读指针进行操作,它们是:
istream &istream::seekg(<流中位置>);
istream &istream::seekg(<偏移量>, <参照位置>);
streampos istream::tellg();
其中,streampos
被定义为 long
型量。<流中位置>
和 <偏移量>
都是 long
型量,并以字节数为单位。<参照位置>
具有如下含义:
ios::cur
- 相对于当前读指针所指定的位置;ios::beg
- 相对于流的开始位置;ios::end
- 相对于流的结尾处。
例如,假设 input
是一个 istream
类型的流,则:
input.seekg(-10, ios::cur);
表示使该读指针指向以当前位置为基准向前移动10个字节处。
input.seekg(100, ios::beg);
表示使该指针指向从流开始位置后移100个字节处。
input.seekg(-100, ios::end);
表示使读指针指向相对于流结尾处向前移动100个字节的位置。
操作写指针的成员函数
使用写指针来指示下一次插入操作的位置。在执行插入操作时,随着插入的字节数的增加,写指针相应地向后移动,插入操作结束,写指针指向一个新的当前位置。三个操作写指针的成员函数原型如下:
ostream &ostream::seekp(<流中的位置>);
ostream &ostream::seekp(<偏移量>, <参照位置>);
streampos ostream::tellp();
这三个成员函数的含义与前面讲过的操作读指针的三个成员函数的含义相同,只是它们用来操作写指针。
请记住:由于读函数中曾有 get()
,因此操作读指针时使用 seekg()
;而写函数中曾有 put()
,因此操作写指针时使用 seekp()
。
示例 9.24 分析下列程序的输出结果
#include <iostream.h>
#include <fstream.h>
#include <stdlib.h>
void main() {
fstream file("f6.dat", ios::in | ios::out | ios::binary);
if (!file) {
cout << "f6.dat can't open.\n";
abort();
}
for (int i = 0; i < 15; i++)
file.write((char*)&i, sizeof(int));
streampos pos = file.tellp();
cout << "Current byte number: " << pos << endl;
for (int i = 15; i < 45; i++)
file.write((char *)&i, sizeof(int));
file.seekg(pos);
file.read((char*)&i, sizeof(int));
cout << "The data stored is " << i << endl;
file.seekp(0, ios::beg);
for (int i = 80; i < 100; i++)
file.write((char *)&i, sizeof(int));
file.seekg(pos);
file.read((char *)&i, sizeof(int));
cout << "The data stored is " << i << endl;
file.close();
}
执行该程序输出如下结果:
Current byte number: 60
The data stored is 15
The data stored is 95
说明:该程序中先打开一个文件,打开方式如下所示:
ios::in | ios::out | ios::binary
该方式表示打开的文件是可读可写的二进制文件。打开文件后先写入15个数字,使用了 write()
函数。然后,使用 tellp()
函数记录下当前写指针的位置。接着,又使用 write()
函数向文件中写入30个数字。这时,调用 seekp()
函数将写指针定位到前面记录下来的位置,即写指针指向第一次写的15个数字的后面位置,调用 read()
函数读出该位置的值,并显示到屏幕上,则为15。
然后,再调用下述函数:
file.seekp(0, ios::beg);
将写指针移到被打开的文件头,即准备从头开始写这个文件。使用 write()
函数,从文件头开始写入20个数字。又将读指针移到前面曾被用 tellp()
函数记录下来的位置,即从文件头向后数第15个数据,读出并显示该数值为95。
file.seekp(20, ios::cur);
将写指针向后移动20个字节,即5个数据项。移动前,写指针已是第6个数据项;移20个字节后,写指针指向第22个数据项,该项数据是原来保留的21。这时输出写指针所指向的位置,距文件头的字节数为88。88个字节正好是22个数据项,因为每个数据项占有4个字节。
9.5.5 其他有关文件操作的函数
前面讲过了有关文件的打开关闭函数和读写函数以及定位读、写指针的函数,下面再补充一些有关文件操作的函数。
1. 跳过输入流中指定数量的字符的函数
该函数的原型如下所示:
istream &istream::ignore(int n = 1, int t = EOF);
该函数的功能是从输入流中跳过 n
个字符,或者直到发现终止字符 t
为止。终止字符 t
表示指定的字符。默认情况下,跳过的字符个数为 1,终止字符为 EOF
,即文件结束标志。通常,该函数用来忽略从键盘上键入的输入流中的字符。
以下示例展示了如何使用 ignore()
函数:
#include <iostream.h>
void main() {
int a;
cout << "Input an integer:";
cin >> a;
while (!cin) {
cin.clear();
cin.ignore(80, '\n');
cout << "Try again!" << endl;
cout << "Input an integer:";
cin >> a;
}
cout << "The integer entered is " << a << endl;
}
执行该程序,显示如下信息:
Input an integer: a789
Try again!
Input an integer: 678
The integer entered is 678
说明:
-
该程序使用了
while
循环来实现反复输入直到正确为止。while
循环的条件是!cin
,即判断输入流cin
是否有错误。无错时cin
为非零,有错时cin
为零。 -
在
while
循环体中,有如下一条语句:cin.clear();
其中,
clear()
函数是ios
类中的一个成员函数,其原型如下:void ios::clear(int = 0);
该函数的用途是将错误状态的标志字中的错误标志位清除。然后,跳过这次输入,等待处理下次输入。
-
在
while
循环体内,还出现如下语句:cin.ignore(80, '\n');
这是对
cin
流调用ignore()
函数进行操作。其中,第一个参数80
表示跳过的字符个数最多为 80 个,因为一行字符一般为 80 个,即跳过一行;或者遇到终止符\n
为止,这也表示为一行,因为每一行结束都用\n
字符。该函数在遇到错误时,跳过一行,等待下次输入。
2. 退回一个字符到输入流的函数
该函数原型如下所示:
istream &istream::putback(char ch);
该函数的功能是将读出的指定字符退回到输入流中。其中,ch
是指出要退回输入流的字符。
以下示例展示了如何使用 putback()
函数:
#include <iostream.h>
#include <ctype.h>
int getnum(char *s);
void main() {
char buf[80];
cout << "Enter stream:\n";
while (getnum(buf))
cout << "Digit string is: " << buf << endl;
}
int getnum(char *s) {
int flag = 0;
char ch;
while (cin.get(ch) && !isdigit(ch)) {
if (!cin) return 0;
}
do {
*s++ = ch;
} while (cin.get(ch) && isdigit(ch));
*s = '\0';
flag = 1;
if (cin) cin.putback(ch);
return flag;
}
执行该程序显示如下信息:
Enter stream:
ab76854xy128m
Digit string is: 76854
Digit string is: 128
说明:
-
ctype.h
头文件中包含一些判断函数,该程序中所调用的isdigit()
函数就被包含在该头文件中。该函数用来判断所指定的字符是否是数字字符。如果是数字字符返回非零值,否则返回零。 -
该程序的
getnum()
函数中,flag
是一个标志量。它为 0 表示没有数字串返回,它为 1 表示有数字串返回。getnum()
函数每次处理一个数字串。 -
在
getnum()
函数中的if
语句里使用了:cin.putback(ch);
由于判定一个字符不是数字字符时多读了一个字符,因此用
putback()
函数将它送回到输入流中,再继续下面的程序。实际上,在该程序中不使用该函数退回字符也不影响输出结果。
3. 返回输入流中下一个字符的函数
该函数的原型为:
int istream::peek();
该函数的功能是返回输入流中的下一个字符,但并不提取它。在遇到输入流结束标志时返回 EOF
。该函数已在前面的例子中应用,这里不再举例。