数据的基本介绍
在 C 语言中,变量的类型决定了变量开辟内存空间的大小,如何看待内存空间的视角。
整数类型
类型 | 存储大小 | 值范围 |
---|---|---|
char | 1 字节 | -128 到 127 或 0 到 255 |
unsigned char | 1 字节 | 0 到 255 |
signed char | 1 字节 | -128 到 127 |
int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295 |
short | 2 字节 | -32,768 到 32,767 |
unsigned short | 2 字节 | 0 到 65,535 |
long | 4 字节 | -2,147,483,648 到 2,147,483,647 |
unsigned long | 4 字节 | 0 到 4,294,967,295 |
易错点:
■ 当给无符号对象赋一个负值,结果=(这个负数+无符号类型所表示总个数)%无符号类型所表示总个数。
■ 如果表达式中既有无符号又有有符号类型,带符号数自动转成无符号数。
例子:
//例一:
//错误:变量u永远也不会小于0,循环条件一直成立。
for(unsigned u=10;u>=0;u--){
printf("%d\n",u);
}
//例二:
#include <stdio.h>
int i;
int main()
{
i--;
if (i > sizeof(i))
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
//输出 > 注:当sizeof(i)的返回值是无符号类型,int为有符号类型,所以当i为一个很大的整数大于4,因此i>sizeof(i)成立。
浮点类型
类型 | 存储大小 | 值范围 | 精度 |
---|---|---|---|
float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位小数 |
double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位小数 |
long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位小数 |
void 类型
序号 | 类型与描述 |
---|---|
1 | 函数返回为空 C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status); |
2 | 函数参数为空 C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void); |
3 | 指针指向 void 类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。 |
注意:各种类型的存储大小与系统位数有关。
整形在内存中的存储
原码、反码、补码
对于整数在内存中的存储参见我另外一篇博客,应该是讲清楚了计算机在内存中为什么都会统一使用补码来存储整数。博客链接:彻底理解原码、补码、反码
大小端介绍
为什么有大小端之分?
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
■ 大端存储:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。
■ 小端存储:是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
解释:
数据的高低位:数据的高低是相对的,十位相对于个位是高位,相对于百位又是地位。个十百千万数据位数依次增高。在计算机中数据的存储是以二进制存储的,比如10(0000 0000 0000 0000 0000 0000 0000 1010)从左向右数据位数依次减小。
练习
练习一:
//设计一个小程序来判断当前机器的字节序。
int main()
{
int a=1;
char *p=&a;
if(*p){
printf("小端\n");
}else{
printf("大端\n");
}
return 0;
}
练习二:
//CPU(32位小字节序处理器)
#include<stdio.h>
void main()
{
union
{
short k;
char i[2];
}*s, a;
s = &a;
s->i[0] = 0x39;
s->i[1] = 0x38;
printf("% d\n", a.k);
}
//输出结果是?
练习三
int main() {
char a = -128; //1000 0000
printf("%u\n",a);//变为无符号类型输出,类型提升到了unsigned int
//原码:1000 0000 0000 0000 0000 0000 1000 0000
//反码:1111 1111 1111 1111 1111 1111 0111 1111
//补码:1111 1111 1111 1111 1111 1111 1000 0000
//结果为:4294967168
return 0;
}
练习四
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;//-1 -2 -3 ... -128 127 126 125 ... 2 1 0 因此长度为 255
}
printf("%d", strlen(a));//255
return 0;
}
#include <stdio.h>
unsigned char i = 0;
int main()
{
//死循环,将一直输出,因为无符号的范围为 0-255,这个条件将一直成立
for (i = 0; i <= 255; i++)
{
printf("hello world\n");
}
return 0;
}
浮点型在内存中的存储
常见的浮点数:3.14159 1E10 浮点数家族包括: float、double、long double 类型。那么浮点数在内存中是怎样存储的呢?
根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:(-1)^S * M * 2^E
■(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
■M表示有效数字:1<=M<2。
■2^E表示指数位。
IEEE 754规定: 对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。(1 8 23规则)
IEEE 754规定: 对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。(1 11 52规则)
IEEE 754对有效数字M和指数E,还有一些特别规定。
对有效数字M:
1≤M<2 ,也就是说,M可以写成1.xxxxxx 的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
对指数E情况比较复杂分三种情况:
说明:E为一个无符号整数 这意味着,如果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,即10001001。
E不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。 比如: 0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为1.0*2^(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为。
0 01111110 00000000000000000000000
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)。
举例说明:
在计算机中所有数字都是按照二进制存储的,所以我们在要将浮点数转换为二进制,转换规则。
■先看符号位当s=0,V为正数;当s=1,V为负数。
■将V的整数部分先转化为二进制形式,再将小数部分转化为二进制形式(转化规则:小数部分乘以2,取整数部分依次从左往右放在小数点后,直至小数点后为0)。
■将V转换的整数部分和小数部分写在一起,写为科学计数法1.xxxxxxx*2^n的形式。
■M=小数点后的二进制,s根据上面三种情况求得,对于float中E不全为0或不全为1一般形式E=n+127所得的二进制。
对于数字10.125:为正数S=0。10(1010),0.125(0.001),1010.001=1.010001*2^3 。因此E=3+127=130(10000010)。M=010001。所以10.125的计算机内存中的二进制表示法:
0 10000010 01000100000000000000000 (41 22 00 00)
注:我的计算机是小端存储所以是00 00 22 41。
【例子】
int main()
{
int n = 9;// 0 000 0000 0 000 0000 0000 0000 0000 1001
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);//注: E为全0,符合全0的情况所以V=(-1)^0*2^(1-127)*1.001 一个几乎接近0的数
*pFloat = 9.0;//注:9.0 -> 1001.0 ->(-1)^0*1.001*2^3 -> s=0, M=1.001,E=3+127=130
printf("num的值为:%d\n", n);//写为二进制为:0 10000010 001 0000 0000 0000 0000 0000->1091567616
printf("*pFloat的值为:%f\n", *pFloat);
return 0;
}
觉得写的不错的小伙伴记得三连啊!!!