K&R C阅读笔记之整型升级

最近在看K&R C 的《The C Programming Language》第二版,其中 附录A.6.5 算术类型转换 中是这么说的,

许多运算符都会以类似的方式在运算过程中引起转换,并产生结果类型。其效果是将所有操作数转换为同一公共类型,并以此作为结果的类型。这种方式的转换称为普通算术类型转换。

  • 首先,如果任何一个操作数为long double 类型,则将另一个操作数转换为long double类型。
  • 否则,如果任何一个操作数为double类型,则将另一个操作数转换为double类型。
  • 否则,如果任何一个操作数为float类型,则将另一个操作数转换为float类型。
  • 否则,同时对两个操作数进行整型提升;然后,如果任何一个操作数为unsigned long int类型,则将另一个操作数转换为unsigned long int类型。
  • 否则,如果一个操作数为long int 类型且另一个操作数为unsigned int 类型,则结果依赖于long int 类型是否可以表示所有的unsigned int 类型的值。如果可以,则将unsigned int类型的操作数转换为long int;如果不可以,则将两个操作数都转换为unsigned long int类型。
  • 否则,如果一个操作数为long int类型,则将另一个操作数转换为long int类型。
  • 否则,如果任何一个操作数为unsigned int类型,则将另一个操作数转换为unsigned int类型。
  • 否则,将两个操作数都转换为int类型。

也就是说,平时我们程序中的加减乘除,在运算的时候,如果变量的类型不同,则先会全部提升为同一类型然后再运算。于是马上测试了下面的代码:

#include <stdio.h>
#include <stdint.h>

int main(int argc, char *argv[])
{
    int x = -234;
    unsigned int y = 21;

    printf("x+y=%d\n",x+y);
    getchar();

    return 0;
}

这个输出的结果会是什么呢?在看书中所述的这个转换规则之前,我们可以毫不思考地说出结果是-213,这等于是小学所学的加法运算,当然事实输出的结果也确实是这个。但程序是如何计算的呢?按照上文的解释,intunsigned int 在一起运算,应该先把 x 转换为 unsigned int类型的变量,然后再做运算。如果是程序的初学者,可能认为 x 转换为 unsigned int 后为234,然后加上21,结果应该是255,这明显不对了。呵呵,我刚入门时也这么认为。为了证明这个在计算时,确实是先把x转换为unsigned int,我测试了如下代码:

#include <stdio.h>
#include <stdint.h>

int main(int argc, char *argv[])
{
    int x = -234;
    unsigned int y = 21;
    unsigned int c;

    c = x;
    c = c + y;
    printf("c=%d\n",c);
    getchar();

    return 0;
}

这个代码是为了验证上面的想法,你觉得输出会是多少呢?这个总该是255了吧。哈哈,其实不然,这两份代码的结果都是 -213。证明转换规则是对的。要想搞清楚这里面的来龙去脉,就要弄懂有符号数转换为无符号数时如何转换!

K&R C中关于这个转换是这么说的:

  • 将任何整数转换为某种指定的无符号类型数的方法是:以该无符号类型能够表示的最大值加 1 为模,找出与此整数同余的最小的非负值。在对二的补码表示中,如果该无符号类型的位模式较窄,这就相当于左截取;如果该无符号类型的位模式较宽,这就相当于对带符号值进行符号扩展和对无符号值进行 0 填充。
  • 将任何整数转换为带符号类型时,如果它可以在新类型中表示出来,则其值保持不变,否则它的值同具体的实现有关。

也就是说,转换为无符号数有两个方法。第一个方法是求同余。求之前要先搞清楚什么是同余?

同余是数论中的重要概念。给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a≡b(mod m)。对模m同余是整数的一个等价关系。

弄清楚同余后,我们再看这个例子,unsigned int所能表示的最大值加1为4294967296(即2^32),把它作为mod,设b为-234,求与b同余的最小非负数。那么根据公式,a-(-234)/mod能得到一个整数,那么a的最小非负值应该是4294967296-234=4294967062,就是说x转换为unsigned int后的值为4294967062,它的二进制表示为11111111111111111111111100010110。是不是觉得它就是-234的补码呢!

再看转无符号数的第二个方法。就是看-234的补码是多少!然后再看unsigned int所能表示的位比这个补码大还是小,如果比它大,那么针对有符号数,前面全部补符号位。如果是无符号数,就全部补0。因为-234他是一个int类型,是32位的,补码也是32位,unsigned int也是32位,所以它转换为unsigned int后,等于是把-234的补码原封不动地搬过去。

接下来看相加,那么就是4294967062+21=4294967083,两者相加的过程如下:

    11111111111111111111111100010110  (unsigned int) x
+   00000000000000000000000000010101  (unsigned int) y
----------------------------------------
    11111111111111111111111100101011  (unsigned int) 结果

注意,相加后的结果在第一个例子中,先存放在一个unsigned int的临时变量中,但是printf在输出的时候是%d,也就是输出为int类型,于是就将结果的unsigned int 类型强制转换为int 类型。这里又出现了反向转换,我们再看上面的引用,提到的转换为有符号数的方法,这个结果的二进制值是完全可以用int表示的,所以二进制值不变,并最终被识别为有符号数。从符号位看出是一个负数,从补码反推原码,也是取反再加1。原码为10000000000000000000000011010101,所以输出-213

最后,如果把printf中的%d改成%u会怎么样,结果还会是-213吗?

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值