c++ 在使用早期的cin.get()之后,cin.get()将被忽略

smdnsysy  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(87)

在下面的程序中,如果我在第一个提示符后按回车键,后面的cin语句就会被跳过,有人知道为什么吗?
这段代码是C++,我用Visual Studio 2022运行它。

#include <iostream>
using namespace std;

int main()
{
    char string[21];

    cout << "Enter a string: ";
    cin.get(string, 20);
    cin.ignore(80, '\n');

    cout << "\nPress Enter to continue.\n";
    cin.get(string, 20);
    cin.ignore(80, '\n');

    cout << "\nPress Enter to continue.\n";
    cin.get(string, 20);
    cin.ignore(80, '\n');

    cout << "\nPress Enter to continue.\n";
    cin.get(string, 20);
    cin.ignore(80, '\n');
}

字符串
这段代码来自1998年的教科书。我知道有更现代的方法可以做到这一点,但我想知道为什么这段代码会出现故障。

wlp8pajw

wlp8pajw1#

显示iostate的函数

在下面的程序中,如果我在第一个提示符后按回车键,后面的cin语句就会被跳过,有人知道为什么吗?
我已经修改了你的程序,使它在每次调用std::cin之后显示badbitfailbiteofbit的状态。

// main.cpp
#include <iostream>
#include <format>
#include <string>
#include <string_view>

std::string iostate_info(
    std::string_view const heading,
    std::istream& ist)
{
    auto const state{ ist.rdstate() };
    bool const badbit { (state & std::ios_base::badbit ) == std::ios_base::badbit  };
    bool const failbit{ (state & std::ios_base::failbit) == std::ios_base::failbit };
    bool const eofbit { (state & std::ios_base::eofbit ) == std::ios_base::eofbit  };
    return std::format(
        "{}"
        "\n  badbit  : {}"
        "\n  failbit : {}"
        "\n  eofbit  : {}"
        "\n\n"
        , heading, badbit, failbit, eofbit);
}

int main()
{
    char string[21];

    std::cout << "Enter a string: ";
    std::cin.get(string, 20);
    std::cout << iostate_info("After FIRST std::cin.get(string, 20);", std::cin);
    std::cin.ignore(80, '\n');
    std::cout << iostate_info("After FIRST std::cin.ignore(80, '\\n');", std::cin);

    std::cout << "\nPress Enter to continue.\n";
    std::cin.get(string, 20);
    std::cout << iostate_info("After SECOND std::cin.get(string, 20);", std::cin);
    std::cin.ignore(80, '\n');
    std::cout << iostate_info("After SECOND std::cin.ignore(80, '\\n');", std::cin);

    std::cout << "\nPress Enter to continue.\n";
    std::cin.get(string, 20);
    std::cout << iostate_info("After THIRD std::cin.get(string, 20);", std::cin);
    std::cin.ignore(80, '\n');
    std::cout << iostate_info("After THIRD std::cin.ignore(80, '\\n');", std::cin);

    std::cout << "\nPress Enter to continue.\n";
    std::cin.get(string, 20);
    std::cout << iostate_info("After FOURTH std::cin.get(string, 20);", std::cin);
    std::cin.ignore(80, '\n');
    std::cout << iostate_info("After FOURTH std::cin.ignore(80, '\\n');", std::cin);
}
// end file: main.cpp

字符串
正如您在输出中看到的,当在第一个“Press Enter to continue”请求之后调用std::cin.get时,std::cin被置于失败状态。这是因为在输入任何字符并存储在变量string中之前就遇到了'\n'
一旦std::cin被置于失败状态,就不能输入或忽略任何字符,直到failbit被清除。通常这是通过调用std::cin.clear()来完成的。因为std::cin没有被清除,所以对std::cin.getstd::cin.ignore的后续调用不会尝试输入(或忽略)任何字符。

Enter a string: Hello, world!
After FIRST std::cin.get(string, 20);
  badbit  : false
  failbit : false
  eofbit  : false

After FIRST std::cin.ignore(80, '\n');
  badbit  : false
  failbit : false
  eofbit  : false

Press Enter to continue.

After SECOND std::cin.get(string, 20);
  badbit  : false
  failbit : true
  eofbit  : false

After SECOND std::cin.ignore(80, '\n');
  badbit  : false
  failbit : true
  eofbit  : false

Press Enter to continue.
After THIRD std::cin.get(string, 20);
  badbit  : false
  failbit : true
  eofbit  : false

After THIRD std::cin.ignore(80, '\n');
  badbit  : false
  failbit : true
  eofbit  : false

Press Enter to continue.
After FOURTH std::cin.get(string, 20);
  badbit  : false
  failbit : true
  eofbit  : false

After FOURTH std::cin.ignore(80, '\n');
  badbit  : false
  failbit : true
  eofbit  : false


如果在按Enter之前按一次空格键,则空格存储在变量string中,std::cin不会失败。

  • 旁注:* 我希望你认识到在一个也有using namespace std;的程序中使用string作为你自己的变量是多么容易出错。

iostate_info的替代实现

@大卫C.兰金在下面的评论中指出,如果函数iostate_info不使用rdstate和随之而来的位掩码等低级结构,它可能更容易理解。很公平。我想我使用流太久了,因为rdstate对我来说似乎是合理的!我忽略了一个明显的事实,即函数badfaileof可以相对容易地实现相同的结果。
大卫还指出了std::format的缺点,因为它需要一个C20环境(或更高版本)才能编译iostate_info
这里有一个解决这两个问题的重写。为了更好地衡量,我放弃了std::string_view,以防依赖C
17也有问题。

std::string iostate_info(
    std::string const& heading,
    std::istream& ist)
{
    std::stringstream sst;
    sst << std::boolalpha
        << heading 
        << "\n  badbit  : " << ist.bad()
        << "\n  failbit : " << (ist.fail() && !ist.bad())
        << "\n  eofbit  : " << ist.eof()
        << "\n\n";
    return sst.str();
}

处理“按回车继续..”的一种方法

我比大多数人都严格。当我的程序要求你“按回车”时,你在按回车之前输入了一些垃圾字符,它会抱怨,让你再试一次。你必须在一个空白行(或只包含空格的行)上按回车。我这样做是因为我担心这些垃圾字符实际上可能是下一个提示符的输入。
函数press_enter_to_continue使用std::getline来输入一个空白行。它是一套处理控制台输入的函数的一部分,其中所有的函数都使用std::getline。(例如,参见StackOverflow answer中的函数get_int。)
如果将press_enter_to_continuestd::cin >> value;等操作混合使用,则必须小心std::cin >> value;在输入流上留下的挂起'\n'。您必须在调用press_enter_to_continue之前将其清除,可能是通过调用std::cin.ignore

void press_enter_to_continue(
    std::istream& ist = std::cin, 
    std::ostream& ost = std::cout)
{
    std::string s;
    for (;;)
    {
        ost << "Press Enter to continue...";
        if (!std::getline(ist, s))
        {
            ost << "\nERROR: `std::getline` failed unexpectedly.\n\n";
            return;
        }
        if (trim_whitespace(s).empty())
        {
            ost.put('\n');
            return;
        }
        ost << "Invalid entry. Please reenter.\n";
    }
}


这里是函数trim_whitespace

auto trim_whitespace(std::string const& s) -> std::string
{
    // Trim leading and trailing whitespace from string `s`.
    auto const first{ s.find_first_not_of(" \f\n\r\t\v")};
    if (first == std::string::npos)
        return {};
    auto const last{ s.find_last_not_of(" \f\n\r\t\v") };
    enum : std::string::size_type { one = 1u };
    return s.substr(first, (last - first + one));
}

相关问题