C语言进阶:数据的存储 千变万化

本文是C语言进阶系列的第一期,难度不是很大,但是对于整个学习的过程非常有帮助。

1.数据类型详细介绍

char       //字符数据类型
short      //短整型
int        //整型
long       //长整型
long long  //更长的整型
float      //单精度浮点数
double     //双精度浮点数

这些类型都是比较常见的数据类型,这里再介绍一种类型:布尔类型

在C99标准后,用0表示假,用非0表示真,布尔类型就是如此:

#include<stdbool.h>

int main()
{
    _Bool flag = false;
     if(flag)
    {
        printf("hehe"n");
    }
    return 0;
}

在这段代码中并不会打印hehe,因为布尔类型定义了flag是假的,所以flag代表的就是0

(其实就是int类型的一种,真就是int定义为1,假就是int定义为0)

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]

在C语言中,unsigned的类型是没有负数的,即unsigned>=0,如果存放的是负数那么会转换成一个整数

 int 等价于 signed int,short 等价于 signed short, long 等价于 signed long

但是!char不一样!并没有规定char等价于什么,但是在常见的编译器中 char 等价于unsigned char

1.2 构造类型:

也叫 自定义类型

数组类型
结构体类型  struct
枚举类型    enum
联合类型    union

2.整型在内存中的存储:原码、反码、补码

2.1 介绍 

整数的二进制表示有三种表示形式:原码、反码、补码

正数:原码反码补码相同

负数:

         原码:按照一个数的正、负直接写出来的二进制就是原码

         反码:符号位不变,其他位按位取反

         补码:反码的二进制序列+1

如:

int a = -10

10000000 00000000 00000000 00001010  原码

11111111  11111111   11111111  11110101   反码

11111111  11111111   11111111  11110110   补码

2.2 unsigned char

#include<stdio.h>

int main()
{
    char a = -1;
    signed char b = -1;
    unsigned char c = -1;
    printf("%d %d %d",a,b,c);

    return 0;
}

 这段代码中的a,b,c分别打印出来是多少呢?

 -1在内存中的存放的是32个比特位,4个字节,也就是

11111111 11111111 11111111 11111111   --补码   

但是char类型的只能存1个字节,也就是后面的11111111

于是发生整形提升,按照第一个数字(符号位)提升

于是补码变成了

11111111 11111111 11111111 11111111  所以说打印出来的仍然是-1

但是unsigned char不一样了!

11111111整形提升后,高位因为无符号的原因,补得是0

于是补码变成了00000000 00000000 00000000 11111111

由于这是一个正数,所以原码是一样的,打印出来正好就是255

2.3 %u

#include<stdio.h>
int main()
{
    char a = -128;
    printf("%u",a);
    return 0;
}

%u是打印一个无符号的数,-128在内存中的原码是

10000000 00000000 00000000 10000000

补码是

11111111 11111111 11111111 10000000

整形提升后

11111111 11111111 11111111 10000000

但是%u会把这当成无符号数,所以会把这个当成正数,原码反码补码相同

于是这个数打印出来就是4,294,967,168

2.4 char类型的具体储存 

#include<stdio.h>
int main()
{
    char a[1000];
    int i = 0;
    for(i = 0;i<1000;i++)
    {
        a[i] = -1-i;
    }
    printf("%d",strlen(a));
    return 0;
}

这里的a是多少呢?是不是1001呢?我们仔细来分析:

先说结论:

signed char     取值范围:-128 ~ 127

unsigned char 取值范围:0 ~ 255

我们先看unsigned char:

这是char类型的所有可以存放的数据,由于是无符号类型的char,所以第一位并不是符号位,8位数字都存放的是有效位,所以可以存放0-255的数据

我们再来分析signed char:

 由于第一位是符号位,所以存放的数据只有128。并且特别说明,10000000会被直接识别出来为-128

也就是说,有符号的char是127时,再加1就变成了-128

3.大小端字节序的介绍及判断

为什么计算机中存储的是补码呢?首先我们要知道计算机中只有加法器,减法也得当成加法来算:1+(-1)

因为只有补码能满足各种数据的运算~

 那么什么是大小端字节序呢?在此以VS2019为例,

 可以看到这里的数据超过了一个字节的大小,在内存中存放的时候是有顺序的。

大端字节序存储:11223344按照从低到高的顺序存储。1在低地址,4在高地址。

准确来说是:当一个数据的低字节数据存放在高地址处,高字节的内容放在了低地址处,这种储存方式就是大端字节序存储。

那么VS2019中就是小端字节序存储。

问题来了:写个程序判断当前编译器是大端字节序还是小端字节序

int main()
{
    int a  = 1;
    char* p = (char*)&a;
    if(1 == *p)
    {
        printf("小端");
    }
    else
    {
        printf("大端");
    }
    return 0;
}

 原理是什么呢?

4.浮点型在内存中的储存解析

#include<stdio.h>

int main()
{
    int n = 9;
    float* pfloat = (float*)&n;
    printf("%dn",n);
    printf("%fn",*pfloat);

    *pfloat = 9.0;
    printf("%dn",n);
    printf("%fn",*pfloat);
    
    return 0;
}

 但是这个时候我们发现并不是我们想要的结果。

整数和浮点数在内存中的存储是不一样的。

那么浮点数在内存中是如何存储的呢?

根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:

 首先以5.5为例,5.5的二进制是101.1

那么用754标准,得出为:

-1^0 * 1.011 * 2^2

S = 0;M = 1.011;E = 2

我哦们可以发现这个M总是大于1小于2的 

关于M:

IEEE754规定,在计算机内部保存M时,默认这个数的第一位是1,因此可以被舍去,只保存后面的部分,比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去,这样做的目的,是节省1位有效数字,以float为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字

关于E:

首先,E为一个无符号整数。这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047.但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数。对于8位的E,这个中间数是127,对于11位的E,这个中间数是1023.比如,2^10的E是10,所以保存成10+127=137,即10001001

然后,指数E从内存中取出还可以分成三种情况:

1.E不全为0或不全为1

这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
比如:
0.5 (1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,
则为
1.0*2^(-1),其阶码为-1+127=126,表示为
01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:0 01111110 00000000000000000000000

2.E为全0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,
有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示+0,以及接近于0的很小的数字。

3.E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);

#include<stdio.h>

int main()
{
    int n = 9;
    float* pfloat = (float*)&n;
    printf("%dn",n);
    printf("%fn",*pfloat);

    *pfloat = 9.0;
    printf("%dn",n);
    printf("%fn",*pfloat);
    
    return 0;
}

最后我们再来看到这个代码段:

第一个n = 9,那么9存放的是00000000000000000000000000001001

但是对于浮点数9,第一个0代表的是S,中间8位代表的是E,最后23位表示的是M。

E为全0,这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,这个值是非常小的,有效位在小数点后面很多位,所以打印6位浮点数出来为全0;

第二个*pfloat = 9.0,9.0是十进制的表示,转换为相应的科学计数法为:

0 10000010 00100000000000000000000

这个数浮点数识别出来的就是9.0

但是整数识别出来的就是二进制整数的补码,整型的形式打印出来就是1091567616

我们可以清楚地看到整数和浮点数在内存中的存储是不同的!

也许面试的时候并不会考这么深的知识,但是这是非常修炼内功的知识!

那么今天的知识分享就到这里了,觉得写的还不错的话点赞收藏关注博主吧!

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
)">
下一篇>>