前言
国庆节的今天又是学习生活中收获满满的一天呢。祝大家国庆节快乐呀,不知道大家今天收获怎么样呢。
今天在学习整形存储的相关知识的时候,发现这块知识点真是陷阱丰富,一不小心就会出错,学完后也不禁发出感慨:哇,原来是这样的。趁着知识还是热乎着的,写篇博客记录一下来巩固知识点。
其实干讲也讲不出所以然,所以用几道例题来讲解分析一下就足够了。话不多说,直接上菜。
以下代码均在Visual Studio 2022 32位环境下编译
一、前提知识
1.原码,反码,补码
每个整数都有三种表达形式,即原码,反码和补码。整数在内存中一般是以补码的形式存储的,关于原码,反码和补码的计算,有以下规律。
正数的原码,反码,补码均相同
负数的反码是原码除符号位外,其它位按位取反,补码则是在反码的基础上+1即可
例如:十进制数21
21的二进制为00000000 00000000 00000000 00010101
这就是21的原码,由于21是正数,故其原反补码均相同
21的原码:00000000 00000000 00000000 00010101
21的反码:00000000 00000000 00000000 00010101
21的补码:00000000 00000000 00000000 00010101
再例如:十进制数 -10
-10的二进制为 10000000 00000000 00000000 00001010
这是原码,由于-10是负数,反码是除符号位外其余位按位取反,所以
-10的反码为:11111111 11111111 11111111 11110101
补码是反码的基础上再+1,所以
-10的补码为:11111111 11111111 11111111 11110110
2.signed和unsigned
signed是有符号的意思,其代表含义是最高位并非数字位,最高位只是判断正负的,0为正,1为负。
unsigned是无符号的意思,它说明二进制数的最高位并未符号位,跟其他位一样,是数字位,unsigned所修饰的数不包含负数。
例如:
signed char 所能表达的范围为:-128~127之间
而unsigned char 所能表达的范围为:0~225之间
一般不写修饰符的时候,都是按有符号类型来的。
3.整型提升
这块不多讲,整型提升就是位数少的跟位数多的数运算时,由于位数不够,需要在前面填充数字,一般有以下规律:
有符号的数在进行整型提升的时候,最前面补和符号位相同的符号,补充到位数足够。
无符号的数在进行整型提升的时候,最前面补0,补充到位数足够。
二、关于整型存储的例题
1.例题一
#include<stdio.h> int main() { char a = -1; signed char b = -1; unsigned char c = -1; printf("a=%d,b=%d,c=%d\n", a, b, c); return 0; }
这道题的重点还挺多,然我们一一分析一下。
首先是char a = -1; -1是个整数,整数在内存中都是以补码的形式存储的。
-1在内存中的补码为11111111 11111111 11111111 11111111 占32位,4个字节。
由于a是个char类型,占8位一个字节,所以a会把-1给截断掉,只能取后面的8位,也就是低位的11111111
同理,b和c一样都是11111111但是这些11111111所代表的含义却不是一样的。
a一般是有符号的(取决于编译器),b是有符号的,c是无符号的。
a,b,c在打印中均要求以整形类型打印,但由于a,b,c都只有1个字节,位数不够,只能去进行整形提升,有符号的前面补符号位,无符号的前面补0,所以,最后a,b,c在内存中的补码为:
a:11111111 11111111 11111111 11111111
b:11111111 11111111 11111111 11111111
c:00000000 00000000 00000000 11111111
补码转换成原码再打印出来,则abc的原码为:
a:10000000 00000000 00000000 00000001
b:10000000 00000000 00000000 00000001
c:00000000 00000000 00000000 11111111
则打印出来的数据应该为 -1 -1 225
例题一的结果
2.例题二
#include<stdio.h> int main() { char a = -128; printf("%u\n", a); return 0; }
char 的范围是-128~127之间,a的补码为10000000
%u是按无符号整形打印,首先将a进行整型提升,因为a是负数,所以
a:11111111 11111111 11111111 10000000
又因为要按照无符号打印,所以最前面的就不是符号位了,所以这个数被认为成正数了,也就不需要再转换成原码了。直接打印这个值就行,结果应该是42亿多。
例题二的结果
3.例题三
#include<stdio.h> int main() { char a = 128; printf("%u\n", a); return 0; }
因为有符号的char 的范围是-128~127之间,a的值超出能表示的范围了,128在二进制中的表示为10000000,在char中直接被认作-128了,于是跟例题二一样,这题结果也是42亿多。
例题三的结果
4.例题四
#include<stdio.h> #include<string.h> int main() { char a[1000]; int i = 0; for (i = 0; i < 1000; i++) { a[i] = -1 - i; } printf("%d", strlen(a)); }
这题要打印字符串a的长度,strlen的功能是一直遍历到\0为止,然后返回从开始到\0之前的字符个数,返回类型是unsigned int。
由题目规律可知,for循环的作用是往char数组里面存放-1到-1000的数。马虎大意的人又以为这里会打印1000!其实不是哈,char的范围就只有-128~127之间。当存放完-128后,下一次存放的-129会被char认为成127的,然后-130会被认为126…最终肯定会把0给存放进去。
然后又因为在char类型中,0是等价于\0的,但不等价 ’ 0 ’ 哦。所以strlen在遍历char数组的时候,遇到0就返回了,所以结果应该是225。
例题四的结果
5.例题五
#include<stdio.h> unsigned char i = 0; int main() { for (i = 0; i <= 255; i++) { printf("hello world\n"); } return 0; }
在做过前4道题后,这道题已经很简单了。
unsigned char的范围是0~255之间,而题目中的for循环需要循环256次,而i的值最大也才255,是一直符合循环条件的,所以结果显而易见,将会死循环打印hello world。
例题五的结果
总结
到这里,我们终于讲完了关于整形存储的一些知识点和易错点,其实没想到啊,原来整形提升还蛮细节的嘞,想要真正掌握这方面知识,需要非常了解数据在内存中的存储方式,感觉是很容易被忽略的重点呢。关于浮点数的存储我也打算写一篇。
多的不说,今天国庆节,再次祝大家国庆节快乐!