文章目录
1. 简介
下面是在
C
+
+
{\rm C++}
C++中有关文件读写的几个类ifstream
、ofstream
和fstream
的继承和派生关系。本文将介绍在
C
+
+
{\rm C++}
C++中有关文件读写的操作。
2. 文件基本操作
2.1 创建文件
首先包含头文件#include<fstream>
,然后使用以下基本语句创建文件
ofstream outFile("clients.dat", ios::out | ios::binary);
上述语句是调用ofstream
类的构造函数来创建文件,其中几个参数的含义如下:
clients.dat
:待创建的文件的名称;ios::out
:创建文件的打开方式,这里是ios::out
表示如果要创立的文件已经存在,则新建文件将会覆盖旧文件;ios::app
表示如果要创建的文件已经存在,则新建文件的内容将会追加到旧文件上;ios::binary
:以二进制文件格式打开文件。
在第二部分参数中,借助或运算来同时设置多个参数值。此外,我们也可以先创建类对象,然后调用成员函数创建文件:
ofstream fout;
fout.open("test.out", ios::out | ios::binary);
然后使用以下语句判断文件是否创建成功:
if (!fout) {
cout << "File open error!" << endl;
}
其中,文件名参数可以指定为绝对路径或相对路径。如果没有指定具体路径信息,则程序在当前文件夹下寻找。
2.2 文件的读写指针
在文件的读写中,我们以一个读写指针来判断我们当前操作的具体位置。这与普通意义的指针类型不同,这是文件操作中所特有的一种类型。也就是说,文件的读写指针用于标识文件操作的当前位置,该指针的位置即为读写操作的位置。
ofstream fout("a1.out", ios::app); // 在当前目录下创建文件a1.out
long location = fout.tellp(); // 取得写指针的位置
location = 10;
fout.seekp(location); // 将写指针移至第10个字节处
fout.seekp(location, ios::beg); // 将写指针移至从头开始第10个字节处
fout.seekp(location, ios::cur); // 将写指针移至从当前位置开始第10个字节处
fout.seekp(location, ios::end); // 将写指针移至从尾开始第10个字节处
其中,上面location
的值可以为负值。上面例子是文件写指针的例子,文件读指针的使用方法类似。
ofstream fin("a1.out", ios::app); // 在当前目录下创建文件a1.out
long location = fin.tellg(); // 取得读指针的位置
location = 10L;
fin.seekg(location); // 将读指针移至第10个字节处
fin.seekg(location, ios::beg); // 将读指针移至从头开始第10个字节处
fin.seekg(location, ios::cur); // 将读指针移至从当前位置开始第10个字节处
fin.seekg(location, ios::end); // 将读指针移至从尾开始第10个字节处
2.3 字符文件的读写
字符文件就是我们常使用的记事本文件,通常其后缀名是.txt
。首先,由于文件流也是流,所以前面介绍的流的成员函数和流操作算子也同样适用于文件流。下面是一段程序,其完成的功能是将文件in.txt
内的整数排序后,再将排序后的结果写入out.txt
中。例如,in.txt
文件的内容是1 234 9 45 6 879;则执行程序后,生成的out.txt
文件的内容是1 6 9 45 234 879。
#include<iostream>
#include<fstream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> nums;
// 创建源文件和目标文件
ifstream srcFile("in.txt", ios::in);
ofstream dstFile("out.txt", ios::out);
// 从源文件内读取数据
int x;
while (srcFile >> x)
{
nums.push_back(x);
}
// 对数组排序
sort(nums.begin(), nums.end());
// 将排序后的数组写入目标文件内
for (int i = 0; i < nums.size(); ++i) {
dstFile << nums[i] << " ";
}
// 关闭文件
srcFile.close();
dstFile.close();
return 0;
}
2.4 二进制文件的读写
2.4.1 二进制读文件
首先来看在二进制读文件中常用到的函数:
istream& read(char* s, long n);
函数的作用是将文件读指针指向的地方往后的n个字节的内容读入到内存地址s处,同时文件读指针也会相应地移动n个字节(以ios::in
的方式打开文件时,文件的读指针位于文件开头,这时我们可以不断调用read不断读取整个文件的内容)。
2.4.2 二进制写文件
与二进制读文件类似,二进制写文件中常用到的函数是:
istream& write(const char* s, long n);
函数的作用是将内存地址s处的n个字节的内容写入到文件中写指针的位置,同时同时文件写指针也会相应地移动n个字节(以ios::out
的方式打开文件时,文件的写指针位于文件开头;以ios::app
的方式打开文件时,文件的写指针位于文件结尾)。
2.4.3 例子:二进制文件写入和读取一个整数
#include<fstream>
#include<iostream>
using namespace std;
int main() {
// 创建二进制文件some.dat
ofstream fout("some.dat", ios::out | ios::binary);
// 写入整数
int x = 100;
fout.write((const char*)(&x), sizeof(int));
fout.close();
// 打开二进制文件some.dat
ifstream fin("some.dat", ios::in | ios::binary);
// 读取整数
int y;
fin.read((char*)(&y), sizeof(int));
fin.close();
// 输出
cout << y << endl;
return 0;
}
2.4.4 例子:从键盘输入几个学生的姓名和成绩,并以二进制形式保存
#include<fstream>
#include<iostream>
using namespace std;
// 结构体,学生包含姓名和成绩信息
struct Student {
char name[20];
int score;
};
int main() {
Student s;
ofstream outFile("students.dat", ios::out | ios::binary);
// 从键盘读入学生信息并保存至二进制文件中
while (cin >> s.name >> s.score)
{
outFile.write((char*)(&s), sizeof(s));
}
outFile.close();
return 0;
}
运行上述程序,在当前工作目录下生成文件students.dat
,其大小为72字节。如果用记事本打开该文件则会出现乱码,现在我们用程序来查看该二进制文件中的内容:
#include<fstream>
#include<iostream>
using namespace std;
// 结构体,学生包含姓名和成绩信息
struct Student {
char name[20];
int score;
};
int main() {
Student s;
// 打开该二进制文件
ifstream inFile("students.dat", ios::in | ios::binary);
// 如果文件不存在
if (!inFile) {
cout << "error!" << endl;
return 0;
}
// 从二进制文件中读取并打印
while (inFile.read((char*)(&s), sizeof(s)))
{
cout << s.name << " " << s.score << endl;
}
inFile.close();
return 0;
}
运行上述程序,屏幕显示出刚才我们从键盘输入的内容。
2.4.5 例子:修改二进制文件中的内容
假如上述例子中,从键盘读入的内容是小明 2\n 小张 8\n 小红 5\n,下面程序的功能是将小红更改为小李。
#include<fstream>
#include<iostream>
using namespace std;
// 结构体,学生包含姓名和成绩信息
struct Student {
char name[20];
int score;
};
int main() {
Student s;
// fstream类对象既可以对文件读,也可以对文件写
fstream iofile("students.dat", ios::in | ios::out | ios::binary);
if (!iofile) {
cout << "error!" << endl;
return 0;
}
// 将写指针定位到第三条记录,即将写指针向后移动2条记录的位置
iofile.seekp(2 * sizeof(s), ios::beg);
// 写入修改后的内容
iofile.write("小李", strlen("小李") + 1);
// 将读指针定位到文件开头
iofile.seekg(0, ios::beg);
// 读取二进制文件内容并打印
while (iofile.read((char*)(&s), sizeof(s))) {
cout << s.name << " " << s.score << endl;
}
iofile.close();
return 0;
}
2.4.6 例子:文件拷贝
编写程序完成以下功能copy src.dat dst.dat
,即将src.dat
的内容拷贝到dst.dat
中。如果原dst.dat
已经存在,则原来的文件将会被覆盖。
#include<fstream>
#include<iostream>
int main(int argc, char* argv[]) {
// 程序接受两个参数copy src.dat和dst.dat
if (argc != 3) {
cout << "File name missing!" << endl;
return 0;
}
// 打开文件并判断是否成功
ifstream inFile(argv[1], ios::in | ios::binary);
if (!inFile) {
cout << "Source file open error!" << endl;
return 0;
}
// 打开文件用于写操作
ofstream outFile(argv[2], ios::out | ios::binary);
if (!outFile) {
cout << "New file open error!" << endl;
inFile.close();
return 0;
}
// 拷贝
char c;
while (inFile.get(c)) {
outFile.put(c);
}
inFile.close();
outFile.close();
return 0;
}
3. 二进制文件和文本文件的区别
在不同的操作系统中,二进制文件和文本文件的主要区别是换行符号。在
L
i
n
u
x
/
U
n
i
x
{\rm Linux/Unix}
Linux/Unix系统中,换行符用\n
表示,
A
S
C
I
I
{\rm ASCII}
ASCII码值是0x0a
;在
W
i
n
d
o
w
s
{\rm Windows}
Windows系统中,换行符用\r\n
表示,
A
S
C
I
I
{\rm ASCII}
ASCII码值是0x0d0a
;
M
a
c
O
S
{\rm Mac\ OS}
Mac OS系统中,换行符用\r
表示,
A
S
C
I
I
{\rm ASCII}
ASCII码值是0x0d
。所以,有时候我们在不同系统之间打开记事本文件时,文本没有换行。
同时,在
L
i
n
u
x
/
U
n
i
x
{\rm Linux/Unix}
Linux/Unix系统下,打开二进制文件时是否使用ios::binary
没有显著的区别;而在
W
i
n
d
o
w
s
{\rm Windows}
Windows系统下,如果不使用ios:;binary
,则读取文件时,所有的\r\n
只会被当作一个字符\n
处理,即少读了一个\r
;在写入文件时,单独写入\n
时,系统会在其前自动加上一个\r
,即多写入了一个\r
。
4. 总结
本文介绍在 C + + {\rm C++} C++中的读写文件操作,主要介绍了二进制文件的读写操作。相比于文本文件,二进制文件的存储更加节省空间。不管是什么语言,在文件读写操作时,很重要的一点时相关操作完成后必须关闭文件,避免造成不可知的错误。
参考
- 北京大学公开课:程序设计与算法(三)C++面向对象程序设计.