您好,欢迎来到华佗小知识。
搜索
您的当前位置:首页位运算

位运算

来源:华佗小知识


第13章 位 运 算

C语言之所以具有广泛的用途和强大的生命力,就在于它既具有高级语言的特点,又具有低级语言(汇编语言)的功能。第8章和第10章中介绍的指针运算以及本章将介绍的位运算正是C语言较其他高级语言的优势所在,它们是编写系统程序必不可少的功能。

所谓位运算是指进行二进制数的位运算。在系统软件中常要处理二进制位的问题。例如将一个存储单元中的二进制位左移或右移一位;两个数按位相加等。

13.1 二进制位运算概述

二进制位运算是属于二进制数的逻辑运算。在第3章中我们已经介绍了基本数据的逻辑运算,在此将进一步讨论按位进行的二进制数逻辑运算。

1.一位二进制数的逻辑运算

(1) 逻辑或运算。逻辑或运算是:如果一位二进制数变量A或B两者中至少有一个等于1,则结果为1,否则结果为0。

通常用加号“”表示“或”,运算规则为:

000 011 101 ABF

111 由上式可见,或运算与二进制加法的惟一差别在于111,而不是等于10,无进位关系。(2) 逻辑与运算。逻辑与运算是:只有当一位二进制数变量A与B都取1时,结果为1,

或运算又称为逻辑加。

否则结果为0。通常用乘号“∙”表示“与”,运算规则为:

0∙00 0∙10 1∙00 A∙BF

1∙11 可以看出其运算规则与代数乘法相同,故逻辑与也称为逻辑乘。

(3) 逻辑非运算。逻辑非运算是:输出结果与输入值相反,记为FA,上面的横杠表示非,读作“A非”,其运算规则为:

10

1

2.N位二进制数的逻辑运算

上面讨论了一位二进制数的逻辑运算,对两个N位二进制数进行逻辑运算,是对应的各位进行,位与位之间无进位、借位、溢出等关系。

例13.1 有两个八位二进制数x、y,x10101101,y11001011,分别求xy、x∙y和x。

10101101 10101101

x01010010

∙) 11001011 ) 11001011

xy11101111 x∙y  10001001

从上例中可以看出,对两个N位二进制数进行逻辑或运算,凡对应数位中有1者,结果对应位均为1;对两个N位二进制数进行逻辑与运算,凡对应数位中有0者,结果对应位均为0;对一个N位二进制数进行逻辑非运算,就是按位求其反值。

3.逻辑异或运算

上面已讨论了逻辑或、逻辑与、逻辑非运算,它们是逻辑运算中的基本运算,也就是说其他更复杂的逻辑运算都是由它们结合而成的。逻辑异或运算就是复合逻辑运算之一。由于逻辑异或运算是一个常用的复合逻辑运算,所以我们有必要专门加以讨论。

逻辑异或运算的逻辑功能为: A⊕B  A∙B+A∙B

该逻辑运算也称按位模加法,简称按位加,通常用“⊕”表示。逻辑异或运算规则为:

0⊕00 0⊕11 1⊕01 1⊕10

可以看出,它恰好是一位二进制数不考虑进位的加法运算。 例13.2 x11001001,y10101011,求X⊕Y。

11001001 ⊕) 10101011 x⊕y01100010 A⊕BF

13.2 位 运 算 符

C语言中提供的位运算符包括一元运算符:

 (位求反)

和二元运算符:

&(位与) |(位或) ^(位异或) (左移) (右移) 位运算符的功能是对其操作数按其二进制形式逐位地进行逻辑运算或移位操作。由位运算的特点决定了操作数只能是整数型或字符型的数据,不能为实型数据。

为了举例方便,现设有两个变量: unsigned char c135, d43;

则变量c和d的二进制表示分别为10000111和00101011。

1.位运算符的操作

(1) 按位求反:运算符“”将其操作数逐位地取其反码,即将原来为1的位变为0,为0的位变为1(逻辑非运算)。例如:

c1000011101111000

故c的结果为01111000,即十进制数120。

(2) 按位与:运算符“&”将其两个操作数的对应位逐一进行逻辑与运算。例如: c&d(10000111)&(00101011)00000011

(3) 按位或:运算符“|”将其两个操作数对应位逐一进行逻辑或运算。例如: c | d(10000111) | (00101011)10101111

(4) 按位异或:运算符“^”将其两个操作数对应位逐一进行逻辑异或运算。例如: c^d(10000111)^(00101011)10101100

(5) 按位左移:运算符“”将其左操作数向左移动右操作数所要求的位数,右边空出的位补以0。其一般形式为:

OPRDn

它的移位操作过程为:

舍去n位 向左移动n位 OPRD 补n个0 由于移位运算符右操作数表示移动的位数,所以它必须是一个整型表达式。例如,位运算d1的功能就是把d的数据左移一位。若d00101011(十进制43),则语句“d1;”执行完后,d的值为01010110(十进制数86)。移位操作的过程如下所示:

0 | 0101011 0

此位舍去 补0

又如位运算d2的结果为10101100,即十进数172。从这两个例子可以看出:将一个数左移一位,相当于将该数乘以2;左移两位,相当于该数乘以4。一般说来,将一个数左移n位,就相当于将该数乘以2n。所以,在程序中常用左移位来进行快速的乘法运算。但用这种方法进行乘法运算时,同样要注意溢出问题。例如,若c10000111(十进制135),则语句“c1;”执行后,c的值为00001110,它是十进制数14,而不是c的二倍数270。这是由于移位时将c的最高位移出之故。移位操作的过程如下所示:

1 | 0000111 0 此位舍去 补0

另外还应注意的是:若被移位的是一个带符号数,移位后可能使该数的符号发生变化。 (6) 按位右移:运算符“”将其左操作数向右移动其右操作数所要求的位数,右边空出的位补以0或符号位。其一般形式如下:

OPRDn

它的移位操作过程为:

对无符号数,补n个0 对带符号数,补n个符号位 向右移动n位 OPRD 舍去n位

例如,若c10000111(十进制135),则语句“c2;”执行后,c的值为00100001(十进制33)。移位操作的过程如下所示:

00100001 | 11 补2个0 此2位被舍去

与左移操作相对应,将一个数右移n位,相当于将该数除以2n,其小数部分被忽略。这与整型和字符型数据的除法(整除)完全一致,所以在程序中常用右移位来进行快速的除法

运算。

关于按位右移的补位要说明的是:对于无符号数,右移时左边高位补0。对于带符号数,如果原符号位为0(该数为正),则左边移入0;如果符号位为1(即负数),则左边移入0还是1,要取决于所用的计算机系统。有的系统移入0,有的移入1。移入0的称“逻辑右移”,即简单右移;移入1的称“算术右移”。Turbo C编译系统就是采用算术右移。

2.关于位运算符的说明

(1) 位运算符的优先级。位运算符中按位取反运算符的优先级最高,它比算术运算符、 关系运算符、逻辑运算符和其他位运算符都高。例如:A&B,先进行A运算,然后进行&运算。其次就是左移运算符和右移运算符,它们比关系运算符优先级还要高,比算术运算符低。其余的位运算符的优先级都低于算术运算符和关系运算符,但高于逻辑运算符。位运算符的优先级由高到低依次为:

 、 & ^ | (2) 位运算符与赋值运算符相结合可以组成复合的赋值运算符。它们有: & |   ^

例如,初始值c10000111,d00101011,则c&d相当于cc&d,运算后c00000011,即十进制数3;c2相当于cc2,运算后c00100001,即十进制数33。

(3) 如果两个类型长度不同的数进行位运算,则需要进行补位。如a&b,a为long型,b为int型,系统会将二者右端对齐并对较短的数b进行左补位。如果b为正数,则左侧16位补满0;若b为负数,左端应补满1;如果b为无符号整型数,则左侧补满0。

3.位运算的应用举例

例13.3 设计一个函数,给出一个数的原码,得到该数的补码。

分析:根据补码的定义,一个正数的补码等于该数的原码,一个负数的补码等于该数的反码加1。假设a为16位整数,则步骤为:

(1) 判别给定整数a是正数还是负数。方法是: za&0x8000;

若z等于0,则a为正数;若z为非0,则a为负数。

(2) 如果z非0,则z~a10x8000;否则za。 (3) 返回z。 程序如下: #include stdio.h

void main() { /* 求一个二进制数的补码 */

int a, getbm(int); printf(scanf(printf(}

int getbm(int value) { /* 求一个二进制数补码的函数 */

int z;

zvalue&0x8000; /* 求value的最高位(符号位) */

请输入一个十六进制数: \\nx

, &a);

, getbm(a));

\它的补码是: x\\n

);

if (z0) zvalue; /* 符号位为0,value为正数 */ else { /* 符号位为1,value为负数 */

zvalue1;

zz0x8000; /* 恢复符号位1(负号) */ } return z; }

运行情况如下:

请输入一个十六进制数: 4e5CR

它的补码是: 4e5

请输入一个十六进制数: d555CR

它的补码是: aaab

例13.4 将十六进制数转换为二进制数。

C语言的printf函数提供了x、d、o方式输出一个整数(即十六进制、十进制、八进制形式)。有时我们希望知道某个数的二进制数是什么。由于不能直接用printf函数进行输出,因此需要人工转换。我们可以用位运算符来实现十六进制转换成二进制的功能。

分析:

(1) 为了获得一个16位数value的每一位,可以设置一个屏蔽字mask与该数进行&运算,从而保留(取出)所需的一个位的状态。例如,测试最高位的屏蔽字为mask=0x8000,其二进制为1000000000000000,如果mask&value为0,则最高位为0,否则为1。

(2) 从最高位(第15位)开始,取mask0x8000(二进制为1000000000000000),每测试一次后,mask右移一位,直到第0位(mask0x0000)。

(3) 把mask&value每次测试的结果放在bit中,并输出。 程序如下: #include stdio.h

void main( ) {

int j, value, bit;

unsigned int mask0x8000; printf (scanf(printf(

请输入你的数据(16进制): \\nx

, &value);

, value);

/* 从第15位到第0位逐位进行测试是1还是0,并输出结果 */

for (j0; j16; j) { /* 从高位到低位逐位产生二进制位并输出 */

bit(mask&value)?1: 0; printf(

d

, bit); 

);

if (j7) printf(mask1; }

);

十六进制数4x的二进制形式是:

printf(}

\\n);

运行情况如下:

请输入你的数据(16进制): 57af CR

十六进制数57af的二进制形式是: 0101011110101111

13.3 位 段

在许多应用中,被处理数据的数值范围可能很小,比如,常用1来表示逻辑真,用0来表示逻辑假,表示该逻辑值的变量只需要用一位二进制数就可以。再如,表示一个范围为0

15的无符号数的变量也不需要两个字节,而只需四位就够了。C语言的位段(又称位域)就是为存储这样的数据而提供的一种节省内存的手段。

1.位段的概念和定义

(1) 所谓位段就是以位为单位定义结构体类型成员的长度。例如: struct node {

int num;

unsigned int sex: 1; /* 位段成员sex */ unsigned int age: 7; /* 位段成员age */ struct node *next; } x;

注意:位段成员必须定义为unsigned int型。其中,sex和age是位段成员,sex占1个二进制位,它的值只能是0或1,但这对表示性别已经够了;而age占7个二进制位,它的取值范围为0127,作为表示年龄的变量,这一取值也是可以的。这比sex和age都用int型节省了3个字节。该结构的存储形式如图13-1所示。

num sex age next

(a) 结构体变量x的存储形式

sex age (b) 位段sex和age的存储形式

一个字节

图13-1 位段的存储结构

(2) 在定义位段时,也可以各位段不恰好占满一个字节。如: struct packed_data {

unsigned a: 2; /* 位段成员a */ unsigned b: 6; /* 位段成员b */ unsigned c: 2; /* 位段成员c */ unsigned d: 3; /* 位段成员d */ int i; } data1;

data1

a b c d - - - i 图13-2 有空隙的位段存储结构

结构体变量data1占内存的情形见图13-2所示。其中a、b、c、d共占13位,不满2个字节,其后有3位空闲;后面的i为int型,从下一个字节开始,占2个字节。

2.位段的引用方法

对于结构体变量的位段成员,它与其他类型的成员没有什么不同,既可以通过结构体变量来引用位段成员,也可以通过指向结构体变量的指针来引用位段成员。

例如,对于前面定义的结构体类型node和该类型的变量x,如下语句是对其位段成员的赋值语句:

x.sex1; x.age57;

在赋值时应注意每一个位段成员能存储的最大值,如x.sex成员占1位,只能赋值0或1,如果赋予大于1的值就会产生溢出。

关于位段引用的几点说明:

(1) 不能使用位段成员的地址,这是因为地址是以字节或字为单位的。如“&x.sex”、“&x.age”都是不正确的使用。

(2) 位段被定义为unsigned int型,可以当作整型进行输入、输出和运算。例如: x.agex.age20;

printf(性别: s, 年龄: d\\n

都是合法的。

, (x.sex)?

:

, x.age);

习 题

13.1 设a4,b7,c9,求下列表达式的值。

(1) a | b&c (2) a&b | ~c (3) a^b^c (4) a | b&&c 13.2 设y28,求下列表达式的值。

(1) y&y (2) y&0x0f (3) y2 (4) y3 (5) y5 (6) y^y 13.3 请写出以下程序的运行结果。 (1) #include stdio.h void main() {

char a6, b4; aa^b; bb^a; aa^b; printf( }

(2) #include stdio.h void main() {

unsigned a1200, b, c; int n5; printf( printf( can;

ao\\nbo\\n

, a); , b);

ba(16n);

ad, bd\\n

, a, b);

printf( c|b; printf( }

co\\nco\\n

, c); , c);

13.4 试编写一个函数从一个16位的单元中取出某一位。函数的原型为: int getbit(int value, int n)

其中value为一个整型数据,n为欲取出的位序。

13.5 编写两个函数lrmove、rrmove分别实现左、右循环移位。函数的原型为: int lrmove(int value, int n) int rrmove(int value, int n)

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- huatuo0.cn 版权所有 湘ICP备2023017654号-2

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务