《编程之法》1.4字符串转换成整数

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qibofang/article/details/51923997

题目描述:输入一个由数字组成的字符串,请把它转换成整数输出

分析:int型整数的范围为:-2147483648~+2147483647,这意味着字符串输入太长会没办法正常显示数字,故当转换后的数大于最大正数时,我们显示2147483647;当转换后的数小于最小负数时,我们显示-2147483648。
同时需要考虑字符串中含有非数字字符,以及字符串前有空格等情况,如"  123t444",这时我们规定输出123。此外,还应该考虑到字符串中含有正负号的问题。这些都是小问题,主要需要考虑溢出的情况:

如下,先写一个小程序分析unsigned与int的转换关系:

#include <iostream>
using namespace std;
int main(){
	cout << "-1 = " << -1 << ", unsigned(-1) = " << unsigned(-1) << ", (int)((unsigned)(-1)) =  " << (int)((unsigned)(-1)) << endl;
	cout << "-(unsigned)1 = " << -(unsigned)1 << " 或 " << (unsigned)(-(unsigned)1) << endl;//输出 4294967295 或 4294967295
	unsigned tmp = -(unsigned)1;
	int nums[32], cnt = 0;
	do{
		nums[cnt] = tmp % 2;
		tmp /= 2;
		++cnt;
	}while(cnt < 32);
	cout << "-(unsigned)1的unsigned值为: ";
	for(cnt = 31; cnt >= 0; --cnt)
		cout << nums[cnt];//输出11111111111111111111111111111111
	cout << endl << "-(unsigned)1的int值为: " << (int)tmp << endl;//输出-1
	cout << "(unsigned)~0 >> 1 = " << ((unsigned)~0 >> 1) << endl; //输出2147483647
	cout << "(int)(unsigned)~0 >> 1 = " << (int)((unsigned)~0 >> 1) << endl;//输出2147483647
	cout << "-((unsigned)~0 >> 1) = " << -((unsigned)~0 >> 1) << endl;//输出2147483649
	cout << "-(int)((unsigned)~0 >> 1) = " << -(int)((unsigned)~0 >> 1) << endl;//输出-2147483647
	int MAX_INT = (int)((unsigned)~0 >> 1);
	int MIN_INT = -(int)((unsigned)~0 >> 1) - 1;
}

分析:
第一句:由第一条语句的输出也表明对于代码中的常量,编译器默认它为int型,即有符号型,当加上unsigned做变换后,此后该常量变为无符号型,并按照它所存储的补码形式计算输出,若将其再加上int强制变换,则它此后便又是int型。
第二句到第十三句做了验证和int,unsigned转换分析:对1而言,(unsigned)1的在计算机中的补码存储形式为:00000000000000000000000000000001。对于-(unsigned)1, 加上负号后,就是大学里学过的知识(我记忆的口诀是:若为负数的二,十进制转换,按照"二对加;对十减"):令最高位的符号位变为1,其他位不变,即为它的原码;各位取反(符号位除外),即变为它的反码;最后将反码加1,即变为补码,故最后-(unsigned)1在计算机的补码存储形式为:11111111111111111111111111111111。
-(unsigned)1的值应该是将-(unsigned)1的补码存储形式按照无符号常量的补码存储形式来看,故大小为2^31 + 2^30 + ... + 2^0 = 4294967295。根据第一句分析,(unsigned)(-(unsigned)1) = -(unsigned)1。而(int)(-(unsigned)1)是将它的补码存储形式按照int来看,即需“对十减”,先将11111111111111111111111111111111反过来,为全0,再变为十进制为0,再减1,即(unsigned)(-(unsigned)1)=-1。
因此,我们若想求int的最大正数,而int的最大正数的补码形式为01111111111111111111111111111111,而((unsigned)~0 >> 1)对应的无符号型补码存储形式正好与int型的补码存储形式一致。故可直接int强制转换。
若想求int的最小负数,由于(int)((unsigned)~0 >> 1)已经是int类型,故可直接加负号变为-2147483647,再减去1就为int类型中最小的负数,至于补码的变换就不细述了。
-((unsigned)~0 >> 1):之所以输出2147483649,是因为加上负号后,其补码存储形式01111111111111111111111111111111变为10000000000000000000000000000001,而它又为无符号型,故输出2147483649。
总结:对于程序中使用的常量,编译器默认该常量为int型,当对其使用过unsigned后,该常量就变为unsigned型,此后也可对其再次强制转换为int型。对一个常量取int或unsigned值时,要根据该常量在计算机中的补码存储形式,按照int和unsigned的各自规则得出正确求值。当int转unsigned型时,若int为正或0,则两者补码存储形式一致,大小不会变;若int为负,此时根据int型的补码存储形式按照无符号的求值方式来计算无符号数的大小,大小肯定会改变;当unsigned转int时,若unsigned的二进制最高位为0,很显然,大小不会改变;若unsigned的二进制最高位为1,按照int负数的二进制转十进制的方法求int值,大小肯定也会改变;当对int值加负号时,int值变为其相反数;当对unsigned加负号时,其补码存储形式最高位置1,之后除最高位外按位取反,最后减1,获得其最终补码形式。


如下,先考虑以下有问题的程序片段:

while(isdigit(*str)){
	int c = *str - '0';
	if(sign == '+' && (c > (MAX_INT-n*10))){
		n = MAX_INT;
		break;
	}
	else if(sign == '-' && (c - 1 < (MAX_INT-n*10)){
		n = MIN_INT;
		break;
	}
	n = n*10 + c;
	++str;
}
如输入字符串10522545459,当遍历第十次时,n为1052254545,并进入下次循环中执行c > (MAX_INT-n*10),根据上面的分析,首先MAX_INT肯定小于n*10,且两者为无符号数,相减得到一个负数的补码存储形式,但是得到的相减结果默认为一个无符号常量,而负数的补码存储形式对应的无符号值是很大的,因此c > (MAX_INT-n*10)肯定不成立,即使溢出,也无法进入该处理语句内,无法令最终结果为MAX_INT。

解决方式有两种,a, 既然得到一个负数,就强制转换为int型,即条件语句变为if(sign == '+' && (c > (int)(MAX_INT-n*10))),这样最后相减为负数也能判断出来,并且相减得到的正数的二进制补码最高位也不可能为1;
b,之所以有上述问题的存在,主要原因在于有减法的存在,故将条件语句改为if(sign == '+' && (n == MAX_INT/10 && c > MAX_INT%10) || (n > MAX_INT/10),这样就能考虑到溢出的问题,前者可应对8900000000,1052254545等这种情况,后者可应对2147483649这种情况。
当然n为负数时下面的代码也需要相应的改变,下面是代码:

#include <iostream>
#include <string>
using namespace std;

int StrToInt(string &str){
	int MAX_INT = (int)((unsigned)~0 >> 1);
	int MIN_INT = -(int)((unsigned)~0 >> 1) - 1;
	if(str.size() == 0) return 0;
	//去掉空格
	int i = 0;
	while(str[i] == ' ') ++i;//测试过string会自动过滤字符串前面的空格
	//处理正负
	int sign = 1;
	if(str[i] == '-' || str[i] == '+'){
		if(str[i] == '-')
			sign = -1;
		++i;
	}
	//确定是数字后进行循环
	unsigned n = 0;
	while(i < str.size() && isdigit(str[i])){
		int c = str[i] - '0';
		/**********************处理溢出***********************/
		if(sign > 0 && ((n > (unsigned)MAX_INT/10) || (n == (unsigned)MAX_INT/10 && c > (unsigned)MAX_INT%10))){
			n = (unsigned)MAX_INT;
			break;
		}
		if(sign < 0 && ((n > (unsigned)MIN_INT/10) || (n == (unsigned)MIN_INT/10 && c > (unsigned)MIN_INT%10))){//书中为(unsigned)(MIN_INT/10)),这是因为MIN_INT的补码为100...00,unsigned强制转换后,也正好为2147483648
			n = (unsigned)MIN_INT; 
			break;
		}
		/**********************处理溢出***********************/
		//把之前得到的数字乘以10,再加上当前字符表示的数字
		n = n*10 + c;
		++i;
	}
	//前面是对无符号进行分析,最后将无符号转换为int输出
	if(sign < 0 && n != (unsigned)MIN_INT)
		return -(int)n;
	else
		return (int)n;
}

int main(){
	string str;
	while(cin >> str)
		cout << StrToInt(str) << endl;
	return 0;
}

解法二:
如下,我仅修改了/***/ ~ /***/间的代码,思路是因为无符号范围大,故以无符号数相减,小无符号减大无符号数必定会使二进制最高位变为1,即转成int后变为负数:

#include <iostream>
#include <string>
using namespace std;

int StrToInt(string &str){
	int MAX_INT = (int)((unsigned)~0 >> 1);
	int MIN_INT = -(int)((unsigned)~0 >> 1) - 1;
	if(str.size() == 0) return 0;
	//去掉空格
	int i = 0;
	while(str[i] == ' ') ++i;
	//处理正负
	int sign = 1;
	if(str[i] == '-' || str[i] == '+'){
		if(str[i] == '-')
			sign = -1;
		++i;
	}
	//确定是数字后进行循环
	unsigned n = 0;
	while(i < str.size() && isdigit(str[i])){
		int c = str[i] - '0';
		/**********************处理溢出***********************/
		if(sign > 0 && (c > (int)(MAX_INT - n*10))){
			n = (unsigned)MAX_INT;
			break;
		}
		if(sign < 0 && ((c > (int)((unsigned)MIN_INT - n*10)))){
			n = (unsigned)MIN_INT; 
			break;
		}
		/**********************处理溢出***********************/
		//把之前得到的数字乘以10,再加上当前字符表示的数字
		n = n*10 + c;
		++i;
	}
	//考虑无符号向int数据的转换
	if(sign < 0 && n != (unsigned)MIN_INT)
		return -(int)n;
	else
		return (int)n;
}

int main(){
	string str;
	while(cin >> str)
		cout << StrToInt(str) << endl;
	return 0;
}




猜你喜欢

转载自blog.csdn.net/qibofang/article/details/51923997