【C++基础】第六课:类型转换

原码,反码,补码,类型转换,取模运算,取余运算

Posted by x-jeff on April 24, 2019

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.机器数、真值

1.1.机器数

一个数在计算机中的二进制表示形式,叫做这个数的机器数。机器数是带符号的,其最高位存放符号,非负为0,负数为1。例如:二进制数0000 0011为十进制数3,二进制数1000 0011为十进制数-3。

上述的0000 00111000 0011即为机器数。

1.2.真值

如果机器数1000 0011的最高位不再是符号位,则其对应的值不再是-3,而是$2^7+2^1+2^0=128+2+1=131$。因此,为了区分这种情况,将带符号位的机器数对应的真正数值称为机器数的真值。

2.原码、反码、补码

原码、反码、补码是机器存储一个具体数字的编码方式。

2.1.原码

最高位为符号位,以8位二进制数为例:

  • $+1=[0000 0001]_原$
  • $-1=[1000 0001]_原$

8位二进制数的取值范围为:1111 1111~0111 1111,即-127~127。

2.2.反码

  • 正数的反码就是其本身。
  • 负数的反码是在符号位不变的基础上,其余位取返。

以8位二进制数为例:

  • $+1=[0000 0001]_反$
  • $-1=[1111 1110]_反$

2.3.补码

  • 正数的补码就是其本身。
  • 负数的补码就是在其反码的基础上加1。

以8位二进制数为例:

  • $+1=[0000 0001]_补$
  • $-1=[1111 1111]_补$

2.4.为何要使用原码、反码、补码

对于计算机来说,加减是最基础的运算。如果让计算机在进行加减运算时去辨识符号位,可能会使计算机的基础电路设计变得非常复杂,因此考虑让符号位也参与到加减运算中,从而就衍生出了原码、反码和补码。

例如十进制数的计算:$1-1=0$,我们用原码试验一下:

$(+1)+(-1)=[0000 0001]_原+[1000 0001]_原=[1000 0010]_原=-2$

这个结果明显是不对的,因此引入了反码:

$(+1)+(-1)=[0000 0001]_反+[1111 1110]_反=[1111 1111]_反=[1000 0000]_原=-0$

这时问题出现在了符号位上,机器会认为+0(0000 0000)和-0(1000 0000)是两个数,但是实际上0带符号是没有意义的。

这个时候就需要补码来解决这个问题:

$(+1)+(-1)=[0000 0001]_补+[1111 1111]_补=[0000 0000]_补=[0000 0000]_原=0$

之前出现-0的问题不存在了,而且可以用1000 0000,即-0,表示-128。

❗️因此,计算机是采用补码的方式存储数值的。并且补码表示的数值范围为-128~127,即$-2^7\sim 2^7-1$,(以8位为例)。

3.类型转换

当在程序的某处我们使用了一种类型而其实对象应该取另一种类型时,程序会自动进行类型转换。

例如:

1
2
3
4
5
6
7
bool b=42;//b为真
int i=b;//i=1
i=3.14;//i=3
double pi=i;//pi=3
unsigned char c=-1;//假设char占8比特,c的值为255,对应的字符由使用的字符集决定
signed char c2=256;//假设char占8比特,c2的值是未定义的
unsigned char c3="abcd";//输出c3="d"或者直接报错

类型所能表示的值的范围决定了转换的过程:

  • 当把一个非布尔类型的算术值赋给布尔类型时,初始值为0则结果为false,否则结果为true
  • 当把一个布尔值赋给非布尔类型时,初始值false则结果为0,初始值为true则结果为1。
  • 当把一个浮点数赋给整数类型时,进行了近似处理。结果值将仅保留浮点数中小数点之前的部分。
  • 当把一个整数值赋给浮点类型时,小数部分记为0。如果该整数所占的空间超过了浮点类型的容量,精度可能有损失。
  • 当赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。例如,8比特大小的unsigned char可以表示0至255区间内的值,如果我们赋了一个区间以外的值,则实际的结果是该值对256取模后所得的余数。因此,把-1赋给8比特大小的unsigned char所得的结果是255。

参照第3.1部分,a=-1,b=256,取模运算可以求得c=-1,因此r=-1-256*(-1)=255

  • 当赋给带符号数一个超出它表示范围的值时,结果是未定义的。此时,程序可能继续工作、可能崩溃,也可能生成垃圾数据。

3.1.取模运算和取余运算

对于整型数a,b来说,取模运算或者取余运算的方法都是:

  1. 整数商:$c=a/b$
  2. 计算模或者余数:$r=a-c*b$

❗️求模运算和求余运算在第一步不同:取余运算在取c的值时,向0方向舍入;而取模运算在计算c的值时,向负无穷方向舍入。

举个例子,$a=-7;b=4$:

  1. 求模运算时$c=-2,r=1$
  2. 求余运算时$c=-1,r=-3$

3.2.含有无符号类型的表达式

⚠️如果表达式里既有带符号数又有无符号数,当带符号数取值为负时会被自动地转换成无符号数。

例如,当一个算术表达式中既有无符号数又有int值时,那个int值就会转换成无符号数。

1
2
3
4
unsigned u1=10;
int u2=-42;//模为4294967254
cout<<"u2+u2="<<u2+u2<<endl;//输出-84
cout<<"u1+u2="<<u1+u2<<endl;//如果int占32位,输出4294967264

u1+u2相当于u1加上u2的模。当两个无符号数相减时,得到的结果也是无符号数:

1
2
3
unsigned w1=42,w2=10;
cout<<"w1-w2="<<w1-w2<<endl;//输出32
cout<<"w2-w1="<<w2-w1<<endl;//输出4294967264,即-32的模

4.参考资料

  1. 原码、反码、补码详解(作者:张子秋)
  2. 取模运算(百度百科)