getline函数详解

尊重原创:

http://blog.csdn.net/laomai/article/details/6703173

http://blog.csdn.net/yelbosh/article/details/7483521

[cpp]  view plain copy
  1. ifstream in("file3.txt")  
  2. char buf[3];  
  3. while(!in.eof())   
  4.  {  
  5.        in.getline(buf,  sizeof(buf));  
  6.     cout<<buf<<endl;  
  7. }  

file3.txt的内容如下:

[cpp]  view plain copy
  1. abc  
  2. efg  

在执行的时候程序输出一个ab之后便进入了死循环。原因何在?

getline成员函数的声明如下

basic_istream<Elem, Tr>&

[cpp]  view plain copy
  1. getline(  
  2.      char_type *_Str,   
  3.      streamsize _Count,   
  4.       char_type _Delim  
  5. );  
第一个参数是字符缓冲区地址,第二个是缓冲区长度,第三个是分隔符(默认是回车)。

其实现的大致流程是:

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函数不进行读取操作,如是反复,便导致了死循环。

知道了病因,于是便有了下面的解决方案

[cpp]  view plain copy
  1. while(!in.eof())  
  2. {  
  3.     in.getline(buf,sizeof(buf));//将  
  4.     if(in.fail() && in.gcount()==(sizeof(buf)-1))    
  5.            in.clear();  
  6.        cout<<buf<<endl;  
  7. }  
这里的clear成员函数的作用是将in对象的状态设回正常状态。gcount函数返回上次读操作中从输入流中提取的字符数(包括分隔符)。


首先要明白设计getline函数的目的,其实很简单,就是从流中读取字符串。而且读取的方式有很多,包括根据限定符,根据已读取的字符的个数。
从这个函数的名称来看,它的直观意义是从流中读取一行,但是大家不要被这表面的现象所迷惑。其实如果让我来为这个函数去一个名字的话,
或许我会取一个getString,因为它的目的本来就是从流中读取字符的序列,而不是像get函数那样一次读取一个字符。另外要注意, C++中有两个getline函数,
一个是在string头文件中,定义的是一个全局的函数,函数声明是istream& getline ( istream& is, string& str, char delim )与
istream& getline ( istream& is, string& str );另一个则是istream的成员函数,函数声明是istream& getline (char* s, streamsize n )与istream& getline (char* s, streamsize n, char delim );注意第二个getline是将读取的字符串存储在char数组中而不可以将该参数声明为string类型,因为C++编译器无法执行此默认转换
其实istream的getline是在全局函数的getline函数的基础上,又多了一个终止读取的条件,即根据已读取的字符的个数来判定,
实际上是读取n-1个字符,因为最后要为‘\0’留下一个位置。其他地方二者基本相同。原理想必也很简单。每一次getline,文件指针都不断向下走,
相当于不断的调用get函数并且将已经读取的字符保存下来。当遇到限定符或者已读取的字符个数达到了参数的要求(
或者是由于文件的原因),那么便终止读取。如果是碰到了限定符,那么该字符便会被 extracted and discarded,也就是文件指针向下再移一位,
但是并不保存该字符,也就是每次getline之后,文件指针会停留在限定符的后面(遇到限定符的情况)。
但是看下面的这个情况:
int main(){
int n = 13;
string tem;
ifstream infile("test.txt");
for(int i = 0;i<n;i++){
//getline(infile,tem);
getline(infile,tem,'\t');
cout<<tem<<endl;
}
return 0;
}
按照我的理解的话,那么文件中总共11个字母,当文件指针停在‘\t’之后,k之前的时候,刚好是第八次,第九次getline的时候,由于在读过k之后,遇到了文件结束符,所以get
指针应该停留在k之后,这个时候再getline的话应该是无效的,但是输出结果跟我想的不这说明第九次getline之后,get指针所指向的位置并没有改变,这说明我想的思路有问题
,于是我在网上看了getline函数的源码,其中有一篇注释比较好的:
_Myt& getline(_Elem *_Str, streamsize _Count, _Elem _Delim)   
{// get up to _Count characters into NTCS, discard _Delim   
    _DEBUG_POINTER(_Str);    //判断传入指针的合法性  
    ios_base::iostate _State = ios_base::goodbit;    
    _Chcount = 0; //从输入流中读取的字符数  
    const sentry _Ok(*this, true);  
    /*注:上面这句很关键,它关系到下面的if是否执行,也就是是否读输入流。这句从
语法上看,是 
    sentry是一个class, _Ok是sentry类的一个const对象,构造这个对象时需要传入两个
参数 
    第一个是流对象自身的引用,第二个表示对空白字符(如空格、制表符)的处理方式
,为true时意味着不忽略空白字符,即一个字符一个字符的从输入流中提取。 
    */  
    if (_Ok && 0 < _Count)   
    /* 
************************************************************************** 
    * sentry类内部重载了一个类型转换运算符,它把sentry类的实例转换成了一个bool表达式。 
    * 这个表达式返回sentry类的私有成员_Ok的值。 
    bool sentry::operator bool() const 
    * { // test if _Ipfx succeeded 
    *       return (_Ok); 
    *   } 
    * _Ok这个成员的值由sentry类的构造函数 
    * 在初始化时设置,设置的过程比较麻烦,这里不做赘述(其实我也没看十分明白)。 
    * 但可以肯定的是,当输入流的状态是正常时,这个成员的值也是true, 
    * 反之,则是false。  
    *  
    * _Count是调用者传入的第二个参数,这里用做循环计数器的初值,以后每读一个字
符, 
    * _Count的值会减一。 
****************************************************************************
**/  
    {  
    // state okay, use facet to extract   
    int_type _Metadelim = _Traits::to_int_type(_Delim);   
    int_type _Meta = _Myios::rdbuf()->sgetc();//从输入流读一个字符   
    for (; ; _Meta = _Myios::rdbuf()->snextc()) //snextc()从输入流中读取下一
个字符  
        if (_Traits::eq_int_type(_Traits::eof(), _Meta))   
              {// end of file, quit   
                _State |= ios_base::eofbit;   
                break;   
               }//注:遇到文件尾,getline结束   
        else if (_Meta == _Metadelim) {  
             // got a delimiter, discard it and quit   
            ++_Chcount;    //读取字符数+1  
            _Myios::rdbuf()->sbumpc();  
            /*注:上面这句把结束符读掉了,如果不指定结束符,那就是把'\n'读掉了。  
            但回车符本身并没有拷贝到缓冲区中, 
            这样下次的读操作将从回车符后面的第一个字符开始, 
            */  
            break;   
        }/* 注:遇到结束符,getline结束,注意这里的顺序,它是先判断是否遇到结束符,后判断是否读入了指定个数的。 */  
        else if (--_Count <= 0)   
        {// buffer full, quit   
            _State |= ios_base::failbit;   
            break;   
        }  
        //注:读到了指定个数,执行到这里已经隐含了在指定个数的最后一位仍然不是结束符,  
        //因此该部分将输入流状态置为了错误。  
        //这直接导致了接下来的getline(或者get)以及>>运算符等读操作都不能正确执行)   
        else {  
            // got a character, add it to string   
            ++_Chcount;  //读取字符数加1  
            *_Str++ = _Traits::to_char_type(_Meta);   
        }//注:这一分支将读取到的单个字符拷贝到缓冲区中  
    }   
    *_Str = _Elem();  //  
    /* add terminating null character /*注:前面这句为字符串加入了终止符'\0' 
    因为_Elem()构造了一个ascii码为0的字符对象*/  
    _Myios::setstate(_Chcount == 0 ? _State | ios_base::failbit : _State);  
    /*注:如果没有读入任何字符,要保持执行这一次getline之前的输入流状态, 
    否则根据这一次getline执行的情况,设置输入流为相应状态。 */  
    return (*this);   //返回输入流对象本身  
}   
但是我觉得这其中还是有问题,因为:
sbumpc: advances the get pointer and returns the character pointed by it 
before the call.
snextc: advances the get pointer and returns the character pointed by it 
after the call.
由于是传引用,所以不论调用哪个,都会改变原文件流中get的指针所指向的位置。而且,
告诉大家一个更为惊奇的结果便是:
int main(){
int n = 6;
string tem;
ifstream infile("test.txt");
for(int i = 0;i<n;i++){
getline(infile,tem);
//getline(infile,tem,'\t');
cout<<tem<<endl;
}
return 0;
}
的输出结果为:
a b c d
e f g h
i j k
i j k
i j k
i j k
好吧,可能是编译器的问题,用比的编译器编译运行了一下,结果和我的想法是一致的,跟源码所要表达的也是一致的
所以如果你不断的从文件流中getline的话,如果你想判断是否已经达到文件结尾的话,那么只需判断getline所得到的字符串是否为
空就ok了~
再补充一下, 由于getline函数将istream参数作为返回值,和输入操作符一样也把它作为判断条件。所以如果到达文件结尾的话,那么返回的文件流包含的字符为空,这个false是等价的 ,所以我们也可以用while(getline(infile,str))来对文件流是否达到结尾进行判定。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值