字符串转换成整数

问题描述

LeetCode: String to Integer (atoi)

Implement atoi to convert a string to an integer

分析

需要考虑的可能情况:

  1. 空指针输入
  2. 字符串前的空格
  3. 字符串所表示数值的正负号
  4. 结束条件,遇到非数字或者'\0'结束
  5. 考虑溢出,分别与int值所能表示的最大(0x7fffffff)和最小值(0x8000000)进行比较

测试用例

黑盒测试用例设计方法包括等价类划分法、边界值分析法

  1. 空指针
  2. 字符串前几位为空格、全空格字符串
  3. 带正负号的合法、非法字符串
  4. 以非数字字符结尾的字符串
  5. 溢出,+/- 1

需具备的基本能力

  1. C语言中负数取模的结果等于对应正数取模的相反数。
  2. 计算机中的二进制用补码来表示。32位整型的最大值为2^31-1 (0x7fffffff),最小值为-2^31 (0x8000000),二进制最高位为符号位。-1对应0xffffffff。

核心思想

如何判断是否溢出?

  1. 如果是正数和零,分别和MAX_INT/10,MAX_INT%10来比较

    (result > Integer.MAX_VALUE / 10 || (result == Integer.MAX_VALUE / 10 && digit > Integer.MAX_VALUE % 10))

    如果没有溢出,result = result * 10 + digit;

  2. 如果是负数,分别和MIN_INT/10,MIN_INT%10来比较

    (result < Integer.MIN_VALUE / 10 || (result == Integer.MIN_VALUE / 10 && digit > -Integer.MIN_VALUE % 10))

    当result == Integer.MIN_VALUE / 10成立时,还有另外一种判断溢出的思路,该思路源自JDK中的Integer类对于parseInt的实现

    (result * 10 < Integer.MIN_VALUE + digit)

    如果没有溢出,result = result * 10 - digit;

  3. JDK中的Integer类对于parseInt的实现

    1和2实际上是在对于正负进行分类讨论,而parseInt中将这两种比较统一为一个。因为负数的取值范围要比正数的取值范围大1,因此,无论是对于正数还是负数的比较,都从负数的角度来看,这样可以避免溢出。这里只摘取核心部分,完整源码详见源码部分。

    int result = 0;
    int limit = -Integer.MAX_VALUE; // 注意,这里limit为负值
            if (firstChar == '-') {
                limit = Integer.MIN_VALUE; // limit发生了变化
            } 
        multmin = limit / radix; // 类似于前面提到的Integer.MIN_VALUE / 10
        while (i < len) {
            digit = Character.digit(s.charAt(i++),radix);
            if (result < multmin) { // 注意,这里是负数的视角
                throw NumberFormatException.forInputString(s);
            }
            result *= radix;
            if (result < limit + digit) { // 这个比较非常巧妙,实际上就是在比较result - digit < limit,但直接这样比较,result - digit有可能溢出
                throw NumberFormatException.forInputString(s);
            }
            result -= digit; // 计算出当前转换的结果
        }
    return negative ? result : -result;
    

另一种思路--转换为long long类型的整数

unsigned   int   0~4294967295   
int   2147483648~2147483647 
unsigned long 0~4294967295
long   2147483648~2147483647
long long的最大值:9223372036854775807
long long的最小值:-9223372036854775808
unsigned long long的最大值:1844674407370955161
__int64的最大值:9223372036854775807
__int64的最小值:-9223372036854775808
unsigned __int64的最大值:18446744073709551615

取值范围摘自http://www.cnblogs.com/xiangshancuizhu/archive/2010/12/12/1903719.html

首先result = result * 10 + digit;然后在判断溢出时直接和MAX_INT或者MIN_INT比较即可。

但这种思路有局限性,因为最大类型的整数无法通过这种思路来实现。

需要考虑的细节

  1. 函数名的编写(指向const的指针)

    int atoi(const char *str)

    指向const的指针(const int *)常用作函数的形参。将形参定义为指向const的指针以此确保传递给函数的实际对象在函数中不因为形参而被修改。

  2. 如果自定义MAX_INT和MIN_INT的话,需要将其定义为常量(C++中的内容)

    定义一个变量代表某一常数的方法有一个严重的问题,这个变量可以被修改。const限定符提供了一个解决办法,它把一个对象转换成一个常量。因为常量在定义后就不能被修改,所以定义时必须初始化。

    const int MAX_INT = 0x7fffffff;

    非const变量默认为extern。要使const变量能够在其他的文件中访问,必须显式地指定它为extern。在全局作用域声明的const变量是定义该对象的文件的局部变量,此变量只存在于那个文件中,不能被其他文件访问。

源码

这里引用JDK中的public static int parseInt(String s, int radix)函数所对应的源码,细节已经做过分析了。该函数可以对2-36进制之间的任何字符串进行转换。

注意:为了避免二义性,一个字符必须只能代表一个数,36刚好是10个数字与26个英文字母之和。

对于C/C++相关的代码,为了加入对于进制的支持,需要建立字符和数字的一个映射关系,出现10的部分直接改成进制即可。

public static int parseInt(String s, int radix)
            throws NumberFormatException
{
    /*
     * WARNING: This method may be invoked early during VM initialization
     * before IntegerCache is initialized. Care must be taken to not use
     * the valueOf method.
     */

    if (s == null) {
        throw new NumberFormatException("null");
    }

    if (radix < Character.MIN_RADIX) {
        throw new NumberFormatException("radix " + radix +
                                        " less than Character.MIN_RADIX");
    }

    if (radix > Character.MAX_RADIX) {
        throw new NumberFormatException("radix " + radix +
                                        " greater than Character.MAX_RADIX");
    }

    int result = 0;
    boolean negative = false;
    int i = 0, len = s.length();
    int limit = -Integer.MAX_VALUE;
    int multmin;
    int digit;

    if (len > 0) {
        char firstChar = s.charAt(0);
        if (firstChar < '0') { // Possible leading "+" or "-"
            if (firstChar == '-') {
                negative = true;
                limit = Integer.MIN_VALUE;
            } else if (firstChar != '+')
                throw NumberFormatException.forInputString(s);

            if (len == 1) // Cannot have lone "+" or "-"
                throw NumberFormatException.forInputString(s);
            i++;
        }
        multmin = limit / radix;
        while (i < len) {
            // Accumulating negatively avoids surprises near MAX_VALUE
            digit = Character.digit(s.charAt(i++),radix);
            if (digit < 0) {
                throw NumberFormatException.forInputString(s);
            }
            if (result < multmin) {
                throw NumberFormatException.forInputString(s);
            }
            result *= radix;
            if (result < limit + digit) {
                throw NumberFormatException.forInputString(s);
            }
            result -= digit;
        }
    } else {
        throw NumberFormatException.forInputString(s);
    }
    return negative ? result : -result;
}

拓展

如何判断转换成功?

http://zhedahht.blog.163.com/blog/static/25411174200731139971/

函数返回0,是因为转换失败返回0还是因为字符串中只有字符0?

函数返回MAX_INT,是因为上溢出还是因为字符串恰好是MAX_INT?

两种思路:

  1. bool StrToInt(const char *str, int& num);

    用户使用这个函数的时候会觉得不是很方便,因为他不能直接把得到的整数赋值给其他整形变量,显得不够直观。

    如何在保证直观的前提下当碰到非法输入的时候通知用户呢?

  2. 使用全局变量(C++)

    enum Status {kValid = 0, kInvalid}; int g_nStatus = kValid;

    注意,还可以继续考虑,这样的命名好吗?名称中没有包含其实际用途。

    enum AtoiStatus {atoiValid = 0, atoiInvalid, atoiOverflow}; int g_atoiStatus = atoiValid;

    还可以增加枚举成员,每一种异常对应一个成员值。

  3. 在Java中只需要抛出异常即可,详见源码部分的示例。

如果字符串是其他进制呢?

JDK中实现了public static int parseInt(String s, int radix)函数,详见源码部分。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值