這樣的文件操作有點玄——文件流學習 ( 二 )

在之前的文件流學習中,我們重點解決了文件讀入和輸出的問題,今天我們就接着上次的話頭繼續咯。


文件位置指針

之前討論的讀寫操作,都是 " 從頭開始 " 的操作:從首位開始讀入數據,從首位(如果沒有特殊聲明會將文件中原有的數據清空)開始寫入數據

如果我們要定義特殊的起始位置進行文件讀寫呢?

指向下一個將要讀或寫的字節位置

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; 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章