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的说明),但程序不会自动停下来,所以要判断文件中是否已没有数据。如写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。如关闭文件后重新打开,文件定位指针就在文件头。

posted @ 2014-11-05 09:53  Delphi爱好者2014  阅读(464)  评论(0编辑  收藏  举报