数据类型: 分为两类
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 char
short
unsigned short (int)
signed short (int)
int
unsigned int
signed int
long
unsigned long (int)
signed long (int)
?
浮点型家族
float
double
?
构造类型
数组类型 array
结构体类型 struct
枚举类型 enum
联合类型 union
?
指针类型
整形指针 int* pi;
字符指针 char* pc;
浮点型指针float* pf;
无类型指针(万能指针) void* pv; 【只能接收,不能使用,非要使用的话,需要强制类型转化】
?
空类型
1 .void 表示空类型(无类型)
2 .应用于函数的返回类型,函数的参数,指针类型
实例
#include<stdio.h>
void test()
{
printf("hello!\n");
}
int main()
{
test();
return 0;
}
?
整形在内存中存储
原码 反码 补码
在计算机中的 整形(正,负)有符号数 有三种表示方法: 原码 反码 补码(以二进制码表示)
三种方法均有 符号位 和 数值位 两部分,符号位(二进制最高位)都是 0 表示正, 1 表示 负
而数值位三种表示方法各有不同
原码: 直接将 整形数据 按照 正负的形式 翻译成二进制就可以。
反码: 将原码的符号位不变,其他位依次按位取反就可以得到了
补码: 反码+1
注意 正整数的 原 反 补 三码都是一样的
?
实例(二进制位最高位,是符号位, 0 为正,1 为负)
vs 环境
#include<stdio.h>
int main()
{
int a = 20;
a 为正数,三码相同
int b = -10;
return 0;
}
?
在计算机系统中(内存中),整数 一律 用补码 来 表示 和 存储,原因在于,使用补码,可以将符号位和数值域 统一处理。同时,加法 和 减法 也可以 统一处理(CPU只有加法器)此外,补码与原码互转换,其运算过程相同的,不需要额外的硬件电脑。
例子
#include<stdio.h>
int main()
{
1 - 1;
如果 我们 使用 原码,来进行计算,会如何?
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<stdio.h>
int main()
{
int a = 20;
return 0;
}
?
案例1(写一段代码 告诉我们当前机器的字节序是什么):
写法 1
vs 环境
#include<stdio.h>
int main()
{
int a = 1;
char* p = (char*)&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<stdio.h>
int check_sys()
{
写法1
int a = 1;
return *(char*)&a;
写法2
返回 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<stdio.h>
int main()
{
char a = -1;
100000000000000000000000 00000001 -1 的原码
111111111111111111111111 11111110 -1 的反码
111111111111111111111111 11111111 -1 的 补码
signed char b = -1;
unsigned char c = -1;
printf("a = %d,b = %d,c = %d ", a, b, c);
return 0;
}
?
例题 2
#include<stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);
return 0;
}
?
例题 3
有符号和无符号相加
#include<stdio.h>
int main()
{
int i = -20;
unsigned int j = 10;
+
==
上下两者方法皆可
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<stdio.h>
int main()
{
unsigned int i;
for (i = 9; i >= 0; i--)
{
printf("%u\n", i);
}
return 0;
}
?
例题 5
#include<stdio.h>
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<stdio.h>
unsigned char i = 0;
int main()
{
for (i = 0; i <= 255;i++)
{
printf("hello world!\n");
}
return 0;
}
?
浮点型在内存中的存储
程序一:
#include<stdio.h>
int main()
{
double d = 1E10;
printf("%lf\n", d);
return 0;
}
?
程序二:
#include<stdio.h>
int main()
{
int n = 9;
float* pfloat = (float*)&n;
printf("n的值为:%d\n",n);
printf("pfloat的值:%f\n",*pfloat);
整形存入,整形拿出,没问题;浮点型拿出就有问题
*pfloat = 9.0;
printf("n的值为:%d\n", n);
printf("pfloat的值:%f\n", *pfloat);
浮点型存入,浮点型拿出,没问题;整形拿出就有问题
由上表达式 得出结论: 整形与浮点型 在内存中存储的方式不同
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<stdio.h>
int main()
{
float f = 5.5;
因为 5.5 > 0,所以 S == 0
表达式最终为 (-1) ^ 0 *1.011 * 2^2
S == 0
M == 1.011
E == 2 -> 2+127 = 129 -> 10000001
单精度浮点数存储模型
S EEEEEEEE MMMMMMMM MMMMMMMM MMMM MMM
S ( 1 bit) E( 8 bit) M ( 23 bit )
0 10000001 01100000 00000000 0000 000
0100 0000 1011 0000 0000 0000 0000 0000
4 0 b(11) 0 0 0 0 0 即 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<stdio.h>
int main()
{
int n = 9;
float* pfloat = (float*)&n;
printf("n的值为:%d\n",n);
printf("pfloat的值:%f\n",*pfloat);
单精度浮点数存储模型
S EEEEEEEE MMMMMMMM MMMMMMMM MMMM MMM
S ( 1 bit) E( 8 bit) M ( 23 bit )
9 的 补码为 0 00000000 0000000000000000001001
符合上述情况 2 E全为零
*pfloat = 9.0;
printf("n的值为:%d\n", n);
printf("pfloat的值:%f\n", *pfloat);
return 0;
}
浮点型与 整形 互相转换流程
本文结束
|