C++流状态与输入校验

流状态(Stream states)

ios_base 内部定义了若干状态标志,用于指示流在运行过程中可能遇到的各种情形:

标志位含义
goodbit一切正常
badbit发生致命错误(例如试图读到文件末尾之后)
eofbit流已到达文件结束位置
failbit发生非致命错误(例如用户本应输入整数却键入字母)

虽然这些标志位定义在 ios_base 中,但由于 ios 继承自 ios_base,且书写更简洁,通常通过 ios 来访问(例如 std::ios::failbit)。

ios 还提供了以下成员函数,以便快捷地查询或修改这些状态:

成员函数含义
good()goodbit 置位则返回 true(流正常)
bad()badbit 置位则返回 true(致命错误)
eof()eofbit 置位则返回 true(到达文件尾)
fail()failbit 置位则返回 true(非致命错误)
clear()清除所有标志,恢复 goodbit
clear(state)清除所有标志并设置指定标志
rdstate()返回当前标志集合
setstate(state)设置指定标志

最常见的是 failbit。例如:

int age{};
std::cout << "Enter your age: ";
std::cin >> age;

若用户输入非数字(如 “Alex”),提取失败,std::cin 会置位 failbit。此后,对流的所有进一步操作均被忽略,直到调用 clear() 恢复状态。

输入校验(Input validation)

输入校验即检查用户输入是否符合既定规则。通常分为两类:字符串校验数值校验

字符串校验

策略:先整体读取字符串,再逐字符检查格式。
示例:要求姓名仅含字母或空格。

#include <algorithm>
#include <cctype>
#include <iostream>
#include <ranges>
#include <string>
#include <string_view>

bool isValidName(std::string_view name)
{
    return std::ranges::all_of(name, [](char ch) {
        return std::isalpha(ch) || std::isspace(ch);
    });
}

int main()
{
    std::string name;
    do {
        std::cout << "Enter your name: ";
        std::getline(std::cin, name);
    } while (!isValidName(name));

    std::cout << "Hello " << name << "!\n";
}

电话号码模板匹配

若输入为固定长度,但各位置规则不同,可设计模板:

  • # 匹配任意数字
  • @ 匹配任意字母
  • _ 匹配任意空白
  • ? 匹配任意字符
  • 其余字符须严格一致

示例模板:(###) ###-####

#include <algorithm>
#include <cctype>
#include <iostream>
#include <map>
#include <ranges>
#include <string>
#include <string_view>

bool inputMatches(std::string_view input, std::string_view pattern)
{
    if (input.size() != pattern.size()) return false;

    static const std::map<char, int (*)(int)> validators{
        {'#', &std::isdigit},
        {'_', &std::isspace},
        {'@', &std::isalpha},
        {'?', [](int) { return 1; }}
    };

    return std::ranges::equal(input, pattern, [&](char ch, char mask) -> bool {
        auto found = validators.find(mask);
        return found != validators.end() ? (*found->second)(ch) : ch == mask;
    });
}

int main()
{
    std::string phone;
    do {
        std::cout << "Enter a phone number (###) ###-####: ";
        std::getline(std::cin, phone);
    } while (!inputMatches(phone, "(###) ###-####"));
    std::cout << "You entered: " << phone << '\n';
}

数值校验

  1. 直接提取并检查 failbit
int age;
while (true) {
    std::cout << "Enter your age: ";
    std::cin >> age;

    if (std::cin.fail()) {                     // 提取失败
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        continue;
    }
    if (age <= 0) continue;
    break;
}
  1. 处理尾随垃圾输入
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 清理行尾
  1. 严格校验:若用户输入 34abcd56,仅提取 34,需用 gcount() 判断丢弃字符数是否大于 1:
std::cin.ignore(...);
if (std::cin.gcount() > 1) continue;

按字符串读取后转换数值

借助 <charconv>std::from_chars

#include <charconv>
#include <iostream>
#include <limits>
#include <optional>
#include <string>

std::optional<int> extractAge(std::string_view s)
{
    int value{};
    auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value);
    if (ec != std::errc{} || value <= 0) return std::nullopt;
    return value;
}

int main()
{
    int age{};
    while (true) {
        std::cout << "Enter your age: ";
        std::string str;
        if (!std::getline(std::cin >> std::ws, str)) {
            std::cin.clear(); std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            continue;
        }
        if (auto val = extractAge(str)) {
            age = *val;
            break;
        }
    }
    std::cout << "You entered: " << age << '\n';
}

总结

C++ 的输入校验工作量较大,但可将常用逻辑封装为可复用函数。选择字符串校验还是数值校验取决于需求与性能要求。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"