1.C语言中的数据类型
C语言提供了许多数据类型,使程序员能够有效地管理和操作数据。大致可做以下划分:
这些类型的数据是怎么储存在内存中的呢?这篇文章先介绍整形数据在内存中的存储。
2.整形数据在内存中的存储
2.1原码、反码与补码
数据在内存中以二进制形式储存,这种形式有三种:原码,反码和补码。
三种表示方法均有符号位和数值位两部分,从左到右第一位是符号位, 都是用0表示“正”,用1表示“负”,而数值位三种表示方法各不相同。
【原码】
原码是直接将二进制按照正负数的形式翻译成二进制。如有一个整形变量a=5,由于5为正数,符号位(最高位)为0,所以它的原码即为:
00000000000000000000000000000101
而假如有一个整形变量b = -5,符号位为1,所以它的原码即为:
10000000000000000000000000000101
【反码】
对于正数,反码与原码相同;对于负数:反码是将原码的符号位不变,其他位依次按位取反就可以得到了。如a的反码为:
00000000000000000000000000000101
b的反码为:
1111111111111111111111111111111111010
【补码】
对于正数,补码与原码也相同;对于负数,对应反码+1得到补码。如a的补码为:
00000000000000000000000000000101
b的反码为:
1111111111111111111111111111111111011
对于负整数,三者转换关系如图:
可是为什么要引入补码?
这与硬件实现相关。例如,我们要计算1 - 1时, 由于CPU只有加法器,1 - 1需要改写为1 + (-1),如果使用原码运算:可见得到的结果是-2,这显然不正确。
1的原码 00000001
-1的原码 10000001
两者的原码相加 ----> 10000010 ----> -2
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
2.2大小端序介绍
在计算机系统中,数据可以按照不同的字节顺序存储,主要有大端序(Big Endian)和小端序(Little Endian)两种。
大端(存储)模式 :是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式 :是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
具体系统采取哪种顺序储存,取决于硬件。
要注意:数据都是从较低位置开始向高位置存储的,这里的顺序是指的在存储一个整形数据时,数据的低位和高位在已经划分好的一片整形数据内存空间内的存储顺序。
我们也可以写一个小程序判断当前环境采用的哪一种储存顺序。
#include<stdio.h>
int main()
{
int a = 1;
if (*(char*)&a == 1)
printf("小端");
else
printf("大端");
return 0
}
代码实现原理:
我们已知a的补码为:
00000000000000000000000000000001
数据高位 数据低位
如果系统采用小端模式,示意图如图:
如果系统采用大端模式,示意图如图:
由此可见,在a=1的情况下,采用大小端的区别就在于第一个字节是0还是1.
&a取出a的首地址,类型是int*, 强制类型转换为char*, 再解引用访问第一个字节,如果是1则判断为小端模式,反之。
3.练习题
下面程序输出什么?
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;
}
正确答案:a=-1,b=-1,c=255
解析:
a的原码10000000000000000000000000000001
a的反码1111111111111111111111111111111111110
a的补码1111111111111111111111111111111111111
由于a是char类型, 所以需要截断 得到 11111111
输出格式为%d(即以十进制形式打印有符号的整形)所以需要整形提升(这里是char类型,有符号,说明高位是符号位,所以提升时补符号位1) 得到1111111111111111111111111111111111111
再转化出原码:10000000000000000000000000000001 ,由于%d,最高位表示符号,得到 -1
signed char与 char情况相同。
对于unsigned char, 无符号,其最高位不表示符号, 所以在整形提升时补的是0,得到0000000000000000000000000111111111,由于%d,最高位表示符号,得到255。
P.S.整形提升
定义:
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长 度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。
方法:
按照变量的数据类型的符号位来提升,即变量的类型(是否带符号如signed char 和 unsigned char)决定提升时补充的数字,有符号补充符号位,无符号补充0。
2.十进制形式打印无符号的整形
#include<stdio.h>
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}
正确答案:4294967168
解析:
a的原码100000000000000000000000010000000
a的反码11111111111111111111111111111011111111
a的补码1111111111111111111111111111110000000
截断得到10000000,整形提升,由于a是char类型有符号,补1,得到
11111111111111111111111110000000
由于以%u格式输出(十进制形式打印无符号的整形),所以最高位不代表符号, 即本身就是原码,得到4294967168
3.数据类型的范围
#include<stdio.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
正确答案:255
解析:
理论上循环执行1000次,a数组分别存入:
-1 -2 -3 -4 ... ... -999 -1000
但由于char类型的存储范围为:-127 ~ 127,所以实际上a数组存入的是:
-1 -2 -3 -4 ... ... -128 127 126 125 ... ... 3 2 1 0 ... ...
由于strlen统计的是到'\0'的字符串长度,'\0'的ASCII码为0, 即最后的长度只有255。