发布时间:2024-02-15 08:00
生命不息,运动不止
数据类型的两个作用:
1.决定了使用这个类型定义变量是开辟空间的大小
2.决定了我们看待内存空间的视角
整型家族
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;均被规定为有符号
自己构造的类型,又被称为构造类型
数据在内存中是以2进制存储,VS在展示的时候是以16进制展示的
从有无符号类型(unsigned和signed)来看
对于正负数来看:
对于负数求原反补
原码:有符号数,直接根据正负数值位出二进制序列就是原码
反码:原码的符号位不变,其他位按位取反
补码:反码二进制的最低位+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
有符号数:(图解)
无符号数:(这个比较简单)
0000 0000 0000 0000 0000 0000 0000 0000
到
1111 1111 1111 1111 1111 1111 1111 1111 1111
也就是0到255
先问问大家:吃鸡蛋有规定说从哪个方向敲开吃比较好吗?
实际上都可以,但是总体来说从两端打开是相对比较合适的,但是至于从大的一头开始还是从小的一头开始,各有各的说法。
这也类似我们的大小端字节序
为什么有大小端字节序
由上面数据以二进制补码的形式存储在内存中,如果现有一个十六进制数0x112223344,我们知道电脑内存被划分为一个个聂村单元,每一个内存单元就是一个字节,那么我们还得再细分这个0x11223344这个数,从字节的角度考虑这个数是怎么存储的,即是数据的每一个字节究竟是怎么存储的,这也就是大小端存储存在的理由了。
对于他我们可以以11223344存,也可以44332211这样存,甚至可以31231424这末离谱地存,我们虽然平时看不到这些数据,但是在需要查看内存的时候,为了方便阅读,普遍流行的方式就是大小端存储。(大小端存储取决于编译器)
与此同时,选择大端还是小端关系不大,主要是怎么放就要怎么拿出来,小端存储,就要按照小端读入的反方向读取就可以
大小端字节序的存储规则
设计一个程序来证明当前机器是大端存储还是小端存储
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;
}
猜一猜打印的结果
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;
}
运行结果:
关键点提示:
猜一猜打印的结果
int main()
{
char c = -128;
printf(\"%u\\n\", c);
return 0;
}
关键点提示:
整形提升为Int:1111 1111 1111 1111 1111 1111 0000 0000(整形提升中左边补原符号位1)
转化为无符号整数:结果
按%u打印时:被看成无符号数来读取,
猜一猜打印的结果
int main()
{
char c = 128;
printf(\"%u\", c);
return 0;
}
关键点提示:
猜一猜打印的结果
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
猜一猜打印的结果
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的,所以是死循环。
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
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;
}
结果是死循环
关键点提示:
变式:当把for(int i=0;i<=255;i++)这个就不会发生截断,然后int能存的下这个补码,读出来就是256就会跳出来,次数就变成256次了。
当我们光太业余的看得出的答案,那是因为我们没有将数据先存起来,而是直接就拿来就用,正确做法是先存(考虑正负数的原反补(也就是数据的类型)),再截取(当int 转char),再拿(考虑变量的类型和char变int 的整型提升)
关注我一起成长