剑指offer 把字符串转换成整数(C++)题目有bug

题目描述

将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0

输入描述:

输入一个字符串,包括数字字母符号,可以为空

输出描述:

如果是合法的数值表达则返回该数字,否则返回0

示例1

输入

+2147483647
1a33

输出

2147483647
0

解题思路

首先这道题目 一定是有bug存在的,问题就在于int类型范围的特性问题,而且,系统显示通过的代码实际上也是错的,我查看了很多通过的代码,几乎都存在一些问题,但下面我也给出了正确的程序。

int类型数值范围

原来的计算机试16位,整型范围是−32768~32767-32768~327673276832767,现在的计算机试32位的,整型范围是 变成了-2的31次方到2的31次方。这样的话范围是 −2147483647~2147483647-2147483647 ~214748364721474836472147483647但是由于人为规定的100000…000(31个0)100000…000(31个0)100000000310−2147483648-21474836482147483648,所以范围就变成了−2147483648~2147483647-2147483648~214748364721474836482147483647

int的范围是:−2147483648~2147483647-2147483648~214748364721474836482147483647 unsigned int的范围是:0~42949672950~429496729504294967295。如果超出这个范围会出现循环比如−2147483648−1=2147483647-2147483648-1=214748364721474836481=21474836474294967295+1=04294967295+1=04294967295+1=0 ;

int的模是 2147483648(2312^{31}231)。unsigned int的模是4294967296429496729642949672962322^{32}232);比如4294967297%4294967296=14294967297\%4294967296=14294967297%4294967296=1(与超出范围得到的结果相同429496729742949672974294967297超出2所以−1+2=1-1+2=11+2=1);

计算机的处理方式真的很神奇,这样最大的数+1+1+1变成了最小的数,最小的数−1-11又成了最大的数。所有的数都连成了一个圆环。

本题思路

1、
首现,没有搞懂题意,题目的隐含意思是不会出现乘除号,只会出现加减号,而且必须是在字符串的第一位,即不会出现像"235×34235\times34235×34" 或 “45/2345/2345/23” 或"23+4523+4523+45“或者”132−234132-234132234"这样运算符在字符串中间的情况。

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) = -21474836482147483648×(1)=2147483648的情况发生。

return (num * sign);

C 和 C++ 的任何一个知识点 都值得去认真推敲。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值