540 likes | 654 Views
第 13 章 位运算. 13.1 位运算符和位运算. 13.1 位运算符和位运算. 由于 C 语言是介于高级语言和汇编语言之间的一种计算机 语言,它可用于开发系统软件,并且可以直接对地址进行运算 ,因此, C 语言提供了位运算的功能。 所谓位运算是指二进制位的运算。在系统软件中,常常需 要处理二进制位的问题。例如,将一个存储单元中存储的数据 的各二进制位,左移位或右移位,两个数按位相加等。位运算 是 C 语言不同于其它高级语言的又一特色。. 13.1 位运算符和位运算.
E N D
13.1 位运算符和位运算 • 由于C语言是介于高级语言和汇编语言之间的一种计算机 • 语言,它可用于开发系统软件,并且可以直接对地址进行运算 • ,因此,C语言提供了位运算的功能。 • 所谓位运算是指二进制位的运算。在系统软件中,常常需 • 要处理二进制位的问题。例如,将一个存储单元中存储的数据 • 的各二进制位,左移位或右移位,两个数按位相加等。位运算 • 是C语言不同于其它高级语言的又一特色。
13.1 位运算符和位运算 • 前面介绍的各种运算都是以字节作为最基本单位进行的。但在很多系统程序中常要求在位(bit)一级进行运算或处理。C语言之所以具有广泛的用途和强大的生命力,就在于它既具有高级语言的特点,又具有低级语言的功能。因而C语言提供了位运算的功能,这使得C语言也能像汇编语言一样用来编写系统程序。
13.1 位运算符和位运算 13.1.1 位运算符 位运算符的功能是对其操作数按其二进制形式逐位地进行逻辑运算或移位运算。由位运算的特点确定操作数只能是整数类型或字符类型的数据,不能是实型数据。C语言提供6种位运算符。
13.1 位运算符和位运算 表13-1 位运算符
13.1 位运算符和位运算 • 13.1.2 按位与运算 • 按位与运算符“&”是双目运算符。其功能是参与运算的 • 两数各对应的二进位相与。只有对应的两个二进位均为1时, • 结果位才为1,否则为0。 • 0&0=0; 0&1=0; 1&0=0; 1&1=1 • 参与运算的数以补码方式出现。 • 例如:9&5可写算式如下: • 00001001 (9的二进制补码) • & 00000101 (5的二进制补码) • 00000001 (1的二进制补码) • 可见9&5=1。 • 按位与运算具有以下特征:
13.1 位运算符和位运算 • (1)任何位上的二进制数只要和0进行与运算,该位即被屏蔽(清零)。 • (2)任何位上的二进制数只要和1进行与运算,该位保留原值不变。 • 利用这一特征可以实现如下功能: • (1)清零。 • 如果想将一个单元清零,也就是使其全部二进制位为0,只需与0 • 进行与运算,即可达到清零的目的。 • 例如:12&0 • 00001010 (10的二进制补码) • & 00000000 (0的二进制补码) • 00000000 (0的二进制补码)
13.1 位运算符和位运算 • (2)取一个数的某些指定位。 • 如有一个整数a(2个字节),要想高8位清0,保留低8位,可 • 进行a&255运算(255的二进制数为0000000011111111)。如果想取2个 • 字节中的高位字节,可进行a&65280(65280的二进制数为 • 1111111100000000)。 • (3)要想哪一位保留下来,就与一个数进行与运算,此数在该位取 • 1,其它位取0。 • 例如,85(85的二进制数为01010101),想把其中左边第3、4 • 、7、8位保留下来,设计一个数,其左边第3、4、7、8位为1其它位 • 为0,即十进制数51。将这两个数进行位与运算即可。
13.1 位运算符和位运算 • 例13.1 验证26&108的结果。 • 分析:a=00011010(十进制数26),b=01101100(十进制数108),则 • a&b=00001000(十进制数8)。 • 00011010 • & 01101100 • 00001000 • 根据上述分析编写的程序如下: • #include <stdio.h> • void main() • { int a=9,b=5,c; • c=a&b; • printf("a=%d\n b=%d\n a&b =%d\n",a,b,c); • }
13.1 位运算符和位运算 • 例13.2 编写一个程序,对两个整型变量、整型常量分别进行按位 • 与运算,并输出它们的值。 • #include <stdio.h> • void main() • { int x,y; • x=25;y=568; • printf("x&y:%d\n ",x&y); • printf("3&14:%d\n ",3&14); • printf("-3&14:%d\n ",-3&14); • printf("12&12:%d\n ",12&12); • }
13.1 位运算符和位运算 • 13.1.3 按位或运算 • 按位或运算符“|”是双目运算符。其功能是参与运算的两数各对 • 应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为 • 1。 • 0|0=0;0|1=1;1|0=1;1|1=1 • 参与运算的两个数均以补码出现。 例如: • 9|5可写算式如下: • 00001001 • | 00000101 • 00001101 (十进制为13) • 可见9 |5=13。
13.1 位运算符和位运算 • 按位或运算具有以下特征: • (1)任何位上的二进制数只要和1进行或运算,该位即为1。 • (2)任何位上的二进制数只要和0进行或运算,该位保留原值不变。 • 利用这一特征可以实现如下功能: • 按位或运算常用来对一个数的二进制位中一个或几个指定位置1。 • 对应于a要置1的位,b对应的位为1,其余位为0。则a与b或就可以使a • 中指定位置1。 • 例如: • a=01100000,要使a的后4位置1,则可设置b后4位为1,其余位为0,即b=00001111。 • 01100000 • | 00001111 • 01101111
13.1 位运算符和位运算 • 例13.3 验证26|108的结果。 • 分析:a=00011010(十进制数26),b=01101100(十进制数108)。则 • a|b=01111110(十进制126)。 • 00011010 • | 01101100 • 01111110 • 根据上述分析编写的程序如下: • #include <stdio.h> • void main() • { int a=9,b=5,c;c=a|b;printf("a=%d\nb=%d\n a|b=%d\n",a,b,c); • }
13.1 位运算符和位运算 • 13.1.4 按位异或运算 • 按位异或运算符“^”是双目运算符。其功能是参与运算的两数各 • 对应的二进位相异或,当两对应的二进位相异时,结果为1。 • 0^0=0; 0^1=1; 1^0=1; 1^1=0 • 参与运算数仍以补码出现。 • 例如,9^5可写成算式如下: • 00001001 • ^ 00000101 • 00001100 (十进制为12)
13.1 位运算符和位运算 • 位异或运算有以下方面的应用: • (1)使特定位翻转。 • 假设有01101011,想使其低4位翻转,即1变为0,0变为1。可以 • 将它与00001111进行按位异或运算,结果为01100100,结果值的低4 • 位恰好是原数低4 位的翻转。01101011 • ^ 00001111 • 01100100 • 要使哪几位翻转,就将该几位置为1,其余位置为0,将原数与 • 这个数进行按位异或运算即可。这是因为原数中的1与1进行^运算得 • 0,原数中的0与1进行^运算得1。 • 例如,有01111011,想使第3至7位翻转,只要与00111110进行 • 按位异或运算即可。 • 01111011 • ^ 00111110 • 01000101
13.1 位运算符和位运算 • (2)与0进行异或运算,保留原值。 • 例如: • 13^0=13 • 00001101 • ^ 00000000 • 00001101 • (3)交换两个变量的值,不用中间变量。 • a=5,b=8。如果想使a与b的值交换,可用以下赋值语句来实现: • a=a^b; • b=a^b; • a=a^b; • a=0101 • ^ b=1000 • a=1101
13.1 位运算符和位运算 • a=1101 • ^ b=1000 • b=0101 (十进制5) • a=1101 • ^ b=0101 • a=1000 (十进制8) • 可见,变量a与b的值进行了交换。a=a^b,实际上进行了下面两 • 步运算: • ① b=b^a=b^(a^b)=b^a^b=a^b^b=a^(b^b)=a^0=a • ② a=a^b=(a^b)^a=a^b^a=(a^a)^b=0^b=b • 例13.4 验证按位异或26^108运算。 • 分析:a=00011010(十进制数26),b=01101100(十进制数108)。 • 则a^b=01110110(十进制118)。
13.1 位运算符和位运算 • 00011010 • ^ 01101100 • 01110110 • 根据上述分析编写的程序如下: • #include <stdio.h> • void mian() • { int a=26,b=108,c; • c=a^b; • printf("a=%d\n b=%d\n a^b=%d\n",a,b,c); • getch(); • }
13.1 位运算符和位运算 • 程序运行结果: • a=26 • b=108 • a^b=118
13.1 位运算符和位运算 • 13.1.5 按位求反运算 • 求反运算符“~”为单目运算符,具有右结合性。 • 其功能是,对参与运算的数的各二进制位进行“取反”运算。即: • 对应的二进制位为0时,结果为1,为1时,结果为0。参与运算数均 • 以补码出现。 • 例如,a=00000000 00011010(十进制数26), • 则~a=11111111 11100101(十进制数-27)。 • 0000000000011010 • ~ • 1111111111100101 • “~”运算符的优先级比算术运算符、关系运算符、逻辑运算符和 • 其它运算符都高。例如:~a&b,先进行~a运算,然后进行&运算。
13.1 位运算符和位运算 • 例13.5 验证按位求反运算~6。 • 程序如下: • #include <stdio.h> • void main() • { int a=6,b; • b=~a; • printf("a=%d\n~b=%xH",a,b); • } • 程序运行结果: • a=6; • -b=fff9H • 程序说明:程序是按照数据补码进行按位取反的,输出十进制 • 数据运算较大,所以本例以十六进制输出6(0000000000000110), • 则按位取反后~6(1111111111111001)。
13.1 位运算符和位运算 • 例13.6 编程输出按位取反运算的值。 • 程序如下: • #include <stdio.h> • void main() • { int x=-25; • unsigned int y=0; • printf("~25:%d\n ",~25); • printf("~x:%d\n ",~x); • printf("~y(1):%d\n ",~y); • printf("~y(2):%u\n ",~y); • } • 程序运行结果: • ~25:-26 • ~x:24 • ~y(1):-1 • ~y(2):65535
13.1 位运算符和位运算 • 13.1.6 左移运算 • 左移运算符“<<”是双目运算符。 • 其功能把“<<”左边的运算数的各二进位全部左移若干位,由 • “<<”右边的数指定移动的位数,高位左移后溢出,舍弃不起作用, • 低位补0。 • 例如:a<<4指把a的各二进位向左移动4位。 • 再如:a=00000011(十进制3),左移4位后为00110000(十进制48) • 。左移一位相当于该数乘以2,左移二位相当于该数乘以22=4,左移 • n位相当于该数乘以2n,上面举的例子3<<4=48,即3乘了24=16。
13.1 位运算符和位运算 • 例13.7 对整型变量b进行按位左移2位运算,输出左移运算后的值。 • 程序如下: • #include <stdio.h> • void main() • { unsigned a,b; • b=12; a=b<<2; • printf("a=%d\n",a); • } • 程序运行结果: • a=48
13.1 位运算符和位运算 • 13.1.7 右移运算 • 右移运算符“>>”是双目运算符。 其功能是把“>>”左边的运算数 • 的各二进位全部右移若干位,移到右端的低位被舍弃,对于无符号 • 数,高位补0。“>>”右边的数指定移动的位数。 • 例如:a=15 • a>>2表示把000001111右移为00000011(十进制3)。应该说明的是 • ,对于有符号数,在右移时,符号位将随同移动。当为正数时,最 • 高位补0,而为负数时,符号位为1,最高位是补0或是补1取决于编 • 译系统的规定。Turbo C和很多系统规定为补1。右移一位相当于该 • 数除以2,右移n位相当于该数除以2n。
13.1 位运算符和位运算 • 例13.8 先右移,后进行与运算。 • 程序如下: • #include <stdio.h> • void main() • { unsigned a,b; • printf("input a number:"); • scanf("%d",&a); • b=a>>5; • b=b&15; • printf("a=%d\tb=%d\n",a,b); • } • 程序运行结果: • input a number:21↙ • a=21 b=0
13.1 位运算符和位运算 • 13.1.8 不同长度的数据进行位运算 • 位运算的运算数可以是整型和字符型数据。如果两个运算数类 • 型不同时位数也会不同。遇到这种情况,系统将自动进行如下处理: • (1)先将两个运算数右端对齐。 • (2)再将位数短的一个运算数往高位补充,即无符号数和正整 • 数左侧用0补全;负数左侧用1补全;然后对位数相等的这两个运算 • 数,按位进行位运算 。
13.1 位运算符和位运算 • 例13.9 分析下面程序的运行结果。 • #include <stdio.h> • void main( ) • { char a='a',b='b'; • int p,c,d; • p=a; • p=(p<<8)|b; • d=p&0xff; • c=(p&0xff00)>>8; • printf("a=%d\nb=%d\nc=%d\nd=%d\n",a,b,c,d); • } 程序运行结果: a=97 b=98 c=97 d=98
13.1 位运算符和位运算 • 例13.10 编写一个程序,实现一个无符号整数的循环左移n位。 • 分析:先将无符号整数d高端的n位数通过右移操作移至低端的n位上(高端全为0),把结果存入中间变量a中。再通过左移运算将d左移n位(低端移入的全为0),把结果存入另一中间变量b中。最后利用按位或运算将这两个中间变量中的内容“拼装”在一起,完成循环左移功能。
13.1 位运算符和位运算 • #include <stdio.h> • unsigned int left(unsigned int d,int n) • { unsigned int a,b,c; • a=d>>(16-n); • b=d<<n; • c=a|b; • return(c); • } • void main() • { unsigned int d; • d=0x6271; • printf("%x\n",left(d,3)); • } 程序运行结果: 138b
13.1 位运算符和位运算 • 例13.11 编写一个程序,将一个无符号整型的前8位和后8位交换。 • 分析:首先将d右移8位得到高8位数,把结果存入中间变量a中 • 。再将d与0377(高8位全为0,低8位全为1)进行按位与低8位数, • 将其左移8位放到高端,把结果存入中间变量b中。最后将a和b进行 • 按位或得到最后结果。 • 根据上述分析编写的程序如下:
13.1 位运算符和位运算 • #include <stdio.h> • unsigned int swap(unsigned int d) • { unsigned int a,b,c; • a=d>>8; • b=(d&0377)<<8; • c=a|b; • return(c); • } • void main() • { unsigned int d; • d=0x6271; • printf("%x\n",swap(d)); • } • 程序运行结果: • 7162
13.2 位段结构 • 13.2.1 位段的概念 • 有些信息在存储时,并不需要占用一个完整的字节,而只需占 • 几个或一个二进制位。例如,在存放一个开关量时,只有0和1两种 • 状态,“真”或“假”用1或0表示,用一位二进制位即可。为了节省存 • 储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域 • ”或“位段”。所谓“位段”是把一个字节中的二进位划分为几个不同的 • 区域,并说明每个区域的位数。每个域有一个域名,允许在程序中 • 按域名进行操作。这样就可以把几个不同的对象用一个字节的二进 • 制位域来表示。 • 13.2.2 位段结构的定义和位段变量的说明
13.2 位段结构 • C语言中没有专门的位段类型,位段的定义要借助于结构体, • 即以二进制位为单位定义结构体成员所占存储空间,从而就可以按“ • 位”来访问结构体中的成员。位段结构的定义与结构体定义相仿,其 • 一般形式为: • struct位段结构名 • { • 位段列表 • }; • 其中位段列表的形式为: 类型说明符 位段名:位段长度 • 例如: • struct bs • { int a:8; • int b:2; • int c:6; • };
13.2 位段结构 • 位段变量的说明与结构变量说明的方式相同。可采用先定义后 • 说明,同时定义说明或者直接说明这三种方式。 • 例如: • struct bs • { int a:8; • int b:2; • int c:1; • }data; • 说明data为bs变量,共占两个字节。其中位段a占8位,位段b占2 • 位,位段c占6位。 • 说明: • (1)一个位段必须存储在同一个字节中,不能跨两个字节。如一 • 个字节所剩空间不够存放另一位段时,应从下一单元起存放该位段 • 。也可以有意使某位段从下一单元开始。
13.2 位段结构 • 例如: • struct bs • { unsigned a:4 • unsigned :0 /* 空域 */ • unsigned b:4 /* 从下一单元开始存放 */ • unsigned c:4 • } • 在这个位段定义中,a占第一字节的4位,后4位填0表示不使用, • b从第二字节开始,占用4位,c占用4位。 • (2)由于位段不允许跨两个字节,因此位段的长度不能大于一个 • 字节的长度,也就是说不能超过8位二进位。
13.2 位段结构 • (3)位段可以没有位段名,这时它只用来作填充或调整位置。无 • 名的位段是不能使用的。 • 例如: • struct k • { int a:1 • int :2 /* 该2位不能使用 */ • int b:3 • int c:2 • }; • 从以上分析可以看出,位段在本质上就是一种结构类型,不过 • 其成员是按二进位分配的 。
13.2 位段结构 • 13.2.3 位段的引用 • 和结构体成员的引用相同,其一般形式为:位段变量名.位域名 • struct bs • { int a:8; • int b:4; • int c:1; • }data; • data.a;data.b;data.c; • 表示引用变量data中的位段a、b、c。
13.2 位段结构 • 13.2.4 位段的赋值 • 位段可以在定义的同时赋初值,形式与结构体变量赋初值相同。位段也可以进行赋值操作,例如: • data.a=1; • data.b=4; • data.c=6;
13.2 位段结构 • 13.2.5 使用位段的注意事项 • (1)位段可以初始化,形式与结构体变量赋初值相同,位段也可 • 以进行赋值。 • 例如:data.b=2; • 赋值时应注意位段的取值范围,例如:data.c=2;就会产生错误 • 的结果。因为data.c只占1位,只能取值0或1。 • 对于赋值语句:data.c=2; 系统并不报错,而是自动截取所赋值 • 的低位:2的二进制码是10,取低一位为0。所以data.c的值为0。
13.2 位段结构 • (2)位段可以进行算术运算,系统自动将其转换成整型数。 • 例如: • data.b=5; • data.c=1; • data.a=data.b+data.c; • (3)可以用整型格式符(%d、%o、%x、%u)进行输出。 • 例如:printf(“%d\n”,data.a); • (4)不能对位段求地址。 • 由于位段没有地址,所以不能对位段求地址,也不能通过 • scanf()函数给位段赋值,不能用指针变量指向位段。
13.2 位段结构 • 例 13.12 分析下面程序的运行结果。 • #include <stdio.h> • void main() • { struct bs • { unsigned a:1; • unsigned b:3; • unsigned c:4; • }bit,*pbit; • bit.a=1; • bit.b=7; • bit.c=15; • printf("%d,%d,%d\n",bit.a,bit.b,bit.c); • pbit=&bit; • pbit->a=0; • pbit->b&=3; • pbit->c|=1; • printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); • }
13.2 位段结构 • 程序中定义了位段结构体类型bs,三个位域为a,b,c。说明了 • bs类型的变量bit和指向bs类型的指针变量pbit。这表示位段也是可以 • 使用指针的。程序的9、10、11三行分别给三个位段赋值。(应注意 • 赋值不能超过该位段的允许范围)程序第12行以整型量格式输出三个 • 域的内容。第13行把位段变量bit的地址送给指针变量pbit。第14行用 • 指针方式给位段a重新赋值,赋为0。第15行使用了复合的位运算符 • “&=”,该行相当于: pbit->b=pbit->b&3 位段b中原有值为7,与3 • 作按位与运算的结果为3(111&011=011,十进制值为3)。同样,程序 • 第16行中使用了复合位运算“|=”,相当于:pbit->c=pbit->c|1其结果 • 为15。程序第17行用指针方式输出了这三个段的值。
13.2 位段结构 • 例13.13 编写一程序,输出位段变量的值。程序如下: • #include <stdio.h> • void mian() • { int x; • struct kzds; • { unsigned int qw:1; • unsigned int zw:2; • unsigned int hw:3; • unsigned int mw:1; • }x1,x2; • x1.qw=1; x1.hw=5; • x=x1.qw+x1.hw/2; • printf("x1.qw =%u\n ", x1.qw); • printf("x1.hw =%u\n ", x1.hw); • printf("x =%d\n ", x); • }
13.3 程序举例 • 例13.14 编写一个使用位运算复合赋值运算符的程序。程序如下: • #include <stdio.h> • void main() • { char a=9,b=9,c=9,d=9,e=9; • a<<=1; /* 等价于a=a<<1 */ • b>>=1; /* 等价于b=b>>1 */ • c&=5; /* 等价于c=c&5 */ • d|=5; /* 等价于d=d|5 */ • e^=5; /* 等价于e=e^5 */ • printf("%d %d %d %d %d\n",a,b,c,d,e); • } • 程序运行结果: • 18 4 1 13 12 • 本程序中赋值表达式的处理过程与+=、-=、*=、/=、%=相同。
13.3 程序举例 • 例13.15 编写一个程序,其功能是将正整型数组中所有元素转换 • 为不大于它的最大偶数,并显示输出。 • 为了将一个正整数转换为不大于它的最大偶数,只需将该正整 • 数所对应的二进制数的最低位清0即可,即用0xfffe与该正整数做“按 • 位与”运算。 • 根据上述分析编写的程序如下:
13.3 程序举例 • #include <stdio.h> • void main() • { int i; • int a[10]={23,14,24,31,46,55,33,68,27,40}; • for(i=0;i<10;i++) • printf("%5d",a[i]); • printf("\n"); • for(i=0;i<10;i++) • a[i]=a[i]&0xfffe; /* 将正整数转换为不大于它的最大偶数*/ • for(i=0;i<10;i++) • printf("%5d",a[i]); • printf("\n"); • } 程序运行结果: 23 14 24 31 46 55 33 68 27 40 22 14 24 30 46 54 32 68 26 40