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