getline函数(精华版)

在我的印象中,getline函数经常出现在自己的视野里,模糊地记得它经常用来读取字符串

。但是又对它的参数不是很了解,今天又用到了getline函数,现在来细细地总结一下:

首先要明白设计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++编译器无法执行此默认转换。

下面根据一个例子简单地介绍一下该函数:

test.txt文件如下所示:

abcd

efgh

ijk

现在先尝试全局函数getline。从函数声明中我们观察到两种函数声明的不同主要体现在参

数的个数上,如果是两个参数的话,那么默认的限定符便是‘\n’了,但是如果声明了限

定符,'\n'是否仍然有效呢?我写了如下程序做测试:

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;

}

return 0;

}

输出结果是:

abcd

efg

从中可以看出换行符确实失效了。所以getline函数的限定符只有一个,是相互覆盖的。

再来看一下istream的getline函数:

int main(){

char a[3];

ifstream infile("test.txt");

infile.getline(a,3,'c');

cout<<a;

}

输出结果是a

其实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的话应该是无效的,但是输出结果跟我想的不

一样:

a

b

c

d

e

f

g

h

i

j

k

k

k

k

k

这说明第九次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;

}

的输出结果为:

abcd

efgh

ijk

ijk

ijk

ijk

不管按照我的想法还是按照对上面源码的理解,结果都不应该是这个样子。是源码错了,还

是我的理解有问题?希望知道的朋友能指导一下。

==========================================================================

好吧,可能是编译器的问题,用比的编译器编译运行了一下,结果和我的想法是一致的,跟源码所要表达的也是一致的

,所以我原先的想法是没错的,结贴啦~

所以如果你不断的从文件流中getline的话,如果你想判断是否已经达到文件结尾的话,那么只需判断getline所得到的字符串是否为

空就ok了~

再补充一下,由于getline函数将istream参数作为返回值,和输入操作符一样也把它作为判断条件。所以如果到达文件结尾的话,那么返回的文件流包含的字符为空,这个false是等价的 ,所以我们也可以用while(getline(infile,str))来对文件流是否达到结尾进行判定。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏重庆的技术分享区

关于eslint使用规则,和各种报错对应规则

58950
来自专栏猿人谷

PHP常用库函数介绍+常见疑难问题解答

      最近在苦学PHP,虽然PHP在整体功能上不如Java强大,但相比PHP而言Java算是较重量级的,所以在小中型系统的开发上,使用PHP的趋势不可挡,...

22780
来自专栏重庆的技术分享区

关于eslint使用规则,和各种报错对应规则。

ESLint 由 JavaScript 红宝书 作者 Nicholas C. Zakas 编写, 2013 年发布第一个版本。 NCZ 的初衷不是重复造一个轮子...

1.2K70
来自专栏Java帮帮-微信公众号-技术文章全总结

shell编程基础入门

shell编程基础入门 文章最后有下载shell学习指南电子书链接。 1.shell格式:例 shell脚本开发习惯 1.指定解释器 #!/bi...

35340
来自专栏潇涧技术专栏

Python Basics

1.使用glob模块可以用通配符的方式搜索某个目录下的特定文件,返回结果是一个list

10320
来自专栏老九学堂

【超全】C语言小白最容易犯的17种错误,你中了几个?

C编译的程序对语法检查并不像其它高级语言那么严格,这就给编程大佬们留下了“灵活的余地”,但还是由于这个灵活给程序的调试带来了许多不便,尤其对刚刚接触C语言的人来...

45350
来自专栏py+selenium

python爬虫笔记之re.match匹配,与search、findall区别

网上的定义【 从要匹配的字符串的头部开始,当匹配到string的尾部还没有匹配结束时,返回None;  当匹配过程中出现了无法匹配的字母,返回None。】 

2.3K30
来自专栏移动开发

java split()

java提供了split根据传入的分隔符,返回数组的方法.但是对于一些特殊字符我们要注意下,以防获取的不是我们想要的结果. 如想要根据”竖线”分隔:

15610
来自专栏前端说吧

【本周面试题】第一周

1、location对象的search属性拿到url中的查询字符串。传到我们提前封装好的getSearch数组中(或者这一步可以也封装到函数中,但那样函数就只能...

7010
来自专栏Laoqi's Linux运维专列

函数的参数

14370

扫码关注云+社区

领取腾讯云代金券