我们继续看我们没做完的题
练习2
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a=%d,b=%d,c=%d", a, b, c);
return 0;
}
这里先给大家具体看一下数据类型的取值范围



但是还有一个问题,最后我要输出的结果的占位符是"%d",打印的是十进制数字,所以我们还要进行"整型提升",整型提升补的是符号位

所以最后输出的结果就是"-1""-1""255"

练习3
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
char a = -128;
char b = 128;
printf("%u\n%u\n", a,b);
return 0;
}

虽然只取低八位的比特位,但打印的是无符号十进制数,所以打印的是整型提升后的所有比特位

练习4
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main()
{
char a[1000]; //下标是0到999
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i; //-1,-2,-3......
}
printf("%d", strlen(a));
return 0;
}
这里先给大家另一个理解方式

如果顺时针方向循环转动就是0,1,2,3,......126,127,-128,-127......-2,-1,0,1,2......
逆时针方向循环转动就是-1,-2,-3,-127,-128,127,126......2,1,0,-1,-2......
而这里采用逆时针转动,直到遇到"\0"结束,而"\0"的ascii值为0,strlen遇到\0会停下来

练习5
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
首先无符号char的取值范围是0~255,当i==255的时候就要加1,此时加1的结果不是256,而是0

所以这里会陷入死循环
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
return 0;
}
当i==0的时候就要再减1,此时从数学上讲是-1,但unsigned int i=-1的时候是一个无符号数,但-1是一个有符号数,所以就会转化成一个非常大的数,然后再对这个非常大的数循环减

练习6
#define _CRT_SECURE_NO_WARNINGS
#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;
}
&a代表取出的是整个数组的地址然后加1,就跳到数组最后一个地址的后一位,然后再对地址-1就回到数组最后一个字符的地址后解引用
(int)a表示把数组首元素地址取出来后转化为整型数据后加1就是地址加1,比如0x11223343+1后就是0x11223344(多了一个字节),然后ptr2是一个整型类型的指针,解引用后就是多了一个字节的地址的数据,但这是小端存储,就要还原该有的数据,就是02000000


浮点数在内存中的存储
常见的浮点数:3.14159、1E10等,浮点数家族包括:float,double,long double等
浮点数的范围在float.h头文件中展现
我们先给大家一段代码来引入
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为: %d\n", n);
printf("*pFloat的值为: %f\n", *pFloat);
*pFloat = 9.0;
printf("num的值为: %d\n", n);
printf("*pFloat的值为: %f\n", *pFloat);
return 0;
}

这里如果从整数的形似输出浮点数不一样,从浮点数的形式输出整数也不一样,这里就先理解这么多,其他的下面来理解
浮点数的存储
根据国际标准IEEE(电气和电子工程协会)754,任意⼀个二进制浮点数V可以表示成下面的形式

任何一个十进制的浮点数都可以转化为二进制的浮点数,而二进制的浮点数可以用该公式表示
比如5.5这个浮点数我们可以写成101.1这个二进制数形式,还可以表示成(-2)^0*101.1*2^0
s=0,m=101.1,e=0
再比如,5.0这个浮点数,二进制101.0,相当于1.01*2^2,那么,按照上面V的格式,可以得出S=0,M=1.01,E=2
所以浮点数的存储其实就是在存储s,m,e这三个相关的值
IEEE 754规定:
对于32位的浮点数,最高的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M 对于64位的浮点数,最高的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M

浮点数存的过程
IEEE 754对有效数字M和指数E,还有⼀些特别规定
前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。 IEEE 754规定,在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后面的 xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第⼀位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第⼀位的1舍去以后,等于可以保存24位有效数字
至于指数E,情况就比较复杂 首先,E为无符号整数(unsignedint)
这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我 们知道,科学计数法中的E是可以出现负数的,所以IEEE754规定,存⼊内存时E的真实值必须再加上 ⼀个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。⽐如,2^10的E是 10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
float f = 5.5f;//
//5.5
//101.1
//1.011*2^2
//(-1)^0*1.011*2^2
//s=0 m=1.011 e=2
//但是e要加上一个中间值127才可以正确存储,所以e是129
//m存储的是小数点后面的数,就是011,但后面还有很多位,就在后面补0
//m就是01100000000000000000000,补够32个比特位
//所以01000000101100000000000000000000(十六进制:40B00000)就是我们存到内存中的值
return 0;
}

说明这里是小端存储,但是不是所有浮点数都可以准确的存储在内存中,必定会有差值
浮点数取的过程
现在我们回到当开始的代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为: %d\n", n);
printf("*pFloat的值为: %f\n", *pFloat);
*pFloat = 9.0;
printf("num的值为: %d\n", n);
printf("*pFloat的值为: %f\n", *pFloat);
return 0;
}
站在pFloat的角度,它认为它指向的是一个float类型的数据0 00000000 00000000001001,此时它的视角就是s=0,e=00000000,m=00000000001001
当内存中的e全为0的时候,真的e就是1-127=-126
有效的m不再取出数字后加1
最后结果就是(-1)^0*0.0 00000000 0000000000100182^-126,第二个输出就是0.000000
而9.0的二进制是1001.1,相当于1.001*2^3或(-1)^0*1.001*2^3,此时s=0,m=1.001,e=3
现在我把这个数9.0存到n里面去,0 10000010 (m不会把1.001前面的1村存进去,只会存001,但位数不够,在后面补0)00100000000000000000000,现在以%d(有符号数)的形式打印,最高位是0,所以第三个输出就是这个数转化为十进制数
