流状态(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++ 的输入校验工作量较大,但可将常用逻辑封装为可复用函数。选择字符串校验还是数值校验取决于需求与性能要求。