问题代码
学习文件读写时,写了下面这段 demo
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
//以文本文件方式写入
void test01()
{
cout << "<< 写入文件>>" << endl;
int n;
cout << " 要输入的行数:";
cin >> n;
ofstream ofs;
ofs.open("test.txt", ios::out); //创建 test.txtA
string buf;
for (int i = 1; i <= n; i++)
{
cout << i << ":"; //打印当前所在行号
getline(cin, buf); //输入内容
ofs << buf <<endl; //写入文件
}
ofs.close();
}
//以文本文件方式读取
void test02()
{
cout << "\n<< 读取文件>>" << endl;
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << " 文件打开失败!" << endl;
}
string buf;
int i = 1;
while (getline(ifs, buf))
{
cout << i++ << ":";
cout << buf << endl;
}
ifs.close();
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
代码本身没有语法问题,但运行结果如下:
当输入行号后,第一行自动读入了
空内容,直接跳转到第二行
问题排查
首先想到的就是第一次循环自动读入了一个换行符,因为在实际输入内容之前,要先输入行数然后回车。
验证的办法也很简单,把指定行数改成固定行数,去掉实际内容前的行数输入,也即是把 test01()
函数改成如下:
void test01()
{
cout << "<< 写入文件>>" << endl;
ofstream ofs;
ofs.open("test.txt", ios::out);
string buf;
for (int i = 1; i <= 2; i++) //直接指定 2 行
{
cout << i << ":"; //打印当前所在行号
getline(cin, buf); //输入内容
ofs << buf <<endl; //写入文件
}
ofs.close();
}
此时运行结果如下:
此时第一行可以正常输入,显然问题就出在
getline()
之前的那次
cin
输入
此外,将 getline(cin,buf) 换用 cin>>buf 实现,也可以正常输入 (只是 cin 无法正常读取空格)
问题解决
在确定问题后,就去百度了 cin 的详解,在 CSDN 的一篇文章中找到了如下解释:
程序的输入都有一个缓冲区,即输入缓冲区。一次输入过程是这样的,当一次键盘输入结束时会将输入的数据存入输入缓冲区,而 cin 对象直接从输入缓冲区中取数据。正因为 cin 对象是直接从缓冲区取数据的,所以有时候当缓冲区中有残留数据时,cin 对象会直接取得这些残留数据而不会请求键盘输入。
当 cin>> 从缓冲区中读取数据时,若缓冲区中第一个字符是空格、tab 或换行这些分隔符时,cin>> 会将其忽略并清除,继续读取下一个字符,若缓冲区为空,则继续等待。但是如果读取成功,字符后面的分隔符是残留在缓冲区的,cin>> 不做处理。
但是,getline() 读取数据时,并非像 cin>> 那样忽略第一个换行符,getline() 发现 cin 的缓冲区中有一个残留的换行符,不阻塞请求键盘输入,直接读取,送入目标字符串后,再将换行符替换为空字符』\0』。
那么问题就很好解决了,既然已经知道了 getline()
会直接读取 cin 缓冲区中的内容,接下来要做的就是在 getline()
被调用之前清空 cin 缓冲区
清空 cin 缓冲区
网上比较广泛的说法有如下几个:
- cin.sync();
- fflush(stdin);
- cin.ignore(INT_MAX, '\n');
但经过实测,前两种方法均无法在 vs2019 中生效,因此建议使用第三种方法,将 test01()
改成如下:
void test01()
{
cout << "<< 写入文件>>" << endl;
int n;
cout << " 要输入的行数:";
cin >> n;
cin.ignore(INT_MAX, '\n'); //清空 cin 缓存
ofstream ofs;
ofs.open("test.txt", ios::out); //创建 test.txt
string buf;
for (int i = 1; i <= 2; i++)
{
cout << i << ":"; //打印当前所在行号
getline(cin, buf); //输入内容
ofs << buf <<endl; //写入文件
}
ofs.close();
}
再次运行结果一切正常:
cin.ignore(INT_MAX, '\n');
的含义是:当遇到换行符时,清空缓冲区内所有内容 (换行符也被清除),其中 INT_MAX
是 C++中的宏常量,意为 int 最大值,也可以用 std::numeric_limits< streamsize >::max()
代替,意为 IO 流最大字节数
ignore 的函数原型为:istream & ignore(int n =1, int delim = EOF);
为方便理解,也可以写成:cin.ignore(count, c);
其中 c 代表字符,count 代表提取的字符数,当遇到以下三种情况时,清空缓冲区内容:
- 提取的字节数达到 count 数量
- 遇到 EOF 终结符
- 遇到指定的 c 字符 (c 字符也被提取一并清空)
参考文章 1:https://blog.csdn.net/selina8921/article/details/79067941
参考文章 2:https://blog.csdn.net/weixin_35806027/article/details/112994305