读取一整行
cin >> text 只读取一个单词:遇到第一个空格就停止。要读取用户在按下 Enter 之前输入的全部内容(包括空格),请使用 <string> 头文件中的 std::getline:
#include <iostream>
#include <string>
int main() {
std::string name;
std::cout << "Full name: ";
std::getline(std::cin, name);
std::cout << "Hello, " << name << "!\n";
}
输入 Ada Lovelace 会打印 Hello, Ada Lovelace!。如果用 std::cin >> name,它会打印 Hello, Ada!,而 Lovelace 还留在输入中等待读取。
函数签名如下:
std::istream& std::getline(std::istream& input, std::string& text);
std::istream& std::getline(std::istream& input, std::string& text, char delim);
input可以是任何输入流:std::cin、用std::ifstream打开的文件,或由字符串构造的std::istringstream。text的内容会被读到的字符替换(而不是追加)。delim是结束读取的字符,默认为'\n'。分隔符会从流中移除,但不会存入text。- 返回值是流本身,因此可以直接在条件中判断它。
由于 getline 在所有输入流上的用法都一样,本页可运行的示例都从保存固定文本的 std::istringstream 中读取。这样不用输入任何内容,输出也是确定的。如果想改为从键盘读取,把流变量换成 std::cin 即可。
输出:
[Ada Lovelace]
[Alan Turing]
方括号表明换行符不属于任何一个字符串。
cin >> 之后 getline 返回空字符串
这是 getline 最常见的问题。你先用 >> 读取一个数字,再用 getline 读取一行,结果 getline 好像被跳过了:
输出:
age: 30
city: []
input >> age 读取字符 30,在第一个不可能属于数字的字符处停下,也就是换行符。这个换行符留在了流中。随后 getline 读到下一个换行符为止,而它立刻就遇到了一个,于是返回空字符串。如果用的是 std::cin,表现就是程序根本没有停下来等你输入第二个答案。
有两种标准的修复方法,都放在 >> 和 getline 之间。
方法 1:用 ignore 丢弃这一行剩下的内容。
输出:
age: 30
city: [Paris]
ignore(n, '\n') 会一直丢弃字符,直到移除了一个换行符或已丢弃 n 个字符,以先到者为准。传入 std::streamsize 的最大值,意思就是“一直丢到换行符,不管它有多远”。它还会移除用户在同一行数字后面输入的其他内容,比如末尾的空格。从键盘读取时,这一行写作 std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');,并且需要 #include <limits>。
不带参数的 std::cin.ignore() 只丢弃一个字符。当换行符紧跟在数字后面时它能用,但只要数字和换行符之间多了一个空格就会失效。
方法 2:用 std::ws 跳过开头的空白字符。
输出:
age: 30
city: [Paris]
std::ws 是一个操纵符,它会吞掉当前位置的所有空白字符(空格、制表符、换行符)。input >> std::ws 返回流本身,所以可以直接传给 getline。它还会跳过空行和开头的空格,读取姓名、城市时很方便,但如果开头的空格或空行本身就是有意义的输入,这样做就错了。这种情况下请用 ignore。
一直读取到输入结束
getline 返回流本身,而读取失败后流会转换为 false。没有更多行时 getline 就会失败,所以用 while 循环包住它,就能读完每一行并在末尾停下:
输出:
1: [apples 3]
2: [bread 1]
3: []
4: [milk 2]
read 4 lines
从这段输出可以看出两个细节:
- 空行也算一次成功的读取。它得到一个空字符串,循环继续。只有到达输入末尾才会让循环停止。
- 最后一行末尾不需要换行符。如果文本以
milk 2结尾且没有\n,getline仍然会返回这一行,下一次调用才会失败。
对 std::cin 使用同样的循环,会一直读到输入结束。输入结束的情况包括:从文件重定向输入(./program < data.txt),或者用户在 macOS 和 Linux 上按 Ctrl+D,在 Windows 上按 Ctrl+Z 再按 Enter。
不要使用 while (!input.eof()) { std::getline(input, line); ... } 这种写法。eof() 只有在某次读取已经碰到末尾之后才变为 true,所以循环体会多执行一次,此时读取失败,line 还是旧值。应该把 getline 调用直接放进条件里。
逐行读取文件
读取文件用的是同样的循环。用 <fstream> 中的 std::ifstream 打开文件,检查是否打开成功,然后读取:
输出:
1. Ada 92
2. Grace 88
3. Alan 95
if (!file) 能在循环之前发现文件不存在或路径写错的情况。如果没有这个检查,循环只会读到零行,看起来像是一个空文件,而不是一个错误。file 在 main 结束时离开作用域,文件会自动关闭。
在 Windows 上保存的文件,每行以 \r\n 结尾。在 Linux 或 macOS 上读取这样的文件时,getline 会移除 \n 但保留 \r,于是每一行末尾都多了一个看不见的回车符,导致 line == "done" 这样的比较失败。读取之后把它去掉:
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
使用自定义分隔符
第三个参数决定用哪个字符结束每次读取。常见的用法是按逗号拆分一行。先读取这一行,再通过 std::istringstream 拆分它:
输出:
fields: 3
[Ada Lovelace]
[36]
[London]
逗号会从流中移除且不会被保存,和默认形式中换行符的处理方式一样。注意,使用自定义分隔符时,换行符就是普通字符。如果直接对多行文件调用 std::getline(file, field, ','),一行的最后一个字段会和下一行的第一个字段连在一起返回,比如 "London\nGrace Hopper"。每次读取一行、再分别拆分每一行,就能避免这个问题。字符串流页面更详细地介绍了 std::istringstream,包括如何把 "36" 这样的字段转换成数字。
std::getline 与 cin.getline
还有一个名字相近的成员函数:
char buffer[20];
std::cin.getline(buffer, 20); // member function, fixed-size char array
std::string text;
std::getline(std::cin, text); // free function, std::string
cin.getline 写入一个 char 数组,最多存储 size - 1 个字符,外加结尾的 '\0'。如果一行更长,流会进入失败状态,之后的每次读取都会被跳过,直到你调用 std::cin.clear()。使用 std::string 的 std::getline 则不需要操心长度限制。除非你在使用要求 char 数组的 API,否则请选用它。
常见错误
- 混用
>>和getline,却没有清除换行符。 在两者之间加上ignore(...)或使用std::ws。输入和输出页面展示了std::cin中的同一个问题,以及数字读取失败后如何恢复。 - 用
int调用getline(std::cin, number)。getline只能读入std::string。先读取这一行,再用std::stoi或std::istringstream转换。 - 以
eof()作为循环条件。 直接判断getline调用本身:while (std::getline(input, line))。 - 忘记
#include <string>。 用于字符串的std::getline声明在<string>中。有些编译器通过<iostream>间接包含了它,这样的代码换一个编译器就会报错。 - 没有检查文件是否打开成功。 路径错误时
std::ifstream会静默失败,读取循环一次也不执行。
常见问题
C++ 中 getline 是做什么的?
std::getline(stream, text) 从 stream 读取字符到 std::string 类型的 text 中,直到遇到换行符,然后把这个换行符从流中移除,但不存入字符串。和 cin >> text 不同,它会保留空格,所以能读取一整行,比如完整的姓名或一句话。
为什么在 cin >> 之后 getline 会被跳过?
cin >> number 读到你在数字后面敲的换行符就停下,并把这个换行符留在输入缓冲区里。接下来的 getline 立刻遇到它,于是返回空字符串。先用 std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); 丢掉它,或者调用 std::getline(std::cin >> std::ws, text) 跳过开头的空白字符。
如何让 getline 使用换行符以外的分隔符?
把分隔符作为第三个参数传入:std::getline(stream, field, ',')。它会读到下一个逗号,保存逗号之前的文本,并丢弃这个逗号。此时换行符被当作普通字符,所以通常会先读一行,再配合由这一行构造的 std::stringstream 使用。
C++ 中如何逐行读取文件?
用 std::ifstream file("data.txt"); 打开文件,然后循环 while (std::getline(file, line)) { ... }。getline 返回流本身,没有更多行时流会转换为 false,所以循环在文件末尾停止。
cin.getline 和 getline 有什么区别?
std::getline(std::cin, text) 是一个自由函数,读入 std::string,字符串会自动增长以容纳任意长度的行。std::cin.getline(buffer, size) 是成员函数,读入固定大小的 char 数组,行长度超过 size - 1 个字符时会失败。除非你有理由使用原始数组,否则请用 std::getline 配合 std::string。