这样的文件操作有点玄——文件流学习 ( 二 )

在之前的文件流学习中,我们重点解决了文件读入和输出的问题,今天我们就接着上次的话头继续咯。


文件位置指针

之前讨论的读写操作,都是 " 从头开始 " 的操作:从首位开始读入数据,从首位(如果没有特殊声明会将文件中原有的数据清空)开始写入数据

如果我们要定义特殊的起始位置进行文件读写呢?

指向下一个将要读或写的字节位置

istream & ostream类为此设置了专门的成员函数:

istream::seekg(streampos); //读指针直接定位 
istream::seekg(streamoff,ios::seekdir) //读指针相对定位 
ostream::seekp(streampos);
ostream::seekp(streamoff,ios::seekdir) 

//Example
fileObject.seekg(0); //定位在文件开始处
 
fileObject.seekg(n);
fileObject.seekg(n,ios::beg); //定位在文件开始处之后的第n个字节处,默认为beg 

fileObject.seekg(n,ios::cur); //定位在光标开始后的第n个字 节 

fileObject.seekg(n,ios::end); //定位在文件结束前的第n个 字节 

fileObject.seekg(0,ios::end); //定位在文件结束位置

注:流的指针位置类型streampos和流的指针偏移类型streamoff定义为长整型,即可访问文件的最大长度为4G

我们可以注意到,在给出的函数原型中,除了流指针的偏移量(多为int类型),另外还有一个特殊的关键字ios::seekdir,用来规定文件位置指针的属性
在ios类中说明了一个公有枚举类型:

enum seek_dir { 
    beg=0, //文件开头 
    cur=1, //文件指针的当前位置 
    end=2  //文件结尾 
}; 

获取文件中当前位置指针

long istream::tellg(); //返回当前读指针位置 
long ostream::tellp(); //返回当前写指针位置 

tellp()函数:
用来获取 " 输出指针 " 的当前位置(从文件首到当前位置的字节数)
tellg()函数:
用来获取 " 读入指针 " 的当前位置(从文件首到当前位置的字节数)

long location; 
location=fileObject.tellg(); //返回文件读指针的当前位置
ifstream datafile("clients.dat", ios::in); 
datafile.seekg(-20,ios::cur); //表示将文件指针从当前位置向文件头部方向移20个字节
datafile.seekg(20,ios::beg); //表示将文件指针从文件头向文件尾方向移20个字节
datafile.seekg(-20,ios::end); //表示将文件指针从文件尾向文件头方向移20个字节

注:tellg()seekg()往往配合使用,切记指针不可移到文件头之前或文件尾之后

举个栗子

#include<iostream> 
#include<fstream> 
#include<cstring>
#include<cstdlib>
using namespace std;

int main()
{ 
    ofstream fout; 
    fout.open("output.txt"); 
    if (!fout) {
        cerr<<"File could not be opened"<<endl; 
        exit(1); 
    } 
    int num=150; 
    char name[]="John Doe"; 
    fout<<num<<"\n";
    fout<<name<<"\n";
    fout.close();
    
    ifstream fin("output.txt"); 
    int number; 
    char name2[20]; 
    fin>>number; 
    fin.ignore();
    fin.getline(name2,'\n'); //getline确保能够将两个字都读进来,而不会因为中间的space终止读入
    cout<<number<<"  "<<name2<<endl; 
    return 0; 
}

// Output
150 John Doe

注:
在Windows平台下,如果以 " 文本 " 方式打开文件
当读取文件时,系统会将所有的 \r\n 转换成 \n
当写入文件时,系统会将 \n 转换成 \r\n 写入
如果以 " 二进制 " ( binary ) 方式打开文件,则读&&写都不会进行这样的转换


在实际操作中我们会发现,如果在 " 二进制 " ( binary ) 状态下试图向文件中写入 \n 会失败
原因大概是:在二进制状态下每次写入的都是单个字节,而 \n 在Windows平台下会自动转换成 \r\n ,导致写入失败
而试图向文件中写入 \r 则成功
在读取文件时,系统会将 \r 自动转换成 \r\n


使用ios的位测试输入流状态

failbit, badbit, eofbit, goodbit代表输入流的状态,称为:输入状态标记位常量

常量 含义 badbit标记位 failbit标记位 eofbit标记位 Dec
ios::badbit=4 输出(输入)流出现非致命错误,可挽回 1 0 0 4
ios:failbit=2 输出(输入)流出现致命错误,不可挽回 0 1 0 2
ios::eofbit=1 已经到达文件尾 0 0 1 1
ios::goodbit=0 流状态完全正常 0 0 0 0
cin.eof();  //当遇到文件结束符,eofbit被置位,eof()返回为ture,否则为false 
cin.fail();  //流中发生格式错误,failbit被置位,fail()返回为ture,否则为false 
cin.bad();  //当数据丢失,badbit被设置,bad()返回为ture,否则为false 
cin.good();  //如果函数bad,fail和eof全都返回false,goodbit置位,good()返回为ture,否则为false 
cin.rdstate();  //返回流当前状态的错误标记,如果没有任何错误,则返回为goodbit 
cin.clear();  //清除错误状态,恢复为好的状态 
cin.setstate();  //将参数所代表的状态叠加到原始状态上

文件复制

int main() //实现从源文件到目的文件的复制 
{ 
    char ch; 
    ifstream sfile("d:\\Ex\\in.cpp"); 
    ofstream dfile("e:\\out.cpp");  //只能创建文件,不能建立子目录,如路径不存在则失败 
    if (!sfile) { 
        cout<<"不能打开源文件:"<<"d:\\Ex\\in.cpp"<<endl; 
        return -1;
    } 
    if (!dfile) { 
        cout<<"不能打开目标文件:"<<"e:\\out.cpp"<<endl; 
        return -1;
    } 
    sfile.unsetf(ios::skipws);  //关键!!! 把跳过空格的指令清除,即不跳过空格,否则空格全部未复制 
    while (sfile>>ch) dfile<<ch; 
    sfile.close();  //如没有这两个关闭函数,析构函数也可关闭 
    dfile.close(); 
    return 0; 
}

我们在进行文件读入和文件输出的时候,使用的都是经过特殊重载的流操作符:<<>>
当然,我们还可以使用ifstream和ostream中的特殊成员函数:

函数原型:int istream::get();
提取一个字符,包括空格 " ",制表 " \t " ,垂直制表,换页,换行 " \r " 和回车 " \n " 等,与cin有所不同。注意返回为整型


istream & istream::get(char &)
istream & istream::get(unsigned char &);
提取一个字符,放在字符型变量中。单参数函数,并为字符的引用。

在下面的这个栗子中
我们使用输入流成员函数get()从文本文件abc.txt中读取一个字符ch
然后使用输出流成员函数put()将字符ch写入文本文件xyz.txt
继续此过程直到文件结束为止。

#include<iostream>
#include<fstream> 
using namespace std;

int main() 
{ 
    ifstream ifile("abc.txt"); 
    if (!ifile) { 
        cout<<"abc.txt文件打开失败"<<endl; 
        return -1; 
    } 
    ofstream ofile("xyz.txt"); 
    if (!ofile) { 
        cout<<"xyz.txt文件打开失败"<<endl; 
        return  -1; 
    } 
    char ch; 
    while(ifile.get(ch)) ofile.put(ch); 
    ifile.close(); 
    ofile.close(); 
    return 0; 
} 

顺序文件的读取示例

题目要求:查询银行账户,按要求显示出相应的用户。

#include<iostream>
#include<fstream>
using namespace std;

enum RequestType {ZERO_BALANCE=1,CREDIT_BALANCE,DEBIT_BALANCE,END}; 

int getRequest() { 
    int request; 
    cout<<"\nEnter request"<<endl<<" 1 -List accounts with zero balances"<<endl<<" 2 -List accounts with credit balances"<<endl<<" 3 -List accounts with debit balances"<<endl<<" 4 -End of run"<<fixed<<showpoint; 
    do {  
        cout<<"\n? "; 
        cin>>request; 
    } 
    while (request<ZERO_BALANCE&&request>END); 
    return request; 
}
 
bool shouldDisplay(int type,double balance) { 
    if (type==ZERO_BALANCE&&balance==0) return true; 
    if (type==CREDIT_BALANCE&&balance<0) return true; 
    if (type==DEBIT_BALANCE&&balance>0) return true; 
    return false; 
} 

void outputLine(int account,const string name,double balance) { 
    cout<<left<<setw(10)<<account<<setw(13)<<name<<setw(7)<<setprecision(2)<<right<<balance<<endl; 
}

int main() {  
    ifstream inClientFile("clients.dat",ios::in); 
    if (!inClientFile) { 
        cerr<<"File could not be opened"<<endl; 
        exit(1); 
    } 
    int request; 
    int account; 
    char name[30]; 
    double balance; 
    request=getRequest(); 
    while (request!= END) { 
        switch (request) { 
        case ZERO_BALANCE: 
            cout<<"\nAccounts with zero balances:\n"; 
            break;
        case CREDIT_BALANCE: 
            cout<<"\nAccounts with credit balances:\n"; 
            break; 
        case DEBIT_BALANCE: 
            cout<<"\nAccounts with debit balances:\n"; 
            break; 
        } 
        inClientFile>>account>>name>>balance; 
        while (!inClientFile.eof()) {  
            if (shouldDisplay(request,balance)) 
                outputLine(account,name,balance); 
            inClientFile>>account>>name>>balance; 
        }      
        inClientFile.clear();    // reset eof  for next input 
        inClientFile.seekg(0);   // reposition to beginning request = getRequest();     
    } cout<<"End of run."<<endl; 
    return 0; 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章