stoi/stod 不加防护必出错,因它们严格校验格式且不返回解析位置;安全解析需用 strtol/strtod 获取结束指针,或手动扫描提取数字子串并捕获异常。

直接用 stoi 或 stod 解析字符串里的数字,只要输入含非数字字符(比如 "123abc"、" 45.6"、空串、纯空格),程序就崩溃或行为未定义——这不是“能不能用”的问题,是“不加防护就一定出事”。
stoi/stod 默认只认前导空白 + 符号 + 数字,其余全算错误
这两个函数底层依赖 std::strtol/std::strtod,规则很严格:
-
"123"→ 成功;"123abc"→ 抛std::invalid_argument(不是截取 123) -
" 45.6"→ 成功(自动跳过前导空格);"\t\n45.6"→ 同样成功 -
" "或""→ 抛std::invalid_argument -
"1e1000"(溢出)→ 抛std::out_of_range
它们**不提供“解析到哪为止”的位置信息,也不容忍尾部垃圾字符**。想安全提取,必须自己控制解析边界。
用 strtol/strtod 替代,拿到解析结束位置再判断
strtol 和 strtod 是 C 标准库函数,但 C++ 中仍可直接用,优势是返回 char* 指向第一个未转换字符,你能据此判断是否“整段合法”或“只取前面有效部分”:
立即学习“C++免费学习笔记(深入)”;
std::string s = "123abc";
char* end;
long val = std::strtol(s.c_str(), &end, 10);
if (end == s.c_str()) {
// 一个数字都没识别到(如空串、纯字母)
} else if (*end == '\0') {
// 全部字符都参与了转换 → 完全合法
} else {
// end 指向 'a',说明 "123" 是有效前缀,后面是垃圾
}注意:strtol 不抛异常,错误时返回 0 或 LONG_MIN/LONG_MAX 并设置 errno;strtod 同理,但需检查 errno == ERANGE 判断溢出。
提取“字符串中第一个连续数字子串”要手动扫描
如果原始需求是像从 "price: $123.45 (tax included)" 中捞出 123.45,stoi/stod 完全无能为力——它们要求整个字符串符合格式。此时得自己遍历:
- 跳过开头非数字、非符号、非小数点的字符
- 记录起始位置,继续读直到遇到非法字符(如字母、多于一个小数点、连着两个符号)
- 对截出的子串调用
stod,并用try/catch捕获溢出
示例关键逻辑:
size_t i = 0;
// 跳过非起始字符
while (i < s.length() && !std::isdigit(s[i]) && s[i] != '-' && s[i] != '+') ++i;
size_t start = i;
// 扫描数字主体(允许一个 '.',不允许 "1.2.3")
int dot_count = 0;
while (i < s.length()) {
if (std::isdigit(s[i])) {
++i;
} else if (s[i] == '.' && dot_count == 0) {
++dot_count;
++i;
} else {
break;
}
}
if (i > start) {
std::string num_str = s.substr(start, i - start);
try {
double d = std::stod(num_str);
// 使用 d
} catch (const std::exception&) {
// 溢出或格式异常(如 ".e")
}
}别忽略 locale 和负号嵌套等边界情况
默认 C locale 下 strtol 认 "-123",但若程序切换过 locale(比如用了 std::locale::global(std::locale("zh_CN"))),某些 locale 可能允许千分位符号或不同负号,导致解析错位。更隐蔽的是:"--123"、"-+45" 这类输入,strtol 会停在第一个 '-' 后,认为无效,但你可能期望跳过所有前导符号——这只能靠预处理。健壮性真正的难点不在“怎么转”,而在“怎么定义什么是‘可接受的输入’”。










