在最近的一个项目中需要对传入的字符串参数转换成 int
类型,并对转换后的值进行判断,看是否在 0-7
内,最直接的办法就是调用函数:
#include <stdlib.h>
int atoi(const char *nptr)
但是这个函数有一个明显的问题,对于非法的输入,返回值都为零,而且不进行错误检查。
这就造成当函数返回零的时候无法判断输入的是字符 0
还是非法字符。
如下面的输入和对应的输出:
atoi("aaa") = 0
atoi("0") = 0
atoi("123abc") = 123
atoi("abc123") = 0
第一行和第二行输出一样但是输入不一样,造成程序无法区别非法输入和 0
, 第三行在我们的需求中属于非法输入,但却切断字符返回正常整数。这就造成如果输入的是 3a
,返回值则为 3
,和我们期望的返回不一致。
下面给出针对这个项目需求的改进方法。
使用atoi转换前进行检查
既然atoi函数本身的功能不能满足我们的要求,那我们就自己进行检查。
在下面的代码中,我们用返回值来指示传入的字符是否非法,如果非法,返回1,否则返回零。字符串的转换结果使用函数参数返回。
转换函数在第3行检测空指针和空字符;第6行的循环跳过所有前面的空格;第8行检测是否有正负号;第10行的循环遍历剩下的字符,如果有非数字字符,则返回1。否则调用atoi函数进行转换。现在这个函数可以满足我们项目的全部要求。
但是,这个函数的代码比较比较复杂,各种检查不容易理解和维护。进一步的改进请往下看。
int atoi_c(const char* buf, int* result)
{
int offset = 0;
if (buf == NULL || *buf == '\0')
return 1;
while(buf[offset] == ' ')
offset++;
if(buf[offset] == '+' || buf[offset] == '-')
offset++;
while (buf[offset] != '\0')
{
if(buf[offset] > '9' || buf[offset] < '0'){
return 1;
}
offset++;
}
*result = atoi(buf);
return 0;
}
使用strtol检查非法字符
通过查阅man手册,发现 strtol
函数可以通过对参数进行检查来判断输入的字符是否是合法的字符。
#include <stdlib.h>
long int strtol(const char *nptr, char **endptr, int base);
第一个参数就是要转换的字符串;第二个参数非 NULL
时,储存第一个非法的转换字符;第三个参数是转换的进制。有了第二个参数我们可以通过检查它是不是字符串的最后一个字符(\0
)来判断整个字符串是不是合法的。
改进之后的代码如下所示:
int atoi_c(const char* buf, int* result)
{
char* invalid;
*result = (int)strtol(buf, &invalid, 10);
if(*invalid != '\0')
return 1;
return 0;
}
但是这个代码还是有一点问题,当我们将 long int
类型的值强制转换成 int
时,就有可能发生溢出,导致最后的转换结果出错。而且我们只需要对 0-7
的值进行判定,所以使用 unsigned int
足够了。
我们继续对代码进行改进。
使用strtoumax改进代码
#include <inttypes.h>
intmax_t strtoimax(const char *nptr, char **endptr, int base);
在上面的函数原型中,各个参数的作用和 strtol
的一样但是这个函数有控制溢出的能力,更多的信息可以查看 man 3 strtoumax
。
函数的返回值 intmax_t
在 Ubuntu2004
中的定义是 unsigned long int
,为了适应返回值,我们可以对返回值先进行比较,如果大于 unsigned int
的最大值的话,返回错误。最后的代码如下:
#include<inttypes.h>
int atoi_c(const char* buf, unsigned int* result)
{
if (buf == NULL || *buf == '\0')
return 1;
char* invalid;
uintmax_t ret;
ret = strtoumax(buf, &invalid, 10);
if(*invalid != '\0')
return 1;
if(ret> UINT32_MAX)
return 1;
*result = (unsigned int)ret;
return 0;
}