首页 > 代码库 > VC++ 之 文件操作

VC++ 之 文件操作

文件的基本概念

本节中文件指的是磁盘文件。

C++根据文件(file)内容的数据格式,可分为两类:

  • 文本文件:由字符序列组成,在文本文件中存取的最小信息单位为字符(character),也称ASCII码文件。
  • 二进制文件:存取的最小信息单位为字节(Byte)。


C++把每个文件都看成一个有序的字节流,每一个文件或者以文件结束符(end of file marker)结束,或者在特定的字节号处结束,如下图所示。



当打开一个文件时,该文件就和某个流关联起来了。对文件进行读写实际上受到一个文件定位指针(file position pointer)的控制。

输入流的指针也称为读指针,每一次提取操作将从读指针当前所指位置开始,每次提取操作自动将读指针向文件尾移动。输出流指针也称写指针,每一次插入操作将从写指针当前位置开始,每次插入操作自动将写指针向文件尾移动。

文件的打开与关闭

文件使用的5步骤:
①说明一个文件流对象,这又被称为内部文件:
    ifstream ifile;//只输入用
   ofstream ofile;//只输出用
    fstream iofile;//既输入又输出用

②使用文件流对象的成员函数打开一个磁盘文件。这样在文件流对象和磁盘文件名之间建立联系。文件流中说明了三个打开文件的成员函数。
    void ifstream::open(const char*,int=ios::in,int=filebuf::openprot);
    voidofstream::open(const char*,int=ios::out,int=filebuf::openprot);
    void fstream::open(const char*,int,int=filebuf::openprot);
第一个参数为要打开的磁盘文件名。第二个参数为打开方式,有输入(in),输出(out)等,打开方式在ios基类中定义为枚举类型。第三个参数为指定打开文件的保护方式,一般取默认。所以第二步可如下进行:
    iofile.open(“myfile.txt”,ios::in|ios::out);

上面三个文件流类都重载了一个带默认参数的构造函数,功能与open函数一样:
    ifstream::ifstream(const char*,int=ios::in,int=filebuf::openprot);
    ofstream::ofstream(const char*,int=ios::out,int=filebuf::openprot);
    fstream::fstream(const char*,int,int=filebuf::operprot);
所以①和②两步可合成: fstream iofile(”myfile.txt”,ios::in|ios::out);

③打开文件也应该判断是否成功,若成功,文件流对象值为非零值,不成功为0(NULL),文件流对象值物理上就是指它的地址。因此打开一个文件完整的程序为:
fstream iofile(”myfile.txt”,ios::in|ios::out);
if(!iofile)
{ //“!”为重载的运算符
       cout<<”不能打开文件:”<<”myfile,txt”<<endl;
       return -1;
} //失败退回操作系统

④使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写,这在下一节中讨论。

⑤关闭文件。三个文件流类各有一个关闭文件的成员函数 :
    void ifstream::close();
    void ofstream::close();
    void fstream::close();
使用很方便,如:
    iofile.close();

关闭文件时,系统把该文件相关联的文件缓冲区中的数据写到文件中,保证文件的完整,收回与该文件相关的内存空间,可供再分配,把磁盘文件名与文件流对象之间的关联断开,可防止误操作修改了磁盘文件。如又要对文件操作必须重新打开。

关闭文件并没有取消文件流对象,该文件流对象又可与其他磁盘文件建立联系。文件流对象在程序结束时,或它的生命期结束时,由析构函数撤消。它同时释放内部分配的预留缓冲区。

文本文件的读写

文本文件的顺序读写:顺序读写可用C++的提取运算符(>>)和插入运算符(<<)进行。

例1:文件复制

#include<iostream>#include<fstream>#include<cstdlib>using namespace std;int main(){    char ch;    ifstream sfile("d:\\Ex9_6\\Ex9_6.cpp");    ofstream dfile("e:\\Ex9_6.cpp");  //只能创建文件,不能建立子目录,如路径不存在则失败    if(!sfile){        cout<<"不能打开源文件:"<<"d:\\Ex9_6\\Ex9_6.cpp"<<endl;        return -1;    }    if(!dfile){        cout<<"不能打开目标文件:"<<"e:\\Ex9_6.cpp"<<endl;        return -1;    }    sfile.unsetf(ios::skipws);      //关键!把跳过空格控制位置0,即不跳过空格,否则空格全部未拷贝    while(sfile>>ch)dfile<<ch;    sfile.close();                  //如没有这两个关闭函数,析构函数也可关闭    dfile.close();    return 0;}

例2:按行复制文本文件

#include<iostream>#include<fstream>#include<cstdlib>using namespace std;int main(){    char filename[256],buf[100];    fstream sfile,dfile;    cout<<"输入源文件路径名:"<<endl;    cin>>filename;//对路径各方面而言空格是无关紧要的,否则要用getline()等成员函数    sfile.open(filename,ios::in);//打开一个已存在的文件    while(!sfile){        cout<<"源文件找不到,请重新输入路径名:"<<endl;        sfile.clear(0);//清状态字        cin>>filename;        sfile.open(filename,ios::in);    }    cout<<"输入目标文件路径名:"<<endl;    cin>>filename; //只能创建文件,不能建立子目录,如路径不存在则失败    dfile.open(filename,ios::out);    if(!dfile){        cout<<"目标文件创建失败"<<endl;        return -1;    }    while(sfile.getline(buf,100)){//按行拷贝  A行        if(sfile.gcount()<100) dfile<<buf<<\n;//因回车符未送到  B行        else dfile<<buf;//本行大于99个字符,还未读到回车换行符,所以不加‘\n‘    }     sfile.close();    dfile.close();    return 0;}

例3:文本式数据文件的创建与读取数据

#include<fstream>#include<iostream>#include<iomanip>#include<string>using namespace std;class inventory{    string Description;    string No;    int Quantity;    double Cost;    double Retail;public:    inventory(string="#",string="0",int=0,double=0,double=0);    friend ostream&operator<<(ostream&dist,inventory&iv);//重载插入运算符    friend istream&operator>>(istream&sour,inventory&iv);//重载提取运算符};     //流类作为形式参数必须是引用inventory::inventory(string des,string no,int quan,double cost,double ret){    Description=des;    No=no;    Quantity=quan;    Cost=cost;    Retail=ret;}ostream &operator<<(ostream&dist,inventory&iv){    dist<<left<<setw(20)<<iv.Description<<setw(10)<<iv.No;    dist<<right<<setw(10)<<iv.Quantity<<setw(10)<<iv.Cost<<setw(10)<<iv.Retail<<endl;    return dist;}//写入文件是自动把数转为数字串后写入istream&operator>>(istream&sour,inventory&iv){    sour>>iv.Description>>iv.No>>iv.Quantity>>iv.Cost>>iv.Retail;    return sour;}//从文件读出是自动把数字串转为数读出,函数体内>>功能不变int main(){    inventory car1("夏利2000","805637928",156,80000,105000),car2;    inventory motor1("金城125","93612575",302,10000,13000),motor2;    ofstream distfile("d:\\Ex9_9.data");    distfile<<car1<<motor1;//注意ofstream是ostream的派生类    distfile.close();    cout<<car1;    cout<<motor1;    cout<<car2;    cout<<motor2;    ifstream sourfile("d:\\Ex9_9.data");//这样分两次打开,可避免读文件时,误改了源文件    sourfile>>car2>>motor2;    sourfile.close();    cout<<car2;    cout<<motor2;    return 0;}

资源获取是由构造函数实现,而资源释放是由析构函数完成。所以与内存动态分配一样,由文件重构对象放在构造函数中,把对象存入文件则放在析构函数中。参见后面章节。

二进制文件的读写

 1、对二进制文件进行读写的成员函数
    istream&istream::read(char *,int);
    //从二进制流提取
    istream&istream::read(unsigned char*,int);
    istream&istream::read(signed char *,int);
    //第一个参数指定存放有效输入的变量地址,第二个参数指定提取的字节数,
    //函数从输入流提供指定数量的字节送到指定地址开始的单元

    ostream&ostream::write(const char *,int);
    //向二进制流插入
    ostream&ostream::write(const unsigned char *,int);
    ostream&ostream::write(const signed char *,int);
    //函数从该地址开始将指定数量的字节插入输入输出流

 2、文件结束判断:读函数并不能知道文件是否结束,可用状态函数int ios::eof()来判断文件是否结束。必须指出系统是根据当前操作的实际情况设置状态位,如需根据状态位来判断下一步的操作,必须在一次操作后立即去调取状态位,以判断本次操作是否有效。

3、例4:创建二进制数据文件,以及数据文件的读取。

#include<fstream>#include<iostream>#include<iomanip>#include<string>using namespace std;class inventory{    string Description;    string No;    int Quantity;    double Cost;    double Retail;public:    inventory(string="#",string="0",int =0,double =0,double =0);    friend ostream &operator<<(ostream&,inventory&);    void Bdatatofile(ofstream&dist);     //文件流类作为形式参数必须是引用    void Bdatafromfile(ifstream&sour);};inventory::inventory(string des,string no,int quan,double cost,double ret){    Description=des;    No=no;    Quantity=quan;    Cost=cost;    Retail=ret;}ostream &operator<<(ostream&dist,inventory&iv){    dist<<left<<setw(20)<<iv.Description<<setw(10)<<iv.No;    dist<<right<<setw(10)<<iv.Quantity<<setw(10)<<iv.Cost<<setw(10)<<iv.Retail<<endl;    return dist;}void inventory::Bdatatofile(ofstream&dist){    dist.write(Description.c_str(),20); //由string类的c_str()函数转为char*    dist.write(No.c_str(),10);    dist.write((char*)&Quantity,sizeof(int));    dist.write((char*)&Cost,sizeof(double));    dist.write((char*)&Retail,sizeof(double));}void inventory::Bdatafromfile(ifstream&sour){    char k[20];    sour.read(k,20);    Description=k;    sour.read(k,10);    No=k;    sour.read((char*)&Quantity,sizeof(int));    sour.read((char*)&Cost,sizeof(double));    sour.read((char*)&Retail,sizeof(double));}//由此可见读和写是完全对称的过程,次序决不能错int main(){    inventory car1("夏利2000","805637928",156,80000,105000),car2;    inventory motor1("金城125","93612575",302,10000,13000),motor2;    ofstream ddatafile("d:\\Ex9_10.data",ios::out|ios::binary);    car1.Bdatatofile(ddatafile);    motor1.Bdatatofile(ddatafile);    cout<<"对象car1:"<<endl;    cout<<car1;    cout<<"对象motor1:"<<endl;    cout<<motor1;    cout<<"对象car2:"<<endl;    cout<<car2;    cout<<"对象motor2:"<<endl;    cout<<motor2;    ddatafile.close();    ifstream sdatafile("d:\\Ex9_10.data",ios::in|ios::binary);//重新打开文件,从头读取数据    car2.Bdatafromfile(sdatafile);                         //从文件读取数据拷贝到对象car2    if(sdatafile.eof()==0) cout<<"读文件成功"<<endl;     cout<<"对象car2:"<<endl;    cout<<car2;    motor2.Bdatafromfile(sdatafile);                 //继续从文件读取数据拷贝到对象motor2    if(sdatafile.eof()==0) cout<<"读文件成功"<<endl;    cout<<"对象motor2:"<<endl;    cout<<motor2;    sdatafile.close();    return 0;}

这两项操作设计为成员函数,给出与例3不同的读写方式。

4、二进制文件优点:可以控制字节长度,读写数据时不会出现二义性,可靠性高。同时不知格式是无法读取的,保密性好。文件结束后,系统不会再读(见eofbit的说明),但程序不会自动停下来,所以要判断文件中是否已没有数据。如写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。如关闭文件后重新打开,文件定位指针就在文件头。

VC++ 之 文件操作