我C++的getline输入为什么出现了问题?——两个样例

前言的前言

如果你清楚C++输入输出、缓冲区等概念,而且清楚下面这两个例子为什么不对,那你就可以太长不看了。

  1. 先读取一个整数n,再读取一行字符串,之后输出。

    输入:

    2
    wupeng
    

    输出:

    wupeng
    

    错误示范:

    /// 错误示范!
    int n; 
    cin >> n; 
    string s;
    getline(cin, s); // 错误示范!
    cout << s << endl; 
    /// 错误示范!
    
  2. 先读取一个整数n,表示后面的行数,再读取n行以,分隔的不含,的string(每行5个),把他们放进一个二维vector中。别整什么,替换成空格,我就是要放到vector中!

    输入:

    2
    a,b,ca,dw,ew
    wp,wu,peng,uin,y
    

    输出:

    a b ca dw ew
    wp wu peng uin y
    

    错误示范:

    /// 错误示范!
    int n;
    cin >> n;
    vector<vector<string>> v(n, vector<string>(5));
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < 5; ++j) {
            getline(cin, v[i][j], ','); /// 错误示范!
        }
    }
    /// 错误示范!
    

前言

一直没把输入输出当回事,毕竟笔试题什么的都是固定好的输入格式。牛客的OJ在线编程常见输入输出练习刷完就再也没管过了,直到有一次想玩点花的,用getline+分隔符读取输入出现了BUG,才正视我根本不懂C++的输入。

当时把我整晕的我想实现的例子:

先读取一个整数n,表示后面的行数,再读取n行以,分隔的不含,的string(每行5个),把他们放进一个二维vector中。别整什么,替换成空格,我就是要放到vector中!

输入:

2
a,b,ca,dw,ew
wp,wu,peng,uin,y

输出:

a b ca dw ew
wp wu peng uin y

当时就,这我熟啊,getline可以使用分隔符参数,读cin不就完事儿。下面是这个思路的错误示范:

/// 错误示范
int n;
cin >> n;
vector<vector<string>> v(n, vector<string>(5));
for (int i = 0; i < n; ++i) {
    for (int j = 0; j < 5; ++j) {
        getline(cin, v[i][j], ','); /// 错误示范
    }
}
/// 错误示范

你会发现你的vector里面多了一些换行符,对于前言第一个例子也一样,只会输出一个换行。

你可能很快就反应过来了:我把结尾的换行符也读取进去了!那么为什么呢?C++到底是怎么读取键盘输入的?这中间都发生了什么?以及最重要的——我要怎么才能把这个思路写对?

太长不看版

  1. 第一个样例,因为getline会把2后面的换行符也get到s中,所以输出s会直接换行,一眼看起来就像是s没有输入成功一样。解决也很简单:既然多了一个换行符,那我先读取或者忽略了就完事儿 :

    int n;
    cin >> n;
    string s;
    
    cin.ignore(); // 1. ignore默认是跳过1个字符或遇到EOF,函数详细使用自行查阅
    
    // cin.get() 或 getchar() // 2. 直接读取一个字符,也就把换行符用掉了
    
    getline(cin, s);
    cout << s << endl;
    
  2. 第二个样例,在开头加上上述解决方案以后,你会发现输入两行字符串后还是没有输出。因为中间的换行符也被读取上了,也就是v[0]内容其实是:a, b, ca, dw, ew\nwp,\n是换行符,那么你输入的第二行也就只有三个值:wu, peng, uin,第四个值y\n后面还没逗号,C++还在等你输入呢

    要按这个思路继续的话,可以用stringstream解决,这个库如果不了解自行查阅,在此不赘述:

    int n;
    cin >> n;
    cin.ignore();
    vector<vector<string>> v(n, vector<string>(5));
    for (int i = 0; i < n; ++i) {
        string line;
        getline(cin, line);
        stringstream ss(line);
        for (int j = 0; j < 5; ++j) {
            getline(ss, v[i][j], ',');
        }
    }
    

所以C++到底对我输入的东西做了什么?

C++缓冲区

首先从缓冲区说起,不想para了直接从这篇粘过来:

缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

为何引入缓冲区呢?

比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。

又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。

现在您基本明白了吧,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

多的不再说,只需要理解,在C++程序等待你输入时,你输入到console中的内容并不是直接被C++读取,而是进入了输入缓冲区。

回到样例

在第一个样例中,你输入完2,敲回车以后缓冲区的内容就是:

2\n

cin >> n会从缓冲区读取一个整数,这么说可能不太合理,但缓冲区剩下的东西:

\n

然后运行到getline,不等你想输入字符串呢,getline就发现,缓冲区这不还有东西吗?我直接拿去就是。于是你的输出变成了这样:

在这里插入图片描述

因此,我们用ignore或者读一个字符的方式把输入的回车从缓冲区清掉,输入缓冲区也就空了,getline就会重新等着我们输入字符串,然后给到s。(至于这里为什么不会把换行符给到s,应该是因为这种正常情况getline本身会忽略最后的换行符)。


我们再看样例2:

为什么使用stringstream就解决了问题呢?

我理解,while(getline(cin, v[i][j], ','))这条语句,只要cin缓冲区有效,就会无限制读取下去,比如这么一条语句:

string str;
while (getline(cin, str, ',')) {
    cout << str << endl;
}

会不断的读取输出读取输出……,中间的换行符也会被当做str的一部分,并不会像getline(cin, s)只把一行内容给s。

而先使用getline拿到一行的内容(不带后面的换行符),再将这部分内容赋给ss,这么一来我们相当于使用stringstream自己构建了这行内容的缓冲区(这么说不太准确,但先这么理解),读到结尾自然也就停下来了。

结语

再简单的东西也可能绊倒自己。保持饥饿,保持憨憨。

  • 18
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WuPeng_uin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值