问题
- 把一个字符串转换成整数。
- 当输入的字符串是一个空指针或者含有非法的字符时,应该返回什么值呢?0怎么样?那怎么区分非法输入和字符串本身就是”0”这两种情况呢?
问题分析
在学C语言的时候,学过
int atoi (const char * str);
第一 atoi()
函数会扫描参数 str
字符串,所以在使用指针之前,我们要做的第一件事是判断这个指针是不是为空。如果试着去访问空指针,将导致程序崩溃。还有不能为空字符。
注意:不要弄混空指针(NULL)和空字符(’\0’)。字符串名字也为一个指针。
if (str == NULL || *str == '\0' || t == NULL){ //t是传过来的指针
return 0;
}
第二、str
字符串可能不完全是数字字符,如果str
以空格开头,应跳过前面的空白字符(格)(例如空格,tab缩进,\t,\n等,可以通过 isspace()
函数来检测),直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时(‘\0’)就没有必要再继续转换,结束转换并返回结果。
while(isspace(*str)) //跳过空白格
{
str ++;
}
or
while (*str <= 32 && *str > 0)
{
str ++;
}
第三、处理完空白字符后,判断第一个有效字符是否为正负号。
注意:先默认为正号。
第四、也是最后一个要考虑的问题:溢出问题。由于输入的数字是以字符串的形式输入,因此有可能输入一个很大的数字转换之后会超过能够表示的最大的整数而溢出。
易错点:
#include <limits>
if(num > std::numeric_limits::max())//不要用这个,因为等你溢出后,已经超过最大值了
现在已经分析的差不多了,开始考虑编写代码。首先我们考虑如何声明这个函数。由于是把字符串转换成整数,很自然我们想到:
int StrToInt(const char* str);
这样声明看起来没有问题。但当输入的字符串是一个空指针或者含有非法的字符时,应该返回什么值呢?0怎么样?那怎么区分非法输入和字符串本身就是”0”这两种情况呢?
接下来我们考虑另外一种思路。我们可以返回一个布尔值来指示输入是否有效,而把转换后的整数放到参数列表中以引用或者指针的形式传入。于是我们就可以声明如下:
bool StrToInt(const char *str, int& num);
这种思路解决了前面的问题。但是这个函数的用户使用这个函数的时候会觉得不是很方便,因为他不能直接把得到的整数赋值给其他整形变量,显得不够直观。
前面的第一种声明就很直观。如何在保证直观的前提下当碰到非法输入的时候通知用户呢?一种解决方案就是定义一个全局变量,每当碰到非法输入的时候,就标记该全局变量。用户在调用这个函数之后,就可以检验该全局变量来判断转换是不是成功。
分析总结
这个函数需要一个字符指针做为输入,需要一个变量返回转换后的结果,另外还需要返回转换的状态(成功转换和转换失败)。因此有下面两种函数声明方式。第一种声明方式第一种声明方式对用户而言非常直观,但需要使用了全局变量记录转换的状态,不够优雅;而第二种声明方式是用bool型返回值来表明转换的状态,转换结果用参数返回,在很多API中都用这种方法,但该方法声明的函数使用起来不够直观。网上的第一种声明方式的实现较多,可能是受C语言影响太大,毕竟在C语言提供的库函数中就是这种方式。
代码展示:
1.第一种声明方式
参照atoi函数的全局变量来设计的。
库函数atoi的原理:
atoi通过全局变量来区分返回0的情况。
如果是非法输入,返回0,并把这个全局变量设为特殊标志;
如果输入是”0”,则返回0,不会设置全局变量。
enum Status {kValid = 0, kInvalid};//枚举类型
int g_nStatus = kValid;//设置为全局变量
///
// Convert a string into an integer
///
int StrToInt(const char* str)
{
g_nStatus = kInvalid;
long long num = 0;
if(str != NULL && *str != '\0')//至少保证字符串不为空
{
const char* digit = str;//用了一下中间变量,保存原始字符串,防止被破坏
// the first char in the string maybe '+' or '-'
while (isspace(*digit))
{
digit ++;
}
bool minus = false;
if(*digit == '+')//通过第一个字符判断是正是负
digit ++;
else if(*digit == '-')
{
digit ++;
minus = true;
}
// the remaining chars in the string
while(*digit != '\0')//字符串结束标志
{
if(*digit >= '0' && *digit <= '9')
{
num = num * 10 + (*digit - '0');
// overflow
if(num < 0)整数溢出的情况
{
num = 0;
break;
}
digit ++;
}
// if the char is not a digit, invalid input
else
{
num = 0;
break;
}
}
if(*digit == '\0')
{
g_nStatus = kValid;
if(minus)
num = 0 - num;//转化为负数的方式
}
}
return static_cast(num);
}
2.第二种声明方式
#include <stdio.h>
#include <limits>
#include <string.h>
#include <stdlib.h>
using namespace std;
bool StrToInt(const char* str,long int & result)//传地址达到双向传址
{
long int num = 0;
bool flag = true;
if(str == NULL || *str == '\0' || result == NULL)//字符串为空时,转换失败,此处不能用if(str == NULL)
{
flag = false;
return flag;
}
while(*str <= 32 && *str > 0) //c++中可以用isspace()跳过空白格
{
str++;
}
bool minus = false;//默认为'+'
if(*str == '+')//通过第一个有效字符判断是正是负
str ++;
else if(*str == '-')
{
str ++;
minus = true;
}
while(*str != '\0')//字符串结束标志
{
if(*str <= '9' && *str >= '0')//c++可以用库函数isdigit()来判断
{
num = num * 10 + (*str - '0');
if(num < 0)//整数溢出的情况,若溢出肯定变为负数
{
flag=false;
break;
}
str ++;
}
else
{
if(num==0) //当遇到第一个非数字字符时,num为0,转换失败
flag=false;
break;
}
}
if(flag)
{
if(minus)
result = 0 - num;//转化为负数的方式
else
result = num;
}
return flag;
}
int main ()
{
int i=0;
char myStr[100];
//cin.getline(myStr,20);
long int temp;
printf("输出long int型的最大值:");
printf("%d\n",std::numeric_limits<long int>::max());
printf("请输入第 %d 组测试数据\n",++i);
while(gets(myStr) != NULL)//c++中cin.getline(字符串名字,分配空间)函数来输入字符串
{
if(StrToInt(myStr,temp))
{
printf("转化成功,%d",temp);
} else {
printf("转化失败");
}
}
return 0;
}
讨论:在面试时,我们可以选用任意一种声明方式进行实现。但当面试官问我们选择的理由时,我们要对两者的优缺点进行评价。第一种声明方式对用户而言非常直观,但使用了全局变量,不够优雅;而第二种思路是用返回值来表明输入是否合法,在很多API中都用这种方法,但该方法声明的函数使用起来不够直观。
最后值得一提的是,在C语言提供的库函数中,函数atoi能够把字符串转换整数。它的声明是int atoi(const char *str)。该函数就是用一个全局变量来标志输入是否合法的
参考材料:
1.C语言实现把字符串转换成整数
2.C++实现把字符串转化为整数
3.《剑指offer》 何海涛著