第九章 位运算
第一节 位与进制
bit是数字技术二进制的位数。1位二进制称1bit,它由两个码组成,即0和1,代表线路的两种状态。位是内存的最小单位。在计算机中每8bit构成1个字节(byte),分别是bit0,bit1,bit2,bit3,bit4,bit5,bit6,bit7,即8bit=1byte。
0 0 0 0 0 0 0 0
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
其中左边的位称为高位,右边的位称为低位。
二进制码转输速率称为比特率(bps或b/s)。
如果一个字节的bit2=1、bit6=1、其它bit为0,那这个数应该是:
0 1 0 0 0 1 0 0
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
如果一个字节的bit0、bit2、bit4、bit6为1,其它bit为0,那么这个数应该是:
0 1 0 1 0 1 0 1
bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
以此类推。
在第二章第二节当中我们就讲到过,GVM2支持的数据进制有十进制和十六进制,由于bit是一个二进制串,不能直接使用,所以就必须进行数制转换,下面我们讲一下数制之的转换方法。
1、 二进制转十进制:将二进制数的各位以当前位的bit数为指数,以2为底数的幂相加,得到的结果就是对应的十进数。如:
将11010110转换成十进数:
1*2^7+1*2^6+0*2^5+1*2^4+0*23+1*22+1*21+0*2°
=128+64+0+16+0+4+2+0
=214
将00001010转换成十进数:
01*2^7+0*2^6+0*2^5+0*2^4+1*23+0*22+1*21+0*2°
=0+0+0+0+8+0+2+0
=10
1、 十进制转二进制:每次进行一次除以2的操作,商只取整数部份,并把余数记录下来,直到最后高为0为止,这时把所有余数从下到上连接起来就是对应的二进数:如:
将214转换成二进制数:
我们看到,所有余数从下到上连接起来就成了11010110。像这种方法就叫做“除2取余法”。
2、 二进制与十六进制互转:每四位二进数对应一位十六进制数按下表的数据一一对应更换就行了。
十六进制 0 1 2 3 4 5 6 7
二进制 0000 0001 0010 0011 0100 0101 0110 0111
十六进制 8 9 a b c d e f
二进制 1000 1001 1010 1011 1100 1101 1110 1111
如:
11010110转成十六进数为:0010对应d,0110对应6,所以11010110转成十六进数就是0xd6。
又如:
将0x8f转成二进数:8对应1000,f对应1111,所以0x8f转成二进数就是10001111。
3、 十进制与十六进制互换:为了方便,十六进制转十进制一般我个人是先将十六进制换成二进制以后再把这个二进制转成十进制,十进制转十六进制则先将十进制数转换成二进制数以后再将二进制数转成十六进制数。如:
将0x58转成十进数:先转成二进数为01011000,再转成十进数为88。
又如:将185转成十六进
数:先转成二进数:
得10111001;再换成十六进数为:0xb9。
习题8-1:
1、指出下列各数的各bit是多少?
11011010 234 0x89 118 0xfa
2、将下表填好:
第二节 位运算符和位运算
在计算机程序中,数据的位是可以操作的最小数据单位,理论上可以用“位运算”来完成所有的运算和操作。一般的位操作是用来控制硬件的,或者做数据变换使用,但是,灵活的位操作可以有效地提高程序运行的效率。GVM语言提供了位运算的功能, 这使得GVM语言的功能大大地提高了。
GVM2提供了以下六种位运算符:
& 与运算
| 或运算
^ 异或运算
~ 非运算(求补)
>> 右移运算
<< 左移运算
按位与运算(&),双目运算。二个位都置位(等于1)时,结果等于1,其它的结果都等于0。
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
与运算的一个用途是检查指定位是否置位(等于1)。
例如一个BYTE里有标识位,要检查第4位是否置位,
00110010 - b
& 00010000 - & 0x10
----------------------------
00010000 - result
可以看到第4位是置位了。
按位或运算( | ) 双目运算。二个位只要有一个位置位,结果就等于1。二个位都为0时,结果为0。
1 | 1 == 1
1 | 0 == 1
0 | 1 == 1
0 | 0 == 0
或与运算也可以用来检查置位。
例如要检查某个值的第3位是否置位:
可表达为:
00110010 - b
| 00000100 - | 0x04
----------------------------
00110110 - result
异或运算(^)双目运算。二个位不相等时,结果为1,否则为0。
1 ^ 1 == 0
1 ^ 0 == 1
0 ^ 1 == 1
0 ^ 0 == 0
异或运算可用于位值翻转。
例如将第3位与第4位的值翻转:
可表达为:
00110010 - b
^ 00011000 - ^0x18
--------------------
00101010 - result
00101010 - b
^ 00011000 - ^0x18
--------------------
00110010 - result
非运算(~)单目运算。位值取反,置0为1,或置1为0。非运算的用途是将指定位清0,其余位置1。非运算与数值大小无关。
例如将第1位和第2位清0,其余位置1:
可表达为:
00000011 - 0x03
11111100 - ~0x03 b
非运算和与运算结
合,可以确保将指定位清0。如将第4位清0:
可表达为:
00110010 - b
& 11101111 - ~0x10
--------------------
00100010 - result
移位运算(>> 与 <<)
将位值向一个方向移动指定的位数。右移 >> 算子从高位向低位移动,左移 << 算子从低位向高位移动。往往用位移来对齐位的排列
例如
00001100 - b
00110000 - b << 2
00000011 - b >> 2
其实:左移相当于进行乘以2的操作,而右移则相当于除以2的操作。
在GVM2当中,当数据左移时,其右边的空出位均补0,如:
00000101<<2=00010100
当数据右移时,如果是char型数据,其左边都补0,其类型数据则在其左边补最高位的数,如:
10001010>>2=00100010
0000111100001111>>2=0000001111000011
1100110011001100>>2=1111001100110011
下面我们来看一个例子:
例 7-2-1 :求一个long型数据的补码
分析:我们知道,一个数在内存当中是以二进数补码的形式存在的,那么我们只要把它内存中的每一位数都一一取出来显示就可以了,首先我们可以把这个数与1进行位与,从而能得到最右边的一位数据,得到最右边的数据后再把该数向右移一位,重复上述方法,直到每一位都取完为止:那程序可以这么写:
long inputnum()//数值输入函数
{
//返回值为输入的数值
long value;
char t;
for(;;)
{
TextOut(0,0," ");//消除上次输入的数值
DigitOut(0,0,value);
t=getchar();
if(t>='0' && t<='9')
{
if(value<0 && value>-100000000)value=value*10-(t-48);
else if(value>=0 && value<100000000)value=value*10+(t-48);
}
else
if(t=='u')value=-1*value;
else
if(t==29)value=value/10;
else
if(t==13)return value;//按输入键返回输入的数值
}
}
void getbit(long a,char str[64])
{//将十进数化成二进制并显示在屏幕上
int i,b;
long num;
char bit[17];
;
memset(bit,0,17);
ClearScreen();
num=a;
for(i=15;;i--)
{
if(i<0)break;
bit[i]=(a&1)+48;//取最右边一位数,并转成字符.
a=a>>1;//右移一位
}
sprintf(str,"%d的补码为%s",num,bit);//将值写回主函数answer变量当中.
}
void main()
{
char answer[64];
getbit(inputnum(),answer);//显示结果
TextOut(0,0,answer);
getchar();
}
运行1:输入12345
12345的补码为0011000000111001
运行2:输入-12345
-12345的补码为1100111111000111
利用位运算可以很直观地把内存中的数据展现出来。并且位
运算有时能完成其它操作不能完成的操作,所以位运算一定要掌握好。
习题8-2:
1、 检查下列数是bit6是否置位。
01110010 210 0x 3a
2、 用0x56对下列数据进行异或加密,然后解密。
10110100 255 0xd8
3、把45写在int的高八位中,再把0x45写在同一个int数据的低八位中,最后把这个int数据转成十进数。
第三节 位段
我们在本章第一节就提到了,位是内存中最小的单位,而我们存放信息都是以字节为单位,实际上,有时存储的信息不止一个字节,如int数据占用两个字节,long数据占用四个字节等。而有些信息却不必用一个字节来表示,如:我们用1表示“真”,用0表示“假”,即只要1,0两个数据,实际上只要一位就可以了,如果用一个字节,太浪费空间了。又如:GVM支持16位色,即两个字节表示屏幕上的一个点,两个字节就应该有16位二进数,而屏幕对应有三种颜色,所以GVM的颜色是RBG=565的,什么意思呢?R=RED,即红色,B=BLUE即蓝色,G=GREEN即绿色,RBG=565它表示其中用的该数据的高五位表示红色的数据,用中六位表示蓝色的数据,用低五位表示绿色的数据,见下图:
像这种在一个结构单位中以位为单位来指定其成员所占内存长度的,其中每个成员所占的内存就叫做位段或位域(bit field)。
例 7-3-1 :像上面的例子来说,用一个十六位数据来存放r b g三个成员,使其最终值为0100101101101100。那么要怎么才能往这三个位段里赋值呢?下面我们来看:
分析:首先我们可以先定义r=00001001,b=00011011,g=00001100,再定义一个变量long color(由于我们处理的颜色值可能会使int型数据最高位为1,造成数据溢出,所以我们定义一个long变量,但只用其低十六位),先把r的值赋给color,使其值为0000000000001001,然后我们让color的每位进行左移6位的操作(因为b的长度为6bit),使color的值变成:0000001001000000,这时我们再让color与b进行位或,让color变成:0000001001011011,然后再把color中各位向左移5位(因为g的长度为5bit),使其值变成:010010110110000,最后再让color与g进行位或,使color的值变成:0100101101101100。我们先把r,b,g的值转成十进数或十六进数,r=0x09,b=0x1b,g=0x0c,下面我们来看程序,
void bit(long a)
{//将十进数化成二进制并显示在屏幕上
int i,b,c,d;
ClearScreen();
TextOut(0,0,"二进数:");
TextOut(0,20,"十进数:");
DigitOut(56,20,a);
for(i=15;;i--)
{
if(i<0)break;
b=a&1;//最右边一位数就是余数,
a=a>>1;//右移一位相当于除以二
DigitOut(56+i*8,0,b);//显示当前余数
}
}
void main()
{
long color;
char r,b,g;
r=0x09;
b=0x1b;
g=0x 0c ;
color=r;//将r的值赋给color
color=color<<6;//让color的每位进行左移6位
color=color|b;//让color与b进行位或
color=color<<5;//让color的每位进行左移5位
color=color|g;//让color与g进行位或
bit(color);//显示结果
getchar();
}
运行:
二进数:0100101101101100
十进数:19308
看看,结果与我们事先预算的一致。
有些时候,我们在从函数中返回的值不止一个的时候,我们就可以利用位段来存放返回值。如:
例 7-3-2 :写一个函数,让这个函数产生两个0~255之间的随机数,把这两个数带回主函数。
分析:假设产生的两个随机数分别为11001000(a=200)和01100100(b=100)。那么我们先把11001000赋给一个int型数并向左移8位,使之变成1100100000000000再与01100100进行位或,变成1100100001100100。然后把这个返回值带回主函数,再进行拆解,拆解的方式是:先把1100100001100100向右移8位,变成0000000011001000,赋给一个char型的会把低八位赋给char型数据,即11001000,这个数就是a,而如接将1100100001100100赋给一个char型数据以后就成了01100100,这个数就是b,所以程序可以这么写:
long Rand ()
{
char a,b;
a=200;
b=100;
return a<<8|b;
}
void main()
{
int num;
char a,b;
num= Rand ();
a=num>>8;
b=num;//&a;
DigitOut(0,0,a);
DigitOut(0,20,b);
getchar();
}
运行:
200
100
测试成功,下面我们把Rand函数中的相关语句换成题目要求中的语句:
long Rand ()
{
char a,b;
a=rand()%256;
b=rand()%256;
return a<<8|b;
}
void main()
{
int num;
char a,b;
num= Rand ();
a=num>>8;
b=num;//&a;
DigitOut(0,0,a);
DigitOut(0,20,b);
getchar();
}
运行:
205
38
程序就这么简单。请各位学习者下来多多阅读本节的内容,加以理解。
习题7-3:
1、写一个函数,让它按要求保留出一个int型数据中的一段数据不变,其它位置0,如:
void getbit(int value,int m,int n)
表示从value中保留从左边第m位开始到第n位结束的几个数据,其它位置0,然后以二进制串的形式输出结果。
2、产生一个int型的随机数,取它从左至右的所有奇数位上的数并存放在一个char型数据中,分别以二进制串和十进数显示出来。
3、主函数中产生两个0~10000之间的int型随机数,写一个函数,计算出这两个数的和与差,再把结果返回到主函数,并在主函数中按要求显示结果:如:假设两个分别为5000和3000,显示结果如下:
5000+3000=8000
5000-3000-2000
二进制 10010011 00101100 00010001
十六进制 0x56 0xdf 0x 8a