字符串与数字转换:atoi、atof、sprintf、sscanf 的用法,手写 itoa 函数

字符串和数字之间的转换,是C语言里最基础也最常用的操作之一。我做了这么多年嵌入式开发,几乎每个项目都离不开这几个函数。今天咱们就把它们彻底讲透。

一、为什么需要转换?

你想想看,我们写程序时,数据在内存里是以二进制存的。但用户看到的、文件里存的、串口发出来的,都是字符串。比如你从键盘读了个"123",它其实是三个字符'1'、'2'、'3',不是整数123。反过来,你想把计算结果打印到屏幕上,也得把数字变成字符串。

说白了,转换就是数据在「人眼可读的形式」和「机器可算的形式」之间来回倒腾。

二、atoi:字符串转整数

atoi 全称是 ASCII to Integer。原型很简单:

#include <stdlib.h>
int atoi(const char *str);

它把字符串开头的数字部分解析出来,遇到非数字字符就停。比如:

char buf[] = "   -42abc";
int val = atoi(buf);  // val = -42

嗯,这里要注意:atoi 会跳过开头的空白字符(空格、制表符、换行),然后处理可选的正负号,接着读连续的数字。

踩坑提醒: 如果字符串不是以数字开头,atoi 返回 0。但 0 本身也是合法数字,所以你没法区分是转换成功还是失败。我个人习惯用 strtol 替代,它能提供更详细的错误信息。

我曾经在一个数据采集项目里,因为没检查 atoi 的返回值,结果传感器坏了输出乱码,程序一直当0处理,查了两天才发现。从那以后,我对这类函数都格外小心。

三、atof:字符串转浮点数

atof 是 ASCII to Float,用法和 atoi 类似:

#include <stdlib.h>
double atof(const char *str);

它支持科学计数法,比如 "3.14e2" 会被解析成 314.0。

char buf[] = "  3.14159";
double pi = atof(buf);  // pi = 3.14159
小技巧: atof 同样有精度和错误处理的问题。如果你需要高精度或错误检测,建议用 strtod

四、sprintf:数字转字符串

sprintf 是格式化输出的利器。它把各种类型的数据按指定格式写入字符串缓冲区。

#include <stdio.h>
int sprintf(char *buf, const char *format, ...);

看个例子:

char buf[64];
int num = 255;
sprintf(buf, "十进制:%d,十六进制:%x,八进制:%o", num, num, num);
// buf = "十进制:255,十六进制:ff,八进制:377"
安全警告: sprintf 不检查缓冲区长度!如果格式化后的字符串超过 buf 大小,就会缓冲区溢出。我建议用 snprintf,它能指定最大写入长度。
snprintf(buf, sizeof(buf), "数值:%d", num);  // 安全写法

五、sscanf:字符串解析利器

sscanf 是 scanf 的字符串版本,从字符串里按格式提取数据。

#include <stdio.h>
int sscanf(const char *str, const char *format, ...);

比如解析一个日期字符串:

char date[] = "2024-03-15";
int year, month, day;
sscanf(date, "%d-%d-%d", &year, &month, &day);
// year=2024, month=3, day=15
核心要点: sscanf 的返回值是成功匹配并赋值的参数个数。一定要检查这个返回值!如果返回 3 说明三个都解析成功,返回 2 说明只解析了前两个。

我记得有一次解析GPS数据,字符串格式偶尔会乱,没检查返回值就直接用,结果程序跑着跑着就崩了。加上返回值检查后,问题立刻定位到了。

六、手写 itoa 函数

标准C库没有 itoa(整数转字符串),但很多平台自己实现了。咱们自己写一个,加深理解。

思路很简单:不断取模得到最低位,然后除以10去掉最低位,直到数字变成0。注意处理负数和0的特殊情况。

// 手写 itoa:整数转字符串(十进制)
void my_itoa(int num, char *str) {
    int i = 0;
    int is_negative = 0;
    
    // 处理0的特殊情况
    if (num == 0) {
        str[i++] = '0';
        str[i] = '\0';
        return;
    }
    
    // 处理负数
    if (num < 0) {
        is_negative = 1;
        num = -num;  // 注意:INT_MIN 取反会溢出!
    }
    
    // 逐位取出数字(结果是逆序的)
    while (num > 0) {
        str[i++] = (num % 10) + '0';
        num /= 10;
    }
    
    // 添加负号
    if (is_negative) {
        str[i++] = '-';
    }
    
    str[i] = '\0';
    
    // 反转字符串
    int start = 0;
    int end = i - 1;
    while (start < end) {
        char temp = str[start];
        str[start] = str[end];
        str[end] = temp;
        start++;
        end--;
    }
}
注意: 上面代码对 INT_MIN(-2147483648)取反会溢出,因为正数最大是2147483647。实际项目中可以用 unsigned 类型来避免这个问题。

改进版:

void my_itoa_safe(int num, char *str) {
    // 用 unsigned 避免溢出问题
    unsigned int unum;
    int i = 0;
    int is_negative = 0;
    
    if (num == 0) {
        str[i++] = '0';
        str[i] = '\0';
        return;
    }
    
    if (num < 0) {
        is_negative = 1;
        unum = (unsigned int)(-(num + 1)) + 1;  // 安全取反
    } else {
        unum = (unsigned int)num;
    }
    
    while (unum > 0) {
        str[i++] = (unum % 10) + '0';
        unum /= 10;
    }
    
    if (is_negative) {
        str[i++] = '-';
    }
    
    str[i] = '\0';
    
    // 反转
    int start = 0, end = i - 1;
    while (start < end) {
        char t = str[start];
        str[start] = str[end];
        str[end] = t;
        start++;
        end--;
    }
}

七、知识体系总览

下面这张图帮你理清这几个函数的关系和使用场景:

字符串与数字转换函数体系 字符串 ↔ 数字转换 字符串 → 数字 atoi 字符串 → int atof 字符串 → double sscanf 格式化解析 数字 → 字符串 sprintf 格式化输出 itoa(手写) 整数 → 字符串 ⚠️ 使用注意事项 • atoi/atof:不检查错误,非法输入返回0 • sprintf:不检查缓冲区长度,建议用 snprintf • sscanf:务必检查返回值,确认解析成功个数

八、总结与避坑

这几个函数,说白了就是C语言里最常用的「翻译官」。我给你的建议是:

  • 能用 snprintf 就别用 sprintf —— 安全第一
  • 能用 strtol/strtod 就别用 atoi/atof —— 错误处理更完善
  • sscanf 一定要检查返回值 —— 我吃过这个亏
  • 手写 itoa 时注意 INT_MIN 的溢出 —— 用 unsigned 绕过去
个人习惯: 我一般在项目里封装一层转换函数,统一用 snprintf 和 strtol,这样接口清晰,出问题也好排查。你也不妨试试。

好了,字符串和数字转换这块就讲到这里。代码写多了你会发现,这些基础函数用好了,能省不少调试时间。


公众号:蓝海资料掘金营,微信deep3321