数据存储(二)

187 阅读6分钟
int main()
{
    int i = -20;
    unsigned int j = 10;
    printf("i+j=%d",i+j);
    return 0;
}

image.png

这段程序最后的输出结果是-10。下面给出我的计算过程。

int i = -20;
-20的原码:10000000 00000000 00000000 00010100
-20的反码:11111111 11111111 11111111 11101011
-20的补码:11111111 11111111 11111111 11101100
-20放进了整型变量i中,i中就保存了
11111111 11111111 11111111 11101100
unsigned int j = 10;
10的原码:00000000 00000000 00000000 00001010
10的反码:00000000 00000000 00000000 00001010
10的补码:00000000 00000000 00000000 00001010
10放进无符号整型变量j中,j中就保存了
00000000 00000000 00000000 00001010
i和j相加,就是他们保存的补码相加
11111111 11111111 11111111 11101100
00000000 00000000 00000000 00001010
11111111 11111111 11111111 11110110 - 补码
%d是打印有符号的十进制整数,我们先将补码转成原码:
11111111 11111111 11111111 11110101 - 反码
10000000 00000000 00000000 00001010 - 原码
再将原码转换为十进制,结果就是-10    
int main()
{
    unsigned int i;
    for(i = 9; i>=0; i++)
    {
        printf("%u\n",i);
    }
    return 0;
}

image.png

这段程序运行结果是一个一直输出i值的死循环......

unsigned int i;
i是一个无符号整数,无符号数有一个特点就是没有负数,恒为正
for(i = 9; i>=0; i++)
而这个for循环的条件是当i<0时才能跳出循环,结果就是i不断的减一,无限循环下去
当i0时,i再减一,i就变成了-1。在计算机里面是计算的补码,-1的补码:
11111111 11111111 11111111 11111111
将它放到无符号整数变量i里面,就会按照无符号整数来解析它。
直接将 11111111 11111111 11111111 11111111 转换为10进制,
而且最高位的1代表数值,而不是符号位。
int main()
{
    char a[1000];
    int i;
    for(i=0; i<1000; i++)
    {
        a[i] = -1-i;
    }
    printf("%d\n",strlen(a));
}

最后的输出结果是什么,数组a里面的存入的是哪些元素呢? image.png

strlen(a)
strlen函数是计算字符串长度的函数,当遇到'\0'时才会停止计算,返回结果。
'\0'也就是0,所以strlen(a)是在计算a中'0'元素之前的元素个数。'0'元素是
指a这个数组中有一个元素里面放的是'0'。
那么假如a数组中没有0元素呢?结果会是一个随机值吗?怎么确定a数组中就会
元素'0'呢?
我们根据程序来复盘一下a数组中会被放入哪些元素
i=0, 1, 2, ... 126, 127, 128,129 ...
a{-1,-2,-3,... -127,-128,-129,-130...}
a数组是字符型,取值范围是-128~127,前128个元素,即-1,-2,-3......-128
在取值范围内,可以存入数组a。从第129个元素开始,值从-129,-130往后发展,
这些数值都不在char范围内,所以存进数组的不是-129,-130这些数。

image.png

 通过上面这张图我们可以知道-128减一以后,应该是127127再减一是126。
 所以-129 对应127,-130对应126,以此类推的这样下去,最后就会减到0。
 数组a中就会放入0元素。在0元素以后,再往后一个元素就又会放入-1。所以
 数组这1000个元素,就是-1,-2...-127,-128,127,126...1,0这些元素为
 一个组,循环的往数组a中放,直到放满1000个元素为止。
 说回strlen(a),计算a0元素之前的元素个数。-1,-2...-127,-128128个
 元素,127,126...1,0128个元素,去除0元素就是127个元素。
 128个+127个 = 255个。最终结果255个元素。
unsigned char i = 0;
int main()
{
    for(i=0; i<=255; i++)
    {
        printf("Hello World!\n");
    }
    return 0;
}

image.png

这段代码的结果是无限次的输出"Hello World!"

变量i是无符号字符类型的,它的取值范围是0~255。
for(i=0; i<=255; i++)
for循环的循环条件是i<=255,这与它的取值范围十分吻合,是恒成立的。
也就是说无论怎样它都不可能跳出这个范围,for循环也就无法结束。
Hello World!会无限次的输出。

浮点数在内存中的存储

int main()
{
    int n = 9;
    float* pFloat = (float*)&n;
    printf("n = %d\n",n);
    printf("*pFloat = %f\n",*pFloat);
    *pFloat = 9.0;
    printf("n = %d\n",n);
    printf("*pFloat = %f",*pFloat);
    return 0;
}

你认为这段程序的最终运行结果是什么?最开始,我认为这段代码的最终运行结果是

n = 9
*pFloat = 9.000000
n = 9
*pFloat = 9.000000

但是运行结果是

image.png

显而易见,我们认为的浮点数在内存中的存储方式并不是它实际的存储方式。那么浮点数是怎么存储的呢?

就以9.0这个浮点数为例,浮点数有实数和小数部分,他们被小数点隔开。 是以9.0 --> 1001.0 在IEEE中说明,任意一个浮点数都可以用下面这个公式表示

(-1)^S * M * 2^E
(-1)^S,这是用来表示正负的,当S为0时,(-1)^0结果为1,即为正;
        当S为1时,(-1)^1结果为-1,即为负。
M为有效数字,大于等于1,小于2
E为科学计数值

将9.0 --> 1001.0 转换为公式表示就是 首先9.0是整数,所以S为0;M为1.001;1.001 --> 1001.0小数点移动3位,则E为3。 所以

(-1)^0 * 1.001 * 2^3

公式中决定每个浮点数不同的其实是S,M,E这三个值,计算机只要记录下这三个值就可以还原一个浮点数。

float在内存中占4个字节,也就是32个比特位。这32个比特位如何分配,如何存储? 我们上面提到要存储一个浮点数,只需要存储S,M,E这三个值就可以了。

S值为01,只需要一个比特位就可以存储了,下图中蓝色的比特位存储S值。
E表示的科学计数的值。图中的八个红色比特位用于存储E,八个比特位可以
存储0~255,都是正数。可是我们知道科学计数值是有负值的。
例如浮点数0.5 --> 0.1 --> (-1)^0 * 1 * 2^(-1)
于是科学家们规定E值加上一个中间数127,然后存入这八个比特位。
M值都是1.xxx,前面的"1."都是确定的,只需要往计算机存入"xxx"部分即可。
下图的绿色部分共23位用于存储M值。

image.png 下面我们将9.0存入计算机

9.0 --> (-1)^0 * 1.001 * 2^3 --> 0 10000010 00100000000000000000000 

上面提到的是float单精度的存储情况,还有double双精度的存储情况。其实存储的原理和方法都是相同的,不同点在于double的空间更大位数更多,分配情况有所不同。

double8个字节,64个比特位
S值仍占1个比特位
E值占11个比特位,中间值为1023
M值占剩余的所有比特位

image.png

当我们从内存中将值拿出时,根据公式还原出浮点数的十进制形式。

S值直接拿出,没什么可说的
E值分三种情况:
1.当E值不是全0不是全1的情况下,根据二进制计算出十进制,减去127即可。
2.当E值是全0的情况下,当E的存储值为0,说明他真正的值为-127,我们可以
  大概估算一下,+/- 1.xxx * 2^(-127) --> +/- 1.xxx / 2^127,
  这是一个正负无限趋近于0的值。
  所以当E全0时,规定E值为1-127(1-1023),M值不再加上1,而是还原为0.xxx
  表示这是无限趋近于0的数
3.当E值是全1的情况下,当E的存储值全为1,说明真正的值为128,大概估算一下,
  +/- 1.xxx * 2^(128),这是一个正负无穷大的数字
M值在第一种情况下,直接拿出来加上1,第二种情况不加1

此时我们在回头来看最开始的那个程序

int n = 9;
n的补码:00000000 00000000 00000000 00001001
float* pFloat = (float*)&n;
当pFloat指针指向n,pFloat会以单精度浮点数的存储方法来解析n的补码
0 00000000 00000000000000000001001
(-1)^0 * 00000000000000000001001 * 2^(-126)
--> +0.00000000000000000001001 / 2^126
%f只获取6位,前六位已经是0.000000,
printf("*pFloat = %f\n",*pFloat);
这句语句的输出自然是0.000000
9.0 --> 1001.0 --> (-1)^0 * 1.001 * 2^3 -->
0 10000010 00100000000000000000000
n是整型,自然以整型的方式去读取,最后得出的结果就是1091567616