题目描述
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0
输入描述:
输入一个字符串,包括数字字母符号,可以为空
输出描述:
如果是合法的数值表达则返回该数字,否则返回0
示例1
输入
+2147483647
1a33
输出
2147483647
0
解题思路
首先这道题目 一定是有bug存在的,问题就在于int类型范围的特性问题,而且,系统显示通过的代码实际上也是错的,我查看了很多通过的代码,几乎都存在一些问题,但下面我也给出了正确的程序。
int类型数值范围
原来的计算机试16位,整型范围是−32768~32767-32768~32767−32768~32767,现在的计算机试32位的,整型范围是 变成了-2的31次方到2的31次方。这样的话范围是 −2147483647~2147483647-2147483647 ~2147483647−2147483647~2147483647但是由于人为规定的100000…000(31个0)100000…000(31个0)100000…000(31个0)为−2147483648-2147483648−2147483648,所以范围就变成了−2147483648~2147483647-2147483648~2147483647−2147483648~2147483647。
int的范围是:−2147483648~2147483647-2147483648~2147483647−2147483648~2147483647 unsigned int的范围是:0~42949672950~42949672950~4294967295。如果超出这个范围会出现循环比如−2147483648−1=2147483647-2147483648-1=2147483647−2147483648−1=2147483647;4294967295+1=04294967295+1=04294967295+1=0 ;
int的模是 2147483648(2312^{31}231)。unsigned int的模是429496729642949672964294967296(2322^{32}232);比如4294967297%4294967296=14294967297\%4294967296=14294967297%4294967296=1(与超出范围得到的结果相同429496729742949672974294967297超出2所以−1+2=1-1+2=1−1+2=1);
计算机的处理方式真的很神奇,这样最大的数+1+1+1变成了最小的数,最小的数−1-1−1又成了最大的数。所有的数都连成了一个圆环。
本题思路
1、
首现,没有搞懂题意,题目的隐含意思是不会出现乘除号,只会出现加减号,而且必须是在字符串的第一位,即不会出现像"235×34235\times34235×34" 或 “45/2345/2345/23” 或"23+4523+4523+45“或者”132−234132-234132−234"这样运算符在字符串中间的情况。
2、
由于int类型具有循环性,每次溢出则数值变号,如果给出的数值一直在变大,岂不是又有再次变号的可能(通过用VS做实验 会的),这样此题根本就没法解,只能是认为溢出的话不会导致数值又一次的循环回来。
3、
这是将字符数字转换为整型数字的关键代码,num初试为0。
num = 0;
num = num * 10 + str[i] - '0';
4、
下面语句很巧妙,如果字符串的第一个字符输入了符号,i=1i = 1i=1,如果没有输入,i=0i = 0i=0。
for(int i = (str[0] == '+' || str[0] == '-') ? 1 : 0; i < str.length(); i++)
代码实现(错的 但OJ系统却通过了)
class Solution {
public:
int StrToInt(string str) {
if(str.length() == 0)
return 0;
int sign = 1;
if(str[0] == '+')
sign = 1;
if(str[0] == '-')
sign = -1;
int num = 0;
for(int i = (str[0] == '+' || str[0] == '-') ? 1 : 0; i < str.length(); i++)
{
if(str[i] > '0' && str[i] < '9') // 这里的区间就存在问题 但还是显示通过了
{
num = num * 10 + str[i] - '0';
if (num <= INT_MIN && sign == 1)
return 0;
}
else
return 0;
}
return num * sign;
}
};
上述这个代码之所以通过了,是因为OJ系统只存储了几个特定的数值去试,结果上述程序刚好满足而已。
代码实现(正确的 但OJ系统无法通过)
class Solution{
public:
int StrToInt(string str)
{
if (str.length() == 0)
return 0;
int sign = 1;
if (str[0] == '+')
sign = 1;
if (str[0] == '-')
sign = -1;
int num = 0;
for (int i = (str[0] == '+' || str[0] == '-') ? 1 : 0; i < str.length(); i++)
{
if (str[i] >= '0' && str[i] <= '9')
{
num = num * 10 + str[i] - '0';
if (num < 0 && sign == 1)
return 0;
if (num < 0 && num != INT_MIN && sign == -1)
return 0;
}
else
return 0;
}
return (num * sign);
}
}
代码解读
和上面OJ通过但是错误的代码相比,有以下改进改:
1、读取0~9字符时,区间出错
if (str[i] >= '0' && str[i] <= '9')// 应该是小于等于 大于等于
2、当数值为正并且数值大于INT_MAX时,由于循环问题,该数字由整数变为负数,所以只要最后程序循环的时候,num变为了负数,就说明数值已经发生了越界。
if (num < 0 && sign == 1)
return 0;
3、当数值为负数并且小于 INT_MIN 时。由于循环体中判断的永远都是无符号数,但是正数最大值的绝对值是比负数最小值的绝对值小1的,所以当 str 等于INT_MIN 时,循环体中的 num 越界 1 位,自动循环到负数的最小值即 INT_MIN 对于负数来说 不算越界,所以代码要如下这样写;但是 str 值小于 INT_MIN 的话,就发生了越界,此时 循环体中的 num 值(已经发生了循环)就会大于 负数的最小值即 INT_MIN ,所以只要 num 不等于 INT_MIN 的并且为负数,说明str传递的负数值也发生了越界。
if (num < 0 && num != INT_MIN && sign == -1)
return 0;
4、当传入 -2147483648 (负数最小值)时,由上述讲解,程序成功通过for循环,返回下述代码时,−2147483648×(−1)-2147483648 \times (-1)−2147483648×(−1) 本来应该是 2147483648 , 但是因为超过了 INT_MAX ,发生了数值的循环,又变成了 INT_MIN ,所以才会有−2147483648×(−1)=−2147483648-2147483648 \times ( -1) = -2147483648−2147483648×(−1)=−2147483648的情况发生。
return (num * sign);
C 和 C++ 的任何一个知识点 都值得去认真推敲。