C语言-数据存储

一 、数据类型的介绍

1、数据类型的简介

生活中,整数是一个数的类型,小数也是一个数的类型,整数和小数都是用来描述一个数的,数据类型是C语言从现实生活中抽象出来的,C语言中的数据类型也是用来描述一个数的。
刚开始学数据类型会对数据类型比较陌生,平时要多思考,多揣摩,数据类型就是数的代表,是用来描述数的。和生活中的整数是一个意思。
char //字符数据类型
short //短整型
int //整形
long //长整型
long long //更长的整形
float //单精度浮点数
double //双精度浮点数

2、对类型归类

整型分为无符号整型和有符号整型,一般默认为有符号整型,例如int就是signed int ,但char型不固定,C语言没有规定char是signed char还是unsigned char,在大多数编译器里面是signed char。
无符号整型描述的是一个数没有符号,没有正负号,那这个数就默认为正数,正数的原反补码都相同,在通过原码计算正数的时候,原码表示的数没有符号,所以此时的原码中没有符号位,全是有效位,全部都可以参与运算;有符号整型描述的是一个数有符号,有正负号,那这个数有可能是正数也有可能是负数,如果是正数,正数的原反补码都相同,在通过原码计算正数的时候,由于原码表示的正数是有符号的,所以此时的原码中最高位是符号位,不能参与运算;如果是负数,正数的原反补码不相同,补码取反+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]
浮点型家族:
float
double
构造类型:
数组类型
结构体类型 struct
枚举类型 enum
联合类型 union
指针类型:
int* pi
char* pc
float* pf
void* pv
这里是引用

2、为什么要将整型和浮点型这两种数据类型细化出好几种呢?

上面我们说了,数据类型是从生活中抽象出来的,生活中有整数,小数和字母,那只抽象出三种就可以了,为什么还要对整型进行划分,对浮点型进行划分呢?

答: 不同的数据类型能够代表的数的范围不同,在表示数的时候,选择合适的数据类型,用来表示不同的数,能够增加空间的利用率,减少空间的浪费。
打个比方说:数据类型就像酒店房间的类型,给房间规定好哪个房间是单人间,哪个房间是双人间,三人间,如果一个客人来住,那就给他安排单人间就可以,安排双人间和三人间就是浪费了。

3、数据类型的意义

数据类型决定了两个方面:
第一个方面:数据类型就像变量的主人,它控制着,决定着变量能够向内存申请多大字节的空间,如果一个变量的类型是short类型,变量就能够向内存申请2个字节的空间,如果一个变量的类型是int类型,变量就能够向内存申请4个字节的空间。
第二个方面: 数据类型决定着我们看待内存空间的视角,也就是决定着我们会认为内存空间放的是什么,如果一块内存空间对应的变量类型是int类型,那我们,计算机等等,都会认为里面存放的是int类型的值,如果一块内存空间对应的变量类型是short类型,那我们,计算机等等等,都会认为里面存放的是short类型的值。
打个比方:如果一个瓶子上写着可口可乐,我们就会认为里面是可乐,如果一个瓶子上写着雪碧,我们就认为里面是雪碧,如果一个瓶子上写着白开水,我们就认为里面是白开水。
%d和%u的区别:
使用%d时,我们会认为内存里面存放的是有符号的整数,整数有可能是正数也有可能是负数,表示整数的二进制数的最高位是符号位,如果符号位是1,则是负数,求负数时,要根据原码求,内存中放的是补码,负数的原反补都不相同,对补码取反,+1,求得原码,再求负数。如果符号位是0,则表示正数,求正数时,也要根据原码求,内存中放的是补码,而正数的原反补都相同,直接根据补码,求得了正数。
使用%u时,我们认为内存里面存放的是无符号的数,则是正数,表示正数的二进制数中没有符号位,全是有效位,求正数的时候,内存中放的是补码,需要根据原码求正数,正数的原反补都相同,直接根据补码求得正数。

二、整型向内存存放时,存放的是什么?

整型向内存存放的时候,存放的是它的二进制形式的补码。

1、为什么要存放补码?

计算机只认识0和1,所以就向内存存放二进制形式的整数,整数的二进制的表现形式又分为原码,反码,和补码。
因为原码是正宗的,反码和补码是变换出来的,所以就有了这个疑问,为什么不放原码,这个正宗的进去,而是放整数的补码进去。下面三点原因,进行了解释:

第一点:原码不够忠心,它的符号位不能参与运算,将补码存放进去,在参与运算的时候,补码的符号位也可以参与运算,而原码就不行。
第二点:原码的能力有限,使用补码可以进行加减法的运算,而原码不能进行减法的运算,cpu只能进行加法的运算,当有减法的运算时,计算机只能将减法转化为加法的形式,转化形式之后,对原码计算之后,不能得到正确的结果,只有补码才可以得到正确的结果。
第三点:通过补码得原码非常简单,两者的转换过程是一样的,如果补码的符号位是1,表示的整数是负数,最后想要得到原码,直接对补码取反,+1,就可以得到,非常方便,不需要额外的硬件电路。
注意:存放的是二进制数,但为了表示方便,在显示内存中存放的数字时,显示的是16进制的,用四位二进制数表示一位16进制,因为16进制最大能表示的数是15,15用四位二进制数表示,所以16进制在表示二进制时,统一用一位16进制表示四位二进制数。献祭四位二进制数才能得到一位16进制数。

2、介绍二进制的原码反码和补码

这些进制都是数值的表现形式,就比方说将数值比喻为孙猴子,他有81种形态,但每一种形态都表示孙猴子,而数值呢,进制就是它的表现形式,每一种进制都表示这个数值。
二进制也是用数值的表现形式,二进制在表现数值的时候,有三种表现方法,分别为原码,反码和补码。
这三种表现方法都分为符号位和数值位,最高位是符号位,表示正数时,符号位是0,表示负数时,符号位是1。
根据二进制求十进制时,只能通过原码求十进制的数,原码是正宗的,反码和补码是变换得来的。
正数的原码,反码和补码都相同,正数比较惨,只有一种二进制序列可以表示它。
负数的原码,反码和补码都不相同,原码取反得到反码,反码+1得到补码。

三、通过整型创建的变量的取值范围是多大?

char类型的变量的取值范围是-128-127
char类型的变量占用内存8个比特位的空间,一个比特位可以存放一位二进制数,所以一个char类型的变量可以存放8位二进制数,这八位二进制数的组合为00000000-11111111,这些个组合。由于整数向内存存放的时候,是存放的二进制形式的,所以这些二进制数都可以表示整数,所以这个变量就有了取值范围,变量的类型是char类型的,所以站在这个变量的角度来看,它里面存放的数,都是有符号的数,用二进制表示这些数,所以这些数对应的二进制数的最高位是符号位,符号位是0,就是正数,符号位是1,就是负数,所以00000000-01111111,这些二进制数表示的整数都是正数,10000000-11111111这些二进制数表示的整数都是负数,所以这个变量就有了取值范围是:-128-127。

那char类型的变量它的值是如何变换的呢?
对char类型的变量不断的+1,它的值一直在循环的变化,下面是对它的值变化的分析:
由于一个char类型的变量可以存放8位二进制数,所以这些二进制数可以为00000000-11111111之间的任何一个数。
假设有一个铁环,我们将这些二进制数按照顺序,全部沾到铁环的边缘处,将a放到数的初始处,初始处的二进制数放到a里面,对应的整数是0,使a一直顺着铁环走,a的值变成了1,走到最下端时,a的值变成127,继续顺着铁环走,a的值变成-128,再继续走,a的值变成了-1,此时a走到了顶点处,在向前走,a回到了初始处,a的值又变成了0。由此可以看出,a的值由于不断的+1,值是在0-127-(-128)-(-1)-0,不断的循环的变化的。
画图理解:在这里插入图片描述

练习题:

#include <stdio.h>
unsigned char i = 0;
int main()
{
    for(i = 0;i<=255;i++)
   {
        printf("hello world\n");
   }
    return 0; 

unsigned char类型的变量的取值范围是0-255,如果对unsigned char类型的变量不断的+1,则它的值是在0-255-0之间循环的变化,当值是255,对变量+1之后,值不会变成256,而是回到了起点,也就是0,for语句的循环条件是i<=255,i是一直满足这个循环条件的,所以循环语句就会一直执行,不停的打印hello world。

unsigned int i;
for(i = 9; i >= 0; i--) {
    printf("%u\n",i);

unsigned int类型的变量的取值范围是0-4294967286,对变量不断的+1,变量的值也只是在0-4294967286-0之间变化,当值是4294967286,对变量+1,值会变成0,并不会变成4294967287,反过来也一样,对变量不断地-1,变量的值也只是在0-4294967286-0之间变化,当值是0时,对变量-1,变量的值不会变成-1,而是变成4294967286,变量的值永远不会小于0,所以i>=0,这个循环条件也会一直满足,i的值也在循环的打印。

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

strlen是库函数,是用来统计\0,之前出现多少个字符的,出现一个,就会计一个数,\0的ascii值是0,真正存入内存的是它的ascii值,也就是0,也就是看0之前出现多少个字符,但不包括0。
数组a的每个元素的类型是char类型的,每个元素的取值范围是-128-127,刚开始将-1放进去,所以接下来的顺序是-2,-3,,,-128,再减一不会是-129,而是127,接着顺序是126,,,,0,而strlen是统计0之前出现了多少字符,所以在0之前一共放进去了128+127=255个字符。

四、在向内存存放整型的补码时,存放的顺序是怎样的?

存放的顺序有两种,一种是正着存放进去,也就是原封不动,直接放进去,另外一种是倒着存放进去,就是对数据调个头。然后放进去,分别给这两种方式取的名字是大端字节序存储和小端字节序存储。字节序的意思是以字节为单位,讨论他们存放的顺序。
其实存放的顺序有好多种,补码作为一串二进制数,设置有一个字节大小的二进制数为一组,那这一串二进制数就可以分成好几组,这好几组在向内存存放的时候,有很多种顺序,但是只要记住存放的顺序,在拿出来的时候,也就是在使用的时候,因为在使用的时候,必须要拿出来才能使用,根据存放的顺序拿出来就行,但计算机要记住这些顺序太麻烦了,所以就只保留了两种存储简单的顺序,一种正着存放,一种倒着存放。
下面具体介绍这两种方式:
这里我们说内存空间的地址左到右是低地址到高地址,数据的二进制数从左到右是高位到低位,头是高位,尾是低位。
第一种: 将二进制数的低位保存在高地址的内存空间,二进制数的高位保存在低地址的内存空间,原封不动直接存放进去,这种存储形式就是正着存放的,就叫做大端字节序存储。
第二种: 将二进制数的低位保存在低地址的内存空间,二进制数的高位保存在高地址的内存空间,这是倒着存放的,就叫做小端字节序存储。
画图理解:
大端存储:
在这里插入图片描述
小端存储:
在这里插入图片描述

练习题:使用C语言判断当前机器的存储顺序是大端还是小端
这个问题其实和生活中如何判断一个人是左撇子还是右撇子是一个问题,那如何去判断这个人呢?就是让这个人左一件事,看他使用的是哪只手,就知道他是左撇子还是右撇子,所以这个问题也是一样的,要想知道机器存储顺序是是大端还是小端,就让机器,去存储一个数,再看这个数是如何存放的,如果这个数的二进制数的低位放在了他所占用的空间的第一个字节处,那他就是倒着存放进去的,就是小端存储顺序,如果这个数的低位没有放在他所占用的空间的第一个字节处,那他就是正着存放的,就是大端存储顺序。
所以我们使用了一个非常简单的数1,就可以判断出来。
具体的思路是:取出1所占用的空间的第一个字节大小的空间的地址,对地址解引用找到空间的值,如果值是1,则为小端,如果是0,则为大端。

#include<stdio.h>
int main()
{
	int a = 1;
	char* p = (char*)&a;
	if (*p == 1)
	{
		printf("小端字节序存储\n");
	}
	else if (*p == 0)
	{
		printf("大端字节序存储\n");
	}
	return 0;
}

五、整型的练习题

1、

//输出什么?
#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; }

思路:
1:向内存存放
2:打印
1: 字符型a的空间只有1个字节的大小,只能存放8位二进制数,而-1是整型,要占用4个字节的空间,要存放32位二进制数,所以只能将-1的补码的低八位存放到a里面,这种形式叫做截断。
打个比方说:要将2米的棍子放到只能存放1米的背包里面,只能将2米的棍子截断,然后放到背包里面。
2:存到里面之后,要打印有符号整型的a
打印分两步:
第一步:对a整型提升
对a整型提升,提升到整型,整型占32个比特位的空间,存放32位二进制数,而a里面只有八位,此时a的类型是有符号的字符型,所以计算机会认为a里面存放的是有符号的数,a里面放的是有符号数的补码,符号位是最高位,符号位是1,则补1,补全32位,如果a的类型是无符号的字符型,所以计算机认为里面存放的是无符号的数,这个数数的补码没有符号位,全是有效位。
第二步:打印的是有符号的,所以计算机会认为里面存放的有符号的数,补码的最高位应该是符号位,符号位是1,则里面存放的是负数,对补码取反,+1得到原码,就可以得到10进制的数,最后打印的是这个10进制的数。
画图理解:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、

#include <stdio.h>
int main()

{
    char a = -128;
    printf("%u\n",a);
    return 0; }

这里是引用
在这里插入图片描述

3、

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

这里是引用
在这里插入图片描述

4、

int i= -20;
unsigned  int  j = 10;
printf("%d\n", i+j); 

这里是引用
在这里插入图片描述

六、数据类型中的浮点型在内存中存储的是什么?

浮点数和整数在内存中的存放的是不同的,整数在内存中存放的是它的补码。
浮点数在内存中存放的是用科学计数法表示的浮点数的S,M和E。
IEEE制定了754标准,754标准规定了二进制形式的浮点数可以用科学计数法表示:(-1)^S * M * 2^E, 2代表二进制, 和10进制的科学计数法一样,如果十进制的10用科学计数法表示为:1.0*10^1,这里的10代表十进制。
(-1)^S表示的是符号位,当S=0时,浮点数是正数,当S=1时,浮点数是负数。
M表示的是有效数字,因为M是二进制数组成的,所以M的范围是[1,2)。
2^E是指数位,E是指数。
由于所以的二进制形式的浮点数都可以写成这种形式,所以只需要将S,M,E存入内存即可,再使用浮点数的时候,取出S,M,E,对这三个数拼接,就又可以得到二进制形式的浮点数。
举个例子:十进制浮点数:5.0,写成二进制浮点数:101.0,再用科学计数法表示为:(-1)^0 * 1.01 * 2^2
754标准规定了在存放这三种值时,这两种类型的空间如何划分。
对于float类型的浮点数,它占用32个比特位的空间,规定最高的1位是符号位,存放S的值,接着的8位存放E的值,剩下的23位存放M的值。
对于double类型的浮点数,它占用64个比特位的空间,规定最高的1位是符号位,存放S的值,接着的11位,存放E的值,剩下的52位存放M的值。
754标准规定了这三种值在空间的位置之后,还规定了如何存放进去。
S的值直接放就可以。
对于M的值,由于每一个浮点数的M的小数点之前都是1,则可以不用存这个1,只需要在读取这三种值的时候,最后把1添上就可以了,省出来的一个比特位,可以用来存放一位有效数字,这里可以理解为这个1是为国捐躯了,提高了精度,小数点右边的数直接存放进去,M如果没有占满它的空间,则默认补0。
对于E的值,754标准规定了E的内存空间的类型是无符号整型,所以在里面存放的E应该是无符号整数,也就是正数,但E作为指数,也有可能是负数,所以为了修正这个bug,754又规定了在存入内存时,E必须要加上一个中间值,防止是负数,float类型的浮点数的E,在存入内存时,应该加上中间值127,再转化为二进制数存入内存,double类型的浮点数的E,再存入内存时,应该加上中间值1023之后,再转化为二进制数存入内存。
754标准规定了在取的时候,如何取, 因为如果要使用这个浮点数的话,就必须要从内存中取出来,才能使用。
E代表指数,当存放到内存的E的二进制数不全为1,也不全为0,也就是既有0,也有1,那就怎么存放的,怎么拿出来就行,求出E的值,存放时,由于不能存放负数进去,所以统一+了127或者1023,所以在拿出的时候,为了得到E的真实值,则要减去127或者1023,取出M的值,在小数点的前面补上1,则最后组成的科学计数法形式的二进制数就是要使用的值,在转化成二进制的浮点数即可。
当存放到内存的E的二进制数全为0,也就是E的十进制的数是0,我们知道在存入内存时,E加了127或者1023,所以E的真实值是-127,或者是-1023,此时二进制浮点数表示出的形式:1.xxxx * 2^(-127),最后的结果是无限接近于0的数字,前面在加上正负号,也就是±无限接近于0的数,所以754标准直接规定了,E不用算了,直接等于1-127或者1-1023,得出的值就是E的真实值。取出的M,不用在小数点添1,直接添0,用来表示无限接近于0的数。
当存放到内存的E的二进制数全为1,E作为指数,就代表了浮点数是个无穷大的数。

七、浮点型的练习题

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; }

第一个printf,要使用n,就要先取出来,n的类型是整型,所以我认为内存空间里放的是整型,所以以整型的方式取出来。
第二个printf,通过解引用找到了n的内存空间,指针变量的类型是float,所以站在指针变量的角度,会认为它指向的内存空间里放的是浮点数,所以以浮点数的形式取出来,里面的二进制数是:00000000000000000000000000001001,取出的形式是按照浮点数的形式取出的,最高位是符号位,接着的8位是指数,后面的是M的值,由于指数都是0,所以我们认为浮点数是0,所以输出0.000000。
第三个printf,将9.0存入pFloat指向的内存空间里去,pFloat的类型是float类型,站在pFloat的角度,会认为它指向的内存空间要存放的值是浮点数,所以存放9.0时,以浮点数的形式向内存存入,所以存入的二进制数是:01000001000100000000000000000000,然而在取出的时候,要取出n的值,n的类型是整型,所以站在n的角度看,认为里面存放的是整型,所以取出的时候,将值看作整型取出来,整型存在内存的是补码,所以直接对二进制数求他对应的正数即可,也就是求01000001000100000000000000000000对应的正数,结果为1091567616。
第四个printf,将9.0存入内存时,*pFloat的类型是float,所以站在pFloat的角度,会认为它指向的内存空间里面放的是float类型的值,所以存放9.0时,要以浮点数的存放方式在里面存放,在取出的时候,也会以浮点数的形式取出来。

八、向内存存放时,要占用的空间大于申请的空间怎么办?

例如
char a = 10;
10是整型,它的二进制数有32位,占用4个字节的空间,char类型的变量只能向内存申请一个字节的空间,也就是只能存放8位二进制数,所以只能对10的二进制数进行截断,存10的二进制数的低八位。

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值