Menu

C 语言字符串转 int:atoi、strtol 与数字转字符串

在 C 语言中实现文本和数字的互相转换:为什么 atoi 无法报告错误,如何配合 endptr 和 errno 正确使用 strtol 和 strtod,用 snprintf 把数字转成字符串,以及用 '0' 处理单个数字字符的技巧。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

来自用户、文件和命令行的文本都是字符。"42" 是三个字节:'4'、'2'、'\0',在你转换之前,它里面没有任何东西是数字 42。C 提供了两代转换函数:短小、老旧、无法报告错误的一代,以及更长但能报告错误的一代。本页介绍这两者,并解释为什么实际的程序都用第二组。

老一代:atoi、atol、atof

它们的名字就说明了用途:ASCII 转 int、转 long、转 double。一个参数,一个结果。

开头的空白会被跳过,读取可选的正负号,接着读取数字,之后的内容全部忽略。这很方便,直到输入出错:

三行都打印 0。没有返回码,没有标志,没有办法问一句“转换成功了吗?”,而且超出 int 范围的值是未定义行为,不会报告错误。在玩具程序里转换自己写死的字面量,这还能忍。但对于任何由人或文件提供的内容,它就是一台悄无声息的 bug 制造机。

正确的工具:strtol

strtol(string to long)做的是同样的转换,但多了两个报告结果的渠道。

long strtol(const char *text, char **endptr, int base);

endptr 会被设为函数没有读取的第一个字符。base 是进制:用 10,或者用 0 让字面量自己的前缀决定(0x 表示十六进制,以 0 开头表示八进制)。

读取 endptr 能回答 atoi 回答不了的问题:

  • end == input 表示什么都没转换:文本不是以数字开头的。
  • *end != '\0' 表示后面还有内容。这算不算错误由你决定:解析 "42 apples" 时没问题,但如果整行都应该是一个数字,那就有问题了。

溢出通过 errno 单独报告。完整、正确的写法值得写一次然后反复使用:

有三个细节保证了它的正确性。调用前的 errno = 0 很重要,因为库函数只会设置 errno,从不清除它,之前某次调用留下的旧值会被误认为是这次调用的失败。strtol 返回 long,它可能比 int 更宽,所以针对 INT_MIN/INT_MAX 的范围检查要和 ERANGE 分开。最后,用返回值表示是否成功、通过指针传出结果,是 C 函数表达“要么得到一个值,要么失败”的方式,因为返回值无法同时承担两项工作。

strtoul 是对应的无符号版本,strtoll/strtoull 用于 long long。

其他进制

进制 16 读取十六进制;进制 0 会检查前缀并自行决定。想知道一个字符串在另一种进制下表示什么,可以把它粘贴到进制转换器里。

进制 0 在一种特定情况下是个陷阱:像 "012" 这样补零的十进制数会被当作八进制读取,结果是 10。如果输入是十进制,就明确写 10。

浮点数:strtod

strtod 是针对 double 的同一种用法,而 atof 和 atoi 有同样的缺陷。

strtod 还接受科学计数法("1.5e3")、十六进制浮点数,以及 "inf" 和 "nan" 这样的单词。区域设置会影响小数分隔符,解析在别处生成的文件时,这一点常让人意外。在默认的 "C" 区域设置下,分隔符始终是点。

数字转文本:snprintf

反过来转换时,标准答案是 snprintf。它最多写入你指定的大小,始终以 '\0' 结尾,并返回完整结果所需的长度。

返回值就是检查截断的依据。如果它大于或等于缓冲区大小,说明文本没有放下:

有两样东西不要用:itoa 不属于标准 C,用了它的代码不是到处都能编译;sprintf 就是没有大小限制的 snprintf,它会毫无提示地写过缓冲区的末尾。

单个字符与 '0'

数字字符 '0' 到 '9' 保证是连续的,所以直接相减就能在数字字符和它的值之间转换。

只有数字是这样的。字母并不保证连续,所以用 c - 'a' 做字母运算是不可移植的。相信这个减法结果之前,先用 isdigit 判断:

转换成 unsigned char 是必需的,原因和字符串一页中讲的一样:<ctype.h> 中的函数对负数参数是未定义的,而普通 char 可能是有符号的。

该用哪个函数

  • 来自用户、文件或 argv 的文本 → 用 strtol / strtod,并做完整检查。
  • 你自己写的、完全可控的字面量 → 用 atoi 也可以,虽然不出彩。
  • 把数字写入缓冲区 → 用 snprintf,并检查返回值。
  • 单个数字字符 → 先 isdigit,再减去 '0'。
  • 永远不要用 → gets、sprintf、itoa。

常见问题

C 语言中如何把字符串转换成 int?

正确的做法是 strtol(text, &end, 10):它返回转换后的值,把 end 设为第一个没有被读取的字符,并通过 errno 报告溢出。atoi(text) 写起来更短,但对 "0" 和 "hello" 都返回 0,无法区分这两种情况。

C 语言的 atoi 有什么问题?

它无法报告失败。atoi("abc") 返回 0,和真正的零没法区分,而溢出是未定义行为,不会报告错误。只要文本来自用户、文件或命令行参数,就应该用 strtol。

C 语言中如何把 int 转换成字符串?

用 snprintf(buf, sizeof buf, "%d", n)。它是标准函数,受缓冲区大小限制,并返回完整结果所需的字符数,所以返回值大于或等于 sizeof buf 就说明文本被截断了。itoa 不是标准 C。

C 语言中如何把字符 '7' 变成数字 7?

减去 '0':int digit = c - '0';。在所有 C 字符集中,数字字符都是连续的,所以相减得到 0 到 9。先用 isdigit((unsigned char)c) 做判断,因为对其他字符做这种运算没有意义。

Coddy 编程语言插图

和 Coddy 一起学编程

开始