很多时候,我们用getline函数读取文本文件的每一行,留待以后进行处理(如:用istringstream对象解析行中的内容),代码片段如下:
ifstream in ("Test.txt");
if (!in)
return ;
不过最近在处理一个文本的时候,发现getline读取到文件中间的某一行,就返回false了。不能读取完整个文件。我到文本文件的那一行用UE去看,发现很多乱码,显示如下:string sLine;
while (getline (in, sLine))
{
// do something......
}
&_N??/f?:N?l??JT???I{?_ @b???*N?NMbOUS@w0
debug进入到getline函数内部,看到代码如下:
代码逻辑很简单,就是一个for循环,不断读取字符,并做判断,分以下四种情况:
const typename _Traits::int_type _Metadelim =
_Traits::to_int_type(_Delim);
typename _Traits::int_type _Meta = _Istr.rdbuf()->sgetc();
for (; ; _Meta = _Istr.rdbuf()->snextc())
if (_Traits::eq_int_type(_Traits::eof(), _Meta))
{ // end of file, quit
_State |= ios_base::eofbit;
break;
}
else if (_Traits::eq_int_type(_Meta, _Metadelim))
{ // got a delimiter, discard it and quit
_Changed = true;
_Istr.rdbuf()->sbumpc();
break;
}
else if (_Str.max_size() <= _Str.size())
{ // string too large, quit
_State |= ios_base::failbit;
break;
}
else
{ // got a character, add it to string
_Str += _Traits::to_char_type(_Meta);
_Changed = true;
}
_CATCH_IO_(_Istr)
}
1. 遇到文件结尾(end of file),设置状态为ios_base::eofbit后返回
2. 遇到分隔符(delimiter),丢弃掉分隔符,并break出循环
3. 当字符串太大,无法存储,设置状态ios_base::failbit后返回
4. 否则,将字符attach到字符串尾部,继续循环。
debug发现,getline是在情况1的时候退出的,就是遇到了文件尾。
接下来的问题,文件尾是什么?上网上看了下,文本文件没有一个特定的字符来表示文件末尾,不像c字符串用'\0'来表示文件尾。文件尾字符的存在没有必要,因为文件对象已经记录了文件的大小。在读取的时候,随时判断是否达到了文件的大小;如果达到了,就返回EOF。EOF在VS中用宏在stdio.h中定义为-1,代码如下:
#define EOF (-1)
上面代码中_Traits::eof()就是返回EOF的值。
分析原因,是在文本文件流中有一个字符的值是-1(实际值是255,对应blank 'FF',在linux的terminal中,ctrl+D能够在流中插入文件尾,表示用户结束屏幕输入),这个字符被正常独取出来,而读取的总字符数也没有达到文件大小,但是getline在这个字符和EOF比较中,发现两者相等,就认为读取到了文件尾,就结束读取,设置流的相关状态,blablalblaa...,以后的都知道了。
那么这个问题如何解?网上给了一个方法:用二进制形式读取。因为对于文本文件,-1意味着文件尾;而对于二进制文件,-1并不是文件尾。而且,看getline源代码,可以在二进制模式下工作。
修正上面的读取代码为:
ifstream in ("Test.txt", ios_base::binary);
if (!in)
return ;
string sLine;
while (getline (in, sLine))
{
// do something......
}
读取成功,问题解决。
接下来,我又将读取的内容写到文本文件中,代码如下:
ifstream in ("Test.txt", ios_base::binary);
ofstream out ("Test2.txt")
if (!in || !out)
return ;
string sLine;
while (getline (in, sLine))
{
out << sLine <<"\n";
}
用文本文件方式读取,依然出问题;用二进制ios_base::binary读取,依然没问题,呵呵。看来输出流是把输入流(无论是文本形式还是二进制形式)中的内容原原本本地写到文件中了,这中间没有任何过滤行为。
OK,问题解决,写完。