C++ getline 读取字符串

从键盘读取文本时,如果输入的内容包含空格,比如英文姓名 "Jack Smith" 或短句 "Hello lvyenet",直接使用 std::cin >> 只能读到第一个空格前面的单词。

如果想要把包含空格的一整行内容完整读取出来,在 C++ 中可以使用 <string> 头文件为 std::string 提供的 std::getline() 函数。

cin >> 读取字符串的局限

在学习 std::getline() 之前,我们先来看看使用 std::cin 读取字符串时的具体表现。

对于字符串来说,>> 运算符是按 “空白字符” 来切分输入内容的,比如普通空格,以及换行符 \n、制表符 \t 等转义字符。一旦在读取过程中遇到空白字符,std::cin >> 就会认为当前这个字符串已经读完了,并立即停止读取。

示例 1:cin >> 遇到空格停止

#include <iostream>
#include <string>

int main() {
    std::string text;
    std::cout << "请输入一句话:";
    std::cin >> text;
    std::cout << text << '\n';
    return 0;
}

运行之后,当我们输入 “Hello lvyenet”,其输出结果如下。

请输入一句话:Hello lvyenet
Hello

分析:

在这个例子中,虽然我们在键盘上输入的是 "Hello lvyenet",但变量 text 最终只拿到了 "Hello"。

这是因为 std::cin >> 读到第一个空格时就停止读取了,空格后面的 "lvyenet" 则被留在了输入缓冲区里。因此,std::cin >> 只适合读取不带空格的单个词,无法读取包含空格的整行句子。

C++ getline() 读取整行

为了解决遇到空格就被截断的问题,<string> 头文件提供了 std::getline() 函数。它会从输入流中连续读取所有字符,包括句子中间的空格,一直读到按下 Enter 键产生的换行符 \n 为止。

语法:

std::getline(std::cin, 字符串变量);

说明:

其中,getline 是 “get line(获取一行)” 的意思,调用时需要传入以下 2 个参数:

  • std::cin(必选):表示从键盘标准输入读取数据,需要引入 <iostream> 头文件。
  • 字符串变量(必选):用来保存读取结果的 std::string 变量,需要引入 <string> 头文件。

需要注意的是,std::getline() 读到行尾时,会自动把末尾的换行符 \n 从输入流中取走并丢弃,不会把换行符存进字符串变量里。

示例 2:读取带空格的整行文本

#include <iostream>
#include <string>

int main() {
    std::string text;
    std::cout << "请输入一句话:";
    std::getline(std::cin, text);
    std::cout << text << '\n';
    return 0;
}

运行之后,当我们输入 “Hello lvyenet”,其输出结果如下。

请输入一句话:Hello lvyenet
Hello lvyenet

分析:

std::getline(std::cin, text); 表示把整行输入全部读入 text 中,两个单词中间的空格也完整地保留了下来。

除了能读取空格之外,std::getline() 还支持直接读取空行。如果我们在输入时什么文字都不写,直接按下 Enter 键,它会立即结束读取并得到一个空字符串 "",而不会像 std::cin >> 那样一直停在终端等待输入。下面再来看一个例子。

示例 3:getline() 读取空行

#include <iostream>
#include <string>

int main() {
    std::string remark;
    std::cout << "请输入备注:";
    std::getline(std::cin, remark);
    std::cout << "[" << remark << "]" << '\n';
    return 0;
}

运行之后,当我们直接按下 Enter 键,其输出结果如下。

请输入备注:
[]

分析:

直接按下 Enter 键时,输入流里只有一个换行符 \n。std::getline() 遇到这个换行符后,认为当前这一行已经读完了,于是把空字符串 "" 存入 remark 中,两侧加上中括号后的输出结果为 []。

cin >> 与 getline() 混用问题

单独使用 std::getline() 时非常简单。初学的小伙伴最容易踩坑的地方,是前面先使用了 std::cin >> 读取数字或单词,紧接着又在后面调用 std::getline()。

1. 为什么 getline() 会被直接跳过?

当我们把 std::cin >> 和 std::getline() 直接连在一起写时,运行程序往往会出现一个奇怪的现象:输入完第一个数据按下回车后,程序根本不等我们输入后面的字符串,就直接运行结束了。请看下面例子。

示例 4:混用 cin >> 与 getline()

#include <iostream>
#include <string>

int main() {
    int age = 0;
    std::string name;

    std::cout << "请输入年龄:";
    std::cin >> age;

    std::cout << "请输入姓名:";
    std::getline(std::cin, name);

    std::cout << "年龄:" << age << '\n';
    std::cout << "姓名:[" << name << "]" << '\n';
    return 0;
}

运行之后,当我们输入 “20” 并按下 Enter 键,其输出结果如下。

请输入年龄:20
请输入姓名:年龄:20
姓名:[]

分析:

咦?为什么还没来得及输入姓名,name 就直接变成空字符串了,而且输出的 “年龄:20” 还跟 “请输入姓名:” 挤在了同一行呢?

这其实跟临时存放键盘输入内容的 “输入缓冲区” 有关。我们按执行顺序来看看缓冲区里发生了什么:

  • 输入数据:我们在键盘上输入 20 并按下 Enter 键,此时输入缓冲区里实际存入的是 "20\n"。
  • 执行 cin >>:std::cin >> age 只负责把前面的整数 20 读走,而按下 Enter 键产生的换行符 '\n' 依然残留在输入缓冲区里。
  • 执行 getline():紧接着调用 std::getline(std::cin, name) 时,它直接遇到了上一行残留的 '\n',误以为我们已经按下了回车。于是它读出空字符串 "" 并立即结束,没有停下来等待输入。

正因如此,终端没有停顿换行,后面的 “年龄:20” 直接紧贴着 “请输入姓名:” 打印在了同一行。整个读取与换行符残留的过程如下图所示。

cin 残留换行符导致 getline 读取空字符串

2. 使用 cin.ignore() 清除残留换行符

弄清楚原因之后,解决办法就很简单了:在 std::cin >> 和 std::getline() 之间,调用 std::cin.ignore() 把输入缓冲区里残留的换行符清理掉即可。

语法:

std::cin.ignore(最大字符数, 停止字符);

说明:

其中,ignore 在英文中是 “忽略、丢弃” 的意思。std::cin.ignore() 会从输入缓冲区中依次丢弃多余的字符:只要丢弃的数量达到设定的上限,或者中途遇到并丢弃了指定的停止字符,就会立即停止清理。

平时清理当前行残留的换行符时,我们一般写成 std::cin.ignore(1000, '\n')。第 1 个参数只是设定一个足够大的清理上限,第 2 个参数 '\n' 才是真正的停止标志,所以平时写 100 或 1000 效果都是一样的。

示例 5:清除残留的换行符

#include <iostream>
#include <string>

int main() {
    int age = 0;
    std::string name;

    std::cout << "请输入年龄:";
    std::cin >> age;

    // 清除当前行残留的换行符
    std::cin.ignore(1000, '\n');

    std::cout << "请输入姓名:";
    std::getline(std::cin, name);

    std::cout << "年龄:" << age << '\n';
    std::cout << "姓名:[" << name << "]" << '\n';
    return 0;
}

运行之后,当我们依次输入 “20” 和 “Jack Smith”,其输出结果如下。

请输入年龄:20
请输入姓名:Jack Smith
年龄:20
姓名:[Jack Smith]

分析:

在这个例子中,std::cin.ignore(1000, '\n'); 会把输入缓冲区里上一行残留的换行符 '\n' 丢弃掉。缓冲区清理干净后,后面的 std::getline() 就能正常等待我们输入 "Jack Smith" 了。

在清理残留换行符时,有时大家也会看到不传参数的写法:

// 写法 1
std::cin.ignore();

// 写法 2(推荐)
std::cin.ignore(1000, '\n');

其中,写法 1 默认只丢弃一个字符,而写法 2 会一直清理到当前行的换行符为止。如果用户在输入数字 20 后直接按了回车,两种写法的效果是一样的。

但如果用户在输入 20 后不小心多敲了几个空格才按回车,写法 1 只会丢掉第一个空格,而写法 2 能把多余的空格连同换行符一起清理干净。因此在实际开发中,我们更推荐使用写法 2。

注意

混用 cin >> 与 getline() 时:只有前面刚使用过 std::cin >>,紧接着又要调用 std::getline() 时,中间才需要加上 std::cin.ignore(1000, '\n') 清理残留的换行符。

连续调用多次 getline() 时:由于每次 std::getline() 都会自动把行尾的换行符取走丢弃,两次调用之间千万不要加 ignore(),否则反而会把下一行输入的开头字符吞掉。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号