您现在的位置是:首页 >技术杂谈 >学C的第二十二天【深度剖析数据在内存中的存储:1. 数据类型介绍;2. 整型在内存中的存储】网站首页技术杂谈
学C的第二十二天【深度剖析数据在内存中的存储:1. 数据类型介绍;2. 整型在内存中的存储】
=========================================================================
相关代码gitee自取:C语言学习日记: 加油努力 (gitee.com)
=========================================================================
=========================================================================
1. 数据类型介绍
(1). 基本的内置类型(C语言自带类型):
char -- 字符数据类型 -- 1字节
short -- 短整型 -- 2字节
int -- 整型 -- 4字节
long -- 长整型 -- 4字节 或 8字节
sizeof(long) >= sizeof(int)
long long -- 更长的整型 -- 8字节
float -- 单精度浮点数 -- 4字节
double -- 双精度浮点数 -- 8字节
类型的意义:
1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)
2. C语言规定了:sizeof(long) >= sizeof(int),所以long的大小可以是4字节或8字节
3. 把整型分为短整型、整型和长整型的原因:有些整型数据可能比较小,使用short就够了,如:年龄。short的范围:-32768 ~ 32767。
4. 对于整型类型,还分为 有符号(signed) 和 无符号(unsigned)
(2). 类型的基本归类:
整型类型:(只有整型分有符号和无符号)
char:
unsigned char
signed char
(字符存储时,存储的是ASCII码值,是整型,所以归类时把char放在整型类型中)
(直接写成 char 是 signed char 还是 unsigned char 是不确定的,取决于编译器)
short:
unsigned short [int]
signed short [int] == short [int]
( [int]通常会省略掉 )
int:
unsigned int
signed int == int
long
unsigned long [int]
signed long [int] == long [int]
( [int]通常会省略掉 )
long long
unsigned long long [int]
signed long long [int] == long long [int]
( [int]通常会省略掉 )
signed:二进制位的最高位是 符号位,其它位都是 数值位
unsigned:二进制位的最高位也是 数值位,即所有位都是 数值位
例如(其它整型类型以此类推):
(signed char 范围是:-128~127,补码:10000000无法转换为原码,被直接当成-128)
(unsigned char 范围是:0~255,8位全是数值位,无负数)
浮点数类型:
float
double
long double
构造类型(自定义类型):
数组类型:
(数组的元素个数 和 数组的类型 发生变化时,数组类型就不一样了:)
int arr1[10]; 类型是 int [10]
int arr2[5]; 类型是 int [5]
char arr3[5]; 类型是 char [5]
(这是三个不同的数组类型)
结构体类型 struct
枚举类型 enum
联合类型 union
指针类型:
int* pi;
char* pc;
float* pf;
void* pv;
空类型:
void 表示 空类型(无类型):通常应用于函数的返回类型、函数的参数、指针类型。
2. 整型在内存中的存储
变量的创建时要在内存中开辟空间的,空间的大小是根据不同的类型而决定的
而开辟空间后,数据在所开辟内存中是如何存储的呢?
(1). 整数用二进制表示的三种表示形式:原码、反码、补码
原码:
正数:直接将数值按照正负数的形式翻译成二进制得到原码
负数:直接将数值按照正负数的形式翻译成二进制得到原码
或者
反码按位取反得到原码
再或者
补码按位取反,再+1得到原码
反码:
正数:原码、反码、补码 都相同
负数:原码的符号位不变,将其它位依次按位取反得到反码
或者
补码-1得到反码
补码:
正数:原码、反码、补码 都相同
负数:反码+1得到补码
(2). 符号位 和 数值位(整数)
上面三种表示形式都有 符号位 和 数值位 两部分:
符号位:
二进制最高位的一位叫做符号位,
符号位 用 0 表示 “正”;
符号位 用 1 表示 “负”。
数值位:
除了符号位,其它位都是数值位
对于正数:原码、反码、补码 都相同
对于负数:三种表示方法各不相同(参考上面)
(演示代码:)
#include <stdio.h> int main() { int num = 10;//创建一个叫num的整型变量,这时num向内存申请4个字节来存放数据 // 4个字节 - 32比特位 //00000000000000000000000000001010 -- 原码 //00000000000000000000000000001010 -- 反码 //00000000000000000000000000001010 -- 补码 int num2 = -10; //10000000000000000000000000001010 -- 原码 //11111111111111111111111111110101 -- 反码 //11111111111111111111111111110110 -- 补码 return 0; }
对于整型来说:数据存放在内存中其实存放的是补码
在计算机系统中,数值一律用 补码 来表示和存储。
原因在于:使用补码,可以将符号位和数值位统一处理(把符号位也看成数值位来计算);
同时,加法和减法也可以统一处理(CPU只有加法器),
此为,补码和原码相互转换,其运算过程是相同的
(原码转换为补码按位取反再+1,补码转换为原码也可以按位取反再+1),
不需要额外的硬件电路。
只有加法器,计算减法时:1 - 1 --> 1 + (-1) ,
假设计算用的是原码,算出来的是 -2,是错误的
而用补码进行计算后,再用原码表示,结果则是对的
(为什么会倒着存储呢?)
(3). 大小端介绍
字节序:
以字节为单位,讨论存储顺序(大端字节序存储 / 小端字节序存储)
低位 / 高位:
十进制:数字123,1是百位,2是十位,3是个位。这里的1就是高位,3就是低位。
十六进制:0x 11 22 33 44,这里 11 就是高位,44就是低位。
大端字节序存储:
大端(存储)模式:指数据的低位字节内容保存在内存的高地址中,而数据的高位字节内容,保存在内存的低地址中;
(低位高地址,高位低地址)
小端字节序存储:
小段(存储)模式:指数据的低位字节内容保存在内存的低地址中,而数据的高位字节内容,保存在内存的高地址中;
(低位低地址,高位高地址)
为什么有大端和小段:
一个数据只要超过一个字节,在内存中存储的时候就必然涉及到顺序的问题,所以要有大端和小端的存储模式对该数据进行排序。
为什么会有大小端模式之分,是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit 。但是在C语言中除了 8bit 的 char 之外,还有 16bit 的 short 类型,32位的 long 类型(具体要看编译器),另外,对于位数大于8位的处理器,例如 16位 或者 32位 的处理器,由于寄存器宽度大于一个字节,那么就存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式 。
例如:一个 16bit 的 short 类型 x ,在内存中的地址为 0x0010,x 的值为 0x1122,那么 0x11 为高字节,0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即地址 0x0010 中, 0x22 放在高地址中,即地址 0x0011 中。小端模式则相反。
我们常用的 x86 结构是小端模式,所以上面的图数据会“倒着放”,低位字节放在了低地址,高位字节放在了高地址。而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择为大端模式还是小端模式。
(4). 写个程序判断大小端:
思路:
有变量a,存放在内存中的十六进制数为:01 00 00 00(小端存储),地址第一位是1,
如果是大端存储:则应该是:00 00 00 01,地址第一位是0,
可以把 a 的地址取出第一位,如果
第一位地址 == 1,说明是小端存储,
第一位地址 == 0,说明是大端存储,
取出 int类型a 的 地址 第一位方法:
*(char*)&a
把 int* 强制转换为 char*,再解引用,即可取出一位地址的内容。
实现代码:
#include <stdio.h> int check_sys() { int a = 1; //要大于一个字节的数据,才有顺序可言 判断大小端 //if (*(char*)&a == 1) // //把int*强制转换a的地址为char*再解引用,判断地址第一位的内容 //{ // return 1; //} //else //{ // return 0; //} //可以直接写成 return *(char*)&a; } int main() { int ret = check_sys(); if (ret == 1) { printf("小端 "); } else { printf("大端 "); } return 0; }
(5). 练习:(重点在注释)
1. 区别 unsigned 和 signed :
//练习:1.区别 unsigned 和 signed #include <stdio.h> int main() { char a = -1; //char 在 VS中 默认是 signed char //10000000000000000000000000000001 -- 原码 //11111111111111111111111111111110 -- 反码 //11111111111111111111111111111111 -- 补码 // -1是整数,存进char类型中会发生截断 // 11111111 -- 补码,截断获取最低8位 // 最高位 是 符号位 signed char b = -1; //和 char a 相同 unsigned char c = -1; // 11111111 -- 补码,截断获取最低8位 // 最高位 是 数值位 printf("a=%d,b=%d,c=%d", a, b, c); //%d - 十进制形式打印有符号整型数据, //这里会发生 整型提升 // 11111111 -- 补码,截断获取最低8位 // // 整型提升,有符号位按符号位补满,补满后: //11111111111111111111111111111111 -- 整型提升后补码 //11111111111111111111111111111110 -- 反码 //10000000000000000000000000000001 -- 原码 // // 整型提升,无符号位高位补0,补满后: //00000000000000000000000011111111 -- 整型提升后补码 // 整数原码、反码、补码相同 return 0; }
2. 使用 %u 打印 有符号整型:
(补充:%u -- 十进制形式打印无符号的整型)
(1). 打印 -128:
//练习:2.使用 %u 打印 有符号整型: #include <stdio.h> int main() { char a = -128; //10000000000000000000000010000000 -- 原码 //11111111111111111111111101111111 -- 反码 //11111111111111111111111110000000 -- 补码 // 截断后: // 10000000 -- 截断后补码 printf("%u ", a); // %u -- 十进制形式打印无符号的整型 // 对 char 变量 打印 整型数字,进行整型提升 // char类型 有符号位,按符号位补满: //11111111111111111111111110000000 -- 补满后的补码 //因为是以 无符号整数 打印,所以 原码、反码、补码 相同 // 那么这个数就很大了 return 0; }
(2). 打印 128:
//练习:3.使用 %u 打印 有符号整型: #include <stdio.h> int main() { // -128 改成 128 char a = 128; //00000000000000000000000010000000 -- 原码 //11111111111111111111111101111111 -- 反码 //11111111111111111111111110000000 -- 补码 // 截断后: // 10000000 -- 截断后补码 // 跟-128是一样的, //只是原码的符号位不一样,但截断后都是10000000 printf("%u ", a); // %u -- 十进制形式打印无符号的整型 // 对 char 变量 打印 整型数字,进行整型提升 // char类型 有符号位,按符号位补满: //11111111111111111111111110000000 -- 补满后的补码 //因为是以 无符号整数 打印,所以 原码、反码、补码 相同 // 那么这个数就很大了 return 0; }
3. 用%d打印:有符号整型 + 无符号整型
//练习:3. 用%d打印:有符号整型 + 无符号整型 #include <stdio.h> int main() { //有符号整型: int i = -20; //10000000000000000000000000010100 -- 原码 //11111111111111111111111111101011 -- 反码 //11111111111111111111111111101100 -- 补码 //无符号整型: unsigned int j = 10; //00000000000000000000000000001010 -- 原码 // 原码、反码、补码 相同 printf("%d ", i + j); // i的补码 和 j的补码 相加 //11111111111111111111111111101100 -- i的补码 // + //00000000000000000000000000001010 -- j的补码 // = //11111111111111111111111111110110 -- 两补码相加后的补码 //该补码再通过%d打印有符号数,最高位是符号位,知道补码,要计算得到原码 //11111111111111111111111111110101 -- 反码 // 反码 符号位 不变,其它位 按位取反 //10000000000000000000000000001010 -- 原码 -》 -10 return 0; }
4. 使用无符号整数(无负数)进行自减循环
//使用无符号整数(无负数)进行自减循环 #include <stdio.h> #include <windows.h> int main() { unsigned int i; for (i = 9; i >= 0; i--) { printf("%u ", i); Sleep(1000); //单位是毫秒,休眠1秒再继续下个语句 } return 0; }
5. 字符数组存储整型数字:
//字符数组存储整型数字: #include <stdio.h> #include <string.h> int main() { char a[1000]; //char类型数组,整型数字只能存储0~-128 int i; for (i = 0; i < 1000; i++) { a[i] = -1 - i;//-1,-2...... } //-1,-2,-3...-128,127,126,...3,2,1,0 -- 存一轮:256个元素 //-1,-2,-3...-128,127,126,...3,2,1,0 -- 存一轮:256个元素 //。。。。。 printf("%d", strlen(a)); //strlen 是求字符串长度的, //统计的是 之前出现的字符的个数 // 的ASCII码值是 0,找到0就停止计算 return 0; }
6. unsigned char 取值范围:
//unsigned char 取值范围: #include <stdio.h> unsigned char i = 0;//全局变量 //unsigned char 的整型取值范围是:0~255 int main() { // 255 再 +1 又变成 0 for (i = 0; i <= 255; i++) { printf("hello world "); } return 0; }