👦个人主页:Weraphael
✍🏻作者简介:目前正在回炉重造C语言(2023暑假)
✈️专栏:【C语言航路】
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍
目录
- 一、数据类型介绍
- 1.1 基本的内置类型
- 1.2 类型的基本归类
- 二、整型在内存中的存储
- 2.1 原码、反码、补码
- 2.2 探讨:为什么整型内存中存的是补码,而不是反码和原码?
- 三、 大小端介绍
- 3.1 经典大小端面试题
- 四、练习
- 4.1 例1
- 4.2 例2
- 4.3 例3
- 4.4 例4
- 4.5 例5
- 4.6 例6
- 4.7 例7
- 4.8 例8
- 五、浮点数在内存中的存储
- 5.1 浮点数存储规则
- 5.2 浮点数存储模型
- 5.3 特别规定
- 5.4 浮点数存储的例题
- 5.4.1 例1
- 5.4.2 例2
一、数据类型介绍
1.1 基本的内置类型
char //字符数据类型
short //短整型
int //整型
long //长整型
long long //更长的整型
float //单进度浮点型
double //双精度浮点型
类型的意义:
- 使用这个类型开辟内存空间的大小(大小决定了其使用范围)
- 看待内存空间的视角
1.2 类型的基本归类
- 整型家族
charunsigned charsigned char//后面带括号的可省略
shortunsigned short (int) signed short (int)intunsigned intsigned intlongunsigned long (int)signed long (int)long longunsigned long long (int)signed long long (int)
char
属于整型并不奇怪,因为字符在存储的时候在内存存储的是ASCII
值,因为ASCII
是整数,所以在归类的时候,字符就属于整型家族。- 不管是
long long
/long
/short
/int
+ 变量都等价于signed long long
/long /short /int
+ 变量,但注意:char
到底是signed char
还是unsigned char
完全取决于编译器,常见的char
是有符号的
- 浮点数家族:
float
double
- 构造类型(又称自定义类型):
数组类型 int[]、char[]...
结构体类型 struct
枚举类型 enum
联合类型 union
- 指针类型
int *p;
char *p;
float* p;
void* p; //无具体类型的指针
- 空类型
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型
二、整型在内存中的存储
我们之前讲过一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。那么数据在所开辟的内存空间中到底是如何存储的?
比如:
int a = 20
int b = -10;
我们知道int
需要开辟4个字节的空间,那么这4个字节的空间到底该如何使用呢?要知道这些首先必须知道什么是原码、反码、补码:
2.1 原码、反码、补码
我们再回头讨论整型在所开辟的空间中到底是如何存储的?
对于整形来说:数据在内存中存储的是二进制序列的补码。
#include <stdio.h>
int main()
{int a = 20;//整数的原码、反码、补码相同//原码:00000000 00000000 00000000 00010100//反码:00000000 00000000 00000000 00010100//补码:00000000 00000000 00000000 00010100int b = -10;//原码:10000000 00000000 00000000 00001010//反码:11111111 11111111 11111111 11110101 //符号位不变,其他位取反//补码:11111111 11111111 11111111 11110110 //反码+1return 0;
}
接着我们可以通过调试分别查看变量a
的内存和变量b
的内存:
我们发现它们是按十六进制数存储的,这是因为如果是二进制的话,显得过于太长了
接下来分别写出a和b的十六进制,我们发现它们是倒着存放的(后面大小端介绍为什么是倒着放):
#include <stdio.h>
int main()
{int a = 20;//整数的原码、反码、补码相同//原码:00000000 00000000 00000000 00010100//反码:00000000 00000000 00000000 00010100//补码:00000000 00000000 00000000 00010100//十六进制:00 00 00 14int b = -10;//原码:10000000 00000000 00000000 00001010//反码:11111111 11111111 11111111 11110101 //符号位不变,其他位取反//补码:11111111 11111111 11111111 11110110 //反码+1//十六进制:ff ff ff f6return 0;
}
2.2 探讨:为什么整型内存中存的是补码,而不是反码和原码?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
这里可以举个例子帮助大家理解:
三、 大小端介绍
- 大端:又称大端字节序存储,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。
- 小端:又称小端字节序存储,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
文字有点干巴,我画图来帮助大家理解:
假设有一个十六进制位:0x 00 11 22 33 44
,怎么知道数据的低位和高位呢?举个例子123,个位数的3就是低位,1就是高位,在上面的数据中,44就是低位,00就是高位。
【小端模式 - x86环境】
【大端模式 - x64环境】
3.1 经典大小端面试题
问:如何设计一个程序去判断当前的系统是大端还是小端呢?(请用编程实现)
思路:这里我们只要拿
1
就非常好判断,因为1
的十六进制为0x00 00 00 01
,在小端的存储模式是0x 01 00 00 00
,大端则是0x 00 00 00 01
,所以只需要判断第一个字节即可,是1
就是小端,是0
就是大端。
【代码实现】
#include <stdio.h>
int main()
{int a = 1;// char类型的指针一次只访问一个字节char* p = (char*)&a;if (*p == 1){printf("小端\n");}else{printf("大端\n");}return 0;
}
四、练习
4.1 例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;
}
【解析】
整型提升:点击跳转
4.2 例2
#include <stdio.h>
int main()
{char a = -128;printf("%u\n", a);return 0;
}
【解析】
4.3 例3
#include <stdio.h>
int main()
{char a = 128;printf("%u\n", a);return 0;
}
【解析】
4.4 例4
#include <stdio.h>
int main()
{char a = 128;printf("%u\n", a);return 0;
}
【解析】
4.5 例5
#include <stdio.h>
int main()
{int i = -20;unsigned int j = 10;printf("%d\n", i + j);return 0;
}
【解析】
4.6 例6
#include <stdio.h>
int main()
{unsigned int i;for (int i = 9; i >= 0; i--){printf("%u\n", i);}return 0;
}
【解析】
i
的类型是unsigned int
,是无符号整型,说明i
不可能为负数,因此以上代码发生死循环。
4.7 例7
#include <stdio.h>
#include <string.h>
int main()
{char a[1000];for (int i = 0; i < 1000; i++){a[i] = -1 - i;}printf("%d", strlen(a));return 0;
}
【解析】
strlen
只需计算'\0'
之前的所有字符,所以只需要找到'\0'
即可,其本质就是0
。注意:有符号的char
的取值范围:-128~127
。则-1、-2、-3...-128、127、126、125...1、0
。因此一共有127 + 128 = 225
4.8 例8
#include <stdio.h>
unsigned char i = 0;
//0~255
int main()
{for (i = 0; i <= 255; i++){printf("hello world\n");}return 0;
}
i
的类型是无符号char
,因此范围:i
的范围是0~255
,永远都不可能超过225
。所以循环里的内容恒成立,所以结果为死循环。
五、浮点数在内存中的存储
5.1 浮点数存储规则
注意:整型和浮点数在内存中的存储是截然不同的!
浮点数在计算机内部的表示方法:
任意一个二进制浮点数可以表示成下面的形式:(-1)S * M * 2E
- (-1)S表示符号位,当S = 0,浮点数为正数;当S = 1,浮点数为负数。
- M表示有效数字,其范围:大于等于1,小于2。
- 2E表示指数位
举个例子来说:
十进制的
5.0
,写成二进制是101.0
,就相当1.01×2²
。那么,按照上面的格式,就可以得出S = 0
(浮点数为正数),M = 1.01
,E
= 2。
有了S、M、E
,那浮点数在内存中又怎么表示呢?
5.2 浮点数存储模型
IEEE 754规定:
- 对于
32
位的浮点数,最高的1
位是符号位S
,接着的8
位是指数E
,剩下的23
位为有效数字M
- 对于
64
位的浮点数,最高的1
位是符号位S
,接着的11
位是指数E
,剩下的52
位为有效数字M
5.3 特别规定
注意:对于有效数字M
和指数E
,还有一些特别规定:
- 前面说过,
1≤M<2
,也就是说,M可以写成1.xxxxxx
的形式,在计算机内部保存M
时,默认这个数的第一位总是1
,因此可以被舍去,只保存后面的xxxxxx
部分。也就是说,浮点数存入内存时1.xxxxxx
中的1可以省略。比如保存1.01
的时候,只保存01
,剩下位补0。最后等到读取的时候,再把第一位的1
加上去。- 对于
E
,规定:存入内存时E
的真实值必须再加上一个中间数,对于8
位的E
,这个中间数是127
;对于11
位的E
,这个中间数是1023
。比如,210的E
是10
,所以保存成32
位浮点数时,必须保存成10+127=137
,即10001001
。等到读取的时候再减去对应的中间数。
然后,指数E
从内存中==取出==还可以再分成三种情况:
- E不全为0或不全为1
规定:指数E的计算值减去对应的中间值(127或1023),得到真实值,再将有效数字M前加上第一位的1。
比如:
0.5的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为
1.0 × 2-1,其阶码为-1+127=126
,表示为01111110
,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:00111111000000000000000000000000- E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。- E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)
5.4 浮点数存储的例题
5.4.1 例1
#include <stdio.h>
int main()
{float f = 5.5f;return 0;
}
【图解】
5.4.2 例2
#include <stdio.h>
int main()
{int n = 9;float* p = (float*)&n;printf("n的值为:%d\n", n);printf("*p的值为:%f\n", *p);*p = 9.0;printf("num的值为:%d\n", n);printf("*p的值为:%f\n", *p);return 0;
}
【程序结果】
【图解】