今天一个学生写了如下代码段,其目的将一个文本文件的内容输出到屏幕上。
ifstream in("file3.txt")
char buf[3];
while(!in.eof())
{
in.getline(buf, sizeof(buf));
cout<<buf<<endl;
}
file3.txt的内容如下:
abc
efg
在执行的时候程序输出一个ab之后便进入了死循环。原因何在?
经过一个小时的查资料和看代码,终于大致搞清问题所在,与getline的实现机制有关。
getline成员函数的声明如下
basic_istream<Elem, Tr>&
getline(
char_type *_Str,
streamsize _Count,
char_type _Delim
);
第一个参数是字符缓冲区地址,第二个是缓冲区长度,第三个是分隔符(默认是回车)。
其实现的大致流程是:
1、首先判断istream的failbit位是否为1,为1的话意味着输入流的状态有错误,则不进行读操作,getline函数结束执行
2、从当前位置开始从输入流中依次读取单个字符并拷贝到缓冲区,直到遇到下列条件满足时,循环结束。
(1)遇到文件尾时停止读操作,并设置流对象的结束标记为1
(2)读到调用者指定的分隔符时,此时将分隔符之前的字符拷贝到缓冲区中,但分隔符本身不拷贝进去,并且下次读操作将从分隔符后的下一个字符开始。
(3)已经读了n-1个字符(n是调用者传入的第二个实参_Count的初值),此时要把流对象的错误标志位置1(为什么要这么干,我也不知道,个人觉得这么设计不太合理....)
当循环结束后,gelline函数会在字符串的尾部加一个C风格的结束符'\0'。
这样,学生遇到的现象就可以解释了。
首先,由于file3.txt文件是存在的,所以in对象开始时的状态是正常的,因此第一次getline将会执行,由于缓冲区的长度是3,因此在读完ab两个字符之后getline内部的循环便终止了。此时getline会把in对象的failbit设为1,但文件还未读到尾部,所以in.eof()为false,这样在第二次进入while循环体时,循环条件!in.eof()为true,于是继续执行getline函数
,但是由于第一次的getline操作已经把in对象的failbit设为1,第二次的getline便不进行任何读操作了,此时流的指针和流的状态均未发生变化,于是第三次循环时与第二次循环一样,循环条件为真,可以进入循环体,in对象的failbit设为1,getline函数不进行读取操作,如是反复,便导致了死循环。
知道了病因,于是便有了下面的解决方案
while(!in.eof())
{
in.getline(buf,sizeof(buf));//将
if(in.fail() && in.gcount()==(sizeof(buf)-1))
in.clear();
cout<<buf<<endl;
}
这里的clear成员函数的作用是将in对象的状态设回正常状态。gcount函数返回上次读操作中从输入流中提取的字符数(包括分隔符)。
请读者自行分析和改进这个解决方案。