[C]字符串与数值互转换的语义区别

字符串与数值互相转换是非常常用的功能,大家都对它习以为常了。我想除了程序库的编写者之外,没有人会像我这样为了这个问题纠结一两天。

 

C提供了一套函数用于字符串与数值互转换,包括itoa,atoi,strtol等。为了方便叙述,我将这套函数抽象成下面两个伪C函数:

string C_IntToStr(int value, int radix);

int C_StrToInt(string str, int radix);

 

根据函数名称就可以知道它们的用途。注意本文只讨论整型值,浮点数不在该范围内。

 

对于十进制的转换来说,这两个函数的行为很正常;对于非负数的非十进制转换来说,也很正常。这都没什么好说的。可是对于负数的非十进制转换来说就不一样了。先看看下面的调用:

string str = C_IntToStr(-1, 16);

 

执行之后str的值是“ffffffff”。嗯,一切都没有什么问题。

 

在我的理想世界中,C_IntToStr和C_StrToInt应该是相互可逆的,也就是说,经过下面的调用之后:

int a = -1;

string str = C_IntToStr(a, 16);

int b = C_StrToInt(str, 16);

a和b应该都是-1。然而实际上,执行了这段代码之后,b的值是2147483647,恰好是int类型的最大值。如果检查一下errno,会发现它的值是ERANGE,意味着发生了溢出。

 

在这里会产生很多疑惑:ffffffff不就是-1吗?为什么会溢出?为什么返回值是2147483647而不是-2147483648?……

 

回答这些问题之前,要先明确一个事实:我们之所以认为ffffffff等于-1,是从计算机科学家的角度来看的。我们都知道在计算机内负数是用补码来表示的,-1的补码形式是所有位都是1,对于32位的int类型来说,转换成十六进制就是ffffffff。不知道大家有没有注意到,在讨论计算机内部存储的时候,是根本没有正负数概念的——所有数值都是无符号的,负数只是通过一种特殊的方式来表示。所以,我们很自然地把ffffffff看作是计算机内部的存储方式。

 

如果从数学家的角度来看(假设他不了解计算机),十进制的-1转换成十六进制也是-1,ffffffff是十进制的4294967295,而-ffffffff是十进制的-4294967295。从纯数学的角度看来,数值都是有正负之分的,无论它用何种进制来表示。

 

因此,我们得到了字符串与数值互相转换的两种语义:计算机语义和数学语义。计算机语义认为,除了十进制之外,其它进制的字符串都表示数值在计算机内的存储方式;数学语义则认为所有进制的字符串都表示这个数值本身。在计算机语义中,之所以要把十进制与非十进制区分开来,是因为用其它进制来描述的存储方式最终目的都是为了表示十进制数,而且正负数本身就是针对十进制数的——这意味着其它进制的字符串不能带有负号。

 

如果C_IntToStr和C_StrToInt使用了相同的语义,那么它们就是相互可逆的。遗憾的是,它们恰好使用了不同的语义:C_IntToStr使用的是计算机语义;C_StrToInt使用的是数学语义。所以,使用C_IntToStr永远不会得到带有负号的非十进制字符串;使用C_StrToInt时,如果不在字符串前面加个负号,永远不会得到负数。

 

现在可以解答上面提出的三个问题了。从计算机语义来看,C_IntToStr(-1, 16)得到“ffffffff”是显然的。从数学语义来看,ffffffff是4294967295,大于int类型的最大值2147483647,所以C_StrToInt("ffffffff", 16)会判断溢出。最后,如果发生溢出的话,C_StrToInt会根据字符串是否有符号来返回int类型的最小值或最大值:“ffffffff”是正数,所以返回最大值2147483647;如果是“-ffffffff”,则会返回最小值-2147483648。

 

上面的分析基于MSC,我不知道其它C运行库的情况如何。我正在写一个C++函数库,其中有ToString和FromString函数,包装了C的相应函数。一开始C_IntToStr和C_StrToInt的这种不一致性给我带来了非常大的困惑,我为这个问题纠结了好久,现在总算把它们理清了。

 

最后唠叨一下,如果自己要写一套字符串与数值互相转换的函数,一定要明确好所使用的语义,切勿混用。要是必须支持两种语义(不太可能会有这种需求吧!),最好的做法是提供两套这样的函数,一套对应计算机语义,一套对应数学语义。使用计算机语义的时候,还要注意类型字节大小的问题,因为数值类型不只int一种,还有char,short甚至long long。

你可能感兴趣的:(字符串)