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

文章介绍了数据类型的分类,包括整型、浮点型、构造类型和指针类型等,详细阐述了整型在内存中以补码形式存储,以及原码、反码和补码的概念。讨论了大小端存储模式及其原因,并通过案例分析了不同类型数据在内存中的表示和转换。同时,文章还涉及浮点数的内存存储方式,解释了IEEE754标准下的单精度和双精度浮点数表示。
摘要由CSDN通过智能技术生成

目录

数据类型介绍

基本内置类型:

类型的意义:

类型的基本归纳: 

整型在内存中的存储 

原码,反码和补码:

大小端存储模式:

大小端产生原因:

浮点型在内存中的存储


数据类型介绍

基本内置类型:

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 01
	char* 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 char

	char a = -1;
	//原:10000000 00000000 00000000 00000001
	//反:11111111 11111111 11111111 11111110
	//补:11111111 11111111 11111111 11111111
	//取8位:11111111

	signed char b = -1;
	//同a

	unsigned char c = -1;
	//原:10000000 00000000 00000000 00000001
	//反:11111111 11111111 11111111 11111110
	//补:11111111 11111111 11111111 11111111
	//取8位:11111111

	printf("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得255
	
	return 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位:10000000

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

	return 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位:10000000

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

	return 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 11101100

	unsigned int j = 10;
	//原:00000000 00000000 00000000 00001010
	//反:00000000 00000000 00000000 00001010
	//补:00000000 00000000 00000000 00001010

	printf("%d\n",i+j);
	//俩补码相加:
	//11111111 11111111 11111111 11101100
	//00000000 00000000 00000000 00001010
	//11111111 11111111 11111111 11110110:补
	//10000000 00000000 00000000 00001001:反
	//10000000 00000000 00000000 00001010:原->-10

	return 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值是0

	return 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
	//0x40b00000

	return 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
	//0x3f000000

	return 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.000000
	
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n",n);//9
	printf("*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
	//以整数形式打印:1091567616
	
	printf("n的值为:%d\n", n);//1091567616
	printf("*pFloat的值为:%f\n", *pFloat);//9.000000

	return 0;
}

运行结果:

                                                                                                                                                                                                                                                                   

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值