C语言中的数值类型转换:整型提升/寻常算术变化/截断/强制类型转换

整型提升:

K&R C中关于整型提升(integral promotion)的定义为:

"A character, a short integer, or an integer bit-field, all either signed or not, or an object of enumeration type, may be used in an expression wherever an integer maybe used. If an int can represent all the values of the original type, then the value is converted to int; otherwise the value is converted to unsigned int. This process is called integral promotion."


上面的定义归纳下来就是以下两个原则:

1). 只要一个表达式中用到了整型值,那么类型为char、short int整型位域(这几者带符号或无符号均可)的变量,以及枚举类型的对象,都可以被放在这个整型变量的位置。


2). 如果1)中的变量的原始类型值域可以被int表示,那么原值被转换为int;否则的话,转为unsigned int

以上两者作为一个整体,被成为整型提升(Integral promotion)

整型提升的概念容易与普通算术类型转换产生混淆。这两者的区别之一在于后者是在操作数之间类型不一致的情况下发生,最终将操作数转换为同一类型。而在算术运算这种情景下,即使操作数具有相同的类型,仍有可能发生整型提升

例如:

char a, b, c;
c = a + b;

在上述过程中,尽管两个运算符"+"和"="的操作数全为char型,但在中间计算过程中存在着整型提升:对于表达式a+b ,a、b都是char型,因此被提升至int型后,执行“+”运算,计算结果(int型)再赋值给c(char型),又执行了隐式的类型转换

理解了整型提升的概念后,面对下面这个C语言的FAQ,你应该不会产生困惑。

例:

char a;
printf(“sizeof(a)=%d”, sizeof(a));

输出: 1


原因:a不是一个表达式,a是char型,char型占1字节。

printf(“sizeof(‘A’) = %d”, sizeof(‘A’));

输出: 4

原因:字符‘A’是int型,不需整型提升,int型占4字节


char a, b;
printf(“sizeof(a+b)=%d”, sizeof(a+b));

输出: 4

原因:a+b是一个算术表达式,a、b均整型提升(int型),所以占4个字节。


char a, b, c;
printf(“sizeof(c=a+b)=%d”, sizeof(c=a+b));

输出: 1

原因:表达式c=a+b中,a和b是算术运算,因此整型提升(int型),计算结果(int型)再赋值给c(char型),又执行了隐式的类型转换,所以最终占1字节。


来源: >

运算过程中的隐式数据转换如下:
   double      ←   float 高
  ↑
  long
  ↑
 unsigned
  ↑
   int          ←   char,short 低

参数传递:
另一个可能发生隐式参数转换的地方就是参数传递。
 在K&R C中,由于函数的参数也是表达式,所以也会发生类型提升。
在ANSI C中,如果使用了适当的函数原型,类型提升便不会发生,否则也会发生。
在被调用函数的内部,提升后的参数被裁减为原先声明的大小

这就是为什么单个的printf()格式字符串%d能适用于几个不同类型,
short、char或int,而不论实际传递的是上述类型的哪一个。
函数从堆栈中(或寄存器中)取出的参数总是int类,并在printf或其他被调用函数里按统一的格式处理。
printf是一个可变参数函数,此时一般的参数提升始终会发生。
来源: >

消除形参与实参之间的整型提升:
ANSI C函数原型的目的是使C语言成为一种更加可靠的语言。建立原型就是为了消除形参和实参之间的类型不匹配。
如果使用了函数原型,缺省参数提升就不会发生。如果参数声明为char,则实际传递的也是char。


截断/赋值中的类型转换:

当赋值运算符两边的运算对象类型不同时,将要发生类型转换, 转换的规则是:把赋值运算符右侧表达式的类型转换为左侧变量的类型。具体的转换如下:

  (1) 浮点型与整型

  ● 将浮点数(单双精度)转换为整数时,将舍弃浮点数的小数部分, 只保留整数部分

  将整型值赋给浮点型变量,数值不变,只将形式改为浮点形式, 即小数点后带若干个0.

注意:整型转浮点型可能是不准确的:

种类-------符号位-------------指数位----------------尾数位---- float-----第31位(占1bit)---第30-23位(占8bit)----第22-0位(占23bit) double--第63位(占1bit)---第62-52位(占11bit)---第51-0位(占52bit) int-------第31位(占1bit)--------------------------第30-0位(占31bit)
取值范围主要看指数部分: float的指数部分有8bit(2^8),由于是有符号型,所以得到对应的指数范围-128~128。 double的指数部分有11bit(2^11),由于是有符号型,所以得到对应的指数范围-1024~1024。 由于float的指数部分对应的指数范围为-128~128,所以取值范围为: -2^128到2^128,约等于-3.4E38 — +3.4E38 精度(有效数字)主要看尾数位: float的尾数位是23bit,对应7~8位十进制数,所以有效数字有的编译器是7位,也有的是8位,也即一个整数转换为float的话,会表示成科学计数法,由小数(精度)和指数构成,对0,1四舍五入。int可以稳式转换成floatdoublefloat只能强制转换成int,但是可以隐式转换成doubledouble只能强制转换成floatint

  在说明问题之前,还很有必要温习一下计算机组成原理时学习到的一些知识,就是二进制补码表示以及浮点数表示。我想把一个十进制转化为二进制的方法已经不用多费唇舌,只不过为了计算方便以及消除正零与负零的问题,现代计算机技术,内存里存的都是二进制的补码形式,当然这个也没什么特别的,只不过有某些离散和点,需要特殊定义而已,比如-(2^31),这个数在int的补码里表示成1000(31个零),这个生套补码计算公式并不能得到结果(其实不考虑进位的话还真是这个结果,但是总让人感觉很怪)。再者,浮点数,其实就是把任何二进制数化成以0.1....开头的科学计数法表示而已。

  废话说完,这就出现了几个问题,而且是比较有意思的问题。

int i = Int32.MaxValue;
float f = i;
int j = (int)f;
bool b = i == j;

  这里的b,是false。刚才这个操作,如果我们把float换成long,第一次进行隐式转换,第二次进行强制转换,结果将会是true。乍一看,float.MaxValue是比int.MaxValue大了不知道多少倍的,然而这个隐式转换中,却造成了数据丢失。int.MaxValue,这个值等于2^31-1,写成二进制补码形式就是01111(311),这个数,在表示成float计数的科学计数法的时候,将会写成+0.1111(231)*2^31,对于那311,里面的最后8,被float无情的抛弃了,因此,再将这个float强制转换回int的时候,对应的int的二进制补码表示已经变成了0111(231)00000000,这个数与最初的那个int相差了255,所以造成了不相等。

  那么提出另一个问题,什么样的int变成float再变回来,和从前的值相等呢?这个问题其实完全出在那23float的数据位上了。对于一个int,把它写成二进制形式之后,成为了个一32个长度的01的排列,对于这个排列,只要第一个1与最后一个1之前的间距,不超过23,那么它转换成 float再转换回来,两个值就会相等。这个问题是与大小无关的,而且这个集合int这个全集下并不连续

double d = 0.6;
float f = (float)d;
double d2 = f;
bool b = d == d2;

  这里的b,也是false。刚才这个操作,如果开始另d等于0.5,结果就将会是true。乍一看,0.6这个数这么短,doublefloat都肯定能够表示,那么转换过去再转换回来,结果理应相等。其实这是因为我们用十进制思考问题太久了,如果我们0.6化成二进制小数,可以发现得到的结果是0.10011001……(1001循环)。这是一个无限循环小数。因此,不管float还是double,它在存储0.6 的时候,都无法完全保存它精确的值(计算机不懂分数,呵呵),这样的话由于float保存23位,而double保存52,就造成了double转化成 float的时候,丢失掉了一定的数据,非再转换回去的时候,那些丢掉的值被补成了0,因此这个后来的double和从前的double值已经不再一样了。

  这样就又产生了一个问题,什么样的double转换成float再转换回来,两个的值相等呢?其实这个问题与刚才int的那个问题惊人的相似(废话,都和float打交道,能不相似么),只不过我们还需要考虑doublefloat多了3位的指数位,太大的数double能表示但float 不行。

  还有一个算是数学上的问题,什么样的十进制小数,表示成二进制不是无限小数呢?这个问题可以说完全成为数学范畴内的问题了,但是比较简单,答案也很明显,对于所有的最后一位以5结尾的十进制有限小数,都可以化成二进制的有限小数(虽然这个小数可能长到没谱)。

  最后,一个有意思有问题,刚才说过0.6表示成为二进制小数之后,是0.1001并且以1001为循环节的无限循环小数,那么在我们将它存成浮点数的时候,一定会在某个位置将它截断(比如float23位和double52位),那么真正存在内存里的这个二进制数,转化回十进制,到底是比原先的十进制数大呢,还是小呢?答案是It depends。人计算十进制的时候,是四舍五入,计算机再计算二进制小数也挺简单,就是01。对于float,要截断成为23,假如卡在24位上的是1,那么就会造成进位,这样的话,存起来的值就比真正的十进制值大了,如果是0,就舍去,那么存起来的值就比真正的十进制值小了。因此,这可以合理的解释一个问题,就是0.6d转换成float再转换回double,它的值是0.60000002384185791,这个值是比0.6大的,原因就是 0.6的二进制科学计数法表示,第24位是1,造成了进位。

  到了这里,仍然有一事不解,就是对于浮点数,硬件虽然给予了计算上的支持,但是它与十进制之间的互相转换,到底是如何做到的呢,又是谁做的呢(汇编器还是编译器)。这个东西突出体现在存在内存里的数明显实际与0.6不等,但是无论哪种语言,都能够在Debug以及输入的时候,将它正确的显示成 0.6提供给用户(程序员),最好的例子就是doubleToString方法,如果我写double d=0.59999999999999999999999999999d.ToString()给我的是0.6。诚然,对于double来说,我写的那个N长的数与0.6在内存里存的东西是一样的,但是计算机,又如果实现了将一个实际与0.6不相等的数变回0.6并显示给我的呢?

来源: >
 

  (2) 单、双精度浮点型

  ● 由于c语言中的浮点值总是用双精度表示的,所以float 型数据只是在尾部加0延长为doub1e型数据参加运算,然后直接赋值。doub1e型数据转换为float型时,通过截尾数来实现,截断前要进行四舍五入操作。

  (3) char型与int型

  ● int型数值赋给char型变量时,只保留其最低8位,高位部分舍弃。

  ● chr型数值赋给int型变量时, 一些编译程序不管其值大小都作正数处理,而另一些编译程序在转换时,若char型数据值大于127,就作为负数处理。对于使用者来讲,如果原来char型数据取正值,转换后仍为正值;如果原来char型值可正可负,则转换后也仍然保持原值, 只是数据的内部表示形式有所不同。

另有说法:

无符号数截断时,截断后数仍为无符号

有符号数截断时,自动把截断后的数转换为无符号

  (4) int型与1ong型

  ● long型数据赋给int型变量时,将低16位值送给int型变量,而将高16 位截断舍弃。(这里假定int型占两个字节)。

  将int型数据送给long型变量时,其外部值保持不变,而内部形式有所改变。

  (5) 无符号整数

  ● 将一个unsigned型数据赋给一个占据同样长度存储单元的整型变量时(如:unsigned→int、unsigned long→long,unsigned short→short) ,原值照赋,内部的存储方式不变,但外部值却可能改变。

  ● 将一个非unsigned整型数据赋给长度相同的unsigned型变量时, 内部存储形式不变,但外部表示时总是无符号的

  /*例:赋值运算符举例 */

  main()
  { 
            unsigned a,b;
      int i,j;
      a=65535;
      i=-1;
      j=a;
      b=i;
      printf(“(unsigned)%u→(int)%d ”,a,j);
      printf(“(int)%d→(unsigned)%u ”,i,b);
  }


  运行结果为:

  (unsigned)65535→(int)-1

  (int)-1→(unsigned)65535

  ● 计算机中数据用补码表示,int型量最高位是符号位,为1时表示负值,为0时表示正值。如果一个无符号数的值小于32768则最高位为0,赋给 int型变量后、得到正值。如果无符号数大于等于32768,则最高位为1, 赋给整型变量后就得到一个负整数值。反之,当一个负整数赋给unsigned 型变量时,得到的无符号值是一个大于32768的值

  ● c语言这种赋值时的类型转换形式可能会使人感到不精密和不严格,因为不管表达式的值怎样,系统都自动将其转为赋值运算符左部变量的类型。

  ● 而转变后数据可能有所不同,在不加注意时就可能带来错误。 这确实是个缺点,也遭到许多人们批评。但不应忘记的是:c面言最初是为了替代汇编语言而设计的,所以类型变换比较随意。当然, 用强制类型转换是一个好习惯,这样,至少从程序上可以看出想干什么。


来源: >
 

你可能感兴趣的:(C++)