数据结构|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; }

数据结构|data_structural(数据结构)- 重置版
文章图片

数据结构|data_structural(数据结构)- 重置版
文章图片

?
在计算机系统中(内存中),整数 一律 用补码 来 表示 和 存储,原因在于,使用补码,可以将符号位和数值域 统一处理。同时,加法 和 减法 也可以 统一处理(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 . 无符号整数:
原 反 补 三码相同

?
大小端的概念 数据结构|data_structural(数据结构)- 重置版
文章图片

那可不可以用其它方式 存储呢?例如 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; }

数据结构|data_structural(数据结构)- 重置版
文章图片

?
例题 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; }

附图 数据结构|data_structural(数据结构)- 重置版
文章图片

?
例题 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; }

数据结构|data_structural(数据结构)- 重置版
文章图片

?
浮点型在内存中的存储 程序一:
#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 表示指数位
?
例子 数据结构|data_structural(数据结构)- 重置版
文章图片

?
IEEE 754规定: 对于 32位 的浮点数,最高的 1 位 是 符号位 S,接着的8位是指数E,剩下的 23 位为 有效数字M 数据结构|data_structural(数据结构)- 重置版
文章图片

?
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

数据结构|data_structural(数据结构)- 重置版
文章图片

因为 有效数字(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; }

浮点型与 整形 互相转换流程 数据结构|data_structural(数据结构)- 重置版
文章图片

本文结束

    推荐阅读