1、1,第8章 位 运 算8.1 概 述语言是为描述系统而设计的,因此它应当具有汇编语言所能完成的一些功能。语言既具有高级语言的特点,又具有低级语言的功能,因而具有广泛的用途和很强的生命力。第7章介绍的指针运算和本章将介绍的位运算就很适合于编写系统软件的需要。所谓位运算是指进行二进制位的运算。在系统软件中,常要处理二进位的问题。例如,将一个存储单元中的各二进位左移或右移一位,两个数按位相加等。语言提供位运算的功能,与其它高级语言(如PASCAL)相比,它显然具有很大的优越性。为了使没有学过汇编语言的读者对二进制运算能有较好的理解,先介绍有关位的知识。 一、字节和位大多数计算机系统(包含IBM-PC
2、系列)的内存储器是由许许多多被称为“字节”(t)的单元组成的。,2,每一个字节有一个地址。一个字节由若干个二进制位(t)组成。若干个字节组成一个存储单元,称为“字”()。每一个存储单元存放一个数据或一条指令。一个字节一般由个二进位组成,其中最右边的一位称为“最低有效位”或“最低位”,最左面的一位称为“最高有效位”或“最高位”,每一个二进位的值是或。在微型机中一般以个字节存放一个实数,以个字节存放一个整数。最左边的一位(最高位)用作数的符号位。为了表示数值,可以采用不同的方法,一般有:原码、反码和补码。二、原码只将最高位作符号位(以代表正,1代表负),其余各位代表数值本身的绝对值(以二进制表示)
3、。如:,3,的原码为: |代表正一的原码为: 0 1 1 1 | 代表,负 二进制的代表十进制的,为简化起见,我们只用一个字节存放一个整数,如果用两个 字节存放一个整数,情况是一样的,无非把表示成 而已。十的原码为 一的原码为 显然,和一表示的是同一个数,而在内存中却有两个不同的表示。也就是说,的表示 不唯一,这不适合于计算机的运算。,4,三、反码一个数如果值为正,则它的反码与原码相同,如:的反码为。一个数的值如为负,则符号位为,其余各位是对原码取反。如:一的反码为:十的反码为:一的反码为:同样,的表示不唯一。用反码表示的最大值为,最小值为-。的反码为: 一的反码为: 用反码表示数,现已不多用
4、。 四、补码原码和反码都不便于计算机内的运算,因为在运算中要单独处理其符号。,5,例如,对以原码表示的和一相加,必须先判断各自的符号位,然后对后位进行相应的处理,很不方便。因此,最好能做到将符号位和其它位统一处理。对减法也按加法来处理。这就是“补码”。“补码”的原理可以用时钟来说明,见图。如果要将时针从9点拨到点,可以向前拨,也可以向后拨,其表示如下:1211 110 29 - 38 47 5 6,6,一 (向后拨个字) (向前拨个字) 从图上可见,向前拨个字也能指向。这是由于钟是圆的,点的下一个小时是点。时钟是进制的,可以把点看成点,点就是1点,其实是进位后得到了十二进制数,其中第一个1是进
5、位,即高位,第二个1是低位。高位不保留,只保留低位,因此,点用十二进制数表示为,高位不保留,在时钟上就是点,用十进制数可表示为:一。对十进制数,如果想从得到结果值,可以用减法:一 已知的补数为一,即与互补。因此一可以改写为加法:+ 1,7,再去掉高位,得。在计算机中,以一个有限长度的二进位作为数的模,如果用1个字节表示一个数,一个字节为位,模为256。因为逢56就进,在内存中情况为- |00000000| - 进位被丢弃。 补码是这样规定的:正数:其原码、反码、补码相同。例如,的补码也是。负数:最高位为,其余各位为原码的相应位取反,然后对整个数加。例如:一的原码: 一的补码:第步: ,8,+1
6、 第步: -11111001 即对十各位取反加。也可以这样做:将该负数(不包括)先加;然后将其绝对值以 二进制表示;再对其求反。例如,一先加得,。对以二进制表示为11,再取反得,它就是一的补码,见表。-| 数值 | 原码 | 反码 | 补码 |-|-|-|-| +7 |00000111|00000111 |00000111|-7 10000111 11111000 11111001(表11.1),9,如果已知一个负数的补码,想将其转换为十进制数,可以:先对各位取反;再将其 转换成十进制;()加上负号,减。例如:对,显然是一个负数,取反得11,转换为十进制得,加负号得一,再减得一。也可以这样:补
7、码中的最高位不改动,其余各位取反加,这就得到原码。如,先变成,再加得,它是一的原码。的补码表示为:一的补码可以这样求()最高位为,其余各位为原码取反,即对求反得;加,得,进位被丢弃(因为一个字节只能容纳位,256 只能被存储为),,10,因此一的补码也是。可知,和一的补码表示是相同的。或者说的补码是唯一的。用补码进行运算,减法可以用加法来实现,如十一应得。可以将十的补码和一的 补码相加,就得到结果值的补码。十的补码: 1 1一的补码: -( 相 加) 1 0 0 0 0 0 0 1 进位被舍去。后面位就是1的补码如果将一,同样:一的补码: 十的补码: -(相加) ,11,是一1的补码。表是以补
8、码表示的数的一些例子。 表11.2 -数值 | 补码 -|-0 | 00000000 -1 | 11111111-2 | 11111110 -,12,-3 | 11111101-4 | 11111100 . | . (往下不断减1). | .-127 | 10000001-128 | 10000000 -|-1 | 000000012 | 00000010. | . | .(往下不断加1 ). | . 126 | 01111110127 | 01111111 -,13,可以看出:以补码形式在一个字节(位)中存放一个数,最大值为,最小值为一。 即数的范围为一。计算机是以补码形式存放数的。位运算符
9、 C语言提供如表11.3所列出的位运算符。 -运算符 | 含义 -|-& | 按位与| | 按位或 | 按位异或 | 取反 | 右移| -,14,说明:位运算符中除以外,均为二目(元)运算符,即要求两侧各有一个运算量。运算量只能是整型或字符型的数据,不能为实型数据。下面对各运算符分别介绍如下:一、“按位与”运算符()参加运算的两个运算量,如果两个相应的位都为,则该位的结果值为,否则为。即:=:=;1=;1;例如:并不等于不等于,这是按位与。先把和以补码表示,再进行按位与运算。的补码: 5的补码: 1-,15,: 它是的补码。因此,的值得。 按位与有一些特殊的用途:清零。如果想将一个单元清零,即
10、使其全部二进位为,只要找一个数,它的补码形式中各位的值符合以下条件:原来的数中为1的位,新数中相应位为。然后使二者进行 运算。如:原有数为,另找一个数,设它为,它符合以上条件,即在原数为的位置上,它的位值均为。将两个数进行运算:00101011& 10010100-00000000,16,其道理是显然的。当然也可以不用10010100这个数而用其它数(如11)也可以,只要符合上述条件即可。 .取一个数中某些指定位。如一个整数(个字节),如只想要其中的低字节。只需将与()8按位与即可。见图。-a | 00 10 11 00 | 10 10 11 00 |- -b |00 00 00 00 | 1
11、1 11 11 11 |-c |00 00 00 00 | 10 10 11 00 |-图 11.2,17,=,b为八进制的,只取的低字节,高字节为如果想取两个字节中的高字节,只需:c= (表示八进制的 )。见图11。-a | 00 10 11 00 | 10 10 11 00 |-b | 11 11 11 11 | 00 00 00 00 |-c | 00 10 11 00 | 00 00 00 00 |-图 11.3,18,.要想将哪一位保留下来,就与一个数进行运算,此数在该位取,如:有一数 1 010 1 00,想把其中左面第、.位保留下来,可以这样 01010100 (十进制数84)
12、(&) 00111011 (十进制数59)-00010000 (十进制数16)即=,=,=b=6二、按位或运算符(| )两个相应位中只要有一个为,该位的结果值为。即:|;|=; |0,;|=。例如| 将八进制数与八进制数进行按位或运算。,19,00110000 (|) 00001111-00111111- 把低位全置。如果想使一个数a的低位改为,只需将a与1进行按位或运算可。按位或运算常用来对一个数据的某些位定值为.如是一个整数(1位),有表达式 | 则低位全置为。高位保留原样。三、“异或”运算符( ),也称运算符它的规则是:参加运算的两个相应位同号,则结果为(假);异号则为(真)。,20,即
13、:=: =; 1;如:00111001 (十进制数5,八近制数) 00101010 (十进制数42 , 八进制数 0 5 2 ) 00010011 (十进制数19 , 八进制数0 2 3 ) 即5,结果为(八进制数)。异或的意思是:判断两个相应的位值是否为“异”,为“异”(值不同)就取真(),否则为假()。下面举例说明运算符的应用:()使特定位翻转假设有,想使其低位翻转,即:变为,变为。,21,可以将它与进行运算,即 结果值的低位正好是原数低位的翻转。要使哪几位翻转就将与其进行运算的数中该几位置为即可。这是因为原数中值为的位与进行运算得,原数中的位值与进行运算的结果得。()与相,保留原值如00
14、001010 00000000-00001010,22,因为原数中的与进行运算得,得,故保留原数。()交换两个值,不用临时变量假如,b=。想将和b的值互换,可以用以下赋值语句实现:=b;ba;=b; 可以用下面的竖式来说明:a= 011() b= 100-a= 111 -(ab的结果,a已变成),23,() b= 100-b= 011 -(ba的结果,b已变成) ( ) a= 111-a= 100 - (ab的结果,a已变成)即等效于以下两步:()=bb=bb=它相当于上面的前两个赋值语句:“a;”和“a;”。 的结果为,因为同一个数与本身相,结果必为。现在b已得到的值。在上式中除了第一个b以
15、外,其余的a,都是指原来的、再执行(b)(b)=bb=b.得到b原来的值。四、“取反”运算符(),24,是一个单目(元)运算符,用来对一个二进制数按位取反,即将变,1变。例如.是对八进制数(即二进制数)按位求反。0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 11 1 1 1 1 1 1 1 1 1 1 0 1 0 1 0 即八进制数。因此,的值为八进制数,不要以为的值是-5。下面举一例说明运算符的应用。想使一个数的最低一位为,若一个整数为位,可以用 即二进制数,如果的值为八进制数, 的运算可以表示如下,25,00 00 00 00 00 11 11 01 (&)11 11 11
16、11 11 11 11 1000 00 00 00 00 11 11 00 的最后一个二进位变成。但如果将源程序移植到以位存放一个整数的计算机系统(如一)上,由于一个整数用个字节(位表示),想将最后一位变成就不能用了。读者可以自己算一下,当时,的结果是什么?为了适应以位存放一个整数的计算机系统,应改用 这样改动使移植性差了,可以改用 它对以位和以位存放一个整数的情况都适用,不必作修改。因为在以个字节存储一个整数时,26,,1是(注意不等于一,弄清运算符和负号运算符的不同)。 的补码为 的补码为 1 1 1 1 0 在以个字节存储一个整数时,1是 。运算符的优先级别比算术运算符、关系运算符、逻辑
17、运算符和其它位运算符都高,例如:b先进行运算,然后进行运算。,27,五、左移运算符()用来将一个数的各二进位全部左移若干位。例如 将a的二进制数左移位,右补。若,即二进制数,左移位得00 11 11 00 ,即十进制数。,28,高位左移后溢出,舍弃不起作用。左移位相当于该数乘以,左移位相当于该数乘以。上面举的例子=,即乘了.但此结论只适用于该数左移时被溢出舍弃的高位中不包含的情况。例如,假设以一个字节(8位)存一个整数,若为无符号整型变量,则时,左移一位时溢出的是,而左移位时,溢出的高位中包含。 可以看出,a=左移位时相当于乘,左移2位后,值等于0。左移比乘法运算快得多,有些编译程序自动将乘的
18、运算用左移一位来实现,将乘n的幂运算处理为左移位。,29,六、右移运算符()表示将a的各二进位右移位。移到右端的低位被舍弃,对无符号数,高位补。如=时: 为 00001111 , a 2为 00000011 : 11 此二位舍弃 右移一位相当于除以,右移位相当于除以n。在右移时,需要注意符号位问题。对无符号数,右移时左边高位移入。对于有符号的值,如果原来符号位为(该数为正),则左边也是移入,如同上例表示的那样。如果符号位原来为(即负数),则左边移入还是,要取决于所用的计算机系统。有的系统移入,有的移入。移入0称为“逻辑右移”,即简单右移。移入1的称为“算术右移”。例如,的值为八进制数。,30,
19、: 1 : (逻辑右移时)1: (算术右移时) 在有些系统上,1 得八进制数,而在另一些系统上可能得到的是。urbo C和其它一些编译采用的是算术位移,即对有符号数右移时,左面移入高位的是,31,七、位运算符与赋值运算符结合可以组成扩展的赋值运算符如: =,| =, =, =, = 例如, = 相当于b。 = 相当于:。八、不同长度的数据进行位运算如果两个数据长度不同(例如long型和t型)进行位运算(如b而为og型,b为t型),系统会将二者按右端对齐。如果b为正数,则有左侧1位补满。若b为负,左端应补满。如果b为无符号整数型,则左侧添满,32,$11.3位运算举例例.1取一个整数从右端开始的
20、位可以这样考虑:1.先使右移位。目的是使要取出的那几位移到最右端。右移到右端可以用下面方法实现:a ( 7 4 + 1 ), 即a 42.设置一个低4位全为0的数。,33,可用下面方法实现。() 即将一个全的数左移位,这样右端低位为。见下面所示: : 1 : 0 ():,34,将上面二者进行运算。即:()根据上一节介绍的方法,与低位为的数进行运算,就能将这n位保留下来。程序如下:( ) ,b,;(”n”,35,b=4c= ();= ct(”nn”,a,);运行情况如下:(的值)(的值)的二进制数为 11 1,最后得到的结果为01 1 1,,36,即八进制数。可以任意指定从右面第位开始取其右面位
21、。只需将程序中的”b = ”改成“= ( - )”,以及将”c = ()”改成” c =(n)”即可。例.2循环移位。要求将进行右循环移位。即将中原来左面(1-)位右移位,原来右端位移到最左面n位。今假设用两个字节存放一个整数。 为实现以上目的可以用以下步骤:,37,将的右端位先放到b中的高位中。可以用下面语句实现:=(-n);将右移位,其左面高位位补。可以用下面语句实现:=;将与b进行按位或运算。即c = | b ; 程序如下:( )g ,b,; int ;,38,(”a=o,=”,);b(- n );=n ;= c | b ;t(”,);运行情况如下:=,= 75,39,运行开始时输入八进
22、制数,即二进制 ,1 1 0 1 1 1 1 1 1 0 1 0 1 1循环右移位后得二进制数,即八进制数。同样可以左循环位移。$。4 位 段有时存储一个信息不必用一个或多个字节,可以在一个字节中放几个信息。例如,”真”或 ”假”用或表示,只需1位即可。可以有以下两个办法:一人为地在一个字节中设几项。例如:、b、分别占位、位、位、位。,40,如果想将的值变为(设c原来为),可以这样:将数左移位,使成为右面起第位。将与按位或即可(设t中第位原来为)。如果的原值不为,应先使之为0 . 可以用下面方法: 的二进制表示为:,41,11 11 11 11 00 00 11 11 a b c d 也就是使
23、第位全为,其它位全为。它与进行运算,使第位为,其余各位保留 的原状。这个称为“屏蔽字”,即把c以外的信息屏蔽起来,不受影响,只使c改变为。 但要找出和记住这个数比较麻烦,可以用,42,data data (); 是的最大值, c共占位,最大值为即。 是将移到位。再取反,就使位变成,其余位全是。即:15: 0000000000001111 15 4: 0000000011110000 ( 15 4 ): 1111111100001111 这样可以达到对c清,而不必计算屏蔽码的目的。将上面几步结合起来,可以得到:,43,datadata(154)|(n&15)4 赋以位为 为应赋给的值(例如)。
24、15的作用是只取的右端位的值,其余各位置,即把放到最后位上,( ) ,就是将置在位上。见下面: data ( 15 4 ):,44,11011011 0000 1010 (n&15)4: 00000000 1100 0000 (| 运算) 11011011 1100 1010 可见,data 的其它位保留原状未改变,而第位改变为(即)了。但是用以上办法给一个字节中某几位赋值太麻烦了。可以用下面介绍的位段的方法。,45,二、位段所谓位段是以位为单位定义长度的结构体类型中的成员。例如:struct packed- dataunsigned a:2;unsigned b:6;unsigned c:4
25、;unsigned d:4;int Idata; 其中,b,、d,分别占位、位、,46,位、位。为整型。共占个字节。也可以使各个位段不恰好占满一个字节。如:struct packed-data unsigned a:2; unsigned b:3; unsigned c:4;int I;stuct packeddata data ;,47,其中、b.共占位,占个字节多,不到个字节。它的后面为int 型,占个字节,在、b.之后的位空闲,从另一字节开头起存放。注意,在存储单元中位段的空间分配方向,因机器而异。在中,由右到左分配.对位段中的数据引用的方法。如:data.a =2;data.b =7;
26、data.c =9;注意位段允许的最大值范围。如果写,48,data.a=8 就错了。因为它只占位,最大值为。在此情况下,自动取赋予它的数的低位。例如,的二进制数形式为,而data . a 只有位,取 1000 的低2位, data . a 得值。关于位段的定义和引用,有几点要说明:若某一位段要从另一个字开始存放。可以用以下形式定义: unsigned a:1; 一个存储单元,49,unsigned b:2; unsigned :0;unsigned c:3; (另一单元)本来、b, c应连续存放在一个存储单元(字)中,由于用了长度为的位段,其作用是使下一个位段从下一个存储单元开始存放。因此,现在只将、存储在一个存储单元中,另存放在下一个单元。.一个位段必须存储在同一存储单元中,不能跨两个单元。,50,如果第一个单元空间不能容纳下一个位段,则该空间不用,而从下一个单元起存放该位段。可以定义无名位段。如: :; :;(这两位空间不用) :; :; 在a后面的是无名位段,空间不用。,51,4.位段的长度不能大于存储单元的长度,也不能定义位段数组。位段可以用整型格式符输出。如:(”,”,.,.b,. ): 当然,也可以用u、o、x等格式符输出。位段可以在数值表达式中引用,它会被系统自动地转换成整型数。如data.a+5/data. 是合法的。,