C生万物 | 深度挖掘数据在计算机内部的存储-2

简介: C生万物 | 深度挖掘数据在计算机内部的存储

3、数据范围的介绍

在上面,我们说到了有关【原码】、【反码】、【补码】的一些知识,若是用前面的10去进行标识,可以将它们称之为有符号数

① char与signed char数据范围

  • 首先我们通过下面这幅图来看一看对于有符号的char和无符号的char在内存中所能表示的范围各自是多少
  • 【signed char】:-128 ~ 127
  • 【unsigned char】:0 ~ 255

image.png

但是为什么可以表示成这个范围呢,我们来细讲一下💬

  • 首先来看一下有符号位的char,什么是有符号位?
  • ==也就是最高位的1或者是0不计入数值位,数值位 = 7。0可以用来表示正数,1可以用来表示负数==
  • 因为char数据类型在内存中占1个字节,也就是8个比特位。若是从00000000开始存放,每次+1上去然后逢二进一,之后你就可以得出最大能表示的正整数为【127】,可是呢在继续+1后又会进行进位然后变为10000000,符号位为1,表示为负数,但有同学说:“这不是-0吗,怎么就-128了呢?”继续看下去你就知道了👇image.png
  • 我们先从最下面开始讲起,在上文有说到过,在内存中都是以【补码】的形式进行存放,所以我们看到的1 1111111只不过是补码的形式,若是还要再输出到外界,则需要转换为【原码】的形式,两种方式任选其一,在转换完后就可以发现呈现的数便是我最早给出的数字

image.png

  • 但是对于10000000我们直接将其记作【-128】,它就对应的【-128】在内存中的补码,为什么可以直接这么认为呢?通过去写出【-128】的原、反、补码可以发现是需要9个比特位来进行存放,但是我们知道,对于char类型的数值而言只能存放8个比特位,因此在转换为补码之后会进行一个截断
  • 最后剩下的就是10000000,即为有符号char的最小负数为【-128】

image.png

  • 这么看可能还是有点抽象了,其实你仔细去想一想就可以发现这其实是一个轮回,中间以一条竖线作为分割,右上角从0开始,一直到右下角为正整数的最大值127,接下去如果再进一位的话那就只能变成10000000即为负数的最小值-128,接着再慢慢往上变为【-4】、【-3】、【-2】、【-1】
  • 若此时11111111再+1的话就会变成100000000,但是因为char类型的数据只能存放8个比特位,因此又需要做截断,只剩下00000000,此时又变回了一开始的【0】,形成了一个轮回🧭

image.png

  • 所以有符号char类型的数据范围为-128 ~ 127,你明白了吗👈

② unsigned char数据范围

在看了有符号char的取值范围对于无符号char的数据范围就简单多了

  • 因为是无符号char,所以第一位不作为符号位,算入数值位,此时数值位就不是像上面一样的7位了,而是8位,那么就是从0 ~ 2^8^-1即0 ~ 255

image.png


学会了如何去分析有/无符号char的数据范围,那short呢?int呢?其实都是同理

  • 对于【short】类型的数据,在内存中以2个字节的大小进行存放,也就是16个比特位,它的有符号整数的范围和无符号整数的范围如下图所示👇

image.png

  • 那对于【int】类型的数据来说也是同样的道理,这里就不做展示,数据量太大,读者可以下去自己试着画画看

【总结一下】:

  • signed char —— 【-128 ~ 127】 | unsigned char —— 【0 ~ 255】
  • signed short—— 【-32768 ~ 32767】 | unsigned short —— 【0 ~ 65535】

③ 原码、反码、补码数据范围对比

在上面我们说到了有关无符号数和有符号的数据范围,都是在内存中的存放形式,也就是【补码】的形式,那【原码】和【反码】是怎样的呢?

  • 我画了一张横轴图,读者可根据此图来进行记忆。仔细观察可以发现之前介绍的正数以及负数原、反、补码的规则在这里是成立的,可以先细细看一看下图👇

image.png

  • 相信你对最感觉不一样的地方就是补码的这两个数【-128】和【0】
  • 这里的10000000相信不用我多说了,认真看了上文的一定可以明白,主要来讲一下这个00000000,为什么+0-0的补码都是它们呢?还记得那个轮回的圈吗,当我们最后加到-1的时候,要继续再+1就又变回0了,本来应该是-0才对,不过char类型的数据只能存放8个比特位,所以截断了最前面的1,也就看上去和+0的位置发生了一个重合
  • 所以这样规定:+0-0的补码相同,均为00000000

image.png

能算出8个比特位的数据范围,那么16个、32个、64个...n个比特位的数据都可以算出来✒

image.png

✒七道非常经典笔试题

通过学习了各种数据的范围后,我们趁热打铁,练几道历年在各大厂笔试题中非常经典的一些笔试题:keyboard:

👉在看这一模块之前你要先了解什么是整型提升

  • 有符号的数在整型提升的时候补符号位,无符号的数在整型提升的时候补0

👉并且你要知道以%u%d打印数据有什么区别

  1. %u 是打印无符号整型,认为内存中存放的补码对应的是一个无符号数
  2. %d 是打印有符号整型,认为内存中存放的补码对应的是一个有符号数

① 第一道


#include <stdio.h>
int main()
{
    char a = -1;
    signed char b = -1;
    unsigned char c = -1;
    printf("a = %d, b = %d, c = %d", a, b, c);
    return 0;
}
  • 我们来分析一下,可以看到【a】和【b】都是有符号位的char类型,那它们就是一样的,现在将-1存放到这两个数据中去,首先你应该要考虑到的是一个数据放到内存中去是以什么形式?没错,那就是【补码】
  • 所以我们首先将-1转换为补码的形式


1 0000000 00000000 00000000 00000001
1 1111111 11111111 11111111 11111110
1 1111111 11111111 11111111 11111111
  • 可是呢,需要存放的地方又是char类型的变量,只能存放8个字节,无法放得下这32个字节,因此便需要进行一个截断的操作,放到变量a和变量b中都只剩下11111111这8个字节。
  • 对于变量c来说,它是一个无符号的char类型变量,不过-1存放到它里面还是11111111这8个字节不会改变,只不过在内存中的变化与有符号char不太一样。接下去就来看看会如何进行打印


printf("a = %d, b = %d, c = %d", a, b, c);
  • 可以看到,是以%d的形式进行一个打印,但是呢三个变量所存放的都是char类型的变量,因此会进行一个==整型提升==,只是有符号数的整型提升和无符号数不太一样


//a b - 有符号数
11111111111111111111111111111111 - 补符号位
//c - 无符号数
00000000000000000000000011111111 - 补0
  • 在进行整型提升之后,这些二进制数据还是存放在内存中的,可是要输出打印在屏幕上的话还要转换为【原码】的形式。如何转换的话我上面也有说到过,正数与负数不一样,这里就不在过多赘述💬


11111111111111111111111111111111
10000000000000000000000000000000
10000000000000000000000000000001 ——> 【-1】
00000000000000000000000011111111 ——> 【255】

运行结果如下:

image.png


② 第二道


#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n",a);
    return 0;
}
  • 同理,一个整数存放到内存中,首先要将其转换为【补码】的方式


10000000 00000000 00000000 10000000
11111111 11111111 11111111 01111111
11111111 11111111 11111111 10000000
  • 接着因为这32个二进制位要存放到一个char类型的变量中,因为进行截断10000000
  • 然后在内存中需要进行一个整型提升,char类型的变量将会填充符号位11111111111111111111111110000000
  • 执行打印语句,可以看到这里是以%u的形式进行打印,认为在内存中存放的是一个无符号整数。我们知道,对于无符号整数来说,不存在负数,所以其原、反、补码都是一样的,因此在打印的时候就直接将其转换为十进制进行输出


printf("%u\n",a);
  • 可是这么大的数字,要如何去进行计算呢,有同学说计算机不就能计算吗,我们来来如何使用专属的【程序员】计算器

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9e741e54b8a9437d9f409a84c741c166~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.awebp#?w=529&h=618&s=217849&e=gif&f=108&b=f4f4f4


运行结果如下:

image.png


③ 第三道


#include <stdio.h>
int main()
{
    char a = 128;
    printf("%u\n",a);
    return 0;
}
  • 接下去我们来看第三道题,可以看出和上面那题基本基本一样,只是把-128变成了128而已
  • 如果是【128】的话放到内存中就不需要像负数那样还要进行很多的转化了,因为正数的原、反、补码都一致


00000000 00000000 00000000 10000000
  • 同理进行截断操作后位为10000000,那后面就是一样的了 ,同上

运行结果如下:

image.png


④ 第四道

  • 接下去我们来做第四道题,本题展示一下我做题的过程,希望读者也可以跟着我一起这样来做,工整地一步一步对内存中的数据进行运算,最后自信地算出答案,不要怕麻烦,不然你永远都做不成事情👈


int main(void)
{
  int i = -20;
  //1 0000000 00000000 00000000 00010100
  //1 1111111 11111111 11111111 11101011
  //1 1111111 11111111 11111111 11101100
  unsigned int j = 10;
  //0 0000000 00000000 00000000 00001010
  printf("%d\n", i + j);
  //1 1111111 11111111 11111111 11101100
  //0 0000000 00000000 00000000 00001010
  //------------------------------------------
  //1 1111111 11111111 11111111 11110110
  //1 1111111 11111111 11111111 11110110
  //1 0000000 00000000 00000000 00001001
  //1 0000000 00000000 00000000 00001010 —— 【-10】
  //按照补码的形式进行运算,最后格式化成为有符号整数
  return 0;
}
  • 好,一样来进行讲解,本次我们用到的是两个int类型的数据,一个是有符号的,一个是无符号的。但无论是有符号还是无符号,放到内存中都是要转换为补码的形式,==所以若是你碰到很复杂的题目,不要害怕,先把数字在内存中补码的形式写出来,然后再慢慢地去分析:mag:==
  • 接下去很直观,就是对算出来的两个补码一个二进制数的相加运算,注意这里是将整数存放到int类型的变量中去,所以不需要进行【截断】和【整型提升】


1 1111111 11111111 11111111 11101100
   0 0000000 00000000 00000000 00001010
------------------------------------------
   1 1111111 11111111 11111111 11110110
  • 在运算之后要以%d的形式进行打印输出,那就会将内部中存放的补码看做是一个有符号数,既然是有符号数的话就存正负,可以很明显地看到最前面的一个数字是1,所以是负数,要转换为原码的形式进行输出


1 1111111 11111111 11111111 11110110
1 0000000 00000000 00000000 00001001
1 0000000 00000000 00000000 00001010 —— 【-10】

运行结果如下:

image.png


⑤ 第五道

  • 接下去第五道,是一个for循环的打印


int main(void)
{
  unsigned int i;
  for (i = 9; i >= 0; i--)
  {
    printf("%u\n", i);
  }
  return 0;
}

我们可以先来看一下运行结果

  • 有同学就很诧异😮为什么会陷入死循环呢?这不是就是一个正常的打印过程吗?
  • 其实,问题就出在这个unsigned,把它去掉之后就可以正常打印了:computer:

image.png

  • 回忆一下我们在将无符号整数的时它的数据范围是多少呢
  • 对于char类型来说是0 ~ 255;
  • 对于short来说是0 ~ 65536;
  • 对于int类型来说是0 ~ 16,777,215
  • 对比进行观察其实可以发现它们的数值范围都是 > 0的,所以对于无符号整数来说就不会存在负数的情况。因此这个for循环的条件【i >= 0】其实是恒成立的,若是当i == 0再去--,此时就会变成【-1】
  • 对于【-1】我们有看过它在内存中的补码形式为11...11是全部都是1,而此时这这个变量i又是个无符号的整型,所以不存在符号位这一说,那么在计算机看来它就是一个很大的无符号整数。此时当i以这个数值再次进入循环的时候,继续进行打印,然后执行--i,最后知道其为0的时候又变成了-1,然后继续进入循环。。。

光是这么说说太抽象了,我们可以通过Sleep()函数在打印完每个数之后停一会,来观察一下


#include <windows.h>
int main(void)
{
  unsigned int i;
  for (i = 9; i >= 0; i--)
  {
    printf("%u\n", i);
    Sleep(200);
  }
  return 0;
}
  • 接着你便可以发现,当i循环到0的时候,突然就变成了一个很大的数字,这也就是印证了我上面的说法


⑥ 第六道

  • 本题和四五道的原理是一样的,对于unsigned char来说,最大的整数范围不能超过255,所以当这里的【i】加到255之后又会再+1就会变成00000000,此时又会进入循环从0开始,也就造成了死循环的结果


unsigned char i = 0;
int main()
{
  for (i = 0; i <= 255; i++)
  {
    printf("hello world\n");
  }
  return 0;
}

image.png

所以对于有符号数和无符号数的数据范围一定要牢记于心:heart:对边界值做到非常敏感


⑦ 第七道

  • 最后一道,我们来做做综合一些的,涉及到字符串函数strlen,如果有不了解的同学可以去了解一下👈


int main()
{
  char a[1000];
  int i;
  for (i = 0; i < 1000; i++)
  {
    a[i] = -1 - i;
  }
  printf("%d", strlen(a));
  return 0;
}
  • 首先来看函数主体,也是一个for循环,在开头定义了一个char类型的数组,大小为1000,。在循环内部呢对它们进行一个初始化,那最后里面的数字一定是


-1  -2  -3  -4  -5  -6  -7 ...
  • 但是呢,我们在上面学习过的有符号signed char,它和char是一样的,数据的范围在【-128 ~ 127】,所以当i加到128的时候,这个位置上的值变为【-129】,此时在计算机内部会将它识别成【127】,同理【-130】会被识别成为【126】。。。依次类推,最后当这个值为【0】的时候若再去减就会变成【-1】,然后又变成【-2】【-3】【-4】。。。一直当这个i累加到1001的时候截止
  • 如果忘记的可以再看一下下面这幅图

image.png

  • 最后,我们要通过strlen()去求这个数字的长度,对于strlen()来说,求的长度是到\0截止,那也就是上面的【0】,不需要去关心后面的第二、三轮回
  • 那其实这也就转变成了让我们去求有符号char在内存中可以存储多少个。这很简单,范围是-128 ~ +127,二者的绝对值一加便知为255


//-1  -2  -3  -4  -5  -6  -7 ...-128  127  126  ...  0 -1 -2 -3....
printf("%d", strlen(a));

来看一下运行结果

image.png

到这里,七道非常经典的笔试题就全部讲完了,你学会【废︿( ̄︶ ̄)︿】了吗

相关文章
|
存储 小程序 网络协议
C生万物 | 深度挖掘数据在计算机内部的存储-1
C生万物 | 深度挖掘数据在计算机内部的存储
64 0
|
存储 编译器 程序员
C生万物 | 深度挖掘数据在计算机内部的存储-3
C生万物 | 深度挖掘数据在计算机内部的存储
52 0
|
存储 自动驾驶 量子技术
新型量子计算机首次打破二进制,信息存储在钙原子中
新型量子计算机首次打破二进制,信息存储在钙原子中
|
存储 运维 固态存储
一文读懂人类信息存储进化史
感兴趣的同学可以点击文字最下方的链接,了解详情哦
1176 0
一文读懂人类信息存储进化史
|
存储 算法 文件存储
|
大数据 数据采集 数据挖掘