引言:在上篇文章中我们讲解了有关数据在内存中存储的相关内容,并初步了解了数据在内存中存储的大小端问题。我们知道c语言中大多数据类型有着明确的定义,例如整型分为有符号整型和无符号整型,但是唯独char类型是个个例,有人会说char类型是可以打印出负数的,它是有符号类型的。事实上,C99标准并未规定char类型是有符号的还是无符号的,这取决于编译器和硬件平台,就VS2022而言,char类型被默认为是有符号类型的,具体类型可通过查看头文件limits.h得知,若是CHAR_MIN的值为0,char则为无符号类型,若是SCHAR_MIN的(-128),char则是有符号型的,本篇将通过几道例题带领大家深入理解char类型和大小端字节序相关问题。(以VS2022为例)
一.对于signed char和unsigned char取值范围
在介绍例题之前,首先要先为大家介绍一下signed char和unsigned char取值范围是多少以及是如何计算出来的,有助于后续问题的理解。
注意:对于负数而言,内存中存储的补码一定要转换为原码再计算值。
同理对于unsigned char:
通过上面两张图我们得到了signed char和unsigned char取值范围如下:
signed char:-128~127
unsigned char:0~255
为了方便记忆,可以用时钟图辅助,脑海有了这样一幅图我们便能快速计算出目标值:
eg1:
#include <stdio.h>
int main()
{
//x86平台
char a = -1;//有符号类型
//10000000000000000000000000000001 ——原码
//11111111111111111111111111111110 ——反码
//11111111111111111111111111111111 ——补码
//但char类型只能存储一个字节即8个bit,所以会发生“截断”
//11111111(只取补码的后8位)
//最终以%d的形式打印,所以需要进行位数提升,对于有符号类型,前面需要用符号位补齐
//11111111111111111111111111111111 ——提升后补码
//10000000000000000000000000000001 ——最终需打印出的原码 -1
signed char b = -1;
//与char同理
unsigned char c = -1;
//1000000000000000000000000000001 ——原码
//1111111111111111111111111111111 ——补码
//11111111 ——截断
//对于unsigned char来说,提升的时候前面用0补齐
//0000000000000000000000011111111 ——提升后的补码
//正数的原码,反码,补码相同所以最终打印的结果就是:
//0000000000000000000000011111111 —— 255(符号位为0)
printf("a=%d,b=%d,c=%d", a, b, c);
return 0;
}
结果如何呢?
a=-1,b=-1,c=255
上文我们已经知道在vs2022中 char类型被认为是有符号类型的,所以对于char和signed的char输出结果是一致的。这里要着重强调一下unsigned char类型,有人会说unsigned类型怎么能存储负数呢?实际上,数据在内存中存储的时候该怎么存就怎么存,而数据对应的类型实际上是决定内存是如何看待数据的,根据不同类型对存进去的数据进行截断后才是最终存进去的内容,所以不要先入为主地纠结于类型对应的范围。
小小总结一下char类型究竟是如何计算的:1.首先根据具体数值写出原码,计算补码(负整数才存在原,反补的差别,正整数三者均相同)。2.根据数据对应的类型发生截断。3.根据打印所需类型发生自动类型转换,例如从char到int需要进行提升,若是signed char,高位补符号位,若是unsigned char,高位补0。4.二进制转换。
eg2:(1):
#include <stdio.h>
int main()
{
char a = -128;
//10000000000000000000000010000000 ——原码
//11111111111111111111111101111111
//11111111111111111111111110000000 ——补码
//10000000 ——a(截断)
printf("%u\n", a);//%u 无符号10进制整数
//%u的形式打印,是认为a中存放的是无符号数
//a是char类型,首先要整型提升(因为是char,所以高位补1)
//11111111111111111111111110000000 ——a
//又因为%u认为它a为无符号整数,所以不存在原反补,最高位1也不是符号位了
//11111111111111111111111110000000 ——a:4294967168
return 0;
}
(2):
#include <stdio.h>
int main()
{
char b = 128;
//00000000000000000000000010000000 ——原码
//01111111111111111111111101111111 ——反码
//01111111111111111111111110000000 ——补码
//10000000 ——b
printf("%u\n", b);
//00000000000000000000000010000000 ——整型提升(高位补符号位0)
//对于%u,原反补相同
//00000000000000000000000010000000 ——b:4294967168
return 0;
}
这两个的结果如何呢?
a=4294967168
b=4294967168
eg3:(1):
#include<stdio.h>
int main()
{
char a[1000] = { 0 };
int i;
for (i = 0;i < 1000;i++)
{
a[i] = -1 - i;
//-1 -2 -3...-127,-128,127,126...3,2,1,0,-1,-2......
}
printf("%d", strlen(a));
//strlen在遇到'\0'的时候停下来,返回'\0'之前的字符数
//即:-1 -2 -3...-127,-128,127,126...3,2,1的个数
//255
return 0;
}
eg4:
#include <stdio.h>
unsigned char i = 0;
int main()
{
for(i = 0;i<=255;i++)
{
printf("hello world\n");
}
return 0;
}
结果会一直循环打印hello world,这是因为unsigned char 取值范围是0~255,当i=255时再加1就会变成0,结果会陷入死循环。
(2):
#include <stdio.h>
#include<windows.h>
int main()
{
unsigned int i;//i恒>=0
for (i = 9; i >= 0; i--)
{
//死循环
//9,8,7,6,5,4,3,2,1,0,一个非常大的正整数.....
printf("%u\n", i);
Sleep(1000);
}
return 0;
}
eg5:
#include <stdio.h>
//X86环境 ⼩端字节序
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);
return 0;
}
结果如下:
4,2000000
让我们详细分析一下这个代码:
注意:指针+1是根据指针类型的不同跳过不同的字节;而整型+1就是单纯+1,头脑一定要清醒。
以上便是延续上篇的例题内容,创作不易,望大家多多关注,感谢。