9.数据存储【C语言】

1.数据类型

基本的内置类型

char        //字符数据类型
short       //短整型
int         //整形
long        //长整型
long long   //更长的整形  C99才提供
float       //单精度浮点数
double      //双精度浮点数
//C语言有没有字符串类型?

long long是C99标准才引入的,某些旧的编译器无法支持的
int在16位机器下是2个字节(早期)
c语言只规定了sizeof(long)>=sizeof(int)

#include <stdio.h>
int main()
{
    printf("%d\n", sizeof(int)); 4
 	printf("%d\n", sizeof(long)); 4
	return 0;
}

类型种类参考

布尔类型(C99引入)
_Bool 表示真假

需要引入对应头文件#include <stdbool.h>

//0表示假
//非0表示真
int main()
{
	int flag = 0;
	if (flag)
	{

	}
	return 0;
}
#include <stdbool.h>
int main()
{
	_Bool flag = true;
	if (flag)
	{
		printf("hehe\n");
	}
	return 0;
}

c语言中可以完全没有_Bool
转到定义可以发现

#define false 0
#define true 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]
int a = 10;默认是signed int
unsigned int age = 20;
short ==> signed short (int)
long ==> signed long (int)
int可以省略不写
    
而char到底是 signed char 还是 unsigned char 取决于编译器
常见的编译器下:char ==> signed char

%d打印有符号数
%u打印无符号数

unsigned 要配合%u来使用

int num = 10;
printf("%d\n", num);
num = -10;
printf("%d\n", num);

unsigned int num = 10;
printf("%u\n", num);

num = -10;
printf("%u\n", num);//很大的一个数

浮点数家族

float
double

构造类型

也叫自定义类型

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

int a[10];类型为int [10]
int b[5]; 类型为int [5]

指针类型

int* pi;
char* pc;
float* pf;
void* pv;

空类型

void 表示空类型
通常应用于函数的返回类型、函数的参数、指针类型

2.整型在内存中的存储

数据在内存中以2进制的形式存储
内存中存的是2进制,但在调试器的内存窗口中是以16进制的形式进行展示,以便节省空间

最右边的是将内存中的数据解析成文本内容展示,参考意义不大

image-20220113211428861

整数:

整数二进制有3种表示形式:原码 反码 补码
正整数三码相同
原码:按照一个数的正负,直接写出来的二进制就是原码
反码:符号位不变,其他位按位取反
补码:反码+1

最高位是符号位,后面的是有效位
符号位为1表示负数,0表示正数

-10
10000000000000000000000000001010
11111111111111111111111111110101
11111111111111111111111111110110
ff ff ff f6
因此内存中展示的就是ff ff ff f6,但需要倒着存放
f6 ff ff ff

为什么存的是补码呢

cpu只能做加法 不能做减法
减法是由加法模拟
乘法就是多个数相加

1-1 =1+(-1)
如果用原码去计算 算出来的是-2 结果有误
00000000000000000000000000000001
10000000000000000000000000000001
10000000000000000000000000000010
用补码计算
00000000000000000000000000000001
11111111111111111111111111111111
100000000000000000000000000000000只有32,高位舍去
00000000000000000000000000000000
结果才正确
使用补码,可以将符号位和数值域统一处理
此外,"补码与原码相互转换",运算过程是相同的,
不需要额外的硬件电路
对补码再次取反加1,又能得到原来的原码
11111111111111111111111111111111
10000000000000000000000000000000
10000000000000000000000000000001

使用补码,可以将符号位和数值域统一处理

大小端

image-20220113211530745
为什么是倒着存的呢?

大端字节序:
把数据的低位字节序的内容存放在高地址处,高位字节序的内容存放在低地址处
小端字节序:
把数据的低位字节序的内容存放在低地址处,高位字节序的内容存放在高地址处

地址从左到右是由低到高的
0x11223344 44是低字节序(类比个位十位百位千位)
地址从左到右(从上到下)增大

0x11223344 放到内存中是44332211就是小端存储

image-20220113212654764

为什么出现大小端存储?

存在如何安排多个字节存储的问题,32位,64位

image-20220113213202692

练习

1.大小端

请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。

#include <stdio.h>
int check_sys()
{
    int i = 1;
    return (*(char *)&i);
}
int main()
{
    int ret = check_sys();
    if(ret == 1)
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
    return 0;
}
//&i访问的int* 要强制转换成char* 来访问第一个字节

char类型数据没有大小端问题
大小端是字节序,2个及以上字节才有
char类型数据只有1个字节

image-20220113213301977

2.截断

//输出什么?
#include <stdio.h>
int main()
{
   char a= -1;//一个整数放到1个字节里面必然会j
   signed char b=-1;
   unsigned char c=-1;
   printf("a=%d,b=%d,c=%d",a,b,c);//-1 -1 255
   return 0;
}
char a = -1;
10000000000000000000000000000001   原码
11111111111111111111111111111110   反码
11111111111111111111111111111111   补码
补码存到a里面去,发生截断,只截断最低的8个bit位
a里面放的是 11111111
char a 与signed char b等价(VS编译器)
c里面放的也是 11111111
-1是整数,a是char类型,会发生截断
%d格式打印,a 是char类型,也就是signed char"发生整型提升",高位补符号位
11111111111111111111111111111111  整型提升之后的补码
打印打的是原码
10000000000000000000000000000000
10000000000000000000000000000001      -1
11111111
c是unsigned char
c也要发生整型提升,"无符号高位补符号位"
00000000000000000000000011111111   补码/反码/原码
正数3码相同
以%d格式打印,认为内存中存的是有符号数
2^8 - 1 = 255

3.%u打印负数

#include <stdio.h>
int main()
{
   char a = -128;
   printf("%u\n",a);//4294967168
   return 0;
}
-128
10000000000000000000000010000000   -128原码
11111111111111111111111101111111
11111111111111111111111110000000 
存到 char a中会发生截断 10000000
整型提升后
11111111111111111111111110000000%u打印,认为内存中存的是无符号数
三码相同
转换为10进制:4294967168

4.%u打印正数

char a = 128;%u打印
00000000000000000000000010000000
截断后10000000放到a里面
整型提升
11111111111111111111111110000000
%u无符号数打印
转换为10进制:4294967168

5.正负相加

int i= -20;
unsigned  int  j = 10;
printf("%d\n", i+j);
//按照补码的形式进行运算,最后格式化成为有符号整数
(不用再去考虑把 int 转换为 unsigned int 再计算,内存中肯定是会转换的)
10000000000000000000000000010100
11111111111111111111111111101011
11111111111111111111111111101100  -20补码
    
00000000000000000000000000001010   10补码
    
11111111111111111111111111110110    加完之后的补码
11111111111111111111111111110101
10000000000000000000000000001010    -10  原码

image-20220113215416591

6.unsigned int i循环变量

#include <stdio.h>
#include<Windows.h>
int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--) 
	{
		printf("%u\n", i);
		Sleep(1000);//为了看得更清楚
	}
	return 0;
}

死循环了

image-20220113215653878

无符号整数i恒大于0的
一旦变成-1,-1放到unsigned int里面
11111111111111111111111111111111
每一位都是有效位
再以%u打印
对应十进制4294967295
2 ^ 32 - 1

7.char取值范围

int main()
{
   char a[1000];//没有初始化,里面都是随机值
   int i;
   for(i=0; i<1000; i++)
  {
       a[i] = -1-i;
  }
   printf("%d",strlen(a));//strlen遇到0会停止  255
    //strlen计算长度不包括\0
   return 0;
}

image-20220113215955889

char数组最多储存 2 ^ 8 - 1个元素
signed char 1byte = 8bit
unsigned char 1byte = 8bit

内存中放的是补码
如果是无符号char取值范围是0 ~ 255
有符号的char -128 ~ 127//10000000直接解析成-128
11111111  补码
11111110
10000001    -1(原码)

10000001   补码
10000000
11111111   -127(原码)
10000000直接解析成-128
110000000		-128  9101111111
110000000     -补码
取完8位也还是 10000000

image-20220113220148584

11111111 + 1 就变成 00000000

image-20220113220538940

又strlen遇到\0才停止故刚好转一圈停下来255个数
\0的ASCII值也就是0

image-20220113220932656

巧记:超出范围的数据如果是正数则减去256,如果是负数则加上256
128减去256 = -128

signed short 16 bit
00000000 00000000   0
00000000 00000001   
00000000 00000010    
01111111 11111111    32767
10000000 00000000   -32768
...
11111111 11111110    -2
11111111 11111111   -1

signed short 取值范围就是 -32768~32767 2的15次方是32768

unsigned short 则是 0 ~ 65536 (2 的 16次方) 16个1

#include<limits.h>中有数据类型的取值范围

image-20220113220736514

8.无符号变量

#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
i<=255恒成立

使用无符号变量要小心!!!一不注意就死循环了

3.浮点型在内存中的存储

常见浮点数

3.14159
1E10 科学计数法1.0 * 10^10
浮点数表示的范围:float.h中定义

image-20220113221317815

存储规则

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

(-1)^S * M * 2^E
(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,"大于等于1,小于2。"
2^E表示指数位。
十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 
(-1)^S * M * 2^E
s=0,M=1.01,E=2
    
十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 
s=1,M=1.01,E=2
十进制5.5   101.1     1.011 x 2 ^ 2(二进制)
-1 ^ 0 x 1.011 x 2 ^ 2
    
123.4 = 1.234 x 10 ^ 2
* 2 ^ 2  等价于小数点右移2

float32bit位
最高位当符号位 1/0
(-1)^S * M * 2^E

image-20220114115918962

double 64个bit位

image-20220114120552093

1≤ M <2 ,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被"舍去",只保存后面的xxxxxx部分。
比如保存 1.01的时候,只保存 01,等到读取的时候,再把第一位的 1加上去。
这样做的目的,是节省1位有效数字,提高精度
以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
至于指数E,情况就比较复杂。
首先,"E为一个无符号整数"unsigned int)
这意味着,如果E为8位,它的取值范围为0~255;
如果E为11位,它的取值范围为0~2047。
    
但是科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须"再加上一个中间数",这样就能处理负数了
对于8位的E,这个中间数是127;
对于11位的E,这个中间数是10232^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,对应二进制为10001001

image-20220114120434862

从内存中取出指数E

1.E不全为0或不全为1
指数E的值减去 127/1023 得到真实值
再将有效数字M前加上第一位的1
2.E全为0
真实的E是 1-127
±1.xxx * 2^(-)127  无限接近于0的数
浮点数的指数E等于1-127(或者1-1023)即为真实值
有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数
表示±0,以及接近于0的很小的数字
3.E全为1
±1.xxxx * 2^(128)
表示±∞

例子

int main()
{
    int n = 9;
    float *pFloat = (float *)&n;
    printf("n的值为:%d\n",n);//9
    printf("*pFloat的值为:%f\n",*pFloat);//0.000000
    *pFloat = 9.0;
    printf("num的值为:%d\n",n);//1091567616
    printf("*pFloat的值为:%f\n",*pFloat);//9.000000
    return 0;
}
//充分证明了浮点数与整数在内存总的存储形式不同

n的值为:9
*pFloat的值为:0.000000
num的值为:1091567616
*pFloat的值为:9.000000

以%f或%lf格式打印,默认小数点后有6位

整型的9放进去,%d打印出来还是9
整型的形式放进去,浮点型方式拿出来不一样
浮点数形式放进去,%d拿出来不一样
浮点数形式放进去,浮点数形式拿出来一样

9的三码相同
00000000000000000000000000001001
9放到n里面去
pFloat指针变量,4个字节
整型的9放进去,%d打印出来还是9
整型的形式放进去,浮点型方式拿出来
站在浮点数的视角去看9
0  00000000  00000000000000000001001
S   E              M
E为全0 E的真实值1-127 = -126
2^(-126)
0.00000000000000000001001

(-1)^0 * 0.00000000000000000001001 * 2^(-126)
所以打印出来是0
*pFloat = 9.0;
printf("num的值为:%d\n",n);
1001.0  --二进制
1.001 * 2 ^ 3
9.0是正数  S=0
E=3  要存3+127进去 130 也就是 10000010
有效数字M等于 001 后面再加200,凑满230 10000010 00100000000000000000000
S  E           M
0x 41 10 00 00在内存中也是小端存储
    00 00 10 41
最后%d打印出来,最高位为0  三码相同
01000001000100000000000000000000
1091567616
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值