getline函数_你get了吗?— C++中get和getline的区别

985ef09120f3d098d53e56f018706791.png

在日常的编程练习中读写文件是再平常不过的场景了,虽然这算是基础中的基础了,但以前用起来总是不求甚解,有时用get有时用getline,几乎每次都是面向搜索引擎编程+能跑就行+过后就忘。。。这次整理了二者的用法和区别(其实就是抄了一下cppreference),希望加深理解,以后用的时候能信手拈来。


[What] std::basic_istream::get

首先,这里说的get指的是专门用于读取输入流的 std::basic_istream::get函数,具体一点就是 std::istream::getstd::ifstream::getstd::istrstream::getstd::iostream::getstd::fstream::getstd::strstream::get及相应的宽字符输入流类型的成员函数get。

get的作用是从输入流中读取(并释放)一个或多个字符。它有以下6种重载形式:

0a09517fe2d71038ba3ac84b1811d506.png

其中(3)(5)默认的delim'n',(4)(6)则可以指定delim

get函数的终止条件如下:

  • 读到eof:以上所有函数当读到流的结尾触发end of file条件时,会执行setstate(eofbit)
  • 读到delim:(3)(4)(5)(6)当将要读取的下一个字符c == delim时,get会将已读取到的字符串存储并从流中释放,但字符c并不会被释放。如果继续读取,第一个读取到的字符就是c
  • 读满s:(3)(4)至多读取count-1个字符存储至字符串s中,因为最后一个字符是0
  • 没读到:如果没有读出任何字符,会执行setstate(failbit)
注意:由于get默认的 delim'n',使得在读文件时的行为表现出来就是读取一行的内容,但它又不会将 'n'读出并释放,这也是容易与getline造成混淆之处。

[How] std::basic_istream::get

  • 下面演示了get函数的具体用法(基本抄了cppreference):
#include <sstream>
#include <iostream>

static void testGet() {
    std::istringstream s1("Hello, world.nCannot read");
    std::istringstream s2("123n|Cannot read");

    // [1]
    char c1 = s1.get(); // reads 'H'
    std::cout << "[1] After reading " << c1 << ", gcount() == " << s1.gcount() << 'n';

    // [2]
    char c2;
    s1.get(c2); // reads 'e'

    // [3]
    char str1[5];
    s1.get(str1, 5); // reads "llo,"
    std::cout << "[3] After reading " << str1 << ", gcount() == " << s1.gcount() << 'n';

    std::cout << c1 << c2 << str1;

    // [5] 读取剩余字符,读到 'n' 停止
    s1.get(*std::cout.rdbuf()); // reads " world."
    std::cout << "n[5] After the last get(), gcount() == " << s1.gcount() << 'n';

    // [4] 读到 '|' 停止,'|' 未释放仍保存在流中
    char str2[10];
    s2.get(str2, 10, '|'); // reads "123n"
    std::cout << "[4]After reading " << str2 << ", gcount() == " << s2.gcount() << 'n';

    // [6] 读到 '|' 停止
    s2.get(*std::cout.rdbuf(), '|'); // reads nothing
    std::cout << "[6]After the last get(), gcount() == " << s2.gcount() << 'n';
}
  • 运行结果:

d2147d3c4349976103752d27903dc1b0.png

C++中有两种getline函数:一种是std::basic_istream::getline,它与上文的get函数都是istream类的成员函数;另一种是std::getline,定义于头文件<string>中,是std命名空间的全局函数。

下面分别介绍这两种getline函数。

[What] std::basic_istream::getline

8f99b0aa8352f7708d6e15cc86042acc.png

getline的作用是从流中读取(并释放)字符串,直到遇到指定的delim。常用的就是(1),它默认的delim'n',等效于getline(s, count, widen('n'))

getline函数的终止条件如下:

  • 读到流的结尾,会执行setstate(eofbit)
  • 下一个字符c == delim,字符c会被读取并释放,但不会被存储;
  • 已经读取了count-1个字符,会执行setstate(failbit)
  • 如果没有读出任何字符(e.g.count < 1),会执行setstate(failbit)

[How] std::basic_istream::getline

#include <iostream>
#include <sstream>
#include <vector>
#include <array>

static void testGetline_istream() {
    std::istringstream input1("abcndefngh");
    std::istringstream input2("123|456|n78");
    std::vector<std::array<char, 4>> v;

    // [1]
    for (std::array<char, 4> a; input1.getline(&a[0], 4);) {
        v.push_back(a);
    }
    for (auto& a : v) {
        std::cout << &a[0] << 'n';
    }

    v.clear();

    // [2]
    for (std::array<char, 4> a; input2.getline(&a[0], 4, '|'); ) {
        v.push_back(a);
    }
    for (auto& a : v) {
        std::cout << &a[0] << 'n';
    }
}
  • 运行结果:

e9c36894e305ba6d4c6a55f5fad5c8f6.png


可以看出,getline函数并不只局限于读取一行的数据,只是默认delim'n'使得它的行为表现出来为读取一行而已。

[What] std::getline

45e6bfebf60540d6ce296d4db539663b.png

std::getline的功能是从一个输入流中读取字符串,然后存储到一个string中。常用(2)默认的delim也是'n',等效于getline(input, str, input.widen('n'))

std::getline的终止条件如下:

  • 读到流结尾,会设置eofbit并返回;
  • 下一个字符c == delim,字符c会被读取并释放,但不会被添加到str中;
  • 已经读取了str.max_size()个字符,会设置failbit并返回;
  • 如果没有读出任何字符,会设置failbit并返回。
注意:其实 std::getlinestd::basic_istream<CharT,Traits>::getline大同小异,可以看作是一个功能的两种接口形式:一个针对C++的 string类型,另一个针对C类型字符串 char *

[How] std::getline

static void testGetline_string() {
    // [2]
    std::string name;
    std::cout << "What is your name? ";
    std::getline(std::cin, name);
    std::cout << "Hello " << name << ", nice to meet you.n";

    // [1]
    std::istringstream input;
    input.str("1|2|3|4|5|6|7|");
    int sum = 0;
    for (std::string line; std::getline(input, line, '|'); ) {
        sum += std::stoi(line);
    }
    std::cout << "nThe sum is: " << sum << "n";
}
  • 运行结果:

2d0de607b6838141bd64eb741a41b260.png

[Warning] 坑点

坑点1:使用getline(s, count)时字符数组s必须要预留足够的空间!!!

上文多次提到eofbitfailbit这两种指定流状态的标志,详情可见std::ios_base::iostate。

下面使用一个例子验证已经读取了count-1个字符时,会执行setstate(failbit)

static void testGetline_failbit() {
    std::istringstream input("123|4567|89");

    // note: the following loop terminates when std::ios_base::operator bool()
    // on the stream returned from getline() returns false
    std::array<char, 4> a;
    while (input.getline(&a[0], 4, '|')) {
        // 读取 "123" 同时 '|' 也被读出
        std::cout << "After reading " << &a[0] << ", gcount() == " << input.gcount() << 'n'; // 4
        std::cout << "After reading " << &a[0] << ", tellg() == " << input.tellg() << 'n'; // 4
    }

    // 读取 "456" 时字符数组读满导致 setstate(failbit) 执行,跳出循环
    std::cout << "After reading " << &a[0] << ", gcount() == " << input.gcount() << 'n'; // 3
    std::cout << "After reading " << &a[0] << ", tellg() == " << input.tellg() << 'n'; // -1
    std::cout << "After reading " << &a[0] << ", rdstate() == " << input.rdstate() << 'n'; // 2 == failbit

    // 设置状态为 goodbit 可找到循环退出时文件流指针的确切位置
    input.clear();
    std::cout << "After clear(), tellg() == " << input.tellg() << 'n'; // 7

    // 移到指针到文件开头
    input.seekg(0, std::ios::beg);
    std::cout << "After seekg(), tellg() == " << input.tellg() << 'n'; // 0
}
  • 运行结果(具体的原因分析都已经在注释中标明):

eee1dc25445d8159c79950378c892e8d.png

坑点2:当使用流处理的方式读数据时,读到行尾并不会跳行!!!

get和getline读取的结果都是C/C++类型的字符串,有时为了读取其他类型的数据(e.g. int, double),我们通常会结合流处理的方式完成类型转换(流处理可以看作是C++类型转换的通杀法:万物转stream,stream转万物)。

但是,当使用流处理读到行尾时,它并不会主动跳转到下一行!如下面的代码示例:当读出123后,流指针还是在第一行,需要调用两次getline函数使指针跳到456所在的行。

static void testGetlineAndIstream() {
    std::istringstream input("123n***n456");
    int a, b;
    std::string str;

    input >> a;
    std::cout << "First reading from stream: " << a << 'n';
    std::getline(input, str);
    std::getline(input, str);
    input >> b;
    std::cout << "Second reading from stream: " << b << 'n';
}
  • 运行结果:

48847731b3c1c7d1b91db76290298540.png

[Summary] 三者的比较

24dacdd8336968b73ccf686485b38a5f.png

[Github] 代码

项目实例均在vs2017上测试,并上传至GitHub,将GetAndGetline设为启动项目即可复现实验结果。

[Reference] 参考

std::basic_istream<CharT,Traits>::get - cppreference.com​en.cppreference.com std::basic_istream<CharT,Traits>::getline - cppreference.com​en.cppreference.com std::getline - cppreference.com​en.cppreference.com std::ios_base::iostate - cppreference.com​en.cppreference.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值