文件读写
1、文件的基本内容
(1)文件和流的关系
可以将顺序文件看作一个有限字符构成的顺序字符流,然后像对
cin, cout 一样的读写。
(2)创建文件
方式一:创建时直接关联文件(初始化)
#include <fstream> // 包含头文件
ofstream outFile(“clients.dat”, ios::out|ios::binary);//创建文件
参数:
- clients.dat”
要创建的文件的名字 - 文件打开方式:
- ios:out:输出到文件, 删除原有内容。
- ios::app:输出到文件, 保留原有内容,总是在尾部添加。
- ios::binary 以二进制文件格式打开文件
方式二:先创建ofstream对象,再用 open函数打开
ofstream fout;
fout.open("test.out",ios::out|ios::binary);
(3)判断是否成功打开文件
if(!fout){
cout << “File open error!”<<endl;
}
(4)文件名的绝对路径和相对路径
- 文件名可以给出绝对路径,也可以给相对路径。
- 没有交代路径信息,就是在当前文件夹下找文件。
- C++的文件分隔符为\ ,例如:“c:\tmp\mydir\some.txt”
(5)文件的读写指针
- 对于输入文件,有一个读指针;
- 对于输出文件,有一个写指针;
- 对于输入输出文件,有一个读写指针;
- 文件指针的作用:标识文件操作的当前位置, 该指针在哪里,读写操作就在哪里进行。
注意: 文件指针不同于普通的指针,它不是指针类型的,但是其功能与普通指针一样,都是指出某一位置。
文件指针举栗子:
void main(){
//写指针
ofstream fout("a1.out",ios::app); //以添加方式打开
long location = fout.tellp(); //取得写指针的位置
location = 10;
fout.seekp(location); // 将写指针移动到第10个字节处
fout.seekp(location,ios::beg); //从头数location
fout.seekp(location,ios::cur); //从当前位置数location
fout.seekp(location,ios::end); //从尾部数location
//读写指针
ifstream fin(“a1.in”,ios::ate); //打开文件,定位文件指针到文件尾
long location = fin.tellg(); //取得读指针的位置
location = 10L;
fin.seekg(location); // 将读指针移动到第10个字节处
fin.seekg(location,ios::beg); //从头数location
fin.seekg(location,ios::cur); //从当前位置数location
fin.seekg(location,ios::end); //从尾部数location
}
注意:location可以为负数。
C++ 获取文件长度的方式:
将文件指针置于末尾,获取文件指针的位置。这个获取位置函数返回的值就是文件的长度,即文件的字节数。
(6)显式关闭文件
为什么要关闭文件?
-
避免不能打开其他文件
有些操作系统打开文件的数量是一定的,如果每打开一个文件不关闭的话,可能造成打不开其它任何文件问题。 -
避免数据丢失
直接向硬盘写入数据的话,硬盘的访问速度很慢。因此现在的操作系统一般先将数据写入到内存缓冲区中,达到一定的条件后( 刷新缓冲区或者缓冲区满了,或者其他条件),再写入硬盘中的文件内。此时才是真正的访问硬盘。如果没有关闭文件,如果我们是向硬盘中的文件写入数据的话,那么就有可能还有一部分数据在内存缓冲区中,没有写入到文件上。
void main(){
ifstream fin(“test.dat”,ios::in);
fin.close();
ofstream fout(“test.dat”,ios::out);
fout.close();
}
2、文件读写函数
(1) ifstream 和 fstream的成员函数
istream& read (char* s, long n);
功能: 将文件读指针指向的地方的n个字节内容,读入到内存地址s,然后将文件读指针向后移动n字节 (以ios::in方式打开文件时,文件读指针开始指向文件开头) 。
(2) ofstream 和 fstream的成员函数:
istream& write (const char* s, long n);
功能: 将内存地址s处的n个字节内容,写入到文件中写指针指向的位置,然后将文件写指针向后移动n字节(以ios::out方式打开文件时,文件写指针开始指向文件开头, 以ios::app方式打开文件时,文件写指针开始指向文件尾部 ) 。
注意: 因为文件流也是流,所以流的成员函数和流操作算子也同样适用于文件流。
3、字符文件读写
写一个程序,将文件 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> v;
ifstream srcFile("in.txt",ios::in);
ofstream destFile("out.txt",ios::out);
int x;
while( srcFile >> x )
v.push_back(x);
sort(v.begin(),v.end());
for( int i = 0;i < v.size();i ++ )
destFile << v[i] << " ";
destFile.close();
srcFile.close();
return 0;
}
3、二进制文件读写
(1)在文件中写入和读取一个整数
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ofstream fout("some.dat", ios::out | ios::binary); //写方式、二进制方式打开
int x=120;
fout.write( (const char *)(&x), sizeof(int) ); //写入
fout.close(); //关闭文件
ifstream fin("some.dat",ios::in | ios::binary); //读方式、二进制方式打开
int y;
fin.read((char * ) & y,sizeof(int)); //读出
fin.close(); //关闭文件
cout << y <<endl; //显示
return 0;
}
(2)从键盘输入几个学生的姓名的成绩,并以二进制文件形式保存
#include <iostream>
#include <fstream>
using namespace std;
struct Student {
char name[20];
int score;
};
int main() {
Student s;
ofstream OutFile( "c:\\tmp\\students.dat",ios::out|ios::binary);
while( cin >> s.name >> s.score )
OutFile.write( (char * ) & s, sizeof( s) );
OutFile.close();
return 0;
}
/*
输入:
Tom 60
Jack 80
Jane 40
^Z+回车
则形成的 students.dat 为 72字节,用 记事本打开,呈现:
Tom 烫烫烫烫烫烫烫烫< Jack 烫烫烫烫烫烫烫蘌 Jane 烫烫烫烫烫
烫烫?
*/
(3)将 students.dat 文件的内容读出并显示
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) ) ) {
int readedBytes = inFile.gcount(); //看刚才读了多少字节
cout << s.name << " " << s.score << endl;
}
inFile.close();
return 0;
}
/*输出:
Tom 60
Jack 80
Jane 40
*/
(4)将 students.dat 文件的Jane的名字改成Mike
int main()
{
Student s;
fstream iofile( "c:\\tmp\\students.dat",
ios::in|ios::out|ios::binary);
if( !iofile) {
cout << "error" ;
return 0;
}
iofile.seekp( 2 * sizeof(s),ios::beg); //定位写指针到第三个记录
iofile.write("Mike",strlen("Mike")+1);//这里要多写入一个字节'\0'
iofile.seekg(0,ios::beg); //定位读指针到开头
while( iofile.read( (char* ) & s, sizeof(s)) )
cout << s.name << " " << s.score << endl;
iofile.close();
return 0;
}
(5)文件拷贝程序mycopy 示例
/*用法示例:
mycopy src.dat dest.dat
即将 src.dat 拷贝到 dest.dat 如果 dest.dat 原来就有,则原来的文件会被覆
盖 */
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char * argv[])
{
if( argc != 3 ) {
cout << "File name missing!" << endl;
return 0;
}
ifstream inFile(argv[1],ios::binary|ios::in); //打开文件用于读
if( ! inFile ) {
cout << "Source file open error." << endl;
return 0;
}
ofstream outFile(argv[2],ios::binary|ios::out); //打开文件用于写
if( !outFile) {
cout << "New file open error." << endl;
inFile.close(); //打开的文件一定要关闭
return 0;
}
char c;
while( inFile.get(c)) //每次读取一个字符
outFile.put(c);//每次写入一个字符
outFile.close();
inFile.close();
return 0;
}
/*
输出:
Tom 60
Jack 80
Mike 40
*/
4、字符文件与二进制文件的区别
文件格式指的是创建文件和使用文件的人的一个约定,即约定文件的每一部分内容是什么形式的(二进制的、字符串的等),一起其他一些约定。本质上,不同格式的数据最终都是二进制串。只是格式不同(数据的约定不同),解析的方式就不同。比如二进制文件就不能采用记事本取解析。
字符格式文件,存储数据的格式就是字符串的格式,此时我们用记事本打开的话,我们是可以看懂的。二进制格式文件,其中的字符部分我们还是能够看懂的,但是其他数据类型是二进制的形式,只有知道这些数据是怎么排布的人,才能够知道怎么使用这些数据。(通过类型强转)。因此如果用字符编码进行解码的话就会出现乱码现象。 而记事本一般就是采用字符编码进行解析数据,因而我们使用记事本打开二进制文件,就会显示乱码。
(1)二种格式的内存差异:
一般二进制文件比纯文本文件更加节约内存。因为纯文本文件需要将空格、换行符存入。另一方面,对于比较大整型数字,比如1000000000,使用纯文本文件需要存10个字节,而使用二进制形式只需要存4个字节。
(2)查找差异:
二进制文件可以以固定空间进行存储,可以采用二分查找,查找速度更快。而文本文件,不行样本信息差异大,空间上不固定,查找麻烦。 (空间固定才可以使用二分查找)
不同的平台,有些操作的符号是不同的,因此处理同一类型的文件,代码上可能会有差异,下面是平台差异。
(3)不同平台的换行符差异
- Linux,Unix下的换行符号:
'\n'
(ASCII码:0x0a
) - Windows 下的换行符号:
'\r\n'
(ASCII码:0x0d0a
) (endl 就是'\n'
) - Mac OS下的换行符号:
'\r'
(ASCII码:0x0d
)
上述不同导致 Linux, Mac OS 文本文件在Windows 记事本中打开时不换行。
(4)不同平台下打开文件方式有所差异
- Unix/Linux下打开文件,用不用
ios::binary
没区别。 - Windows下打开文件,如果不用
ios::binary
,则:读取文件时,所有的'\r\n’
会被当做一个字符'\n'
处理,即少读了一个字符'\r'
。写入文件时,写入单独的'\n'
时,系统自动在前面加一个'\r'
,即多写了一个'\r'
。(有时这样违背我们的本意,例如图像处理里面,也许数据本身就真的是\r\n 或者\n。)
注意: 我们处理的大部分文件都是二进制文件,而不是文本文件。