生命不息,运动不止
数据类型的两个作用: 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进制展示的
调试->窗口->内存->&a如何使用vs在调试时查看内存,速戳VS查看内存方法
从有无符号类型(unsigned和signed)来看
对于正负数来看:
总体来看:
举个例子:
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这个数,从字节的角度考虑这个数是怎么存储的,即是数据的每一个字节究竟是怎么存储的,这也就是大小端存储存在的理由了。
大小端字节序的存储规则
设计一个程序来证明当前机器是大端存储还是小端存储
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;
}
4-2-1运行结果:
关键点提示:
整形提升为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
按有符号读取转换为十进制整数:127 类推。。。
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 的整型提升)