流状态(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';
}
数值校验
- 直接提取并检查 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;
}
- 处理尾随垃圾输入
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 清理行尾
- 严格校验:若用户输入 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++ 的输入校验工作量较大,但可将常用逻辑封装为可复用函数。选择字符串校验还是数值校验取决于需求与性能要求。
