C语言进阶--数据的存储

news/2024/11/9 2:47:58/

目录

数据类型介绍

基本内置类型:

类型的意义:

类型的基本归纳: 

整型在内存中的存储 

原码,反码和补码:

大小端存储模式:

大小端产生原因:

浮点型在内存中的存储


数据类型介绍

基本内置类型:

char字符数据类型
short短整型
int整型
long长整型
long long更长的整型
float单精度浮点数
double双精度浮点数

类型的意义:

  1. 使用这个类型开辟内存空间的大小(大小决定了使用范围);
  2. 如何看待内存空间的视角

类型的基本归纳: 

整型家族:

  1. char:unsigned char,signed char
  2. short:unsigned short [int],signed int
  3. int:unsigned int,signed int
  4. long:unsigned long [int],signed long [int]

浮点数家族:

  1. float
  2. double

构造类型:

  1. 数组类型
  2. 结构体类型 sturct
  3. 枚举类型 enum
  4. 联合类型 union

指针类型:

  1. int* pi
  2. char* pc
  3. float* pf
  4. void* pv

空类型: 

      void:表示空类型(无类型) ,通常应用于函数的返回类型,函数的参数,指针类型

整型在内存中的存储 

原码,反码和补码:

计算机中的有符号数有三种表示方式,即原码,反码和补码。三种表示方式均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位三种表示方法各不相同。 

正数:原,反,补码都相同

负数:原,反,补码需要按照如下规则进行运算

  1. 原码:直接将数值按照正负数的形式翻译成二进制形式就可以得到原码
  2. 反码:将原码的符号位不变,其他位一次按位取反就可以得到反码
  3. 补码:反码+1

注意:对于整型来说,数据存放在内存中存放的其实是数据的补码

原因如下:在计算机系统中,数值一律用补码来表示和存储。原因在于使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加速器)。此外,补码和原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

我们通过以下程序来查看数据在内存中的存储:

 我们发现数据在内存中确实是以16进制的补码形式进行存储的,但是我们发现数据的存放顺序是存在差异的,那这又是为何?

大小端存储模式:

何为大小端?

大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;

小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中

大小端产生原因:

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

案例一:设计一个小程序来判断当前机器的字节序。

//基础版
int ckeck_sys()
{int a = 1;//00 00 00 01char* p = (char*)&a;//强制类型转换,取第一个字节if (*p == 1){return 1;//小端}else{return 0;//大端}return 0;
}//进阶版
int check_sys()
{int a = 1;return *(char*)&a;
}int main()
{int ret = check_sys();if (ret == 1){printf("小端\n");}else{printf("大端\n");}
}

运行结果:

案例二:下例程序输出什么?

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;
}

分析:

int main()
{//char是signed char还是unsigned char,C语言并没有明确规定,取决于编译器,大多数编译器下是signed charchar a = -1;//原:10000000 00000000 00000000 00000001//反:11111111 11111111 11111111 11111110//补:11111111 11111111 11111111 11111111//取8位:11111111signed char b = -1;//同aunsigned char c = -1;//原:10000000 00000000 00000000 00000001//反:11111111 11111111 11111111 11111110//补:11111111 11111111 11111111 11111111//取8位:11111111printf("a=%d,b=%d,c=%d\n",a,b,c);//-1,-1,255//%d是打印有符号数,%d是用来输出十进制的整数,对应的数据类型是 int//当我们打印a时,要发生整型提升,有符号数则直接补符号位,则11111111->11111111 11111111 11111111 11111111->转原码:10000000 00000000 00000000 00000001得-1//b同a一样//当我们打印c时,要发生整型提升,无符号数则直接补0,则11111111->00000000 00000000 00000000 11111111->转原码:00000000 00000000 00000000 11111111得255return 0;
}

运行结果:

案例三:下例程序输出什么?

int main()
{char a = -128;printf("%u\n", a);return 0;
}

分析:

int main()
{char a = -128;//原:10000000 00000000 00000000 10000000//反:11111111 11111111 11111111 01111111//补:11111111 11111111 11111111 10000000//取8位:10000000printf("%u\n",a);//%u是打印无符数,%u以无符号的十进制形式输出整数,对应的数据类型是unsigned int//当我们打印a时,要发生整型提升,有符号数则直接补符号位,则11111111 11111111 11111111 10000000,打印的是无符号数,所以原反补相同,所以直接将二进制转换成十进制输出:4294967168return 0;
}

运行结果:

案例四:下例程序输出什么?

int main()
{char a = 128;printf("%u\n", a);return 0;
}

分析:

int main()
{char a = 128;//原:00000000 00000000 00000000 10000000//反:00000000 00000000 00000000 10000000//补:00000000 00000000 00000000 10000000//取8位:10000000printf("%u\n", a);//%u打印的是无符号的整数,%u以无符号的十进制形式输出整数,对应的数据类型是unsigned int//当我们打印a时,要发生整型提升,有符号数则直接补符号位,则11111111 11111111 11111111 10000000,打印的是无符号数,所以原反补相同,所以直接将二进制转换成十进制输出:4294967168return 0;
}

运行结果:

案例五:下例程序输出什么?

int main() 
{int i = -20;unsigned int j = 10;printf("%d\n", i + j);return 0;
}

分析:

int main()
{int i = -20;//原:10000000 00000000 00000000 00010100//反:11111111 11111111 11111111 11101011//补:11111111 11111111 11111111 11101100unsigned int j = 10;//原:00000000 00000000 00000000 00001010//反:00000000 00000000 00000000 00001010//补:00000000 00000000 00000000 00001010printf("%d\n",i+j);//俩补码相加://11111111 11111111 11111111 11101100//00000000 00000000 00000000 00001010//11111111 11111111 11111111 11110110:补//10000000 00000000 00000000 00001001:反//10000000 00000000 00000000 00001010:原->-10return 0;
}

运行结果:

案例六:下例程序输出什么?

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

分析:

#include<windows.h>
int main()
{unsigned int i;for (i = 9; i >= 0; i--){printf("%u\n",i);//9 8 7 6 5 4 3 2 1 0 (-1的补码:11111111 11111111 11111111 11111111对应的无符号数:4294967295) (-2的补码对应的无符号数:4294967294) 依次循环并进入死循环//i是个无符号类型的,也就是i>=0恒成立,所以这个循环的条件就恒成立Sleep(1000);//单位是毫秒}return 0;
}

运行结果:

案例七: 下例程序输出什么?

int main()
{char a[1000];int i;for(i = 0; i < 1000; i++){a[i] = -1 - i;}printf("%d", strlen(a));return 0;
}

分析:

int main()
{char a[1000];int i;for (i = 0; i < 1000; i++){a[i] = -1 - i;}printf("%d\n",strlen(a));//255//即-1 -2 -3...-128 127(-129对应的二进制截段) 126 125...1 0,strlen读取到0的时候则停止//-129的原码:10000000 00000000 00000000 10000001->反码:11111111 11111111 11111111 01111110->补码:11111111 11111111 11111111 01111111,取8位:01111111得127 //strlen是求字符串的长度,找的是\0的位置,统计的是\0之前出现了多少哥字符,注意'\0'的ASCII值是0return 0;
}

运行结果:

案例八:下例程序输出什么?

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

分析:

unsigned char i = 0;//0-255
int main()
{for (i = 0; i <= 255; i++){printf("hello world\n");//死循环,00000000-11111111,+1得100000000,舍弃最高位1得00000000}return 0;
}

运行结果:死循环

char小结:

char虽然是字符类型,但是字符类型存储时,存储的是字符的ASCII码,而ASCII的值为整数;
char究竟是有符号数还是无符号数,这是不确定的,要取决于对应的编译器;
无符号char:0-255,有符号char:-128-127,-128的补码是:1000 0000
char的具体意义只取决于读取内存数据并解释的编译器,现在大多数编译都将char解释成signed char

                                                           

浮点型在内存中的存储

浮点数家族包括:float,double,long double类型,其表示的范围是由float.h定义

案例分析:

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

运行结果:

num和*pFloat在内存中明明是同一个数,为什么浮点数和整数的解读结果会差这么大?要理解这个结果,一定要明白浮点数在计算机内部的表示方法。

整数和浮点数在内存中的存储方式是有差异的                                                                                                                                                      
任何一个二进制浮点数V都可以表示成:(-1)^S*M*2^E

  1. (-1)^S表示符号位,当S=0时,V为正数;当S=1时,V为负数
  2. M表示有效数字,大于等于1,小于2
  3. 2^E表示指数位

举例:5.5->二进制表示:101.1->(-1)^0*1.011*2^2->S=0;M=1.011;E=2
            9.0->二进制表示:1001.0->(-1)^0*1.001*2^3->S=0;M=1.001;E=3

IEEE 754规定:

  1. 单精度浮点数存储模型:对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M
  2. 双精度浮点数存储模型:对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M 

对有效数字M:

前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。

对指数E:

首先,E为一个无符号整数(unsigned int),这意味着,如果E为8位,它的取值范围为0 ~ 255;如果E为11位,它的取值范围为0 ~ 2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

案例一:

int main()
{float f = 5.5;//101.1//(-1)^0*1.011*2^2//S=0//E=2//M=1.011//存储到内存://E+127=2+127=129//0 10000001 01100000000000000000000//S     E             M//0x40b00000return 0;
}

指数E从内存中取出还可以在分成三种情况:

  1. E不全为0或不全为1:这时,浮点数就采用下面的规则表示,即指数E的计算值减去127或1023,得到真实值,再将有效数字M前加上第一位的1;
  2. E为全0:这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字;
  3. E全为1:这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)

案例二:

int main()
{float f = 0.5;//0.1//(-1)^0*1.0*2^-1//S=0//M=1.0//E=-1//存储到内存://E+127=-1+127=126//0 01111110 00000000000000000000000//S     E              M//0x3f000000return 0;
}

在了解完浮点数的存储方式之后,我们再来回顾之前的案例分析,

int main()
{int n = 9;//00000000 00000000 00000000 00001001//以浮点数的存储方式进行读取//0 00000000 00000000000000000001001//S    E              M//E为全0:浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数//E=1-127=-126//M=0.00000000000000000001001//(-1)^0*0.00000000000000000001001*2^-126=1*0.00000000000000000001001*2^-126,是一个无限接近0的数字,保留八位得0.000000float* pFloat = (float*)&n;printf("n的值为:%d\n",n);//9printf("*pFloat的值为:%f\n", *pFloat);//0.000000*pFloat = 9.0;//9.0//1001.0//1.001*2^3//(-1)^0*1.001*2^3//S=0//M=1.001//E=3//存储到内存中://E=3+127=130//0 10000010 00100000000000000000000//S     E              M//以整数形式打印:1091567616printf("n的值为:%d\n", n);//1091567616printf("*pFloat的值为:%f\n", *pFloat);//9.000000return 0;
}

运行结果:

                                                                                                                                                                                                                                                                   


http://www.ppmy.cn/news/241051.html

相关文章

PS卸载不彻底,ADMUI3删除不掉怎么办

PS卸载不彻底&#xff0c;ADMUI3删除不掉怎么办 将该文件后缀改为txt&#xff0c;重启电脑后即可删除。

adb删除预装程序

adb shellpm uninstall -k --user 0 软件包名

Android使用adb删除应用

adb shell rm data/app 目录下的 apk 和 odex 然后进入 data/data 目录 adb uninstall 对应包名

使用adb卸载内置app

关于使用adb卸载内置app的步骤 1.准备工作&#xff1a; 安装adb 进入官网下载adb http://adbdownload.com. 然后解压缩到任意位置打开手机usb调试 用usb数据线连接手机和安装了adb的电脑 &#xff08;本教程基于win10系统&#xff09;&#xff0c; 并在手机”设置“的”开发者…

通过ADB 删除系统APP

1.adb shell 2.su 3.mount [获取到/dev/...../system(第一个)] 4. mount -o remount,rw /dev/...../system 5.cd system/app 6.ls [查看要删除的APK] 7.chmod 777 ***.apk 8.rm ***.apk

安卓手机用adb命令删除掉应用

试了一下&#xff0c;系统自带的应用也能删掉。 当然了&#xff0c;首先你得在电脑中配置好adb命令运行的环境。什么&#xff0c;不知道怎么配&#xff1f;那就……搜一下&#xff0c;好吧。 然后在电脑中运行这条命令&#xff1a;adb shell pm uninstall -k app的包名 这样…

android studio 彻底删除module方法

当你想在Android Studio中删除某个module时&#xff0c;大家习惯性的做法都是选中要删除的module&#xff0c;右键去找delete。但是 在Android Studio中你选中module&#xff0c;右键会发现没有delete,接下来我详细介绍如何删除 1.在project视图下&#xff0c;右击需要删除mo…

adb 隐藏/删除 app

如果我们需要删除系统内置app&#xff0c;一般情况都是需要root权限&#xff0c;删除时可能还会提示"read-only file system"&#xff0c;从而需要额外执行adb shell mount 命令并且重启。 其实有个更好的办法来达到"删除"系统app的效果&#xff0c;那就是…