Skip to content

18.2 使用 istream 输入

By Alex on March 4th, 2008 | last modified by nascardriver on August 3rd, 2020

翻译by dashjay 2020-08-10

iostream 库是相当复杂的 —— 因此在这些教程中我们将不会有能力去覆盖全面。然而我们将会展示给你最常用的函数。在这次课中,我们将会学习输入这块内容(istrea)。

提取操作符

正如现在很多课程中所看到的那样,我们可以使用提取操作符 (>>) 来从输入流读取信息。C++ 对所有内建的数据类型都有预定义的提取操作符,并且你早已知道如何为自己的类重载提取运算符来直接输出。

当读取字符串时,一个常见是如何避免输入越界。这有一个例子:

char buf[10];
std::cin >> buf;

如果用户输入 18 个字符串会发生什么?缓冲区溢出,糟糕的东西会发生。大体上讲,假设用户会输入多少字符不是一个好办法。

有一个通过操纵器(manipulators)解决此问题的方法。一个 manipulators 是一个对象用来修改一个流,当使用 提取(>>) 或插入(<<)操作符时。一个你早就使用过的 manipulators 就是 std::endl,它可以打印一个新行,并且刷新任何已经缓存的输出。C++ 提供了一个 manipulators 叫做 setw (在 iomanip.h 头中)可以被用来限制读取的字符数量。要使用 setw(),简单的提供一个最大的要读取字符数量作为参数,并且插入到你的输入语句中,像这样:

#include <iomanip.h>
char buf[10];
std::cin >> std::setw(10) >> buf;

This program will now only read the first 9 characters out of the stream (leaving room for a terminator). Any remaining characters will be left in the stream until the next extraction. 这个程序现在将会读取从流中读取前9个字符(留一个空间给终止符)。任何其他的字符都会被留在流中,指导下次提取。

提取和空格

一件到目前为止我们都忘了提起的事情,就是提取操作符和 ”格式化“ 后的数据工作 ———— 是的,它会跳过空白(空白,制表符,新行)。

看一眼下列程序:

int main()
{
    char ch;
    while (std::cin >> ch)
        std::cout << ch;

    return 0;
}

当用户输入下列字符时:

Hello my name is Alex

提取操作符跳过了空格和新行,因此输出是:

HellomynameisAlex

通常,你将会想获取用户输入但是一起空格。为了这么做,istream 类提供了许多函数可以函数。

最有用的就是 get() 函数,可以简单的从输入流中获取一个字符,下面是一个和上方具有用样功能的简单例子,但是它使用 get()

int main()
{
    char ch;
    while (std::cin.get(ch))
        std::cout << ch;

    return 0;
}

现在我们使用下列输入:

Hello my name is Alex

输出则是:

Hello my name is Alex

std::get() 也有一个 string 版本,可以传入一个最大字符数来读取:

int main()
{
    char strBuf[11];
    std::cin.get(strBuf, 11);
    std::cout << strBuf << '\n';

    return 0;
}

如果你输入:

Hello my name is Alex

输出则是:

Hello my n

注意到我们仅仅读取了前 10 个字符(我们不得不为终止符留一个空)。剩下的字符会留在输入流中。

一件有关 get() 的重要的事是他不能读取一个新行字符!这可能会得到一些意料之外的结果

int main()
{
    char strBuf[11];
    // Read up to 10 characters
    std::cin.get(strBuf, 11);
    std::cout << strBuf << '\n';

    // Read up to 10 more characters
    std::cin.get(strBuf, 11);
    std::cout << strBuf << '\n';
    return 0;
}

如果用户输入:

Hello!

程序将会输出

Hello!

然后就突然中止了!为什么它不询问另外10个字符呢?答案就是因为第一次 get() 读取到了新行,然后停止了。第二个 get() 看到这依然想要读取。但是第一个字符是一个新行。因此它立刻中止。

因此,这有另一个函数叫做 getline() 能够想 get() 那样工作,但是也能读取一个新行。

int main()
{
    char strBuf[11];
    // Read up to 10 characters
    std::cin.getline(strBuf, 11);
    std::cout << strBuf << '\n';

    // Read up to 10 more characters
    std::cin.getline(strBuf, 11);
    std::cout << strBuf << '\n';
    return 0;
}

这端代码将会像你所期待的那样工作,即使用户输入一个带有新行的字符串。

如果你需要知道上次 getline() 提取了多少字符,用 gcount()

int main()
{
    char strBuf[100];
    std::cin.getline(strBuf, 100);
    std::cout << strBuf << '\n';
    std::cout << std::cin.gcount() << " characters were read" << std::endl;

    return 0;
}

一个为 std::string 提供的 getline()

有一个特殊版本的 getline() 在 istream 类的外面,被用来读取到 std::stirng 中。这个特殊版本的函数不是 ostream 也不是 istream 的成员。并且它被包含在 string 头部中。这是一个使用它的例子:

# include <string>
# include <iostream>

int main()
{
    std::string strBuf;
    std::getline(std::cin, strBuf);
    std::cout << strBuf << '\n';

    return 0;
}

一些有用的输入函数

这里有一些有用的输入函数你可能会使用:

ignore() 遗弃流中的第一个字符。

ignore(int nCount) 遗弃前 nCount 个字符。

peek() 允许从流中读取一个字符,并且不将他从流中移除

unget() 归还最后一个字符,使得它可以可以下次再被读取。

pushback(char ch) 允许你放置一个你选择的字符进入流中,以便下次调用时读取。

根据你的工作,istream 包含了许多其他的函数和变量可能非常有用。然而那些话题更加适合出现在一个教程或者专注于标准库的书中(例如 “The C++ Standard Library” by Nicolai M. Josuttis)