目录
版本差异:
数据类型:
进制表示:
大小端储存:
数据运算:
浮点型在内存中的存储:
版本差异:
debug和release的区别:
在栈区开辟地址一般是先从高地址开辟
debug创建数组和单个变量后,变量地址放在数组后,即变量地址高于数组地址(先创建变量再创建数组)这种情况下可能会导致死循环(2photos)Release创建数组和单个变量后,变量地址放在数组前,及变量地址低于数组地址(自动进行了代码优化)
以上代码在vscode中进行的
Debug通常为调试版本,包含调试信息,并且不做任何优化,便于程序员调试程序
Release称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用
数据类型:
数据类型详细介绍:
Char1 short2 int4 long4/8(32位平台下和64位平台下的区别)
Long long8(c99) float4 double8
Char在整型家族里,字符的本质是ASCII码值,是整型,所以划分到整型家族
Int a; ----> signed int a;
Unsigned int a; ----> unsigned int a;
Short、long、long long同
Single char 是哪一个标准未定义,是由编译器决定的
符号位是0表示正数,1表示负数
Float的精度低,存储的数据范围较小;double的精度高,存储的数据范围大
Void* 万能指针类型,在指针复习该文中讲解过
Void test(void)
//第一个void表示函数不会有返回值
//第二个void表示函数不需要传任何参数
进制表示:
假设我要表示在10进制下为21的数据
二进制:0b10101
八进制:025
十六进制:0x15
十进制:1*1+10*2 =21
整数的二进制表示有三种表示形式:
正的整数,原码、补码、反码相同
负的需要通过计算
原码:直接通过正负的形式写出的二进制序列就是原码
反码:原码的符号位不变,其他位按位取反
补码:反码+1就是补码
更多原码、反码与补码讲解:操作符讲解
计算机储存的是补码
原因:使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路
进制位计算:
一个16进制位是4个2进制位,因为 2 ^ 4 = 16
大小端储存:
计算机是以字节为单位存入,最后计算机只保留了两种存储形式,假设一个数据地址为0x11 22 33 44 则存入为11 22 33 44 或者44 33 22 11(都是指从低地址到高地址)
11 22 33 44:大端字节序存储,把一个数据的高位字节序的内容存放在低地址处,把低位字节序内容放在高地址处,就是大端字节序存储
44 33 22 11:小段字节序存储,与11 22 33 44 的存储方式相反
巧计:大的数值放高位(高地址)即为大端字节序存储,小的数值放高位(高地址)即为小端字节序存储
数据大小含义
例如int i = 20
i的16进制表示方式即为0x 00 00 00 14,其中14为最大字节序,即使该处代表的是16^0和16^1,但是0永远要比这一字节序小,因此14为最大字节序。同时这两者是连在一起存入的,顺序不会颠倒,因为小端大端的存入都是以字节序的方式。
但上述储存是包含大量0的情况,当每一位上都不是0,最小依旧是16^0和16^1所在字节序,但最大对有符号的应该就是符号位所在序列,无符号的就是2^32的所在序列(可以把16进制位转换成2进制位来思考)
注:一个字节序代表 一个字节/八个比特 的存储序列
那么小端存储的含义即为i是以14 00 00 00存入的,大端存储的含义即为i是以00 00 00 14存入的
小端大端的存储只针对于2字节及以上的数据类型
大端小端究竟怎么存储是由硬件决定的
为什么有大端小端?
数据存储会有顺序问题,为了解决顺序问题使用了大小端存储的这种方式。
代码解决大小端判断问题
请见本文联合体讲解板块:结构体讲解
数据运算:
例1.下述代码输出结果是什么?
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a=%d,b=%d,c=%d", a, b, c);
在继续讲解前,我们首先需要知道字符型数据的取值范围
Signed char最高位是符号位
0表示正数,1表示负数;原码到反码,符号位不变
因此如果计算机中储存的补码是11111111,那么原码为10000001,即-1;同理存储的补码为10000001,那么原码为11111111,即-127;正数补码原码相同,存储的补码为01111111,结果为127
10000000由于不满足上述计算方法,因此计算机直接将他默认为-128,因此char的取值范围为 -128 ~ 127
无符号的最高位存放数据,代表2^7,因此无符号char的取值范围为0 ~ 255
-1的补码为11111111111111111111111111111111
char类型放不下,因此就进行截断。截断是从低位拿到高位(无论大小段存储),因此把最后8位11111111存入;进行%d打印时,会进行整型提升csdn表达式求值板块,补符号位数字,即1,结果即为补码形式的11111111111111111111111111111111,打印的是原码,即1000000000000000000000000000001,即-1
补码 ---> 原码 取反+1 原码 ---> 补码 取反+1
不是说原码到补码是 取反+1,补码到原码就是 -1取反
因此,a和b打印出来均为-1
由于无符号类型整型提升时没有符号位,因此直接补0,故c的补码为00000000000000000000000011111111(对于-1的截断上文已经提过),正数原码、补码相同,打印结果为255
注:数据截断发生在存入过程中,使用时在进行整型提升
例2.下述代码打印结果是?
signed char d = -128;
printf(“%u”, d);
%u --- 打印无符号整数
10000000 00000000 00000000 10000000 --- -128的原码
11111111 11111111 11111111 01111111 --- -128的反码
11111111 11111111 11111111 10000000 +1即为+00000000 00000000 00000000 00000001
上文中的加1即为+00000000 00000000 00000000 00000001(加法的详细介绍在本文表达式求值板块:操作符讲解
相减即为逆向计算
例如11111111 11111111 11111111 10000000 - 1即为+(-1)
结果为11111111 11111111 11111111 01111111
-1的补码为 11111111 11111111 11111111 11111111
11111111 11111111 11111111 10000000 - 11111111 11111111 11111111 11111111
即为 1111111 11111111 11111111 10000000 + 1111111 11111111 11111111 11111111
最后8位,最后算出来为21111111,逢2进1,留下了01111111;前面不断逢2进1,因此先是全部变成0,但由于-1的补码中还有1没有加上去,因此最后结果为
11111111 11111111 11111111 01111111
数据2进制加减规则详细介绍:定点数的运算 —— 原码、补码的加减法运算
文章补充:数据加减运算是通过补码来进行的
10000000(signed char为有符号字符型)--- 截断
11111111 11111111 11111111 10000000 --- 整型提升
打印时打的是无符号字整数,因此计算机将11111111 11111111 11111111 10000000直接看作一个最高位为1的正数打印,补码原码相同
例3.下面代码的打印结果是?
unsigned int i;
for (i = 9; i >= 0; i--);
{
printf("%u", i);
}
先打印9 8 7 6 5 4 3 2 1 0,然后理应上是-1,但由于是无符号整型,所以结果为下
10000000 00000000 00000000 00000001 ---原码
11111111 11111111 11111111 11111110 ---反码
11111111 11111111 11111111 11111111 ---补码
无符号即是直接打印11111111 11111111 11111111 11111111,该数据-1为11111111 11111111 11111111 11111110还是大于0,一直大于等于0,会死循环
最终打印结果:
例4.下述代码的打印结果为?
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i; //-1 -2 -3 -4 …… -999 -1000
}
printf("%d", strlen(a));
//strlen( )函数是求字符串的长度,关注的是字符串中’\0’(数字0,因为在计算机中,\0的ascII值为0)之前出现了多少个字符(即1字节的数据)
对于char这一类型,取值范围为-128~127(上文已经讲解过)
因此对于比 -129 更小的整型数据,是直接换成127,原因如下:
先从00000000开始,每+1,加到最大值,即127,即01111111,再+1,会变成10000000,上文已经说过,这个结果为-128,因此继续+1,即10000001,这是补码,换成原码结果为11111111,即-127,以此类推,最后一个数据为11111111(补码),即10000001(原码),即是-1
理解了上述过程,-1就是进行了逆向计算
因此到0的时候,总共有127+128 = 255 个字符(1字节数据)
1.-129如何变成127的呢?
-129为整型数据,在内存存放为10000000000000000000000010000001
取反+1
原码为 11111111111111111111111101111111
而这个数据如果要放进char类型中,进行截断,只留下了01111111,即127
2. 无符号字符型数据255如何加1变成0的呢?
255为无符号字符型数据的最大取值范围,补码为11111111,+1(00000000 00000000 00000000 00000001)以后,先是进行算数转换,就是00000000 00000000 00000000 11111111 + 00000000 00000000 00000000 00000001,再根据逢2进1的规则,最后补码留下了00000000 00000000 00000001 00000000,然后因为i + 1以后,还存放在i当中,即使i在给i赋值时根据算术转换原则依然是00000000 00000000 00000001 00000000,但i的类型依旧为无符号字符型变量,进行了强制类型转换,因此截断留下00000000,即是0.
浮点型在内存中的存储:
浮点数具体就是指小数点可以浮动的数值
整型数据表示的范围:limits.h中定义
浮点数表示的范围:float.h中定义
那为什么平时我们定义变量不需要引用这两个头文件呢?因为平时给变量初始化时,我们都是自己赋给它值,而这俩头文件里包含的数据都是整型数据最大值等等极限数值,因此平时使用不多
上面两图说明了整型数据和浮点型数据存储方式是不一样的
根据国际标准IEEE754,任意一个二进制浮点数都可以写成下述形式
V = (-1) ^ S * M * 2 ^ E
(-1)^S 表示符号位,S为0,V为正数,S为-1,V为负数,最后结果需要表示成科学计数法的形式
V = 5.0f ---> 101.0(5.0二进制表述形式)---> 1.01 * 2^2 ---> (-1)^0 * 1.01 * 2^2
即S=0,M=1.01,E=2
上例中为什么是2^2而不是10^2呢?
因为上述的101.0是二进制表达,因此是2^2;如果是5.0,那么才可以写成0.5 * 10^1
小数点后的位,权重是从2^(-1) 开始算,往后延申几位,负值减少几位
因此9.5的表示形式为 1001.1 (小数点前的是2^0 + 2^3 = 9 ;小数点后的是2^(-1) = 0.5
但是对于 9.6 这样的数据,2^(-1)为0.5,2^(-2)为0.25,2^(-3)为0.125,以此类推,最后就需要开辟非常非常多的0来满足0.1的需要
两张不同精度的浮点数
32位:
64位:
1≤M<2,也就是说,M可以写成1.xxxxxxx地形式,其中xxxxxxx表示小数部分。
IEEE754 规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以舍去,只保留后面的小数部分;等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。
以32位浮点数为例,留给M只有23位。将第一位的1舍去以后,等于可以保存24位有效数字 ---> 浮点数精度提升了
E为无符号整数,32位浮点数取值范围为0~255,64位浮点数取值范围为0~2047.但是,E是可以出现负数的
例如下图
因此存入内存是E的真实值必须再加上一个中间数,对于8位的E,这个中间数为127;对于11位的E,这个中间数为1023
假设上图的V为两种不同精度的类型,E最后在计算机中存储的值如下:
无论E真实值的正负,都需要加上这个中间值;且存储在计算机中的数值一律不为负
因此对于一个float型数据:5.5,在内存中的储存二进制表示如下:
在011后补0的原因:
省去了1,原本是1.011;如果是在011前补0,那么就会变成1.0000……011,与初始值不符
四个二进制位是一个16进制位,以此上述二进制表示成16进制的话为
4 0 b 0 0 0 0 0,即0x40 b0 00 00
注:float类型需要在数值后加上f,要不然计算机会把它视为double型,但在日常使用中,float i = 5.5,5.5依旧是单精度类型,这是由于前面的i是float型,进行了强制类型转换
指数E的取出分为三种情况
1.E不完全为0或不完全为1:
对于float型 0 10000001 01100000000000000000000,补1、减中间数,最后结果(-1)^0 * 1.01100000000000000000000 * 2^2
2.E全为0:
1.xxxxxxx * 2^(-126) 无限接近于0
3.E全为1:
1.xxxxxxx * 2^(128) 为无穷