【C数据存储】整型在内存中的存储(进阶版)

         生命不息,运动不止

1.数据类型

数据类型的两个作用:
1.决定了使用这个类型定义变量是开辟空间的大小
2.决定了我们看待内存空间的视角

1-1内置类型

  • char
  • short
  • int
  • long
  • long long
  • float
  • double

1-1-1 内置类型的再细分

整型家族
signed char/short/int/long有符号
unsigned char/short/int/long无符号
浮点型家族:
float/double

char比较特殊
char c=10;//无规定为有符号还是无符号,取决于编译器
signed char c=10;//有符号,最高位为符号位
unsigned char c=10;//无符号,最高位为数值位

short int a=10;//有符号
short a=10;//规定为有符号,最高位为符号位
signed short a=10;//有符号
unsigned short a=10;//无符号,最高位为数值位
int /long /short a=10;均被规定为有符号

1-2自定义类型

自己构造的类型,又被称为构造类型

  • 数组类型:如int [10]
  • 结构体 :struct
  • 枚举: enum
  • 联合: union
  • 指针类型: 如int*
  • 空类型: 通常用于函数返回类型 、函数参数上 void test(void)和指针类型void* p

2.数据的原码反码补码

2-1二进制和十六进制

数据在内存中是以2进制存储,VS在展示的时候是以16进制展示的

  • 一个字节占8个二进制位,等价也等于2个十六进制位
    在这里插入图片描述调试->窗口->内存->&a如何使用vs在调试时查看内存,速戳VS查看内存方法在这里插入图片描述

2-2原码,反码,补码

在这里插入图片描述

从有无符号类型(unsigned和signed)来看

  • 有符号数=符号位+数值位
    正数: 0 + 数值位
    负数: 1 + 数值位
  • 无符号数=全是数值位
    全是正数

对于正负数来看:

  • 对于负数求原反补
    原码:有符号数,直接根据正负数值位出二进制序列就是原码
    反码:原码的符号位不变,其他位按位取反
    补码:反码二进制的最低位+1得到

  • 正数的原反补相同

  • 将十进制转换为二进制的求原码技巧:
    写成两个2的整数次方相加的形式,比如10=8+2
    也就是1000+0010=1010

总体来看:

  • 只要是整数,在内存中的都是以补码的形式存储

举个例子:

unsigned int a=1;
int a=1;//signed int a=1;
原码&反码&补码:0000 0000 0000 0000 0000 0000 0000 0001

int b=-1;
原码:1000 0000 0000 0000 0000 0000 0000 0001
反码:1111 1111 1111 1111 1111 1111 1111 1110
补码:1111 1111 1111 1111 1111 1111 1111 1111

2-3有符号和无符号的取值范围

有符号数:(图解)

矩形图:
在这里插入图片描述
环形图:
在这里插入图片描述

无符号数:(这个比较简单)

0000 0000 0000 0000 0000 0000 0000 0000

1111 1111 1111 1111 1111 1111 1111 1111 1111
也就是0到255

  • 补码转原码的小技巧:
    在这里插入图片描述

3.大小端字节序(顺序)

先问问大家:吃鸡蛋有规定说从哪个方向敲开吃比较好吗?
实际上都可以,但是总体来说从两端打开是相对比较合适的,但是至于从大的一头开始还是从小的一头开始,各有各的说法。

这也类似我们的大小端字节序

为什么有大小端字节序

由上面数据以二进制补码的形式存储在内存中,如果现有一个十六进制数0x112223344,我们知道电脑内存被划分为一个个聂村单元,每一个内存单元就是一个字节,那么我们还得再细分这个0x11223344这个数,从字节的角度考虑这个数是怎么存储的,即是数据的每一个字节究竟是怎么存储的,这也就是大小端存储存在的理由了。

  • 对于他我们可以以11223344存,也可以44332211这样存,甚至可以31231424这末离谱地存,我们虽然平时看不到这些数据,但是在需要查看内存的时候,为了方便阅读,普遍流行的方式就是大小端存储。(大小端存储取决于编译器)

  • 与此同时,选择大端还是小端关系不大,主要是怎么放就要怎么拿出来,小端存储,就要按照小端读入的反方向读取就可以

大小端字节序的存储规则

  • 大端字节序存储:
    把一个数的低字节序的内容放在高地址处,反之…
    在这里插入图片描述

  • 小端字节序存储:
    把一个数的低位字节序放在低地址处(记忆:小小小),反之…
    在这里插入图片描述

3-1大小端字节序的笔试题

设计一个程序来证明当前机器是大端存储还是小端存储

int main()
{
	int a = 1;
	//0x 00 00 00 01



//低地址    高地址
		//0x00 00 00 01大端
		//0x01 00 00 00小端
		
	//用char*的指针进行一次解引用,访问一个字节,如果char*的指针拿到的是01那么就是大端存储,如果拿到的是00,那么就是小端存储


	
	char* p = (char*) & a;
	if (*p == 0)
	{
		printf("大端");
	}
	else
	{
		printf("小端");
	}
	return 0;
}
  • 二进制+大小端=整型数据在内存中的存储

4.相关笔试题

4-1

猜一猜打印的结果

int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;
	printf("a=%d\nb=%d\nc=%d", a, b, c);
	return 0;
}

运行结果:

在这里插入图片描述

关键点提示:

  • signed和unsigned char的补码都是11111111,由于是按照%d的形式来打印,所以要整形提升
  • signed类型左边补原符号位(最高位)
  • unsigned类型直接左边补0

4-2

猜一猜打印的结果

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

4-2-1运行结果:
在这里插入图片描述

关键点提示:

  • -128原码:
    1000 0000 0000 0000 0000 0000 1000 0000
    反码:1111 1111 1111 1111 1111 1111 0111 1111
    int补码:1111 1111 1111 1111 1111 1111 1000 0000
    截断后的char补码:1000 0000

整形提升为Int:1111 1111 1111 1111 1111 1111 0000 0000(整形提升中左边补原符号位1)
转化为无符号整数:结果

按%u打印时:被看成无符号数来读取,

在这里插入图片描述

4-3

猜一猜打印的结果

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

运行结果:
在这里插入图片描述

关键点提示:

  • 原码:0000 0000 0000 0000 0000 0000 1000 0000
  • 反码:0111 1111 1111 1111 1111 1111 0111 1111
  • int 补码::0111 1111 1111 1111 1111 1111 1000 0000
  • 截断后的补码:char:1000 0000
  • 整形提升:1111 1111 1111 1111 1111 1111 1000 0000
  • 转化为无符号:结果

4-4

猜一猜打印的结果

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

运行结果:补码相加
在这里插入图片描述

关键点提示:

int i=-20;负数
原码:1000 0000 0000 0000 0000 0000 0001 0100
反码:1111 1111 1111 1111 1111 1111 1110 1011
补码:1111 1111 1111 1111 1111 1111 1110 1100

unsigned int j=10;
补码:0000 0000 0000 0000 0000 0000 0000 1010

补码相加:
1111 1111 1111 1111 1111 1111 1111 1111 0110

按有符号得到的补码:
1111 1111 1111 1111 1111 1111 1111 1111 0110
最高位是1,为负数,要进行补转原,数值位取反,再加1):

数值位取反:
1000 0000 0000 0000 0000 0000 0000 1001
再加1:
1000 0000 0000 0000 0000 0000 0000 1010
得到的十进制数:-10

4-5

猜一猜打印的结果

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

在这里插入图片描述

关键点提示:

i>0的时候,存进去的是原码(补码),读出来的是还是i本身,但是当i<0的时候,i和0做比较的时候就是算读取数据,这个时候和刚才一样,要按照无符号读取
假如存的时候按-1存进去的时候,
-1的补码:1111 1111 1111 1111 1111 1111 1111 1111
按无符号读取的时候,最高位是数值位
,那么就读出一个很大的数,也就是结果,且感性地看,unsigned一定是大于0的,所以是死循环。

4-6

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

运行结果:
在这里插入图片描述

关键点提示:

a[i]<-=128,一切还是正常算
但是当a[i]等于-129的时候(负数)
-129的原码:
1000 0000 0000 0000 0000 0000 1000 0001
-129的反码:
1111 1111 1111 1111 1111 1111 01111 1110
-129的补码(int):
1111 1111 1111 1111 1111 1111 01111 1111

截断后:(char):
0111 1111

按有符号读取转换为十进制整数:127
类推。。。
在这里插入图片描述

4-7

unsigned char i = 0;

int main()
{
	int count = 0;
	
	for (i = 0; i <= 255; i++)
	{
		count++;
		printf("hello world\n");
	}
	printf("%d\n", count);
	return 0;

}

结果是死循环

关键点提示:

  • 当i<=255时,一切正常,
  • 但是当i=256时,正数
  • 原反补:0000 0000 0000 0000 0000 0001 0000 0000
  • 截断(int->char):0000 0000那就是又从0开始了

变式:当把for(int i=0;i<=255;i++)这个就不会发生截断,然后int能存的下这个补码,读出来就是256就会跳出来,次数就变成256次了。
在这里插入图片描述

关于我的一些思考:

当我们光太业余的看得出的答案,那是因为我们没有将数据先存起来,而是直接就拿来就用,正确做法是先存(考虑正负数的原反补(也就是数据的类型)),再截取(当int 转char),再拿(考虑变量的类型和char变int 的整型提升)

          关注我一起成长
  • 29
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 25
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Geek_0

为爱发电

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值