绪论
书接上回,通过初阶知识的积累,相信你对编程有了很大的了解,但前方才是曙光,希望你可以继续和我一起对C语言的进阶知识进行探索学习,后面的难度会开始增加,也回略微有点绕,当相信只要你能认真的通过这篇文章的知识点和练习题就一定可以对数据类型有一个更加深入的了解。
所以安全带系好,发车啦(建议电脑观看)。
附:红色,部分为重点部分;蓝颜色为需要记忆的部分(不是死记硬背哈,多敲);黑色加粗或者其余颜色为次重点;黑色为描述需要
思维导图:
要XMind思维导图的话可以私信哈
目录
1.数据类型介绍
在前面我们已经将C语言中基本的内置类型学习了他们的
名称以及所占内存空间大小
内置类型: char | //字符数据类型 1byte //短整型 2byte //整形 4 //长整型 >=4 //更长的整形 8 //单精度浮点数 4 //双精度浮点数 8 |
不同类型的意义:
- 内存中开辟的内存空间不同
- 看待内存空间的视角不同
1.1整形家族
知识点:
1.1.1整形家族:
char
unsigned char
signed char
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int
long
unsigned long [int]
signed long [int]long long
unsigned long long [int]
.....
附1:
为什么在short 、long后面加上[int]呢?因为short 你可以写成 short int / long int
附2:
unsigned 表示无符号的 、 反之signed 表示有符号的 ;所谓的有无符号就表示这个数的二进制的第一位算不算符号位。
如二进制-1:
他在内存中的存的是补码:
原码:10000000000000000000000000000001
反码:1111111111111111111111111111111111110
补码:1111111111111111111111111111111111111
当其 -1 前加上 unsigned 就代表其最前面的符号位不算也就将他在内存中的补码看成一个正数这个时候又因为,正数的原反补相同所以就会使-1成为一个在无符号整形中最大的一个数。
并且:在不同的编译器下char 不一定直接表示成 signed char(虽然常见的编译器如此),而int ,long ,short 就是完全等价于 signed int 、signed short、signed long
1.1.2为什么将char归为整形:
因为char在内存中以ASCII码值存储在内存中,而ASCII码值是以整形(‘a’ : 97,‘A’:65)的形式存储的所以将char类型归为整形(整形和字符的转化是:‘字符’ - ‘0’(48) = 对应的整形数字;整形+‘0’ =对应的字符数字)。
1.2浮点型家族
知识点:
1.2.1浮点型家族:
float
double
1.3其他类型
知识点:
1.3.1构造类型:
构造类型又称自定义类型有:
数组类型:被定义为构造类型的原因是其可以自定其自身的类型,如:int arr[10] :其类型就是int [10] 、同理你还可以将其改成int arr[20] : int [20]
即当数组的元素个数和元素类型发生变化时,这个数组也会跟着发生改变
而后面的三种情况就比数组类型要相对来说要好理解一点其为构造类型的原因也就是其可以自定义自己的类型(后面还会详细的讲到下面3种类型,早关注不迷路)
结构体类型 struc
枚举类型 enum
联合类型 union
1.3.2指针类型:
这在前面初阶指针已经讲过建议如果记不清了可以去康康
int *pi;
char *pc;
float* pf
void* pv;
1.3.3空类型:
void 表示空类型(无类型)
一般可以用到函数的返回类型、函数的形参。如:void test(void)此时test函数的返回类型是void也就表示不用返回、形参为void就表示其不需要传参进到test函数内。以及用到指针类型:void *处此时这个指针无指定的类型
在前面我们学习到了各种数据类型当他们创建后都会在内存中开辟一定的内存空间,而既然开辟了空间,那同时就需要知道其是怎么将这个数据放到这内存中的。
下面就将写到整形家族和浮点型家族在内存中的存储方式。
2.整形的内存存储
2.1 整形的存储规则
如:int a = 20; int b = -10
内存情况:
附:创建的相邻变量在内存中相差2个地址(vs):其中变量b的在内存中所存为(f6 ff ff ff)以及a在内存中所存(14 00 00 00)
整形存储时他会在内存中的步骤:
- 开辟内存空间
- 数据的存放与取出
- 开辟一个4byte大小的空间(而一个十六进制是4bit 两个 十六进制 就表示一个字节),所以一个整形会由8个十六进制组成 . (其本质应该为二进制但是二进制太长所以将其减化成十六进制展示)并且以补码的形式存进内存中)
- 并且他会大小端问题 将数据存进内存(如:我们可以看到到十六进制的a :0x 00 00 00 14 本应该为(0x 00 00 00 14)可实际上却以每个字节倒的存放了进内存(这就是大小端问题))
详细解释会在下面讲清:
2.2原、反、补码
知识点:
- 我们要知道整形在内存中是以补码的形式内存在的
- 我们应该了解原、反、补的转化规则
细节:
在计算机中整数有 三种 二进制的表示方法:那就是原、反、补
在这三种表示方法中
- 二进制的第一位 表示其 符号位(0为正 ,1为负)
- 其余位表示数值位
附:整数的原、反、补是相同的 所以原、反、补主要是运用到负数中
并且要记住,所有整形的计算都是通过补码进行的(以补码的形式加减乘除)
2.2.1原、反、补的关系
原码(我们看到的当printf打印出的二进制,即若要打印数字将会把内存中的补码转化成原码再进行得出数字打印)
直接将数字翻译成二进制就可以得到。
反码
将原码的符号位不变,其他位依次按位取反就可以得到了。
补码
反码+1就得到补码。
所以若我们要从补码到原码就可以倒回去:-1 、取反
然而,因为补码到原码还可以和原码到补码一样再一次:取反、+1
如:
-1 的原码:
1000 0000 00000000 00000000 00000001
反码(取反):
1111 1111 11111111 11111111 11111110
补码(+1):
1111 1111 11111111 111111111 11111111
反码进行和原码一样的方法:
取反:10000000000000000000000000000000
+1 : 10000000000000000000000000000001
2.2.2为什么要有原、反、补码
- 当我们有补码进行数值的运算时,我们就可以统一符号位和数值位直接进行计算
- 由1同时也就能退出可以让加法和减法统一处理( 1 - 1 = 1 + (-1)),cup中只有加法器
- 同时原码到补码和补码到原码可以通过相同的方法进行转化,这样就不需要额外的硬件电路
练习:
int a = 1; int b = 1; int c = a - b;
如上代码当我们要求c时:1-1 然而,因为cup中只有加法器, 所以就转化成 1 + (-1)
而负数的原反补并不相同所以还需要将-1的补码计算出来最后再与1相加
-1的补码:1111 1111 1111 1111 1111 1111 1111 1111
1的补码: 0000 0000 0000 0000 0000 0000 0000 0001
相加: 1 0000 0000 0000 0000 0000 0000 0000 0000 而因为超出了32位所以第33位就会截断最终变成:00000000 00000000 00000000 00000000 也就是0
这和我们所想的答案也一致
int a = 20; int b = -10;
还是这个例子当我们&b 找到b的地址及他在内存中的存储后
应该如何将这个b存储在内存中:
首先我们写出他在的,int :4byte : 32bit:所以要写32位,同理char就是8位
原码:10000000000000000000000000001010
反码:111111111111111111111111111111110101
补码:111111111111111111111111111111110110
再将二进制的补码转化成十六进制:
补码:1111 1111 1111 1111 1111 1111 1111 1111 0110
F F F F F F F F 6
十六进制:0x FF FF FF F6
本应该为(0x FF FF FF F6)如此可是却以每个字节倒了过来(这就是因为大小端))
此时我相信你已经知道了什么是原、反、补了,那就让我们继续往下了解大小端,
2.3大小端
知识点:
大小端问题:当我们有一个数据,此时根据类型开辟一个内存空间,就需要有一定的规则规定其放进这个内存空间中去(这个规则就是大小端问题)
2.3.1大端:
又称大端字节序存储,是指数据的低位保存在内存的高地址中,而数据的高位保存在内存的低地址中;我来将其更简单的概述就是:不用进行什么改变,就和补码保持一致的放进内存中。
2.3.2小端:
又称小端字节序存储,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。简单来说:小端存储即使是将原本的补码反过来放进内存中。
附:大小端存储又称大小端字节序存储的原因是:
因为:在数值中的排序其实是以字节为单位(每两个十六进制表示为一个字节所以同常都是两个两个的十六进制来排)
如-10:0x ff ff ff f6 因为VS环境下是以小端存储的所以其在内存中的存储,所以最终将会表示成 0x F6 FF FF FF (将F6、FF 、... 、FF 这样的一个byte的进行排序 )
而具体的什么数据的低位、高位我在前面写过一篇blog可以在康康
细节:
2.3.3那为什么我们一定需要大小端存在呢?
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中,0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是
小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端、模式。
简单的概括来说就是:
当我们一个数据的类型所需的空间(int :4byte)要大于一个地址所控制的内存空间(1byte)时我们就需要有一个排序方法,来将还存的int所需的4byte进行排序
练习:
3.浮点型的内存存储
3.1 浮点型的存储规则
知识点:
常见的浮点型:
3.14159
1E10 (1.0 * 10 ^ 10)
细节:
3.1.1浮点型的存储规则
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S * M * 2^E(SME)
(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于2。
2 ^ E表示指数位
例如:如十进制 5.5写成二进制就可以表示成二进制 101.1
小数点前面的好理解就是 5 的二进制表示,而小数点后一位就当写上1时就表示成 2 ^ -1 == 0.5 (看2的权重)所以在小数点后加一个1
若要写成(-1)^S * M * 2^E形式时就需要先写成 : 1.011 * 2 ^ 2 (这里对应十进制缩进小数点时的情况)
所以就可以再写成 (-1)^ 0 * 1.011 * 2 ^ 2
所以套回原来的公式就可以写成:S = 0 ; M = 1.011 ; E = 2 (SME)
所以我们可以发现所有的浮点数写出来最后都只是SME不一样
所以我们只需要知道了一个浮点数的SME得出来就可以得出这个浮点数(故我们只需要将SME存起来后面用时就可以取出这个SME然后得到这个浮点数)
再如:十进制:0.5
-> 0.1
-> 1.0 * 2 ^ -1
-> (-1) ^ 0 * 1.0 * 2 ^ -1
-> S = 0 ; M = 1.0 ;E = -1;
知道了 SME 后我们就需要知道如何将 SME 存进内存中
IEEE 754规定:
对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
用图形表示:
此处可以将SME 记作 SEM进行存储
现在当我们知道了SME就可以进行存储了,其各个都有不同的存储方式,下面我将一一道来:
S:是1就直接存1,是0进存0,进到S区即可
E:
首先E为一个无符号的整形 ,所以 当 E 为 8位 时 其取值范围是 0 ~ 255 为 11位 是其取值范围是 0 ~2047
但是我们知道 E 他可能是一个负值,所以当我们将E存进内存中时要先加上一个中间值(来确保到时候取出来是仍然是一个负数),如32位时要加上一个 127 在存放进E的内存空间中、而64位时要加上一个 1023 在存放进E的内存空间中
如:若E = -1
32位存放时: -1 + 127 = 126 再将 126 的二进制放进 8bit 的空间中
64位存放时: -1 + 1023 = 1022 再将1022的二进制存放进 11bit 的空间中
当然在我们取出时就需要再把之前所加上的值减去;下面是取E的情况
分为三种:
- 内存中的E全为0时: 此时因为原E是加上127为0的所以原E为-127,想想这是一个多么小的数字呀 2 ^ -127 要知道 2 ^ 32 已经是42亿多了 还要在不断的乘上近百个二再取倒数,这将是一个无限接近于0的很小的数字,所以此时我们取出的M将不在加1而是直接表示出来为 0. xxxxxx (又因为double、float他们的有效位数有限所以就会导致截断后直接变成0)
- 内存中的E全为1时: 此时同上因为是+127后才为全1的所以原E应为128(255-127),表示这个数无穷大(不在过多讨论)
- 内存中的E不是以上情况时(有0 有1): 此时就是将原存进的E-127/E-102即可取出
M:对于M来说,他是一个 大于等于1 小于2的数即 1. xxxxx 。所以在我们存储的时候就先暂时抛弃 1. 将小数点后面的数存进内存中 ,这样我们就可以节省一定的内存空间从而然这个浮点数更精准(可以提高精度)
如读取M = 1.01时 就先抛弃1.(后面再加上1即可)即存 01
将01放内存空间的最前面,后面不够的补0
:01000000....
练习:
现在我们再将5.5当作例子进行举例
int main()
{
float a = 5.5f;
// 101.1
// s = 0 ; m = 1.011 ; e = 2
// s直接存 ; e加上127(2+127 == 129) ;先去掉1. 再将011存进后面剩余的补0
// 0 1000 0001 011 0000000000 0000000000
// 40 b0 00 00
//vs是小端即为 00 00 b0 40
return 0;
}
所以在我们要拿出时就是先将s :0拿出 ;然后是e :此时为第三种情况所以129 -127 = 2 : 2^2
m:011那出再加上1 : 1.011
最终得到:(-1)^ 0 * 1.011 * 2 ^ 2
也就是:101.1
本章完。预知后事如何,暂听下回分说。
持续更新大量C语言细致内容,三连关注哈