《斯坦福大学:编程范式》第二节:基本数据类型在内存中的二进制表达
我们以C/C++为例。
基本数据类型有:
bool 1 byte
char 1 byte 256个字符,其中75个字符是常用的
short 2bytes
int 4bytes
long 4bytes
float 4 bytes
double 8 bytes
---------------------------bytes-------------------------------------
1 bytes(字节) = 8 bit(位 binary digit)
1 bit 只能表示两个数字 0和1 ,代表高电压 低电压。
1 byte 可以表示2的8次方 =256个字符。
----------------------------------char------------------------------------------------
'A' = 65 = 2的6次方+ 2的0次方。 内存中 表示为 0100,0001
--------------------------------------short--------------------------------------------
2的16次方数量的数字 :0 到 (2的16次方-1)
比如 1000,0000,0000,0001 = 2的15次方+2的0次方
-------------------------------------内存中二进制的加----------------------------------------------------
1001 1001
+ 0101 0001
= 1110 1010
从低位往高位加即可。
-------------------------------------内存中二进制的减----------------------------------------------------
1001 1001
- 0101 0001
= 0100 1000
从低位往高位减即可。
-------------------------------------以上都说的正数,如何表示负数?-------------------------------------
对于一个二进制数,比如 0000,0011
最高位的0 表示它是 正数3
比如 一个short数, 1011
最高位的1 表示它是 负数
但是,这个负数,不代表 -3
因为二进制的负数,是用补码表示的。
---------------------为什么要用最高位表示负数?——————————————
因为 我们希望二进制的加法能够同时兼容正数和负数
比如 正数7 是 0000,0111
负数7 是 1000,0111
我们希望 他们相加,能很优雅的变成0.
如果按之前的加法运算,则
0000,0111
+1000,0111
= 1000,1000
为 -14,显然不是我们期望的。
一个二进制怎么方便的变成 0 呢?
也就是 0000,0000
我们发现
1111,1111
+0000,0001
= 1,0000,0000
最高位的1,因为没有内存可以存放,溢出了,所以会被抛弃掉,
所以我们得到 0000,0000
所以,我们可以先让一个二进制变成 1111,1111 这种模式。
对于之前的
0000,0111
+ 1111,1000
=1111,1111
很明显,对一个二进制取反,再相加,就得到 所有位为1.
1111,1111
+0000,0001
= 0000,0000
所以我们发现:
0000,0111
+1111,1001
= 0
同样,一个负数,要得到它的正数
比如 1111,1001
取反:0000,0110
加1: 0000,0111
所以一个二进制的负数,是 他的取反+1
-x=!x+1
-----------------------以及如何处理有负数的加法———或者说,如何表示正数之间的减法?———
比如 正数7 是 0000,0111
负数7 是 1000,0111
计算过程为 7 -7 =0;
在二进制里: 我们只有加法,转换为 7
我们对负数7 取反 得 0111,1001
0000,0111
+ 0111,1001
= 1000,0000
-----------------------数据基础类型之间的转换———
本质是位模式的拷贝
-----------------------char转short———
char ch = 'A';
short a = ch;
cout<< a<< endl;
结果是 65。
因为 char 中 ‘A’ 在二进制中是 0100,0001
赋值给short, 则为16位了 : 0000,0000,0100,0001
等于65
-----------------------short转char———
short s = 67;
char ch =s;
cout<<ch <<endl;
输出为 'C'
-----------------------int转short———
长于 16位的 被截断。(第16位是符号位)
int a = 0001,0000,0000,1110, 0100,0000,0000,0001;
short b = a;
那么b = 0100,0000,0000,0001;
-----------------------有符号的short———
short s = -1;
int i = s;
s = 1111,1111,1111,1111, 1111,1111,1111,1111,
i = 1111,1111,1111,1111, 1111,1111,1111,1111, 1111,1111,1111,1111, 1111,1111,1111,1111,
对于有符号的二进制的赋值,
多余的位数,会用符号位 全部赋值, 这叫 “符号扩展”。 为了保证 之前的 +1 形成的 多米罗骨牌 能继续生效。
-----------------------浮点数的表示 float 和double———----------------------------------------------------------------------------------------
float a = (前面位数都为0) 0000,1111
假设 我们的表示方式 跟上面一样
为了表示小数, 我们假设 前面用来表示整数部分,后面8位表示小数部分。
第一个1 为 2的-1次幂, 第二个1 为2的 -2次幂, 第3个1 为2的 -3次幂, 第4个1 为2的 -4次幂,
那么 float a = 0.5 +0.25+ 0.125+ 0.0625
我们发现 按这种思路,无法表达很大和很小的浮点数,而且数字非常不连续。
真正的浮点数表示是?
一个规格化的32位浮点数x的真值为:
x=(−1)s×(1.M)×2E−127
一个规格化的64位浮点数x的真值为:
x=(−1)s×(1.M)×2E−1023
这里的E 是指 exp(指数部分) 最大为255,最小为0
M 指 尾数部分 ,最大为全部位为1,最小为全部位为0
S 指 符号位, 符号位 = 0, 则 -1 的S次幂 = 1; 最终为正数。
-----------------------整数 转 浮点数———----------------------------------------------------------------------------------------
int a = 5;
float b = a;
cout<<b<<endl;
打印为 5,但是在二进制里,这次不是位拷贝了,是如上面例子,转换为 浮点数的表达方式。
浮点数转 整数 同理。 只是截取了整数部分。
——————————————一些疯狂的例子————————————————————————
int i = 37;
float f = * (float *)&i ;
打印 f, 此次 f != 37 了。
这里其实是对 i 的地址求值。
从右往左运算
1. 我们首先 &i 拿到 指向 i的指针, 因为i 是一个int型,所以 &i 是 int* 类型,代表了一个变量的地址,这四个直接存放了
2. 然后我们诱导它变成了 float* 类型 ,
(注意
int b;
float a = b
是强行转换类型 作为float 存储在内存中
而 (float) b 是在cpu计算过程中 诱导 把它认为float, 两者有区别),
这并没有让 各个bit 发生变化, 所以“我只能假装我是其他类型了” 。
3. 然后 对于类型系统来说, 会尝试 解引用为float
所以 37 落在低位的位置,也就是 float的尾数部分。然后左边所有位都是 0 。 所以是一个很小的正的浮点数
-------------------------------------
float f = 7.0f;;
short s = *(short *) &f;
1. float 是32位, short 是16位。
&f 拿到 f的地址。
2. (short *) 代表 这个地址被 当做一个指向short的指针。
3. 所以,我们只会去拿 short这么长的位数,16位,尝试解析成short。
那么从高位拿起,还是低位呢? 从高位拿起!
因为指针是指向 右边高位的第一个bit !
这个跟 位拷贝不同, 位拷贝,是从低位拷贝起。
所以我们拿到 float的高位的16位。 最后当做short解析。
当然,不再是7了。