使用流的磁盘文件I/O—分类
一、磁盘文件I/0(作为字符串存入磁盘)
在处理磁盘文件的时候,需要另一套类:作为输入的类ifstream(派生自istream)、同时最为输入输出的类fstream(派生自iostream)、作为输出的类ofstream(派生自ostream)。
类ifstream、fstream、ofstream在头文件fstream中进行声明。
1.首先看写入数据和读出数据
#include <iostream> #include <fstream> #include <string> using namespace std; int main() { char ch='x',ich; int j=77,ij; double d=32.33,id; string s1="jia",is1; string s2="you",is2; ofstream outfile("fdata.txt"); outfile<<ch <<j <<' ' <<d <<s1 <<' ' <<s2; cout<<"File written\n"; ////用来读取文件fdata.txt中的数据 // ifstream infile("fdata.txt"); // infile>>ich>>ij>>id>>is1>>is2; // cout<<ich<<endl // <<ij<<endl // <<id<<endl // <<is1<<endl // <<is2<<endl; return 0; }
首先,程序中建立了类ofstream对象outfile,同时初始化文件fdata.txt。这里outfile类似于前面程序中的cout,可以使用插入运算符(<<)输出任意变量到文件中。
其次,当程序结束时,对象outfile到了作用域之外,自然会调用析构函数来关闭文件,这里就是为什么如果上面写入和读出在同一个程序中会出现乱码,因为在执行读出程序的时候,文件还没有关闭。
2.上面的例子字符串需要空格隔开,因此字符串中不能有空白符,那么含有空白符的字符串怎么处理呢?
#include <fstream> #include <iostream> using namespace std; //int main() //{ // ofstream outfile("text.txt"); // outfile<<"12312312312312\n" // <<"wqerqerqerqerqer\n" // <<"fjdlsgjalfjlsadfj"; // return 0; //} int main() { ifstream infile("text.txt"); const int MAX=80; char buff[MAX]; while(!infile.eof()) { infile.getline(buff,MAX); cout<<buff<<endl; } return 0; }
注释:① 每行用换行符来指定结束。注意:这是char*字符串,而不是string的对象。(?多数流运算符处理char*字符串时更为容易?)
② 读出的时候,使用istream的成员函数getline每次读入一行,包括空白符,知道遇到默认的换行符。
③ while(!infile.eof()) 可以检测EOF信号
上面只是检测eofbit没有检测failbit位和badbit为等其他错误标志,如果都检测,则 while(infile.good())
其实可以直接检测流。任何流对象(如infile)都有一个返回值用于检测错误条件,有任何错误返回0,情况正常返回非0;所以可以这么写:while(infile)
3. 看一个字符io的例子:
#include<iostream> #include<fstream> #include<string> using namespace std; ////写入文件 //int main() //{ // string str="qweqweqweqweqweqwewq" // " lsdfaldjfaldkfja"; // ofstream outfile("text.txt"); // for (int j=0;j<str.size();j++) // { // outfile.put(str[j]); //首先,把字符str[j]写到流对象outfile中,直到全部写入流中 // //其次,流对象把字符串写入到文件text.txt中 // } // cout<<"File written\n"; // return 0; //} //读出文件(用get) int main() { char ch; ifstream infile("text.txt"); while(infile) { infile.get(ch); //首先,从文件text.txt中读出整个字符串到流对象infile中 //其次,把流对象中的字符逐个读到ch中 cout<<ch; } cout<<endl; return 0; } //读出文件的另一条途径:类ios的成员函数rdbuf //功能:此函数返回与流对象相关联的streambuf(或filebuf)对象的指针, //此相关联对象包含一个缓冲区以保存流中读出的字符 int main() { ifstream infile("text.txt"); cout<<infile.rdbuf(); //通过返回的缓冲区对象的指针来显示infile流对象中的字符串 cout<<endl; }
二、二进制I/O 使用格式化I/O可以写入少量数据到磁盘中,但是如果要存储大量的数据,那么使用二进制I/O最有效。格式化I/O使用字符串,而二进制I/O的数值在磁盘中的存储和计算机内存中一样。如:在二进制I/O中存储float需要四个字节;但是格式化I/O表示中,存储6.02323e13却需要十个字节。首先举个简单的例子:
#include <iostream> #include <fstream> using namespace std; const int MAX=100; int buffer[MAX]; int main() { //写入文件 for(int j=0;j<MAX;j++) buffer[j]=j; ofstream os("edata.bat",ios::binary); //创建输出流 os.write(reinterpret_cast<char*>(buffer),MAX*sizeof(int)); os.close(); // 必须关闭与文件关联的流,这样第二个流才能打开文件 //读出文件 for(int j=0;j<MAX;j++) buffer[j]=0; //清除buffer ifstream is("edata.bat",ios::binary); is.read(reinterpret_cast<char*>(buffer),MAX*sizeof(int)); for (int j=0;j<MAX;j++) { if (buffer[j]!=j) { cerr<<"data is incorrect\n"; return 1; } } cout<<"data is correct\n"; return 0; }
① write和read这两个函数以字节的方式考虑数据,其参数是数据的地址及其长度。
地址必须进行强制转换,使用reinterpret_cast转化成char*类型,长度就是字节的个数。
②在使用二进制数据时,创建流对象的第二个参数的位置必须使用参数:ios::binary;
三、对象I/O
将对象写入磁盘时,一般都使用二进制模式。这样写入到磁盘中的内容其位构造和在内存中相同,并且保证了对象中所含的数值数据也可以正确的被处理。
举例:
#include<iostream>
#include <fstream>
using namespace std;
class person
{
public:
void getdata()
{
cout<<"Enter the name: "; cin>>name;
cout<<"Enter the age: "; cin>>age;
}
void showdata()
{
cout<<"Name: "<<name<<endl;
cout<<"Age: "<<age<<endl;
}
private:
char name[80];
short age;
};
//单个对象的I/O
int main()
{
//写入文件
person per;
per.getdata();
ofstream os("person.bat",ios::binary);
os.write(reinterpret_cast<char*>(&per),sizeof(per));
os.close();
//读出文件
person pers;
ifstream is("person.bat",ios::binary);
is.read(reinterpret_cast<char*>(&pers),sizeof(pers));
pers.showdata();
return 0;
}
//多个对象的I/O
int main()
{
//写入文件
char ch;
person per;
fstream file; //创建输入输出文件流对象
file.open("group.bat",ios::app|ios::out|ios::in|ios::binary);
do
{
cout<<"Enter person's data:"<<endl;
per.getdata();
file.write(reinterpret_cast<char*>(&per),sizeof(per));
cout<<"Enter another person(y/n)? ";
cin>>ch;
} while (ch=='y');
//读出文件
person pers;
file.seekg(0); //保证从文件的开头进行读
file.read(reinterpret_cast<char*>(&pers),sizeof(per));//读取第一个对象实例
while(!file.eof())
{
cout<<"person: "<<endl;
pers.showdata();
file.read(reinterpret_cast<char*>(&pers),sizeof(per));//读取下一个对象实例
}
return 0;
}
注意:①如果写入程序和读出文件为不同的函数的时候,必须要有相同的成员数据,成员函数可以不同(因为他们无需写入磁盘)。然而只有在不使用虚函数的简单类中才可以这么说。
原因:派生类的对象在内存中其数据之前包含有特殊的数值。在使用虚函数时,这些数值用来帮助确定对象的类。如果改变类的成员函数,此数值也跟着改变。
② 还不应该对带有指针数据成员的对象进行磁盘I/O,正如我们想到的,当将对象读出到其他不同内存的时候,指针值就不是正确的了。
③ 函数open
在前面的程序中都是:创建文件并且使用相同的语句初始化:如,ifstream is("person.bat",ios::binary);
但在这个多对象的I/O函数中,使用fstream file;创建文件流,另一个语句使用open把此文件打开file.open("group.bat",ios::app|ios::out|ios::in|ios::binary); ---这对于打开文件有可能出错是有用的。这样也不会存在写入后再读取的时候,必须切断写入流对象与文件的关联的麻烦。
④ 模式位
MSDN搜索:basci_fstream::open
The type is a bitmask type that describes an object that can store the opening mode for several iostreams objects. The distinct flag values (elements) are:
-
app, to seek to the end of a stream before each insertion.
-
ate, to seek to the end of a stream when its controlling object is first created.
-
binary, to read a file as a binary stream, rather than as a text stream.
-
in, to permit extraction from a stream.
-
out, to permit insertion to a stream.
-
trunc, to delete contents of an existing file when its controlling object is created.