赞
踩
要想理解数据的存储,首先要明白最基本的二进制问题,因为,这是计算机中数据最基本的形式,首先看下面的问题:
1、什么是二进制?进制的概念?
2、计算机中为什么要用二进制?
3、二进制和符合人类思维的十进制之间的关系?
4、为什么又会出现八进制、十六进制?
5、所有进制之间的转换?
进制也就是进位制,是人们规定的一种进位方法。 对于任何一种进制—X进制,就表示某一位置上的数运算时是逢X进一位。 十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一
(1)、每一种数制都有笃定的符号集。例如,十进制数制的基本符号有十个:0,1,2。。。,9。
例如,二进制数制的基本符号有两个:
0和1.
(2)、每一种数制都使用位置表示法。即处于不同位置的数符所代表的值不同,与它所在位的权值有关。
例如:十进制1234.55可表示为
1234.55=1×103+2×102+3×101+4×100+5×10(-1)+5×10(-2)
电脑使用二进制是由它的实现机理决定的。我们可以这么理解:电脑的基层部件是由集成电路组成的,这些集成电路可以看成是一个个门电路组成,(当然事实上没有这么简单的)。
当计算机工作的时候,电路通电工作,于是每个输出端就有了电压。电压的高低通过模数转换即转换成了二进制:高电平是由1表示,低电平由0表示。也就是说将模拟电路转换成为数字电路。这里的高电平与低电平可以人为确定,一般地(简易理解),2.5伏以下即为低电平,3.2伏以上为高电平
好处:
(1)技术实现简单,计算机是由逻辑电路组成,逻辑电路通常只有两个状态,开关的接通与断开,这两种状态正好可以用“1”和“0”表示。
(2)简化运算规则:两个二进制数和、积运算组合各有三种,运算规则简单,有利于简化计算机内部结构,提高运算速度。
(3)适合逻辑运算:逻辑代数是逻辑运算的理论依据,二进制只有两个数码,正好与逻辑代数中的“真”和“假”相吻合。
(4)易于进行转换,二进制与十进制数易于互相转换。
(5)用二进制表示数据具有抗干扰能力强,可靠性高等优点。因为每位数据只有高低两个状态,当受到一定程度的干扰时,仍能可靠地分辨出它是高还是低
常用的进制有二进制、十进制、八进制和十六进制
①、八进制、十六进制、二进制-------------->十进制
都是按权展开的多项式相加得到十进制的结果。
比如
二进制1010.1到十进制:1×2^3 + 0×2^2 + 1×2^1 + 0×2^0 + 1×2^(-1)=10.5
八进制13.1到十进制:1×8^1 + 3×8^0 + 1×8^(-1)=11.125
十六进制13.1到十进制:1×16^1 + 3×16^0 + 1×16^(-1)=19.0625
②、十进制-------------->八进制、十六进制、二进制
都是按照整数部分除以基数(r)取余,小数部分乘以基数(r)取整
十进制10.25 到二进制:整数部分除2,一步步取余。小数部分乘2,一步步取整
八进制到十进制,十六进制到十进制都是和上面的一样,只不过不在是除2乘2,而是8或者16了,这是根据自己的基数来决定的。
③、二进制<------------->八进制、十六进制
二进制转换成八进制的方法是:从小数点起,把二进制数每三位分成一组,小数点前面的不够三位的前面加0,小数点后面的不够三位的后面加0,然后写出每一组的对应的十进制数,顺序排列起来就得到所要求的八进制数了。
依照同样的思想,将一个八进制数的每一位,按照十进制转换二进制的方法,变成用三位二进制表示的序列,然后按照顺序排列,就转换为二进制数了。
二进制数10101111.10111转换为八进制的数:(010 101 111.101 110)= 2 5 7.5 6=257.56
八进制数257.56转换为二进制的数:2 5 7.5 6 =(010 101 111.101 110)=10101111.101
二进制转换到十六进制差不多:从小数点起,把二进制数每四位分成一组,小数点前面的不够四位的前面加0,小数点后面的不够四位的后面加0,然后写出每一组的对应的十进制数,然后将大于9的数写成如下的形式,10---->A,11–>B,12---->C,13----->D,14----->E,15---->F,在顺序排列起来就得到所要求的十六进制了。
同样,将一个十六进制数的每一位,按照十进制转换二进制的方法,变成用四位二进制表示的序列,然后按照顺序排列,就转换为二进制数了。
二进制数 10101111.10111转换为十六进制的数:(1010 1111.1011 1000)=A F.B 8=AF.B8
十六进制AF.B8转换为二进制: A F.B 8=(1010 1111.1011 1000)=10101111.10111
其他数据类型在程序运行期间可能会改变或被赋值,这些称为变量(variable)。
变量分为全局变量和局部变量,但是写代码的时候尽量使用局部变量。
/* 函数: 函数的也可以叫做子程序。 这里的 function 既可称为一个函数 */ #include "stdio.h" #include<string.h> #include "math.h" void function(void) { /* 内容 */ } int main() { /* 主函数程序 */ }
/* 全局变量: 在所有函数外部声明的变量,称为全局变量。 var 在这里即是全局变量 这里定义的 in var=0; var既可以在函数function中使用,也可以在主函数main中使用 */ #include "stdio.h" #include<string.h> #include "math.h" int var=0; void function(void) { /* 内容 */ } int main() { /* 主函数程序 */ }
/* 局部变量: 在函数或一个代码块内部声明的变量,称为局部变量。 因为在函数function 中定义了一个变量var, 则var在这里称为是函数function的局部变量 这里定义的 in var=0; var只能在函数function中使用,不能再在主函数main中使用 在那个函数定义的局部变量,只能在那个函数中使用 */ void function(void) { int var=0; /* 内容 */ } int main() { /* 主函数程序 */ }
在C语言中全局变量与局部变量命名可以相同,使用冲突时,局部变量优先被使用。
正常情况下,变量数据的访问 :
**函数中如果出现了和全局变量重名的变量 :**
简单理解,就近原则:谁离访问代码近~优先访问谁**
初学者一般建议不要重复命名
// 注意,这里用的是c99标准
#include "stdio.h"
int a = 1;
int main()
{
printf("%d\n",a); //就近原则,此处输出结果为1 用的是全局变量
int a = 10;
printf("%d\n",a); //此处输出结果为10 用的是局部变量
return 0;
}
和数学的概念一样,在C语言中,整数是没有小数部分的数。
例如,2、-23 和 2456 都是整数。
而3.14.0.22 和 2.000 都不是整数。
浮点数与数学中实数的概念差不多。
2.75、3.16E7、7.00 和 2-8 都是浮点数。
注意,在一个值后面加上一个小数点,该值就成为一个浮点值。
所以,7 是整数,7.00 是浮点数。显然,书写浮点数有多种形式稍后将详细介绍 e 记数法,这里先做简要介绍:3.16E7 表示3.16X10(3.16 乘以 10的7次方)。其中107=10000000,7被称为 10的指数。
区别
int 类型是有符号(既可以表示正数或者负数)整形,int 类型的值必须是整数(正整数/ 负整数/ 零)
int 类型取值范围依据计算机系统而言,早期PC用 16位, 目前个人计算机用的是 64位
ISOC规定了int的实际长度/范围最小为-32768~32767
占用空间: int 建议为一个机器字长。32位环境下机器字长为4字节,64位环境下机器字长为8字节。
符号类型: 有符号类型
能表示的范围:一般64位计算机int取值范围为-2147483648~2147483647
例子:
#include "stdio.h"
int main()
{
int a = -10;
printf("%d\n",a);
return 0;
}
在int前 加入c语言关键字unsigned
则会使int类型从有符号类型转化为无符号类型整数
unsigned int 无符号整形的范围为:0~65535
#include "stdio.h"
int main()
{
unsigned int a = 10;
printf("%d\n",a);
return 0;
}
简单来说,当实际给的数据超出int的范围时,会从他的上/下限开始计数
int的范围是-2147483648~2147483647
所以当数据超过上限的时候,数据则会从 -2147483648
这个下限开始算起
#include "stdio.h"
int a = 2147483648;
int main()
{
printf("%d\n",a); //输出结果为-2147483648
return 0;
}
正数的补码与原码反码都相同
[2^31]原=011111…1111
[2^31]反=011111…1111
[2^31]补=011111…1111
如果疑惑为什么1000…0000表示INT_MIN? 即-2^31呢?
原因如下:前面提到,整型数据都是以补码的形式储存的,对于负数,补码=反码+1;
[-2^31]原=100000…0000
[-2^31]反=111111…1111 (除符号位,其余取反)
[-2^31]补=100000…0000(反码+1,本来加上去,要进一位,但是由于符号位不变,进的那一位被抛弃)
2^31 + 1 =011111…1111(补) + 000000…0001(补) = 100000…0000(补)
100000…0000(补) = [-2^31]
所以得出结论:
INT_MAX + 1 = INT_MIN;
INT_MIN - 1 = INT_MAX;
所以当数据超过下限的时候,数据则会从他的上限开始计数
#include "stdio.h"
int a = -2147483649;
int main()
{
printf("%d\n",a); //输出结果为2147483647
return 0;
}
无符号整型,也是32位,但是与int不同的是,他没有符号位,因此只能表示非负数.
与有符号的同理
超过上限则从下限开始计数,超过下限则从上限开始计数
#include <stdio.h>
int main(void)
{
int i = 2147483647;
unsigned int j = 4294967295;
printf("%d %d %d\n", i, i+1, i+2);
printf("%u %u %u\n", j, j+1, j+2);
return 0;
}
溢出行为是未定义的行为,所以系统并不会告知用户,因此需要在编程的时候注意这类行为
占用的存储空间可能比int少,常用于较小数值的场合来节省空间
占用空间: short 至少占用2个字节。在16位环境下,short 为2个字节,int 为2个字节
符号类型: 有符号类型
占用的存储空间可能比int多,常用于较大数值的场合
占用空间: 在16位环境下,long 为4个字节,对于32位的 Windows中 int 为4个字节,long 也为4个字节
符号类型: 有符号类型
占用的空间可能比long int 多,适用于更大数值的场合
占用空间: 该类型至少占8个字节
符号类型: 有符号类型
适用于非负值的场合,这种类型与有符号类型表示的范围不同,例如16位的unsigned可以表示0-65536,而16位的int表示为-32768~32767
char型 用来储存字符,但是计算机用数字编码储存字符,如美国用ASCII码,所以字符和证书均可以表示char型,但是从技术层面来讲,char是整数类型,char实际存储的是整数而不是字符,即用特定的整数来表示特定的字符(ASCII码)
char是最小的整数类型
2、表示方法
字符可以用字母表示,也可以用整数表示;如在ASCII码中,65代表大写字母A
char grade=’A’;
char grade=65;
注意:char是字符型,用单引号表示,’A’;而字符串用双引号表示,”A”
3、常表示字符
用char表示的字符除了大小写字母,还有以下几种转义字符:
注意,标红为常用转义字符
浮点数也称小数或实数。例如,0.0、75.0、4.023、0.27、-937.198 都是合法的小数。
C语言中采用float和double关键字来定义小数,float称为单精度浮点型,double称为双精度浮点型,long double更长的双精度浮点型。
在任何区间内(如1.0 到 2.0 之间)都存在无穷多个实数,计算机的浮点数不能表示区间内所有的值。
C标准规定,float类型至少能表示6位有效数字(指的是数字的前六位),且取值范围为:
1
0
−
37
到
1
0
37
10^{-37} 到 ~10^{37}
10−37到 1037
通常,系统存储一个浮点数通常要占用32位,有8位用于表示指数的值与符号,剩下24位用于表示非指数的部分
C标准规定,double类型至少能表示10位有效数字(指的是数字的前十位),double类型的至少有13位有效数字
通常,系统存储一个浮点数通常要占用64位,多出来的32位用于表示非指数的部分
当浮点值发生上溢的时候,会给toobig赋一个表示无穷大的特定值,如果用printf打印出来则会显示为inf或者infinity
当浮点值发生下溢的时候,则会丢失精度,因为计算机会把尾数部分的位向右边移动,空出一个二进制位,并且丢弃最后一个二进制数
编写程序的时候,应该合理选择所需的变量及其类型,通常用int
或者float
类型表示数字,用char
表示字符
如果将一个类型的数值初始化给不同类型的变量的时候,编译器会把值转化成与变量相匹配的类型,这样会导致部分数据的丢失
如:
int cost = 12.99
float pi = 3.1415926535
/*最后编译器得出的
cost = 12 (将后面小数部分截断)
pi = 3.14159(只有6位精度)
整型数据都以补码的形式储存在内存中。
无符号整形在数据中的存储无疑是最方便的,因为没有符号位,只表示正数,所以在存储计算方面都很简单。无符号整形在就是以纯粹的二进制串存储在计算机中的。
对于带符号数,机器数的最高位是表示正、负号的符号位,其余位则表示数值。
先看下面的例子:
假设机器字长为8的话:
一个十进制的带符号整形 1,表达为二进制就是 (0000 0001)
一个十进制的带符号整形 -1,表达为二进制就是 (1000 0001)
数值X的原码记为[x]原,如果机器字长为n(即采用n个二进制位表示数据)。则最高位是符号位。0表示正号,1表示负号,其余的n-1位表示数值的绝对值。
数值零的原码表示有两种形式:[+0]原=0000 0000 ,-[0]原=1000 0000.
例子:若机器字长n等于8,则
[+1]原=0000 00001 [-1]原=1000 00001
[+127]原=0111 1111 [-127]原=1111 1111
[+45]原=0010 1101 [-45]原=1010 1101
数值X的反码记作[x]反,如果机器字长为n,则最高位是符号位,0表示正号,1表示负号,正数的反码与原码相同,负数的反码则是其绝对值按位求反。
数值0的反码表示有两种形式:[+0]反=0000 0000 ,-[0]反=1111 1111.
例子:若机器字长n等于8,则
[+1]反=0000 00001 [-1]反=1111 1110
[+127]反=0111 1111 [-127]反=1000 0000
[+45]反=0010 1101 [-45]反=1101 0010
数值X的补码记作[x]补,如果机器字长为n,则最高位是符号位,0表示正号,1表示负号,正数的补码与原码反码都相同,负数的补码则等于其反码的末尾加1。
数值0的补码表示有唯一的编码:[+0]补=0000 0000 ,-[0]补=0000 0000.
例子:若机器字长n等于8,则
[+1]补=0000 00001 [-1]补=1111 1111
[+127]补=0111 1111 [-127]补=1000 0001
[+45]补=0010 1101 [-45]补=1101 0011
可以看到
1+(-1)=0 | (0000 0001)补+(1111 1111)补=(0000 0000)补=(0000 0000)原=【0】 可以看到。没有问题
1+(-2)=-1 | (0000 0001)补+(1111 1110)补=(1111 1111)补=(1000 0001)原=【-1】 可以看到,没有问题
-1+(2)=1 | (1111 1111)补+(0000 0010)补=(0000 0001)补 =(0000 0001)原=【1】 可以看到,没有问题
( 1 ) - ( 1 ) = ( 1 ) + ( -1 ) = ( 0 )
(00000001)原 + (10000001)原 = (10000010)原 = ( -2 ) 显然不正确.
通过上面原码计算式可以看出,当正数加上负数时,结果本应是正值,得到的却是负值(当然也有可能得到的是正数,因为被减数与减数相加数值超过0111 1111,即127,就会进位,从而进位使符号位加1变为0了,这时结果就是正的了)。而且数值部分还是被减数与减数的和。
并且,当负数加上负数时(这里就拿两个数值部分加起来不超过0111 1111的来说),我们可以明显看出符号位相加变为0,进位1被溢出。结果就是正数了。
因此原码的错误显而易见,是不能用在计算机中的。
要谈补码,先看看补数的问题。什么是补数,举个简单的例子,100=25+75。100用数学来说就是模M,那么就可以这样概括。在M=100的情况下,25是75的补数。这就是补数。
25是75的补数,这是在常规世界中,在计算机上就不是这样了,因为,在计算机中,数据存在这溢出的问题。
假设机器字长是8的话,那么能表达的最大无符号数就是1111 11111,在加1的话,就变成1 0000 0000 ,此时因为溢出,所以1去掉,就变成0了,这个很简单,相信学计算机的人都会明白。
也就是说,在计算机中,补数的概念稍微不同于数学之中,25+75=100,考略计算机中的溢出问题,那么25+75就等于0了。也就是说,25和75不是互为补数了。
我觉得用闹钟来比喻这个问题在形象不过了,因为闹钟也存在着溢出的问题,当时间到达11:59 ,在加1分钟的话就变成0:0了,这和计算机的溢出是同一个道理。
那么,有一个时钟,现在是0点,我想调到5点,有两种方法,一个是正着拨5,到5点。第二种方法是倒着拨7,也可以到5点。正着拨5记作+5,倒着拨7,记作-7,而闹钟的M是12,也就是说,在考略溢出的情况下,M=12,5是-7的补数。用个数学等式可以这样表达0+5=0±7,即0+5=0-7
这就是计算机中的数值计算和数学中的计算不同的地方。
明白了计算机中补数的道理,那么就明白补码的问题了。还是用例子说明:
在计算机中计算十进制 1+(-2)。
1的原码是:0000 0001
-2的原码是:1000 0010
-2的补码是:1111 1110 这个二进制换做无符号的整数大小就是254,而8位二进制数的M=28=256。(很多文章中把M写成27,这根本就是不对的,根本没有解决符号位的问题)
你发现什么了没,当换成补码后,-2和254就是补数的关系。
也就是1+(-2) 等价于了 1+254了。
1+(-2) = (-1)
-1 的补码是 1111 1111
这样做,好处在什么地方,你自己都可以看得到:
①、利用补数和溢出的原理,减法变成了加法
②、符号位不在是约束计算的问题,不会存在原码中的问题了,因为变成补码后,虽然最高位依然是1,但是这个1就不在是最为符号位了,而是作为一个普通的二进制位,参与运算了。
所以,这就是补码的原理所在,通过补数和溢出,解决了减法和负数问题。不知道各位理解了没有,额,反正我是通过这种方法安慰自己的,不知道是不是有失偏颇。
如果是正数,直接求它的原码,符号位为0
如果是负数,比较好的方法是先求十六进制,在由十六进制求二进制,符号位为1,在除了符号位都取反,在加1,即可得到补码。
补码就十进制 :
根据符号位判断,如果符号位是0,表示是正数,就是原码,直接转换就十进制即可。
如果符号为是1,表示是负数。那么,连符号位在内都取反,在加1,将该二进制转换为十进制,该十进制数即使该负数的绝对值,加个负号-,就得到该负数。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。