• 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<<'
    ';//因回车符未送到  B行
            else dfile<<buf;//本行大于99个字符,还未读到回车换行符,所以不加'
    '
        } 
        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的说明),但程序不会自动停下来,所以要判断文件中是否已没有数据。如写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。如关闭文件后重新打开,文件定位指针就在文件头。

  • 相关阅读:
    倒下
    我还能相信谁

    工作这点事
    人,这东西
    祝福
    路,公车和鞋子
    那片海
    document.querySelector bug All In One
    js logical or assignment bug All In One
  • 原文地址:https://www.cnblogs.com/delphi2014/p/4075495.html
Copyright © 2020-2023  润新知