57. atoi 的实现:如何将字符串转换为整数?

字符串转整数,这个函数大家肯定不陌生。C 标准库里的 atoi,用起来简单,但真要自己手写一个,坑还真不少。

我记得刚入行那会儿,有个同事直接用了 atoi 处理用户输入,结果输入了个超大数,程序直接崩了。嗯,从那以后我就养成了一个习惯——能自己写就自己写,至少心里有底。

atoi 到底做了什么?

说白了,就是把一串字符变成整数。比如 "123" 变成 123"-42" 变成 -42

但实际场景比这复杂。你想想看,用户可能输入空格、正负号、甚至非法字符。一个健壮的 atoi,必须把这些情况都处理好。

核心逻辑:三步走

我个人习惯把 atoi 的实现拆成三个步骤:

  1. 跳过前导空白——空格、制表符、换行符统统跳过
  2. 处理正负号——遇到 '+''-' 记录符号
  3. 逐字符转换——遇到非数字字符就停止

这里有个细节:atoi 遇到非法字符不会报错,直接返回已转换的部分。这其实是个隐患,我在项目中遇到过因为输入里混了个字母,结果数据对不上的情况。

手写一个 atoi

来看代码。我尽量写得清晰一些,该处理的边界都处理了。

#include <stdio.h>
#include <ctype.h>
#include <limits.h>

int my_atoi(const char *str) {
    if (str == NULL) return 0;

    // 1. 跳过前导空白
    while (isspace((unsigned char)*str)) {
        str++;
    }

    // 2. 处理正负号
    int sign = 1;
    if (*str == '-') {
        sign = -1;
        str++;
    } else if (*str == '+') {
        str++;
    }

    // 3. 逐字符转换
    int result = 0;
    while (isdigit((unsigned char)*str)) {
        int digit = *str - '0';

        // 检查溢出
        if (result > (INT_MAX - digit) / 10) {
            return (sign == 1) ? INT_MAX : INT_MIN;
        }

        result = result * 10 + digit;
        str++;
    }

    return result * sign;
}

这段代码里,我特别加了一个溢出检查。为什么?因为 INT_MAX 是 2147483647,如果输入 "2147483648",直接乘 10 再加 8 就溢出了。我曾经在嵌入式设备上吃过这个亏,整型溢出后数据直接翻转,排查了好久才发现。

溢出检查的原理

你可能会问:为什么用 result > (INT_MAX - digit) / 10 这个写法?

其实很简单。我们想判断 result * 10 + digit 会不会超过 INT_MAX。直接乘可能会溢出,那就先做除法:

  • 如果 result > INT_MAX / 10,那乘 10 肯定超了
  • 如果 result == INT_MAX / 10,就看 digit 会不会让最后一位超限

这个技巧在嵌入式开发里很常用。毕竟很多芯片没有硬件除法,但整数比较还是很快的。

边界情况一览

输入 预期输出 说明
"42" 42 正常情况
" -42" -42 前导空格 + 负号
"4193 with words" 4193 遇到非数字停止
"words and 987" 0 开头就不是数字
"-91283472332" INT_MIN 溢出,返回最小值
"" 0 空字符串
NULL 0 空指针
注意:标准库的 atoi 在溢出时行为是未定义的。也就是说,不同编译器可能给出不同结果。所以我在手写版本里明确返回了 INT_MAXINT_MIN,这样行为可预测。

流程图:atoi 的执行路径

下面这张图把整个流程串起来了。你可以对照着代码看,会更清楚。

开始 str == NULL ? 返回 0 跳过前导空白字符 处理正负号 逐字符转换(含溢出检查) 返回结果 判断节点 处理节点 异常返回
小技巧:如果你在嵌入式环境里用 atoi,建议用 strtol 替代。它提供了更完善的错误检测,能区分「转换结果为 0」和「转换失败」。我在做物联网设备时,就统一用 strtol 处理配置字符串,省了不少调试时间。

常见错误与避坑

  • 忘记处理前导空格——用户输入经常带空格,不跳过就直接报错
  • 符号位处理不当——比如 "+-42" 这种输入,标准做法是返回 0
  • 溢出检查缺失——这是最危险的,可能导致安全漏洞
  • 忽略空指针——传入 NULL 直接崩溃,这在嵌入式里是致命错误

核心要点:

  • 先跳过空白,再处理符号,最后逐位转换
  • 溢出检查用除法,别直接乘
  • 遇到非法字符立即停止,返回已转换部分
  • 空指针和空字符串返回 0

atoi 看起来简单,但真正做好不容易。我每次面试新人,都会让他们手写这个函数。能写出完整溢出检查的,基本代码功底都不差。你试试看,能不能一次写对?