数据类型介绍
前面我们已经学习了基本的内置类型:
char //字符数据类型 1字节 打印%cshort //短整型 2字节 打印%hdint //整形 4字节 打印%dlong =long int //长整型 4/8字节 打印%ldlong long =long long int //更长的整形 8字节 打印%lldfloat //单精度浮点数 4字节 打印%fdouble //双精度浮点数 8字节 打印%lfsizeof(long)>=sizeof(int)
整形打印无符号数将格式中的d换成u就行
类型的基本归类
整形家族:charunsigned charsigned charshortunsigned short [int]signed short [int]intunsigned intsigned intlongunsigned long [int]signed long [int]long long
浮点数家族:floatdoublelong double
构造类型:> 数组类型> 结构体类型 struct> 枚举类型 enum> 联合类型 union
指针类型int *pi;char *pc;float* pf;void* pv;结构体指针等等
整形在内存中的存储
我们之前讲过一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。那接下来我们谈谈数据在所开辟内存中到底是如何存储的?比如:int a = 20;int b = -10;我们知道为 a 分配四个字节的空间。那如何存储?下来了解下面的概念:
原码直接将数值按照正负数的形式翻译成二进制就可以得到原码。反码将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码反码+1就得到补码。
下边我们看一个数据存储的例子就好理解了
可以看到,数据在内存是补码存储的,并且是倒着存储的,本质上内存中存放的是2进制,为了方便显示,我们看到的都是16进制的数字(不要害怕不同进制的数字,本质上进制只是表示数字的一种形式)为什么是这样存储的呢接下来我带大家看一下
讲大小端之前我先说明一下原码反码补码的关系(整数原码,反码,补码相同,负数如下)原码-》补码,先除了符号位按位取反,再+1补码-》原码,先-1,再除了符号位按位取反
我们测试一下这个数据我们知道数据在内存是从低地址向高地址存储的一个数据的低位字节如果存放在内存的低地址处,代表是小端存储,反之是大端存储
有一道笔试题百度2015年系统工程师笔试题:请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)//小端返回1 //大端返回0 int check_sys() {int a = 1;return *(char*)&a; }int main() {int ret = check_sys();if(ret == 1)cout<<"小端"<<endl;elsecout << "大端" << endl;return 0; }
思路是什么呢
我们用char* 强转a的地址,解引用一个字节,如果拿到的是1,代表是小端存储,图上清晰明了很简单
整形家族有这么多类型,我们怎么判断数据在内存的存储呢(注意,数据在内存中一定是以补码存储的,我们计算数据也是计算补码)在vs上我们认为没有unsigned就是有符号类型,有unsigned就是无符号类型,有符号类型第一位存储符号位(正数是0,负数是1),无符号类型每一位都是数值位,一个字节等于8比特位,具体的计算过程上图很明显了(注意:有符号类型第一位符号位不参与计算,只算数值位,所以相对无符号类型的最大值会小一半)
有符号数最高位是1整型提升高位补充符号位1,最高位是0整型提升高位补充符号位0
无符号数整型提升高位补0
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);//-1 -1 255return 0;}先算出补码,后截断,再根据类型整型提升,最后打印(以有符号整形打印需要看最高位,是1说明是负数,需要转化为原码,是0说明是正数,正数原反补相同,直接打印的就是原码,以无符号打印直接补码就是原码(不需要关心符号位,因为无符号数没有符号位,所有位都是数值位)
#include <stdio.h>
int main()
{char a = -1;//10000000000000000000000000000001//11111111111111111111111111111110//11111111111111111111111111111111-截断//11111111 -a//11111111111111111111111111111111//11111111111111111111111111111110//10000000000000000000000000000001--> -1signed char b = -1;//11111111111111111111111111111111//11111111 -bunsigned char c = -1;//11111111 -c//00000000000000000000000011111111//printf("a=%d,b=%d,c=%d", a, b, c);//%d - 十进制的形式打印有符号整型整数//整型提升return 0;
}
2.#include <stdio.h>int main(){char a = -128;printf("%u\n",a);//%u是以十进制形式打印无符号整数return 0;}
//#include <stdio.h>
//int main()
//{
// char a = -128;
// //-128
// //10000000000000000000000010000000//原码
// //11111111111111111111111101111111//反码
// //11111111111111111111111110000000//补码
// //-128的补码
// //10000000//截断
// //11111111111111111111111110000000//有符号最高位补1,进行整型提升
// //
// printf("%u\n", a);
// return 0;
//}
打印42亿多
3.#include <stdio.h>int main(){char a = 128;printf("%u\n",a);return 0;}//这道题跟上边的题结果一样因为截断处都是1000 0000,都是有符号数,以无符号形式打印,所以结果都一样
4.int i= -20;unsigned int j = 10;printf("%d\n", i+j);//按照补码的形式进行运算,最后格式化成为有符号整数//int main() //{ // int i = -20; // //10000000000000000000000000010100 // //11111111111111111111111111101011 // //11111111111111111111111111101100//-20的补码 // // // unsigned int j = 10; // //00000000000000000000000000001010//10的补码 // //11111111111111111111111111101100 // //11111111111111111111111111110110//相加和是补码,要以有符号形式打印,符号位是1是负数,补码要转化为原码进行打印 // //11111111111111111111111111110101 // //10000000000000000000000000001010 -10//转化为原码// printf("%d\n", i + j);//-10 // return 0; //} //
5.unsigned int i;for(i = 9; i >= 0; i--){printf("%u\n",i);}因为i是无符号整形,数据范围是0到42亿多,所以0--之后会是42亿多(-1的补码是32位全1,所以是42亿多)
6.int main(){char a[1000];int i;for(i=0; i<1000; i++){a[i] = -1-i;}printf("%d",strlen(a));return 0;}char是有符号char,最多表示-128到127,所以数组里边只会存连续相同的,-1到-128,再从127到0,这是一轮循环,strlen函数是判断长度,到\0结束,因为\0的ascll码值是0,所以strlen到第一轮0就会结束,0之前是255个字符,所以打印255
7. #include <stdio.h> unsigned char i = 0; int main() {for(i = 0;i<=255;i++){printf("hello world\n");}return 0; }
这个就很简单了,无符号char范围是0~255,只要进入循环i永远小于255,所以死循环
浮点型在内存中的存储
常见的浮点数:3.141591E10浮点数家族包括: float、double、long double 类型。浮点数表示的范围:float.h中定义
浮点数存储的例子:#include<iostream> using namespace std; #include <stdio.h>int main() {int n = 9;////0 00000000 00000000000000000001001//S E M//0 -126 0.00000000000000000001001//(-1)^0 * 0.00000000000000000001001 * 2^-126////E在内存中是全0//float* pFloat = (float*)&n;printf("n的值为:%d\n", n);//9printf("*pFloat的值为:%f\n", *pFloat);//0.000000*pFloat = 9.0;//1001.0//1.001 * 2^3//(-1)^0 * 1.001 * 2^3//S=0 E=3 M=1.001//0 10000010 00100000000000000000000//printf("num的值为:%d\n", n);//1091567616printf("*pFloat的值为:%f\n", *pFloat);//9.0return 0; }
先把这个代码放在这,现在先描述一下浮点数如何在内存存储,再返回来看这道题
第一个是float,第二个是double
下边有点长,我把关键部分加粗了,理解起来轻松些
浮点数存储规则num 和 *pFloat 在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?要理解这个结果,一定要搞懂浮点数在计算机内部的表示方法。详细解读:根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:(-1)^S * M * 2^E(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。M表示有效数字,大于等于1,小于2。2^E表示指数位。举例来说:十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。那么,按照上面V的格式,可以得出S=0,M=1.01,E=2。IEEE 754 对有效数字 M 和指数 E ,还有一些特别规定。前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。 (提高数字的精度,意思就是说M只存小数位,不够补0)至于指数 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,即10001001。然后,指数E从内存中取出还可以再分成三种情况:E 不全为 0 或不全为 1(绝大多数情况,意思就是说怎么放进去怎么拿出来),(放进去+127/1023,拿出来-127/1023)这时,浮点数就采用下面的规则表示, 即指数 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,则其二进制表示形式为:00111111000000000000000000000000E全为0(说明放进去之前是0-127/1023,表示为2^-127/2^-1023,是一个很小的值,可以忽略不计了)(这种情况了解一下就行)这时, 浮点数的指数 E等于1-127(或者1-1023 )即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0 ,以近于0的很小的数字。E 全为 1(说明放进去之前是127/,反正是一个很大的值,也不做重点讨论,了解)这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);好了,关于浮点数的表示规则,就说到这里。
#include <stdio.h> int main() {float f = 5.5;//101.1是2进制的5.5//1.011 * 2^2,将2进制类比位10进制进行表示很简单//(-1)^0 *1.011 * 2^2//将对应的数值填入表达式中//S = 0//M = 1.011//E = 2//0100 0000 1011 0000 0000000000000000//0x40b00000return 0; }
对照上边的浮点数存储模型,5.5是单精度浮点数,对照单精度存储结构,S=0,M=1.011,E=2
S第一个位置就填0,E为2放进去要加127是129,二进制是10000001 ,小数点后边有三位是011,M有23比特位,011后边全补0二进制序列就没毛病了,上边我写的也有
换算成16进制是0x40b00000
可以很清晰的看到内存中是小端存储,我们算的是对的,只要按照这个算法99%的浮点数我们都会算。
理解完了这道题,我们还要再看一下刚才那道题
注意,我们以整形存进去并且以整形往出拿,和以浮点型存进去并且以浮点型往出拿的结果都很单纯,以整形存进去但以浮点数往出拿和以浮点数存进去以整形往出拿的结果大大不同。
#include<iostream> using namespace std; #include <stdio.h>int main() {int n = 9;////0 00000000 00000000000000000001001//S E M//0 -126 0.00000000000000000001001//(-1)^0 * 0.00000000000000000001001 * 2^-126////E在内存中是全0//float* pFloat = (float*)&n;printf("n的值为:%d\n", n);//9printf("*pFloat的值为:%f\n", *pFloat);//0.000000*pFloat = 9.0;//1001.0//1.001 * 2^3//(-1)^0 * 1.001 * 2^3//S=0 E=3 M=1.001//0 10000010 00100000000000000000000//printf("num的值为:%d\n", n);//1091567616printf("*pFloat的值为:%f\n", *pFloat);//9.0return 0; }
整数n在内存中正常存储,原码反码补码相同,注意,我们计算的永远是内存中的补码,打印打的是内存中的原码,当把整数int类型以浮点数形式往出拿的时候,根据国际标准IEEE(电气和电子工程协会) 754标准,将内存以SME表示出来,S是0,E是全0,我们刚才说过E是全0,说明我们拿出来是E是-126,M就是0.00000000000000000001001。可以看到,只要根据公式计算,就很简单。(结果是(-1)^0 * 0.00000000000000000001001 * 2^-126 打印结果是0.00000000,一个很小的数,符合我们的预期)
当我们以浮点数的形式存进去,以整数形式拿出来,9.0二进制表示为1001.0,科学计数法是1.001*2^3,根据IEEE协会标准算出来S E M分别是0,3,1.001,再以刚才的规则将其填进内存,最后以有符号形式打印,符号位是0,是正数,以有符号整数打印,正数的原码反码补码相同,将其以原码形式打印出来就是10亿这个数字。很简单吧
以上就是我对数据类型以及数据存储的介绍,感谢支持!!!以后会创作更多的文章