在之前的文件流學習中,我們重點解決了文件讀入和輸出的問題,今天我們就接着上次的話頭繼續咯。
文件位置指針
之前討論的讀寫操作,都是 " 從頭開始 " 的操作:從首位開始讀入數據,從首位(如果沒有特殊聲明會將文件中原有的數據清空)開始寫入數據
如果我們要定義特殊的起始位置進行文件讀寫呢?
指向下一個將要讀或寫的字節位置
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;
}