在之前的文件流学习中,我们重点解决了文件读入和输出的问题,今天我们就接着上次的话头继续咯。
文件位置指针
之前讨论的读写操作,都是 " 从头开始 " 的操作:从首位开始读入数据,从首位(如果没有特殊声明会将文件中原有的数据清空)开始写入数据
如果我们要定义特殊的起始位置进行文件读写呢?
指向下一个将要读或写的字节位置
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); //定位在文件结束位置
注:
seekg(n)
中设定的n是指针的偏移量,表示指针从当前位置向后移动多少位,所以显示在文本中是第n+1个字符的位置- 流的指针位置类型
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 ) 方式打开文件,则读&&写都不会进行这样的转换
使用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;
}