数据结构|data_structural(数据结构)- 重置版
数据类型: 分为两类 1 .是 c 语言本身具有的类型 , 又称内置类型
char字符类型1字节
short短整形2字节
int整形4字节
long长整形4/8字节
long long更长的整形8字节
float单精度浮点数4字节
double 双精度浮点数8字节
?
2 .自定义类型(构造类型) 类型的意义:
1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)
2. 如何看待内存空间的视角
?
整形家族
char
unsigned char
signed charshort
unsigned short (int)
signed short (int)int
unsigned int
signed intlong
unsigned long (int)
signed long (int)
?
浮点型家族
float
double
?
构造类型
数组类型 array
结构体类型 struct
枚举类型 enum
联合类型 union
?
指针类型
整形指针 int* pi;
字符指针 char* pc;
浮点型指针float* pf;
无类型指针(万能指针) void* pv;
【只能接收,不能使用,非要使用的话,需要强制类型转化】
?
空类型
1 .void 表示空类型(无类型)2 .应用于函数的返回类型,函数的参数,指针类型
实例
#include
void test()// void 在这里 表示 函数 test 无返回值
{printf("hello!\n");
}int main()
{test();
return 0;
}
【数据结构|data_structural(数据结构)- 重置版】?
整形在内存中存储 原码 反码 补码
在计算机中的整形(正,负)有符号数有三种表示方法: 原码反码补码(以二进制码表示)
三种方法均有 符号位 和 数值位 两部分,符号位(二进制最高位)都是 0 表示正, 1 表示 负
而数值位三种表示方法各有不同原码: 直接将 整形数据 按照 正负的形式 翻译成二进制就可以。反码: 将原码的符号位不变,其他位依次按位取反就可以得到了补码: 反码+1注意 正整数的 原 反 补 三码都是一样的
?
实例(二进制位最高位,是符号位, 0 为正,1 为负)
vs 环境
#include
int main()
{int a = 20;
// 00 00 00 14整数数据 的 表示 和 存储 都是 用 补码
a 为正数,三码相同
//0000 0000 0000 0000 0000 0000 0001 0100原码, a是正整数。所以三码相同
//0000 0000 0000 0000 0000 0000 0001 0100反码原码的符号位不变,其他位按位取反
//0000 0000 0000 0000 0000 0000 0001 0100补码反码加一,整形数据存入(a)内存中的是补码
//0000001(16)4十六进制(逢16 进 1)
// 存入内存之后,表示为 14 00 00 00 (小端存储模式:低位字节内容存储于低地址,高位字节内容存储于高位地址)int b = -10;
// 0x ff ff ff f8整数数据 的 表示 和 存储 都是 用 补码
// 1000 0000 0000 0000 0000 0000 0000 1010 原码
// 1111 1111 1111 1111 1111 1111 1111 0101 反码原码的符号位不变,其他位按位取反
// 1111 1111 1111 1111 1111 1111 1111 0110 补码反码加一,整形数据存入(b)内存中的是补码
//fffffff6十六进制 0x ff ff ff f6
//存入内存之后,表示为 f6 ff ff ff (小端存储模式:低位字节内容存储于低地址,高位字节内容存储于高位地址)return 0;
}
文章图片
文章图片
?
在计算机系统中(内存中),整数 一律 用补码 来 表示 和 存储,原因在于,使用补码,可以将符号位和数值域 统一处理。同时,加法 和 减法 也可以 统一处理(CPU只有加法器)此外,补码与原码互转换,其运算过程相同的,不需要额外的硬件电脑。 例子
#include
int main()
{1 - 1;
// 上表达式 在 CPU 看来 就是 1 + ( -1 ) ,因为CPU只有加法器
如果 我们 使用 原码,来进行计算,会如何?
//00000000 00000000 00000000 00000001 // 1原码
//10000000 00000000 00000000 00000001 //-1 原码
//10000000 00000000 00000000 00000010 //-2 原码相加,很明显 用 原码 进行运算是错的,1 - 1 != -2 ,//00000000 00000000 00000000 00000001 // 1补码
//11111111 11111111 11111111 11111111 // -1 补码
//00000000 00000000 00000000 00000000/ /0两补码相加,很明显 用 补码 进行运算 是对的,1 - 1 == 0
return 0;
}
?
整数 (整形数据 一律用补码来 表示 和 存储): 1 . 有符号数 :
正数 ( 原 反 补 三码相同 )
负数 ( 原码 -> 原码 符号位 不变,其余 按位取反 -> 反码 -> 加1 -> 补码[ )
?
2 . 无符号整数:
原 反 补 三码相同
?
大小端的概念
文章图片
那可不可以用其它方式 存储呢?例如 11 33 22 44, 44 22 33 11等等…
答案是可以的。 只要能想到的存储方式都可以(但是请注意:怎么放进去的,怎么拿出来)
为了方便存入和读取数据
所以选取 的是 正着存(小端), 倒着存(大端)
另外十六进制数 11 22 33 44 每两个占一字节 也可以说 按照 字节的顺序 存储方式
即:
大端(储存)模式,是指 数据的 低位 保存在 内存 的 高地址 中,而数据的高位,保存在内存的低地址中
又称:大端字节序(储存) 模式
小端(储存)模式:是指 数据的 低位 保存在 内存 的 低地址 中,而数据的高位,保存在内存的高地址中
又称:小端字节序(储存)模式
?
大小端模式意义:
因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节 8 bit。
但是C语言中除了 8 bit 的 char 之外,还有 16 bit 的 short,32 bit 的 long 型(要看具体的编译器)。
对于位数大于 8 位 的 处理器,例如 16位 或 32 位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个 多个字节安排 的 问题。
因此 大端 存储模式 和 小端 存储模式,就有存在的必要了
实例
#include
int main()
{int a = 20;
// 内存情况:14 00 00 00 (小端)
//0000 0000 0000 0000 0000 0000 0001 0100原码, a是正整数。所以三码相同
//0000 0000 0000 0000 0000 0000 0001 0100反码
//0000 0000 0000 0000 0000 0000 0001 0100补码存入a的是补码
//0000001(16)4十六进制0x 00 00 00 14return 0;
}
?
案例1(写一段代码 告诉我们当前机器的字节序是什么):
写法 1
vs 环境
#include
int main()
{int a = 1;
// 01 00 00 00 (小端存储模式)
char* p = (char*)&a;
// 将 a是整形,取出其地址的类型也是 int。想要存入 字符指针变量 p 中,需要 将 a 的 地址转换成 字符类型(地址的值是不变的)
if (*p == 1)
因为 指针变量p 是 字符类型的,对其解引用,只能访问,p 存的地址 所指向的 空间中 一个字节 的 内容。(访问 是从 低~高位地址开始访问的,
所以第一个访问到 低位第一个数据( 1 byte 的内容【01】,因为需要和 1进行比较,所以需要整形提升,根据最高位进行补位【最高位为0,补零,直至32bit位,即 00 00 00 01 】,其值转换成十进制数,肯定是等于1的){
只要等于1,就说明 该编译器的存储模式为 小端存储模式(小端字节序存储模式)
printf("小端\n");
//所以输出这条语句
}
else
{
如果不等于 1,那肯定就是 大端存储模式了
printf("大端\n");
}
return 0;
}
?
写法2
#include
int check_sys()
{写法1
int a = 1;
return *(char*)&a;
写法2
//char* p = (char*)&a;
//return *p;
返回 1 小端
返回 0大端
}
指针类型决定了指针解引用操作符一次能访问多少个字节,char* p ; *p可以访问一个字节 ,int*p;*p可以访问 4 个字节int main()
{int ret = check_sys();
返回 1 小端
返回 0 大端
if (ret == 1)
{printf("小端\n");
}
else
{printf("大端\n");
}
return 0;
}
文章图片
?
例题 1
vs 环境
#include
int main()
{char a = -1;
100000000000000000000000 00000001-1 的原码
111111111111111111111111 11111110-1 的反码
111111111111111111111111 11111111-1 的 补码
// a 为 有符号字符类型数据;存的是补码第8位(数据截断,截取低位数据 是因为 是小端存储模式);‘’
//打印 a 之前需要整形提升按照符号位补充 根据最高位补位,最高位是 1 补位 就是 32个1,就是 -1 的 补码,
//打印的是原码,所以需要转换成原码, 即 : 10000000 00000000 00000000 00000001
// a 输出的是 -1
signed char b = -1;
//有符号;存的补码8个1(截断);//打印之前需要整形提升(算术逻辑)按照符号位补充 ,
//即 32 个 1.打印的是原码 10000000 00000000 00000000 00000001 即 -1unsigned char c = -1;
// 无符号数 说明无符号数进行整形提升直接补0, 而 -1 的补码位8个1(截断 );
//将其打印之前需要整形提升:00000000 00000000 00000000 11111111将其转化为十进制 即 255printf("a = %d,b = %d,c = %d ", a, b, c);
// -1,-1,255return 0;
}
?
例题 2
#include
int main()
{char a = -128;
//10000000 00000000 00000000 1000 0000;
原码
//11111111 11111111 11111111 0111 1111反码
//11111111 11111111 11111111 1000 0000补码
// 1000 0000 截断printf("%u\n",a);
//打印之前,整形提升(有符号数,根据最高位【1】进行补位):11111111 11111111 11111111 1000 0000
// %u :打印无符号,所以三码相同,直接 将其 二进制转换为 十进制数,进行输出(且 结果恒大于0【非负数】)
// 输出为 4294967168
return 0;
}
?
例题 3
有符号和无符号相加
#include
int main()
{int i = -20;
//10000000 00000000 00000000 0001 0100原码
//11111111 11111111 11111110 1110 1011反码
//11111111 11111111 11111111 1110 1100 补码
unsigned int j = 10;
// 无符号数 三码相同
//00000000 00000000 00000000 0000 1010 原 反 补 码 三码相同//00000000 00000000 00000000 0000 1010j 的 补码
+
//11111111 11111111 11111111 1110 1100i 的 补码
==
//11111111 11111111 11111111 1111 0110结果(补码)
//10000000 00000000 00000000 0000 1001 结果(反码)【补码除了符号位,其余位取反】
//10000000 00000000 00000000 0000 1010 结果(原码) 【补码+1】
上下两者方法皆可
11111111 11111111 11111111 1111 0101反码==补码-1;
10000000 00000000 00000000 0000 1010原码==反码【除符号位,其余位 按位取反】printf("%d\n",i+j);
// 打印是打印原码
10000000 00000000 00000000 0000 1010 转化成时间至数 就是 10
return 0;
}
?
例题 4
#include
int main()
{unsigned int i;
for (i = 9;
i >= 0;
i--)
{printf("%u\n", i);
// 死循环 i为无符号数,所以 i 永远 不可能 小于 0
}
return 0;
}
?
例题 5
#include
int main()
{char a[1000];
int i;
for (i = 0;
i < 1000;
i++)
{a[i] = -1 - i;
}
printf("%d", strlen(a));
因为数据是char类型,有符号范围 -128~127,但是有很多数比 255 要大的数据,造成值大了放不下
所以要把这1000个数 转化为 -128~127,才能放进 字符数组当中
就是说 第128个元素是 -128,第129个元素是 -127以此类推,第 256个元素 就是0,第257个元素是1,按照这种模式,不停的循环赋值,直到赋完1000个元素的值strlen 计算 元素个数,照理来说 是 一千个元素,但是由于 char 的取值特性,它的值当中有一个 1个0, 而'\0'的是ASCII码值 就是 0
,所以 他计算的值 不肯能是 1000 个
详情见下方附图 ,它是顺时针转, 等它转到0 == '\0'时,停下。即 第256个元素 0 == '\0' 会停止计数(strlen 遇到'\0',停止计数,'\0'不被计入),即输出 255【strlen 只计算到255个元素就停止计数了】
return 0;
}
附图
文章图片
?
例题 6
#include
unsigned char i = 0;
// 无符号char 范围 0~255
int main()
{for (i = 0;
i <= 255;
i++)//恒满足,所以死循环, 与 案例5的性质 是一样的 第256个元素为 0,然后开始递增,循此往复。
{printf("hello world!\n");
}
return 0;
}
文章图片
?
浮点型在内存中的存储 程序一:
#include
intmain()
{double d = 1E10;
//1E10: 1 乘以 10 的 10 次方
printf("%lf\n", d);
//10000000000.000000
return 0;
}
?
程序二:
#include
intmain()
{int n = 9;
float* pfloat = (float*)&n;
//强制转化类型
printf("n的值为:%d\n",n);
//n的值为:9
printf("pfloat的值:%f\n",*pfloat);
//pfloat的值:0.000000小数点后面默认6个0
整形存入,整形拿出,没问题;浮点型拿出就有问题*pfloat = 9.0;
printf("n的值为:%d\n", n);
// n的值为:1091567616
printf("pfloat的值:%f\n", *pfloat);
//pfloat的值:9.000000
浮点型存入,浮点型拿出,没问题;整形拿出就有问题由上表达式 得出结论: 整形与浮点型 在内存中存储的方式不同return 0;
}
n 和 *pfloat 是同一个数,为什么浮点数和整数的解读结果会差别这么大?
所以一定要搞懂浮点数在计算机内部的表示方法。
根据国际标准 IEEE(电气和电子工程协会) 754(IEEE 754这是一个标准,规定浮点型在内存中如何存储),任何一个二进制浮点数 V 可以表示下面的形式: (-1) ^ S * M * 2 ^ E
(-1) ^ S 表示 符号位,当 S = 0,V 为正数;当 S = 1,V 为负数( -1 的0次方是1,-1的1次方是 -1 )
M 表示有效数字,大于等于1,小于2.
2^E 表示指数位
?
例子
文章图片
?
IEEE 754规定: 对于 32位 的浮点数,最高的 1 位 是 符号位 S,接着的8位是指数E,剩下的 23 位为 有效数字M
文章图片
?
IEEE 754 对 有效数字 M 和 指数 E ,还有一些特别规定,前面说过, 1 <= M < 2,也就是说,M可以写成1.xxxxxx的形式(其中 xxxxxx 表示小数部分) IEEE 754规定,在计算机内部保存 M 时,默认这个数的第一位总是 1 ,因此可以被舍去
只保存后面的 xxxxxx 部分:比如 保存 1.01,只保存 01。等到读取的时候,再把第一位的 1 加上去。
这样 可以提升一位的精度,对于 32位 来说,等于可以保存 24 位 有效数字
指数E
首先,E 为一个 无符号整数(unsigned int)这意味着,如果 E 为 8 位,它的取值范围为 0~255;如果 E 为 11 位,它的取值范围为 0~2047.
但是,我们知道,科学计数法中的 E 是可以出现负数的,所以 IEEE 754规定,存入内存时 E的 真实值 必须加上一个中间数。对于 8 位的 E,这个 中间数 是 127,对于 11 位的 E,这个中间数是 1023.
比如,
2 ^ 10的 E 是 10,所以保存 32 位 浮点数 时,必须 保存成 10 + 127 = 137,即 1000 1001
?
再来看一个小点
小数 0.5= 0.1 (二进制表示形式) 0 -> 2^0 (1*0 == 0);
1 -> 2 ^ (-1) 等于 (1 * 1/2)= 0.5
文章图片
因为 有效数字(M),要大于等于1,小于2【1 <= M < 2】,
所以: 1 <= M < 2 所以 0.1 的 小数点 右移一位,即 1.0 ^ (-1)
故 (-1) ^ S * M * 2 ^ E == (-1) * 0 * 1.0 * 2 ^ (-1)
S== 0
M == 1.0
E == -1
存入内存时 E,必须 真实值(-1)加上一个中间数(127)。对于 8 位的 E,这个 中间数 是 127
所以 -1 + 127 = 126也就是真正存入 E 的值(存储值)是 126 (转化二进制) 0111 1110,
如果想知道 E 的 真实值 就减去 中间值127。 即126 - 127 = -1
?
例题
#include
int main()
{float f = 5.5;
// 将 浮点数 5.5 转换成 二进制浮点数, 即: 101.1,又因为 1 <= M < 2,小数点向左移动 2 位,故 1.011 * 2^2
因为 5.5 > 0,所以 S == 0
表达式最终为(-1) ^ 0 *1.011 * 2^2
S == 0
M == 1.011// IEEE 754规定,在计算机内部保存 M 时,默认这个数的第一位总是 1 ,因此可以被舍去,只保存小数点后面的 011 部分
E == 2-> 2+127 = 129 -> 10000001单精度浮点数存储模型SEEEEEEEEMMMMMMMM MMMMMMMM MMMM MMM
S ( 1 bit)E( 8 bit)M ( 23 bit )
01000000101100000 00000000 0000 000// 011 后面直接补零0100 0000 1011 0000 0000 0000 0000 0000
40b(11)00000即 0x 40 b0 00 00十六进制数
内存形式 00 00 b0 40(小端)
return 0;
}
?
指数 E 从内存中取出还可以分三种情况: 1. E 不全为0 或 不全为1
这时,浮点数就采用下面的规则表示,即指数 E 的计算值(二进制数转化十进制数) 减去 127(32位)/ 1023(64位) 得到真实值。
再将有效数字 M 前加上一位的 1 ,比如 0.5( 1/2 )的二进制形式为1. 0* 2^(-1),由于规定正数部分必须为 1.即将小数点右移1位,即为 1.0 * 2^(-1),其 阶码(E) 为 -1 + 127 = 126,转化为二进制数为 0111 1110
而 1.0 去掉整数部分还剩一个0 ,(后补)补齐 0 到 23 位 00000000 00000000 0000 000则其二进制表示形式为
0 01111110 00000000000000000000000
?
2 . E全为0 —0 【 00000000 (E)】 01100000000000000000000 等价于 (正负) ± 0.011 * 2 ^(-126) 正负无限接近 0 的数字【正负 是因为 (-1)的S方】
当 E 全为 0 时,浮点数的指数 E 等于1-127【-126】(或者1-1023 【-1022】)即为E 的 存储值,
有效数字 M不在加上第一位的1(因为加上了,等于没加,一个 1点几 的数,乘以 2 的 126次方之一【2^-126】,它是一个无限接近0的数)
直接0.011 * 2^(-126),还原出来的数字,是无限接近于 0 的数字,加上正负,就是正负无穷接近于 0 的数字而是还原为 0. xx xx xx 的小数(保留 小数点后六位),这样做是为了表示 +-(正负) 0,以及接近于 0 的很小的数字。
?
3 . E 全为 1 ---- 0 { 111111111 } 01100000000000000000000
E 的存储值为 255,【E+ 127 = 255】
E 的真实值255 - 127 == 128
(正负+-)1,xxx*2 ^ 128
这时,如果 有效数字M 全为 0(1,0 * 2^128),表示+-(正负)无穷大的数字(正负 取决于 符号位 【(-1)^S 】 )
?
实例
#include
intmain()
{int n = 9;
// 00000000 00000000 00000000 0001001补码
float* pfloat = (float*)&n;
//强制转化类型
printf("n的值为:%d\n",n);
//n的值为:9
printf("pfloat的值:%f\n",*pfloat);
//pfloat的值:0.000000小数点后面默认6个0单精度浮点数存储模型SEEEEEEEEMMMMMMMM MMMMMMMM MMMM MMM
S ( 1 bit)E( 8 bit)M ( 23 bit )
9 的 补码为0000000000000000000000000001001
符合上述情况 2E全为零
// 即 0.000000 0000000000001001 * 2 ^ -126是一个正负无限接近于 0 的数字,浮点型数据 默认 保留 小数点后面 6 位*pfloat = 9.0;
// 1001.0
//-1 ^ 0 * 1.001 * 2 ^ 3E= 3 + 127 =130【10000010】
// 0 10000010 001 00000000000000000000
//正数 2 ^ 24 +2 ^ 20 + 2 ^ 30== 1,091,567,616
printf("n的值为:%d\n", n);
// n的值为:1091567616
printf("pfloat的值:%f\n", *pfloat);
//pfloat的值:9.000000
return 0;
}
浮点型与 整形 互相转换流程
文章图片
本文结束
推荐阅读
- 数据库总结语句
- 《数据结构与算法之美》——队列
- vue组件中为何data必须是一个函数()
- R|R for data Science(六)(readr 进行数据导入)
- 笔记|C语言数据结构——二叉树的顺序存储和二叉树的遍历
- 运行报错Cannot|运行报错Cannot find module '@babel/compat-data/corejs3-shipped-proposals’
- Java深入了解数据结构之栈与队列的详解
- Java集合框架|Java集合框架 数据结构
- 数据结构与算法|【算法】力扣第 266场周赛
- 数据结构和算法|LeetCode 的正确使用方式