字符串转数字时,可能会遇到哪些问题?

结合 C++ 常用的C++11 系列函数(stoi/stod 等)C 风格函数(atoi/atof 等),梳理字符串转数字的常见问题、现象、原因及规避方案。

一、整体分类

主要分为:格式非法数值溢出截断解析空/空白字符串符号与进制问题浮点数特殊场景


二、逐个问题详解 + 示例

1. 字符串含非数字字符(最常见)

场景1:开头就是非法字符

字符串首字符不是数字、正负号,无法解析。

  • atoi/atof:直接返回 0,无任何报错,难以察觉。
  • stoi/stod:直接抛出 invalid_argument 异常,程序崩溃。
cpp 复制代码
string s = "abc123";
int a = atoi(s.c_str());   // 结果:0
int b = stoi(s);           // 抛异常

场景2:中间/末尾夹杂非数字

函数会从左到右解析,遇到非法字符立即停止,只取前面合法部分。

cpp 复制代码
string s = "123xyz456";
int a = atoi(s.c_str()); // 结果:123
int b = stoi(s);         // 结果:123(不会报错,只截断)

2. 数值超出目标类型范围(溢出)

字符串表示的数字太大/太小,超过接收变量的取值范围。

① C 风格 atoi/atol/atof

无报错、无提示 ,直接产生未定义行为,结果乱码、负数、随机值。

cpp 复制代码
// int 范围:-2147483648 ~ 2147483647
string s = "999999999999"; 
int num = atoi(s.c_str()); 
// 溢出,结果不可预测

② C++11 stoi/stoll/stod

溢出会抛出 out_of_range 异常,程序直接终止。

cpp 复制代码
string s = "999999999999";
int num = stoi(s); // 数值超出 int 范围,抛异常

3. 空字符串 / 全空白字符串

  • 空串 ""、全空格 " "、制表符 "\t"、换行 "\n"
  • atoi:返回 0
  • stoi:抛出 invalid_argument 异常
cpp 复制代码
string s = "";
atoi(s.c_str());  // 0
stoi(s);          // 异常

4. 正负号使用异常

  1. 多个正负号 :如 "+-123""--456"
    • atoi 返回 0
    • stoi 抛参数异常
  2. 符号不在开头"123-45"
    解析到 123 就截断,后面忽略。
cpp 复制代码
string s = "-+123";
atoi(s.c_str()); // 0
stoi(s);         // 异常

5. 浮点数专属问题

(1)整型函数解析小数字符串

stoi/atoi 解析带小数点的字符串:

  • 自动截断小数部分,只保留整数:
cpp 复制代码
string s = "3.99";
int a = atoi(s.c_str()); // 3
int b = stoi(s);         // 3

(2)科学计数法

1e32.5E-2 这类科学计数:

  • atof / stod 支持正常转换
  • atoi / stoi 识别到 e/E 停止,只解析前面数字

(3)无穷、非数值(NaN)

字符串 "inf" "nan":浮点函数可识别,整型函数判定为非法。


6. 进制不匹配问题

函数默认只解析十进制

  • 八进制 0123、十六进制 0x1A
    atoi/stoi 不会按进制解析,只会当成普通十进制:
    • "0123" → 123
    • "0x1A" → 0(x 是非法字符)

如果需要解析十六进制/八进制,不能用普通转换函数。


7. 前导空格问题

字符串开头有空格

两类函数都能自动忽略前导空白,正常解析:

cpp 复制代码
string s = "  678";
atoi(s.c_str()); // 678
stoi(s);         // 678

⚠️ 仅开头空格允许,中间/末尾空格仍属于截断。


三、两类函数问题对比表

问题场景 atoi/atof(C风格) stoi/stod(C++11)
非法开头字符 返回 0,无告警 invalid_argument 异常
数值溢出 未定义行为,结果错乱 out_of_range 异常
空串/全空白 返回 0 抛异常
中间含非数字 截断前面合法部分 截断前面合法部分
前导空格 自动忽略,正常解析 自动忽略,正常解析

四、通用规避方案(实用写法)

1. 使用 C++11 系列函数:必须捕获异常

cpp 复制代码
#include <iostream>
#include <string>
#include <stdexcept>
using namespace std;

bool strToInt(const string& s, int& outNum)
{
    try
    {
        outNum = stoi(s);
        return true;
    }
    catch (const invalid_argument&)
    {
        cout << "参数非法" << endl;
    }
    catch (const out_of_range&)
    {
        cout << "数值溢出" << endl;
    }
    return false;
}

2. 使用 C 风格函数:提前做格式校验

手动遍历字符串,判断是否为合法数字、检查范围,避免得到误导性的 0

3. 通用稳健方案:stringstream

流转换会通过状态位判断转换是否成功,不抛异常、不静默出错:

cpp 复制代码
#include <sstream>
bool strToInt(const string& s, int& num)
{
    stringstream ss(s);
    ss >> num;
    // 判断转换是否成功 + 字符串是否全部解析完毕
    return ss.good() && ss.eof();
}

优点:能精准区分「转换失败」和「正常转成 0」,工业代码常用。


五、总结高频考点

  1. C 风格函数最大坑:出错只返回 0,无法区分是真 0 还是转换失败;溢出无提示。
  2. C++11 stoi 系列最大坑:非法格式、溢出直接抛异常,不捕获就崩程序。
  3. 中间含字母/符号:两类函数都会截断解析
  4. 空串、多正负号:属于典型非法格式。
  5. 追求健壮性优先用 stringstream + 状态位判断
相关推荐
karry_k2 分钟前
MyBatis批量insert-select踩坑:useGeneratedKeys=true 可能让PostgreSQL返回大量插入结果
java·后端
karry_k9 分钟前
PostgreSQL 在 MyBatis 中执行正常 SQL 失效:一次 DELETE USING 踩坑记录
java·后端
vibecoding日记2 小时前
双非如何快速入职字节等大厂大模型?真实案例分析:推理优化和投机解码
算法·求职·大模型工程师
yszaygr21384 小时前
Verilog参数化游程编码RLE模块
算法
SamDeepThinking4 小时前
从源码到代码:MyBatis-Flex 与 MyBatis-Plus 的逐项对比
java·后端·程序员
望易4 小时前
刚设计的大模型架构-双域耦合认知框架
算法·架构
她的男孩7 小时前
Spring Boot 接 Flowable 工作流:用 3 个注解搭一个请假审批流程
java·后端·架构
复杂网络8 小时前
多个 Claude Code 与多个 Codex 协同工作:设计与实现方案
算法
荣码8 小时前
LLM结构化输出:让AI返回JSON而不是废话,我踩了4个坑
java·python