30-把字符串转换为数字
11.9 把字符串转换为数字
数字既能以字符串形式存储,也能以数值形式存储。把数字存储为字符串就是存储数字字符。例如,数字 213 以 '2' 、 '1' 、 '3' 、 '\0' 的形式被存储在字符串数组中。以数值形式存储 213 ,存储的是 int 类型的值。
C要求用数值形式进行数值运算(如,加法和比较)。但是在屏幕上显示数字则要求字符串形式,因为屏幕显示的是字符。 printf() 和 sprintf() 函数,通过 %d 和其他转换说明,把数字从数值形式转换为字符串形式, scanf() 可以把输入字符串转换为数值形式。C还有一些函数专门用于把字符串形式转换成数值形式。
假设你编写的程序需要使用数值命令形参,但是命令形参数被读取为字符串。因此,要使用数值必须先把字符串转换为数字。如果需要整数,可以使用 atoi() 函数(用于把字母数字转换成整数),该函数接受一个字符串作为参数,返回相应的整数值。程序清单11.32中的程序示例演示了该函数的用法。
程序清单11.32 hello.c 程序
/* hello.c -- 把命令行参数转换为数字 */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv [])
{
int i, times;
if (argc < 2 || (times = atoi(argv[1])) < 1)
printf("Usage: %s positive-number\n", argv[0]);
else
for (i = 0; i < times; i++)
puts("Hello, good looking!");
return 0;
}
该程序的运行示例:
$ hello 3
Hello, good looking!
Hello, good looking!
Hello, good looking!
$ 是UNIX和Linux的提示符(一些UNIX系统使用 % )。命令行参数 3 被存储为字符串 3\0 。 atoi() 函数把该字符串转换为整数值 3 ,然后该值被赋给 times 。该值确定了执行 for 循环的次数。
如果运行该程序时没有提供命令行参数,那么 argc < 2 为真,程序给出一条提示信息后结束。如果 times 为 0 或负数,情况也是如此。 C 语言逻辑运算符的求值顺序保证了如果 argc < 2 ,就不会对 atoi(argv[1]) 求值。
如果字符串仅以整数开头, atoi() 函数也能处理,它只把开头的整数转换为字符。例如, atoi("42regular") 将返回整数 42 。如果在命令行输入 hello what 会怎样?在我们所用的C实现中,如果命令行参数不是数字, atoi() 函数返回 0 。然而C标准规定,这种情况下的行为是未定义的。因此,使用有错误检测功能的 strtol() 函数(马上介绍)会更安全。
该程序中包含了 stdlib.h 头文件,因为从ANSI C开始,该头文件中包含了 atoi() 函数的原型。除此之外,还包含了 atof() 和 atol() 函数的原型。 atof() 函数把字符串转换成 double 类型的值, atol() 函数把字符串转换成 long 类型的值。 atof() 和 atol() 的工作原理和 atoi() 类似,因此它们分别返回 double 类型和 long 类型。
ANSI C还提供一套更智能的函数: strtol() 把字符串转换成 long 类型的值, strtoul() 把字符串转换成 unsigned long 类型的值, strtod() 把字符串转换成 double 类型的值。这些函数的智能之处在于识别和报告字符串中的首字符是否是数字。而且, strtol() 和 strtoul() 还可以指定数字的进制。
下面的程序示例中涉及 strtol() 函数,其原型如下:
long strtol(const char * restrict nptr, char ** restrict endptr, int base);
这里, nptr 是指向待转换字符串的指针, endptr 是一个指针的地址,该指针被设置为标识输入数字结束字符的地址, base 表示以什么进制写入数字。程序清单11.33演示了该函数的用法。
程序清单11.33 strcnvt.c 程序
/* strcnvt.c -- 使用 strtol() */
#include <stdio.h>
#include <stdlib.h>
#define LIM 30
char * s_gets(char * st, int n);
int main()
{
char number[LIM];
char * end;
long value;
puts("Enter a number (empty line to quit):");
while (s_gets(number, LIM) && number[0] != '\0')
{
value = strtol(number, &end, 10); /* 十进制 */
printf("base 10 input, base 10 output: %ld, stopped at %s (%d)\n",
value, end, *end);
value = strtol(number, &end, 16); /* 十六进制 */
printf("base 16 input, base 10 output: %ld, stopped at %s (%d)\n",
value, end, *end);
puts("Next number:");
}
puts("Bye!\n");
return 0;
}
char * s_gets(char * st, int n)
{
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
while (st[i] != '\n' && st[i] != '\0')
i++;
if (st[i] == '\n')
st[i] = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
下面是该程序的输出示例:
Enter a number (empty line to quit):
10
base 10 input, base 10 output: 10, stopped at (0)
base 16 input, base 10 output: 16, stopped at (0)
Next number:
10atom
base 10 input, base 10 output: 10, stopped at atom (97)
base 16 input, base 10 output: 266, stopped at tom (116)
Next number:
Bye!
首先注意,当 base 分别为 10 和 16 时,字符串 "10" 分别被转换成数字 10 和 16 。还要注意,如果 end 指向一个字符, end 就是一个字符。因此,第1次转换在读到空字符时结束,此时 end 指向空字符。打印 end 会显示一个空字符串,以 %d 转换说明输出 end 显示的是空字符的ASCII码。
对于第2个输入的字符串,当 base 为 10 时, end 的值是 'a' 字符的地址。所以打印 end 显示的是字符串 "atom" ,打印* end 显示的是 'a' 字符的ASCII码。然而,当 base 为 16 时, 'a' 字符被识别为一个有效的十六进制数, strtol() 函数把十六进制数 10a 转换成十进制数 266 。
strtol() 函数最多可以转换三十六进制, 'a'~'z' 字符都可用作数字。 strtoul() 函数与该函数类似,但是它把字符串转换成无符号值。 strtod() 函数只以十进制转换,因此它只需要两个参数。
许多实现使用 itoa() 和 ftoa() 函数分别把整数和浮点数转换成字符串。但是这两个函数并不是C标准库的成员,可以用 sprintf() 函数代替它们,因为 sprintf() 的兼容性更好。