900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > C语言基础09——数据在内存中的存储。整型的存储 大小端讲解 浮点数的存储 杨辉三

C语言基础09——数据在内存中的存储。整型的存储 大小端讲解 浮点数的存储 杨辉三

时间:2020-09-11 01:26:46

相关推荐

C语言基础09——数据在内存中的存储。整型的存储 大小端讲解 浮点数的存储 杨辉三

目录

数据类型

基本内置类型

类型的基本分类

整型在内存中的存储

计算机中整数的三种表示方法:原码、反码、补码

大小端

练习

浮点型在内存中的存储

为什么以下程序输出结果与想象不同?

浮点数存储规则

练习

printf与scanf输入输出格式总结:

%c 用于打印字符

%s 字符串

%d 用于有符号十进制整数

%u 用于无符号十进制整数

%f float浮点型

%lf double浮点型

%x 无符号十六进制整数1122aabb(小写)

%X 无符号十六进制整数1122AABB(大写)

%p 指针地址

%ld 用于long long

%o 无符号八进制

%e 科学计数法输出(小写),一般用于浮点数的打印

%E 科学计数法输出(大写)

输出精度总结:

在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做四舍五入的。

printf("%.3f\n",-0.0049);//-0.005//在计算机内部,无法明确的表明-0.0049。它输出的数是一个离-0.0049很近的一个数,然后用这个数来表达。printf("%.15f\n",-0.0049);//-0.00489999999999999984printf("%.3f\n",-0.000493);//-0.00

八进制和十六进制

/** 一个字面量,以0开头就是八进制;以0x开头就是十六进制。* - 4位二进制可以表示1位十六进制。所以2位十六进制,就可以表示8个二进制位。* 也就是2位十六进制,可以表示一个字节(1字节=8bit位=8个二进制位)的数据。* - 3位二进制可以表示1位八进制。* 因为早起的计算机字长是12的倍数,所以那个时候用八进制来表示会比较合适。*/int main(){printf("%d\n",0123);//83printf("%d\n",0x123);//291printf("%x",255);//ffreturn 0;}

数据类型

基本内置类型

变量的创建是要在内存中开辟空间的。空间的大小根据不同的类型而定。

char//字符数据类型 大小:1个字节short//短整型 2个字节int//整型 4个字节long//长整型 4个字节long long //更长的整型8个字节float//单精度浮点数4个字节double//双精度浮点数8个字节

类型的基本分类

整型

charunsigned charsigned charshort unsigned short [int]signed short [int]intunsigned intsigned intlongunsigned long [int]signed long [int]

这么多整型,我们在使用的时候应该如何选择呢?

为什么有这么多整数呢?

​ 为了准确表达内存,做底层程序的需要

如何选择呢?——没有特殊需要,就选择int类型

现在CPU字长普遍是32/64位,一次内存读写就是一个int,一次计算也是一个int,选择短的类型,不会更快,甚至可能更慢。现在的编译器一般会设计内存对齐,所以更短的类型实际在内存中也有可能占据一个int类型大小(不是类型本身占据,而是浪费掉的)。虽然sizeof告诉你它很小。但是因为内存对齐的原因,更短的类型与下一个类型之间可能存在浪费的空间。

unsigned无符号类型,只是输出的将其当作无符号来输出,其在内存中存储并不受影响。内存本身是怎么计算怎么存储的,不会受unsigned的影响。

浮点数

floatdouble

构造类型

- 数组- struct 结构体类型- enum 枚举类型- union 联合类型

指针类型

char * pc;int * pi;float * pf;cvoid * pv;......

空类型

void表示空类型(无类型),通常用于函数返回类型、函数参数、指针类型

- 函数返回类型void test();- 函数参数void test(void);- 指针void * p;

整型在内存中的存储

char、short、int 等取值范围在limits.h头文件中定义常见浮点数:- 3.141592- 1E10 ,表示:1.0*10的10次方

为什么最高位是符号位呢?

/** 如果是十进制计算,那么我们有一个"-"来表示负数。那么在计算机中是如何进行表示的呢?* 一个字节可以表达的数:00000000~11111111 ,可以表示0~255,一共256个数字。* 三种方案:* 1.仿照十进制:有一个特殊的标志表示负数。* 这种方式,需要额外的一位来标志正负数。* 2.取中间数为0,如10000000表示0(也就是128所在位置表示0),比它小的是负数,比它大的是正数。* 这种方式,我们在得到一个数之后,都要对128个数做减法,才能得到这个数的正确结果。* 3.补码。* - 考虑-1,我们希望-1+1=0,如何做到?*0->00000000 1->00000001怎么样表示-1,然后让其与1相加就可以得到0呢?* - 我们发现11111111+000000001=1 00000000 ,进位之后,发生截断,只取后八位就得到了0。*或者使用0-1,0向前一位借1,然后做减法:1 00000000 - 000000001 = 11111111* - 11111111被当作二进制解析时,是:255*而如果11111111是一个补码,那么就是-1了。* - 我们发现,使用补码的话,不需要再借位,而是数据本身就可以表达其正负。*//** 有符号数* - 二进制的00000000~11111111,转换为十进制就是:0~255,一共有256个数字(包括0)。* 如果这256个数字,一半是正数,一半是负数,那么就是:0~127 与-1~-128。* - 如果是8位二进制,其范围就是:-2的(8-1)次方~2的(8-1)次方-1, 因为负数与正数之间还个0。* 如果是n位二进制,则其范围就是:-2的(n-1)次方~2的(n-1)次方-1* - 整数越界。如00000000~11111111这组范围内。其被分为整数和负数。* 01111111表示127,127+1本来应该是表示128,但是其超过了正数范围,也就是说10000000表示-128。* 也就是从00000000~11111111表示的是:0 1 2 3 ... 126 127 -128 -127 ... -3 -2 -1* 正数一直加,超出了正数范围就成为该范围内最小的负数。负数也一直加,超出了其负数范围,就成为0..1..2** - 如果是-128,其原码应该是:1 10000000,但实际上使用10000000表示了-128,为什么呢?* 把1000 0000看作原码,应该是表示-0,但已经有0000 0000表示+0了。0的正负不影响其值,所以-0的表示就多余了。* 同时:-127原码:1111 1111 ,反码:1000 0000 ,反码+1得到补码:1000 0001* -1 原码:1000 0001 ,反码:1111 1110 ,反码+1得到补码:1111 1111 * -127+(-1) = -128。所以他们的补码进行相加就得到-128 :1 1000 0000 * - 因为char类型只能存8位,所以发生截断,只存储了1000 0000 ,所以就用1000 0000表示-128。* 也就是说-128在内存中存储位1000 0000,可以说1000 0000是-128的补码。* * 无符号数* - 以上这种的是有符号类型的范围。* 如果是无符号类型,也就是说:00000000~11111111,转换为十进制就是:0~255。把它当作没有负数来看待。* 在定义的时候,就需要在其类型前加上unsigned。这样定义出来的数就被当作没有符号的数来处理* - 范围就是:0~2的n次方-1 ,无符号类型的初衷,并不是为了拓展数字的表达范围,因为能表示的范围并没有改变,* 只不过是一个只表示了正数,一个不仅表示正数,也表示负数。都表示256个不同的数。而是为了做纯二进制运算,主要是为了位移。*/

原码、反码、补码详解:C语言——原码, 反码, 补码 详解_蛋翼的博客-CSDN博客目录一. 机器数和真值1、机器数2、真值二. 原码, 反码, 补码的基础概念和计算方法.1. 原码2. 反码3. 补码三. 为何要使用原码, 反码和补码四 原码, 反码, 补码 再深入同余的概念负数取模开始证明一. 机器数和真值在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念.1、机器数一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1.比如,./m0_69066786/article/details/124541454

计算机中整数的三种表示方法:原码、反码、补码

/** 数据在内存中以二进制的形式存储。* 对于整数来说,其二进制有三种表示方式:* - 三种表示方法均有符号位和数值位两部分,符号位都是用0表示”正“,用1表示”负“。* - 正整数:原码、反码、补码相同* - 负整数:原码、反码、补码不同,需要通过计算得出。* 按照数据数值直接写出的二进制序列就是原码,原码最高位符号位不变,其余二进制位按位取反,得到反码;反码+1,得到补码。*/int main(){int a = -10;printf("%0x",a);//将a用16进制打印出来是:fffffff6//原码:10000000 00000000 00000000 00001010 最高位1表示符号位//反码:11111111 11111111 11111111 11110101 原码的最高位符号位不变,其余二进制位按位取反,得到反码//补码:11111111 11111111 11111111 11110110 反码+1就是补码//十六进制: ffff fff6//结论:整数在内存中存储的是补码/** 为什么整形数据存放在内存中,是存放的补码呢?* - 在计算系统中,数值一律用补码来表示存储。原因:使用补码,可以将符号位和数值域统一处理* 同时,加法和减法也可以统一处理(CPU只有加法器)。此外,补码和原码可以相互转换,其运算过程是相同的,不需要额外的硬件电路*///例如当运算1-1时,因为没有减法器,所以其运算应该是:1+(-1)//如果是采用原码进行运算://1的原码: 00000000 00000000 00000000 00000001//-1的原码:10000000 00000000 00000000 00000001//相加: 10000000 00000000 00000000 00000010//最高位符号位表示负数,数值为2,也就是得到-2,但是1-1=0,所以计算机肯定不是采用原码进行计算的//采用补码进行运算//1是正数,正数的原码、反码、补码相同//1的补码: 00000000 00000000 00000000 00000001//-1的原码:10000000 00000000 00000000 00000001//-1的反码:11111111 11111111 11111111 11111110//-1的补码:11111111 11111111 11111111 11111111//1的补码: 00000000 00000000 00000000 00000001//-1的补码: 11111111 11111111 11111111 11111111//相加: 1 00000000 00000000 00000000 00000000//进1后有33位,但是只能存储32位,会发生截断,只存储后32位//也就是00000000 00000000 00000000 00000000,得到0。return 0;}

大小端

大小端字节序指的是数据在电脑上存储的字节顺序。注意:是字节顺序,而不是二进制位顺序。

什么是大端,什么是小端

int main(){//我们发现在内存中数据都是倒着存的,这是什么原因?int a = -10; //f6 ff ff ffint b = 10;//0a 00 00 00//本来应该是十六进制的11223344,结果在内存中反了过来,并且我们可以发现,他是按一个字节大小反着存的。//这个就叫做小端存储模式int c = 0x11223344;// 44 33 22 11/** 什么是大端小端?* - 大端存储模式:是指数据的低位,保存在内存的高地址中;数据的高位,保存在内存的低地址中。* 把数据的低位字节序的内容,放到高地址处;高位字节序的内容存放在低地址处。* - 小端存储模式:是指数据的低位,保存在内存的低地址中;数据的高位,保存在内存的高地址中。* 把数据的低位字节序的内容,放到低地址处;高位字节序的内容存放在高地址处。** 例如 int c = 0x11223344;* 大端存储:11 22 33 44* 小端存储:44 33 22 11*/return 0;}

为什么会有大端小端?

/*- 因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中,除了8bit的char之外,还有16bit(2字节)的short型数据,还有32bit(4字节)的int型数据。另外,对于位数大于8位的处理器,例如16位或32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节排序的问题。- 因此就导致了大端存储模式和小端存储模式。例如:一个16bit的short类型变量x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。 对于大端模式,就将0x11放在低地址中,即放入0x0010中;0x22放在高地址中,即0x0011中。而小端模式,则恰好相反。- 我们常用的x86结构式小端模式,而KEIL C51则为大端模式。很多的ARM、DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端。*/

设计一个程序来判断当前机器的字节序

/** 如:1(00 00 00 01)在内存中的存储就是:* 低地址——————————————————————————————————>高地址* 小端:高字节高地址01 00 00 00* 大端:高字节低地址00 00 00 01*/#include <stdio.h>int check_sys(){int a = 1;//使用char类型指针变量,这样就可以只取a变量第一个字节。char * p = (char*)&a;//取出来然后返回,如果是1,则*p=1,就返回1。如果取出来0,则*p=0,返回0.return *p; //返回1表示小端;返回0表示大端。}int main(){int ret = check_sys();if(ret == 1){printf("小端");}else{printf("大端");}return 0;}

练习

- 因为内存中存储的补码,所以计算的时候也是计算补码。整型提升之后得到的是其补码。- 以%d形式打印,则打印的是原码。- 以%u形式打印,是打印无符号数,如果是要把一个有符号数以%u的形式打印,则是要将这个数的最高位当成数值位,然后打印其原码。因为%u会将其符号位当作数值位,没有负数的概念,也就是说每一个以%u形式打印的数都会被当作一个正数,而正数的原码反码补码相同,所以可以理解为是打印其补码。内存中存储的是什么就打印出什么

以下程序打印输出什么

#include <stdio.h>/** 补充:* - C语言中并没有规定char是signed char 还是unsignend char。取决于编译器,大部分编译器中,char是signed char* - int类型就是signed int类型* - short类型就是sigend short类型*/int main(){//-1是整数,占4个字节。-1的补码:11111111 11111111 11111111 11111111//-1占4个字节,char类型占1个字节,截取后八位:11111111存入a、b、c中char a = -1;//a中存储的是11111111//a是负数,整型提升高位补1。a整型提升的结果:11111111 11111111 11111111 11111111signed char b = -1; //signed表示有符号//b中存储的是11111111//b是负数,整型提升高位补1。b整型提升的结果:11111111 11111111 11111111 11111111unsigned char c = -1;//unsigned是无符号的意思,也就是说c中存储的11111111都是数值位,没有符号位。//c中存储的是11111111//c是unsigned,整型提升高位补0。c的整型提升结果:00000000 00000000 00000000 11111111//c整型提升之后,高位是0,就算是个正数,正数的原码、反码、补码相同。//%d形式打印,是打印有符号数,是打印原码,这里a、b、c都发生整型提升。//a和b整型提升之后都是-1的补码,补码-1得到反码:11111111 11111111 11111111 11111110//反码最高位符号位不变,其余位按位取反,得到原码:10000000 00000000 00000000 00000001// 所以a、b打印出来是-1//c的原码是:00000000 00000000 00000000 11111111,c打印出来就是255printf("a=%d,b=%d,c=%d",a,b,c); //a=-1,b=-1,c=255return 0;}

以下程序打印输出什么

#include <stdio.h>int main(){//-128的原码:10000000 00000000 00000000 10000000//原码最高位不变,其余按位取反,得到反码:11111111 11111111 11111111 01111111//反码+1就是补码:11111111 11111111 11111111 10000000//取后八位存储到a中,就是:10000000。char a = -128;//以%u打印,发生整型提升。a是有符号char,高位补1,整型提升之后的结果:11111111 11111111 11111111 10000000//整型提升之后是补码,但是其以%u打印,也就是说将其看作一个没有符号的数据,所有位数都是数值位。// 而正数的原码、反码、补码相同,所以将其直接打印出来,是一个很大的数。printf("%u",a);//4294967168//这里是以%u形式打印,最高位会被看成数值位,而不是符号位,所以是一个很大的数。return 0;}

以下程序打印输出什么

#include <stdio.h>int main(){//a的原码:00000000 00000000 00000000 10000000//正数的原码、反码、补码相同,取其后八位存储到a中:10000000char a = 128;//以%u形式打印,发生整型提升,a是有符号char,高位补1,整型提升之后的结果:11111111 11111111 11111111 10000000//整型提升之后是补码,但是其以%u打印,也就是说将其看作一个没有符号的数据,所有位数都是数值位。// 而正数的原码、反码、补码相同,所以将其打印出来,是一个很大的数。printf("%u",a);//4294967168return 0;}

char类型的取值范围

/** char类型占一个字节,也就是八位二进制位,其范围:00000000~11111111* 因为是有符号的char,正数:00000000 ~01111111 负数:10000000~11111111* - 正数范围也就是:0~127,* - 10000000~11111111 其最高位是符号位,整形提升之后得到补码,再得到反码原码,* - 10000000整型提升得到:11111111 11111111 11111111 10000000 ,这时补码* 补码-1得到反码:11111111 11111111 11111111 01111111* 反码最高位不变,其余按位取反:10000000 00000000 00000000 10000000* 这就是原码,最高位1表示是负数,数值位是128,就是-128。* - 11111111整型提升得到:11111111 11111111 11111111 11111111* 补码-1得到: 11111111 11111111 11111111 11111110* 反码最高位不变,其余按位取反:10000000 00000000 00000000 00000001* 这是原码,最高位1表示是负数,数值位是1,就是-1。** - 所以char的范围就是:-128~127*/

以下程序打印输出什么

int main(){//10的原码:10000000 00000000 00000000 00010100//10的反码:11111111 11111111 11111111 11101011//10的补码:11111111 11111111 11111111 11101100int i = -20;//10的补码: 00000000 00000000 00000000 00001010unsigned int j = 10;//-20的补码: 11111111 11111111 11111111 11101100//10的补码: 00000000 00000000 00000000 00001010//两补码相加得到补码:11111111 11111111 11111111 11110110//+1得到反码: 11111111 11111111 11111111 11110101//高位不变,其余取反:10000000 00000000 00000000 00001010//最高位1表示负数,数值位是10,所以输出-10printf("%d\n",i+j);//-10//注意这里不要跟上面搞混,这里是以%d形式打印的,打印的是有符号数的,也就是说i+j的结果其最高位是符号位,然后跟上其数值位。如果这里是%u形式打印,将其最高位看作数值位。则也是一个很大的数。return 0;}

以下程序打印输出什么

/** 该程序会死循环。原因:* - i是unsigned int类型,无符号类型,也就是说i最小就是0。会打印9 8 7 6 5 4 3 2 1 0 ...* - 0之后应该是-1,但是因为i是无符号类型,所以负数的补码中最高位的1是被当成了数值位的。* 所以其0之后的,本因该是负数的,符号位被当成数值位之后,永远是大于0的,所以会造成死循环。。** - 如果是以%u形式打印,则-1是4294967295,从这个数每次减1打印。* 因为无符号类型,没有负数的概念。所以就会把i当成一个正数,正数的补码反码原码相同,所以直接打印其补码** - 如果是以%d形式打印,则是打印变量i的原码。因为%d是打印有符号位的,所以会将i中存储的补码转换为原码之后打印出来* 所以-1就打印出-1,根据循环条件每次减1打印。** - 以%d形式打印,在0之后,虽然形式上看起来是负数了。但是变量i本质上是无符号类型的,所以i永远大于0* 所以即使以%d形式打印,也是死循环*/int main(){unsigned int i;for(i=9 ; i>=0 ; i--){printf("%u\n",i);//printf("%d\n",i);}return 0;}

以下程序输出什么

#include <stdio.h>#include <string.h>/** 因为char的范围是:-128~127。* 在该程序中,a数组中存储了1000个元素。* 依次是:-1 -2 -3 -4 .... -128 下一位应该是-129,* -129的原码: 10000000 00000000 00000000 10000001* 符不变,取反:11111111 11111111 11111111 01111110* 反码+1,补码:11111111 11111111 11111111 01111111* 截取后八位存储到char中,符号位是0表示正数,数值位是127。所以-129存储到char中实际存储的是127* 接着-128存储的应该是:127 126 125 124 ...... 1 0,0在char的** char中存储1的时候应该是:00000001,是截取而来。因为是存储负数得到的,补全其原来的补码,应该是:11111111 11111111 11111111 00000001* -1得到反码:11111111 11111111 11111111 00000000,符号位不变,其余按位取反,得到10000000 00000000 00000000 11111111* 也就是说,-255存储到char中,得到的是1。* - 紧接着是-256,原码:10000000 00000000 00000001 00000000,反码:11111111 11111111 11111110 11111111* 反码+1,得到补码:11111111 11111111 11111111 00000000 ,截取后八位,所以存储的是0。* - 紧接着是-257,原码:10000000 00000000 00000001 00000001,反码:11111111 11111111 11111110 11111110* 反码+1,得到补码:11111111 11111111 11111110 11111111 ,截取后八位,所以存储的是-1** 所以实际上存储的数就是在循环变化:-1 -2 ... -128 127 ... 1 0 -1 -2 ... -128 127 ... 1 0 -1...* 直到循环到最后一次,i=999,a[999]=-1000,存储到数组中就是24。** - 最后输出的是strlen(a)的结果,a中有1000个元素,但是strlen()字符串函数遇到字符串结束符'\0'就统计结束* 而'\0'的结果就是0,所以也就是求a数组中第一个0之前的元素的个数。* 也就是-1 ... -128 127 ... 1 ,128个负数+127个正数,遇到0就体停止了,不计算0。就是128+127=255,所以最后输出255*/int main(){char a[1000];int i;for(i=0; i<1000 ; i++){a[i] = -1-i;}printf("%d",strlen(a));//255return 0;}

程序打印输出什么

#include <stdio.h>/** 有符号char的范围是-128~127。* 无符号char,因为其符号位被当作了数值位,所以其范围是:0~255** 以下程序指向,当i=255,循环体执行完之后,++执行,之i=256。* 256是正数,其原码补码相同,原码是:11111111 11111111 11111111 00000000* 截取后八位存储到char中:00000000,存储为0。** 所以i从0~255,然后又从0~255,程序死循环。*/unsigned char i = 0;int main(){for(i=0 ; i<=255 ; i++){printf("hello world\n");}return 0;}

浮点型在内存中的存储

float、double 浮点型取值范围在float.h头文件中定义。浮点数:带小数点的数值。float:单精度浮点数double:双精度浮点数在C语言中,两个整数的运算结果只能是整数。如10/3=3。10和10.0是两个不同的数,10是整数,而10.0是浮点数。当浮点数与整数放到一起运算时,C会将整数转换成浮点数,然后进行浮点数的运算。

为什么以下程序输出结果与想象不同?

#include <stdio.h>int main(){//n变量式int类型,占4个字节,其中存储了9。(整型存储)int n = 9;//float类型也是占用四个字节。取n的地址,强制转换成float*指针类型,然后存储到pFloat指针变量中。float * pFloat = (float*)&n;//以%d的形式打印有符号整型,传入变量n。(整型存储,整型取出)printf("n的值为:%d\n",n);//9//以%f的形式,是打印浮点类型。*pFloat是float*类型指针。(整型存储,浮点型取出)printf("*pFloat的值为:%f\n",*pFloat);//0.000000//使用解引用的方式,将浮点数9.0存储到*pFloat指向的地址。(以浮点型存储)*pFloat = 9.0;//以%d的形式打印有符号整型,传入变量n。(浮点型存储,整型取出)printf("n的值为:%d\n",n); //1091567616//(浮点型存储,浮点型取出)printf("*pFloat的值为:%f\n",*pFloat);//9.000000//发现:整存整取、浮存浮取时,以其对应形式打印,都正确输出。但是当整存浮取、浮存整取时,都出现异常。//说明:整形与浮点型在内存中的存储大不相同。return 0;}

浮点数存储规则

以上程序中,num和*pFloat在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?要理解这个结果,就需要知道浮点数在计算机内部是怎么表示的。

科学记数法

科学记数法是一种记数方法。把一个数表示成a与10的n次幂相乘的形式。(1 ≤ |a| < 10,a不为分数形式,n为整数),这种记数方法叫做科学计数法。例如:- 19971400000000=1.99714*10^13 计算器或电脑表达10的幂一般是用E或e,也就是1.99714E13=19971400000000- 0.00001=1×10^-5,即绝对值小于1的数也可以用科学记数法表示为a乘10 的负n次方的形式。- 小数点向前移动几位,就乘10的几次方。

浮点数的表示(参考十进制的科学记数法记忆)

/** 根据国际标准IEEE(电气电子工程师学会)754标准规定,任意一个二进制的浮点数V可以表示为以下形式:* - (-1)^S * M * 2^E* -1的S次方,表示符号位,当s=0时,V为正数;当s=1,V为负数。S又叫数符。* M表示有效数字,大于等于1,小于2。M又叫尾数* 2的E次方,表示指数位c。E又叫阶码** 例如:* - 浮点数5.5,转换为二进制就是:101.1 (5转换为二进制是101,0.5是二分之一,也就是2的-1次方)* 101.1的小数点向左移动两位,1.011*2^2。(参考十进制科学计数法,10进制是10的幂次方,二进制就是2的幂次方)* 1.011*2^2,也就是4个1.011相加,就可以得到101.1* - 用IEEE754标准表示就是:(-1)^0 * 1.011 * 2^2 。 S=0 M=1.011 E=2** - 浮点数-5.5就可以表示为:-101.1,相当于-1.011*2^2* 用754标准表示就是:(-1)^1 * 1.011 * 2^2 。 S=1 M=1.011 E=2*/

IEEE754标准对有效数字M和指数E的特别规定

/** 前面说过 1 ≤ M < 2 ,也就是说M可以写为1.xxxxxx的形式,其中xxxxxx表示小数部分* - IEEE标准规定,在计算机内部保存M时,因为这个数肯定是1.x的形式,因此第一位可以被舍去,只存储后面的xxxxxx部分* - 例如保存1.01的时候,只保存01。读取时,自动加上第一位的1。这样的目的,是节省1位有效数字。* 32位浮点数的M只有23位,将第一位的1舍去之后,就可以保存1之后的23位有效数字,加上舍去的第一位,一共就可以存储24位有效数字了** 指数E,E为一个无符号整数(unsigned int)* - 意味着,如果E为8位,取值范围就是:0~255;如果E为11位,则取值范围是:0~2047。* - 但是在科学记数法中,E是可以为负数的。所以IEEE754规定:存入内存时E的真实值必须再加上一个中间数。* 对于32位浮点数,E要加上中间数127;对于64位浮点数,E要加上中间数1023。* 比如2^10,E为10,所以保存为32位浮点数时,需要加上127。E就为137,存储到内存中为:1000 1001*/

浮点数5.5在内存中的存储

int main(){/** 浮点数5.5,转换为二进制是101.1,可以写为:1.011*2^2。* - 其中S=0 , M=1.011 , E=2+127* - 所以f以32位浮点数,存储到内存中的应该是:0 10000001 011 0000 0000 0000 0000 0000* 第一位为符号位s,为0表示是整数;接下来的8位是指数位,其中存储E* 在接下来的23位为有效数字位M,有效数字只有三位011,接着在后面补20个0即可。* - 实际存储(二进制)为:0100 0000 1011 0000 0000 0000 0000 0000* 转换为十六进制:4 0 b 00 0 0 0* 每两个十六进制位为一个字节,float为四个字节:40 b0 00 00* - 调试,查看器内存中如何存储* 采用小端存储,即低位低址,在内存中显示为:00 00 b0 40*/float f = 5.5f;return 0;}

将浮点数从内存中取出时,还可以分为三种情况

/** 第一种情况:E不为全0或不为全1* - 如0.5的二进制为:0.1,由于规定正数部分必须为1,即小数点向右移一位,就表示为:1.0*2^-1* E在内存中存储为:-1+127=126,二进制表示为:0111 1110。* 有效数字M=1.0,舍去1,存储一个0,后面补22个0。* 所以浮点数0.5在内存中就存储为0 0111 1110 00000000000000000000000* - 这时,从内存中取出时,根据规则,将指数E的计算值减去127(64位浮点数的E减去1023),得到真实值* 再将有效数字M前加上第一位的1** 第二种情况:E为全0* - 存储:如果E为全0,则说明真实E的值真小,只有当真实E+127=0时,存储到内存中时E才为全0。* 也就是说E=-127,2^-127,这是一个非常小的数字,无限接近于0。当s=0时就为正,当s=1时就为负* - 所以规定:取出时,浮点数的指数E等于1-127(或1-1023)即为真实值。* 并且有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示正负0,以及接近于0的很小的数字。** 第三种情况:E为全1* - 如果E为全1,则说明真实E的值很大,真实E+127=255,存储到内存中的E才为全1,也就是说E=128。* 1.xxxxxx * 2^128,这是一个非常大的数字。当s=0时就为正,当s=1时就为负* - 规定:这时,如果有效数字M全为0,则表示±无穷大(正负取决于符号位S)*/

此时,再返回刚刚那个程序

#include <stdio.h>int main(){int n = 9;//n在内存中存储为:00000000 00000000 00000000 00001001float * pFloat = (float*)&n;//%d以整型取出,打印原码,最高位0表示正数,数值位值为9,所以输出9。printf("n的值为:%d\n",n);//9/** %f,以32位浮点数取出:0 00000000 00000000 00000000 0001001* s=0表示正数。E=0,则按照规定:真实E=1-127=-126。并且有效数字不再加1,而是直接还原为0.xxxxxx的小数* 所以这个数就变成了: 0.00000000000000000001001*2^-126,也就是说这个数无限接近于0* 因为%f只打印小数点后六位,所以就打印出:0.000000*/printf("*pFloat的值为:%f\n",*pFloat);//0.000000*pFloat = 9.0;//9.0转换为二进制:1001.0,就是:1.001*2^3。s=0,E=3+127=130,M存储001,后面补20个0//所以9.0在内存中就是:0 10000010 00100000000000000000000//以%d打印,打印原码。01000001 00010000 00000000 00000000,最高位0表示正数,数值位值为1091567616//所以n输出为1091567616printf("n的值为:%d\n",n); //1091567616/** %f,以32位浮点数取出:0 10000010 00100000000000000000000* s=0表示正数。E=130,则按照规定:真实E=130-127=3。并且有效数字补1,就成为:1.001* 1.001*2^3 ,就是:1001,转换为十进制就是:9* 因为%f只打印小数点后六位,所以就打印出:9.000000*/printf("*pFloat的值为:%f\n",*pFloat);//9.000000return 0;}

练习

程序执行结果

#include <stdio.h>int main(){//200的二进制:00000000 00000000 00000000 11001000//char类型变量a只能存储一个字节,截取后八位存储到a中:1100 1000unsigned char a = 200;//100的二进制:00000000 00000000 00000000 01100100//截取后八位存储到b中:01100100unsigned char b = 100;unsigned char c = 0;//这里发生整型提升,因为a与b都是无符号char,所以高位补零,也就是//00000000 00000000 00000000 11001000 200//00000000 00000000 00000000 01100100 100//00000000 00000000 00000001 00101100 300//截取其后八位,存储到c中:00101100c = a + b;//使用%d形式打印,再次发生整型提升。// a+b,整型提升之后计算,得到的是00000000 00000000 00000001 00101100,不截断存储,直接打印就是300。// c是unsigned char类型,整型提升高位补0:00000000 00000000 00000000 00101100,算出来是44。所以c打印出来是44printf("%d %d",a+b,c);//300 44return 0;}

在32位大端处理器上,变量b等于

#include <stdio.h>int main(){//int占4个字节,所以a存储的应该是:0x 00 00 12 34//因为采用大端存储,也就是:低字节高地址,在内存中存储为:00 00 12 34。//因为采用大端存储,也就是:低字节低地址,在内存中存储为:34 12 00 00。unsigned int a = 0x1234;//这里强制转换为unsigned char类型,只存储了一个字节。所以在32位大端处理器上,变量b等于0x00。unsigned char b = *(unsigned char*)&a;return 0;}

打印杨辉三角

/** 杨辉三角:*1*1 1*1 2 1*1 3 3 1*1 4 6 4 1*1 5 10 10 5 1*/#include <stdio.h>int main(){int arr[10][10] = {0};int i,j;for(i=0 ; i<10 ; i++){for(j=0 ; j<10 ; j++){//每一行第一个元素是1if(j == 0){arr[i][j] = 1;}//当i=j,也就是第一行的第一个、第二行的第二个、第三行的第三个....第n行的第n个,都是1。if(i == j){arr[i][j] = 1;}//当行数≥2、并且列数≥1的时候,就可以计算当前元素的值if(i>=2 && j>=1){arr[i][j] = arr[i-1][j-1] + arr[i-1][j];}}}for(i=0 ; i<10 ; i++){//打印的元素个数:第几行就打印几个。for(j=0 ; j<=i ; j++){printf("%d ",arr[i][j]);}printf("\n");}return 0;}

找凶手

/** 发生了一起谋杀案,警察通过排查,确定杀人凶手必为4个嫌疑犯中的一个。以下是他们的供词:* A说:不是我 、 B说:是C 、 C说:是D 、 D说:C在胡说* 已知:3个人说了真话,1个人说的假话。现在根据这些信息,写一个程序来确定谁是凶手*/#include <stdio.h>int main(){char killer = 0;//假设凶手是A/B/C/D,然后判断他们四个人说的话是否符合3真1假。for(killer='A' ; killer<='D'; killer++){//A说:不是我。转换为判断条件:killer != 'A'//B说:是C。 转换为判断条件:killer == 'C'//C说:是D 。 转换为判断条件:killer == 'D'//D说,C在胡说。 C说凶手是D,D说他胡说;意思就是说D说他自己不是凶手,转换为条件:killer != 'D'//条件为真则返回1,为假则返回0。3真1假,只有加起来是3的时候,此时killer所存储的就是凶手,打印出来即可。if((killer !='A') + (killer == 'C') + (killer == 'D') + (killer != 'D') == 3){printf("凶手是:%c",killer); //C}}return 0;}

有一根香

/** 有一根香,材质不均匀。燃烧完一根香需要一个小时。给你两根香,请确认一个15分钟的时间段。* - 方法:点燃一根香的两端、另一根香的一端。待到点燃两端的那根香燃烧完的时候,时间过了30分钟。此时点燃一端的那根香也燃烧了30分钟,点燃这跟香的另一端,两端同时燃烧,此时到燃尽这个时间段就是15分钟。*/

坐电梯

/** 一个人住在30楼。下雨天电梯有人时,做电梯回家。其他时间做电梯到15楼,然后爬楼梯回家,思考为什么?* * 官方解释:这个人是侏儒,很矮。* - 下雨天的时候,电梯有人,可以帮他按到30楼的按钮。或者他自己用伞点30楼的按钮。* - 电梯没人或没带伞时,只能点到15楼的按钮,然后再爬楼梯回家。*/

赛马

/** 36匹马,6个跑道。不给计数器赛马,比赛多少次,才能确定36匹马中的前三名。* 25匹马,5个跑道?**/

猜名次

/** 5位运动员参加10米跳水比赛,有人让他们预测比赛结果。* A说:B第二、我第三。* B说:我第二、E第四* C说:我第一、D第二* D说:C最后,我第三* E说:我第四,A第一* 比赛结束后,每位选手都说对了一般,请确定比赛的名次。*/#include <stdio.h>int main(){int a,b,c,d,e;for(a=1 ; a<=5 ; a++){for(b=1 ; b<=5 ; b++){for(c=1 ; c<=5 ; c++){for(d=1 ; d<=5 ; d++){for(e=1 ; e<=5 ; e++){//每个人说了两句话,只有一句是真的。所以我们把每个人说的话都分成两个条件。//真为1,假为0。每个人的两个判断条件加起来应该是1。if(((b==2)+(a==3) == 1)&& ((b==2)+(e==4) == 1)&& ((c==1)+(d==2) == 1)&& ((c==5)+(d==3) == 1)&& ((e==4)+(a==1) == 1)){//此时符合5个人所说的他们的话各对一半的情况。//打印时我们发现,会出现名词重复的情况。所以我们加上判断://名次相乘:5*4*3*2*1=120 ,这样就不会出现重复的情况了。if(a*b*c*d*e == 120){printf("a=%d b=%d c=%d d=%d e=%d\n",a,b,c,d,e);}}}}}}}return 0;}

去牛客网,刷智力题…

C语言基础09——数据在内存中的存储。整型的存储 大小端讲解 浮点数的存储 杨辉三角 找凶手 猜名次

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。