C++中get和getline函数的用法区别

5/12/2020 C++

get和getline函数

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

# [What] std::basic_istream<CharT,Traits>::get

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

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

get_cppreference

其中(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<CharT,Traits>::get

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

static void testGet() {
    std::istringstream s1("Hello, world.\nCannot read");
    std::istringstream s2("123\n|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 "123\n"
    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';
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  • 运行结果:

    get_result

两种getline函数

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

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

# [What] std::basic_istream<CharT,Traits>::getline

getline_istream_cppreference

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<CharT,Traits>::getline

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

static void testGetline_istream() {
    std::istringstream input1("abc\ndef\ngh");
    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';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  • 运行结果:

    getline_istream_result

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

# [What] std::getline

getline_string_cppreference

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";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • 运行结果:

    getline_string_result

# [Warning] 坑点

坑点1

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

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

下面使用一个例子验证已经读取了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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  • 运行结果(具体的原因分析都已经在注释中标明):

    getline_failbit_result

坑点2

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

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

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

static void testGetlineAndIstream() {
    std::istringstream input("123\n***\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';
}
1
2
3
4
5
6
7
8
9
10
11
12
  • 运行结果:

    getline_and_istream

# [Summary] 三者的比较

# std::basic_istream<CharT,Traits>::get std::basic_istream<CharT,Traits>::getline std::getline
头文件 <iostream>/<sstream>/<fstream> <iostream>/<sstream>/<fstream> <string>
输出对象 C类型字符串 C类型字符串 C++string对象
释放delim ✔️

# [Github] 代码

项目实例均在vs2017上测试,并上传至GitHub (opens new window)

# [Reference] 参考

std::basic_istream<CharT,Traits>::get (opens new window)

std::basic_istream<CharT,Traits>::getline (opens new window)

std::getline (opens new window)

std::ios_base::iostate (opens new window)