第8章 位运算 本章导读 本章主要知识点 《 C语言程序设计》 (Visual C++ 6.0环境) 第8章 位运算 本章导读 本章是初学C语言者的一大难点,属较高要求,适合于编写系统软件的需要。读者应在掌握了计算机的几种基本数值编码的基础上,开始本章的学习。通过本章的学习我们将近一步体会到C语言既具有高级语言的特点,又具有低级语言的功能,它能直接对计算机的硬件进行操作,因而它具有广泛的用途和很强的生命力。 《 C语言程序设计》 (Visual C++ 6.0环境) 本章主要知识点 (1) 位运算符的含义及使用 (2) 位运算的特殊应用 (3) 位复合赋值运算符的含义及使用 (4) 位段的定义、位段变量的说明及引用 返回本书目录
第8章 位运算 8.1 位运算的C程序实例 8.2 二进制位运算 8.3 位段 8.4 综合实训 第8章 位运算 《 C语言程序设计》 (Visual C++ 6.0环境) 8.1 位运算的C程序实例 8.2 二进制位运算 8.3 位段 8.4 综合实训 返回本章导读
8.1 位运算的C程序实例 【例8.1】 《 C语言程序设计》 (Visual C++ 6.0环境) 从结果可以看出,在这个程序中出现的几种运算符显然不同于以前,它们不是两个十进制数值之间简单的运算,而是十进制数对应的二进制“位”的运算。 返回本章目录
【例8.1】实例 《 C语言程序设计》 (Visual C++ 6.0环境) 【例8.1】编写程序完成对两个整型量进行按位逻辑运算。程序名为l8_1.cpp。 #include "stdio.h" main() { int a,b; printf("input the numbers: "); scanf("%d,%d",&a,&b); printf("%d\n",a&b); /*输出按位与结果*/ printf("%d\n",a|b); /*输出按位或结果*/ printf("%d\n",a^b); /*输出按位异或结果*/ printf("%u\n",~a); /*输出 a的按位取反结果*/ } 《 C语言程序设计》 (Visual C++ 6.0环境) 运行情况为: input the numbers: 输入:9,5 < CR > 1 13 12 65526 程序演示 返回本节目录
[例8.1]程序演示 弹出运行结果窗口 输入源程序 返回例题 返回本节目录
8.2 二进制位运算 8.2.1 二进制位运算 8.2.2 位复合赋值运算符 《 C语言程序设计》 (Visual C++ 6.0环境) 8.2 二进制位运算 所谓位运算是指进行二进制位的运算。在系统软件中,常要处理二进制位的问题。C语言提供了按位运算的功能,这使得它与其它高级语言相比,具有很强的优越性。 《 C语言程序设计》 (Visual C++ 6.0环境) 8.2.1 二进制位运算 8.2.2 位复合赋值运算符 返回本章目录
8.2.1 二进制位运算 《 C语言程序设计》 (Visual C++ 6.0环境) C语言提供了六种位运算符(见表8-1): 类 型 8.2.1 二进制位运算 C语言提供了六种位运算符(见表8-1): 《 C语言程序设计》 (Visual C++ 6.0环境) 表8-1位逻辑运算与移位运算 类 型 运算符 含义 位逻辑 & 按位与 | 按位或 ^ 按位异或 ~ 取反 移位运 算 符 << 左移 >> 右移 返回本节目录
8.2.1 二进制位运算 《 C语言程序设计》 (Visual C++ 6.0环境) 说明: 8.2.1 二进制位运算 说明: ①运算量只能是整型或字符型的数据,不能为实型或结构体等类型的数据。 ②六个位运算符的优先级由高到低依次为:取反、左移和右移、按位与、按位异或、按位或。 ③两个不同长度的数据进行位运算时,系统会将二者按右端对齐。下面对各种位运算符介绍如下: 《 C语言程序设计》 (Visual C++ 6.0环境) 1.“按位与”运算符(&) 4.“求反”运算符 (~) 2.“按位或”运算符(|) 5.“左移”运算符(<<) 3.“按位异或”运算符(^) 6.“右移”运算符(>>) 返回本节目录
1.“按位与”运算符(&) 《 C语言程序设计》 (Visual C++ 6.0环境) (1)规则: 参与运算的两数(以补码方式出现)各对应的二进位相与(即逻辑乘),只有对应的两个二进位均为1时,结果位才为1,否则为0,它是双目运算符。即:0&0=0;0&1=0;1&0=0;1&1=1。 例如: 9&5可写算式如下: 9的二进制补码: 0 0 0 0 1 0 0 1 5的二进制补码: 0 0 0 0 0 1 0 1 & ___________________ 0 0 0 0 0 0 0 1(1的二进制补码) 可见9&5=1。 《 C语言程序设计》 (Visual C++ 6.0环境) 返回8.2.1目录
1.“按位与”运算符(&) 《 C语言程序设计》 (Visual C++ 6.0环境) (2)特殊用途: ①清零 按位与运算通常用来对某些位清0。由按位与的规则可知:为了使某数的指定位清零,可将该数按位与一特定数。该数中为1的位,特定数中相应位应为0;该数中为0的位,特定数中相应位可以为0也可以为1。由此可见,能对某一个数的指定位清零的数并不唯一。 【例8.2】 ②取一个数中某些位 可将该数与一个特定数进行&运算,对于要取的那些位,特定数中相应的位设为1。【例8.3】 ③取出数中某一位 要相将一个数的某一位保留下来,可将该数与一个特定数进行&运算,特定数的相对应的那位应为1。【例8.4】 《 C语言程序设计》 (Visual C++ 6.0环境) 返回本节目录
【例8.2】实例 《 C语言程序设计》 (Visual C++ 6.0环境) 【例8.2】对原数00110110中为1的位清零。 原数补码: 0 0 1 1 0 1 1 0 清零的数: 1 1 0 0 0 0 0 0 (或01000000、00000000等) & ___________________ 0 0 0 0 0 0 0 0 程序可写为: #include "stdio.h" main() { int a=0x36,b=0xc0,c; c=a&b; printf("a=%x\nb=%x\nc=%x\n",a,b,c); } 《 C语言程序设计》 (Visual C++ 6.0环境) 运行情况为: a=36 b=c0 c=0 程序演示 返回1目录
[例8.2]程序演示 弹出运行结果窗口 输入源程序 返回例题 返回1目录
【例8.3】实例 《 C语言程序设计》 (Visual C++ 6.0环境) 【例8.3】把a的高八位清0,保留低八位。 #include "stdio.h" main() { int a,b=255,c; scanf("%d",&a); c=a&b; printf("a=%x\nb=%x\nc=%x\n",a,b,c); } 《 C语言程序设计》 (Visual C++ 6.0环境) 运行情况为: 输入:920< CR > a=398 b=ff c=98 程序演示 返回1目录
[例8.3]程序演示 弹出运行结果窗口 输入源程序 返回例题 返回1目录
【例8.4】实例 《 C语言程序设计》 (Visual C++ 6.0环境) 【例8.4】编写程序将a(=9)的最低位取出。程序名为l8_4cpp。 #include "stdio.h" main() { int a=9,b=1,c; c=a&b; printf("a=%x\nb=%x\nc=%x\n",a,b,c); } 运行情况为: a=9 b=1 c=1 《 C语言程序设计》 (Visual C++ 6.0环境) 程序演示 返回1目录
[例8.4]程序演示 弹出运行结果窗口 输入源程序 返回例题 返回1目录
2.“按位或”运算符(|) 《 C语言程序设计》 (Visual C++ 6.0环境) (1)规则 参与运算的两数(以补码出现)各对应的二进位相或(即逻辑加)。只要对应的两个二进位有一个为1时,结果位就为1,它是双目运算符。即:0|0=0;0|1=1;1|0=1;1|1=1。 例如:9|5可写算式如下:0 0 0 0 1 0 0 1 0 0 0 0 0 1 0 1 | _________________ 0 0 0 0 1 1 0 1 (十进制为13) 可见9|5=13。 (2)特殊用途 将一个数据的某些指定的位置为1。 将该数按位或一个特定的数,该特定的数的相应位置为1。【例8.5】 《 C语言程序设计》 (Visual C++ 6.0环境) 返回8.2.1目录
【例8.5】实例 《 C语言程序设计》 (Visual C++ 6.0环境) 【例8.5】将一个数的低5位置为1。程序名为l8_5.cpp。 只需将该数与“00011111”进行&运算。如: # # # # # # # # (#可代表0或1) 0 0 0 1 1 1 1 1 | _______________ # # # 1 1 1 1 1 #include "stdio.h" main() { int a,b=31,c; scanf("%d",&a); c=a|b; printf("a=%x\nb=%x\nc=%x\n",a,b,c); } 运行情况为: 输入:5< CR > a=5 b=1f c=1f 《 C语言程序设计》 (Visual C++ 6.0环境) 程序演示 返回2目录
[例8.5]程序演示 弹出运行结果窗口 输入源程序 返回例题 返回2目录
3.“按位异或”运算符(^) 《 C语言程序设计》 (Visual C++ 6.0环境) (1)规则 参与运算的两数(以补码出现)各对应的二进位相异或,当两对应的二进位相异时,结果为1,它是双目运算符。即:0^0=0;0^1=1;1^0=1;1^1=0。 例如:9^5可写成算式如下:0 0 0 0 1 0 0 1 0 0 0 0 0 1 0 1 ^_____________ 0 0 0 0 1 1 0 0 (十进制为12) 可见9^5=12。 (2)特殊用途 ①使特定位翻转 【例8.6】 要使哪几位翻转就将与其进行“按位异或”运算的数的相应位置为1。 ②使特定位保留原值 要使哪几位保留原值就将与其进行“按位异或”运算的数的相应位置为0。 ③交换两个值,不用临时变量 【例8.7】 《 C语言程序设计》 (Visual C++ 6.0环境) 返回8.2.1目录
【例8.6】实例 《 C语言程序设计》 (Visual C++ 6.0环境) 【例8.6】将01110001的低4 位翻转,高4位保留原值。 0 1 1 1 0 0 0 1 0 0 0 0 1 1 1 1 ^ ______________ 0 1 1 1 1 1 1 0 (十进制126) 程序可写为: #include "stdio.h" main() { int a=0x71,b=0xf,c; c=a^b; printf("a=%x\nb=%x\nc=%x\n",a,b,c); } 运行情况为: a=71 b=f c=7e 《 C语言程序设计》 (Visual C++ 6.0环境) 程序演示 返回3目录
[例8.6]程序演示 弹出运行结果窗口 输入源程序 返回例题 返回3目录
【例8.7】实例 《 C语言程序设计》 (Visual C++ 6.0环境) 【例8.7】设有整型数 a=5,b=7。编写程序利用位运算,将a和b的值互换。 程序中,通过顺序使用a=a^b;b=b^a;a=a^b;三个赋值语句将两变量a、b的值互换。具体计算过程如下: 第一步: a 0000000000000101 b 0000000000000111 a=a^b 0000000000000010 第二步: a 0000000000000010 b=b^a 0000000000000101 (b的值为5) 第三步: a 0000000000000010 b 0000000000000101 a=a^b 0000000000000111 (a的值为7) 《 C语言程序设计》 (Visual C++ 6.0环境) 返回3目录
【例8.7】实例 《 C语言程序设计》 (Visual C++ 6.0环境) 程序名为l8_7.cpp。 #include "stdio.h" main() { int a=5,b=7; printf("a=%d,b=%d\n",a,b); a=a^b;b=b^a;a=a^b; } 运行情况为: a=5,b=7 a=7,b=5 《 C语言程序设计》 (Visual C++ 6.0环境) 程序演示 返回3目录
[例8.7]程序演示 弹出运行结果窗口 输入源程序 返回例题 返回3目录
4.“求反”运算符 (~) 《 C语言程序设计》 (Visual C++ 6.0环境) (1)规则 对参与运算的数的各二进位按位求反,它是单目运算符,具有右结合性。即:~0=1;~1=0。 例如:~9的运算为 0000000000001001 ~ __________________ 1111111111110110 (2)用途 适当的使用可增加程序的移植性。如要将整数a的最低位置为0,我们通常采用语句a=a&~1;来完成,因为这样对a是16位数还是32位数均不受影响。 返回8.2.1目录
5.“左移”运算符(<<) 《 C语言程序设计》 (Visual C++ 6.0环境) (1)规则 把“<<”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补0,它是双目运算符。 例如:a<<4指把a的各二进位向左移动4位。如a=00000011(十进制3),左移4位后为00110000(十进制48)。 (2)特殊用途 左移1位相当于该数乘以2;左移n位相当于该数乘以2n 。但此结论只适用于该数左移时被溢出舍弃的高位中不包含1的情况。 左移比乘法运算快得多,有的C编译系统自动将乘2运算用左移一位来实现。 《 C语言程序设计》 (Visual C++ 6.0环境) 返回8.2.1目录
6.“右移”运算符(>>) 《 C语言程序设计》 (Visual C++ 6.0环境) (1)规则 把“>>”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。 (2)特殊用途 右移1位相当于该数除以2;右移n位相当于该数除以2n。 (3)说明 对于有符号数,在右移时,符号位将随同移动。当为正数时,最高位补0;而为负数时,符号位为1,最高位是补0还是补1取决于计算机系统的规定。移入0的称为“逻辑右移”;移入1的称为“算术右移”。我们可以通过编写程序来验正所使用的系统是采用“逻辑右移”还是“算术右移”。很多系统规定为补1,即“算术右移”。 如: a: 1001011111101101 a>>1: 0100101111110110 ( 逻辑右移) a>>1: 1100101111110110 ( 算术右移) 《 C语言程序设计》 (Visual C++ 6.0环境) 返回8.2.1目录
8.2.2 位复合赋值运算符 《 C语言程序设计》 (Visual C++ 6.0环境) 8.2.2 位复合赋值运算符 《 C语言程序设计》 (Visual C++ 6.0环境) 位运算符与赋值运算符结合组成位复合赋值运算符。位复合赋值运算符与算术复合赋值运算符相似,它们的运算级别较低,仅高于逗号运算符,是自右而左的结合性。 1.分类 位复合赋值运算符如表8-2所示: 2.运算过程 (1)先对两个操作数进行位操作。 (2)再将结果赋予第一个操作数(因此第一个操作数必须是变量)。如:a&=2;表示a=a&2;。 返回本节目录
表8-2 位复合赋值运算符 《 C语言程序设计》 (Visual C++ 6.0环境) 运算符 名称 例子 等价于 &= 位与赋值 a&=b 表8-2 位复合赋值运算符 《 C语言程序设计》 (Visual C++ 6.0环境) 运算符 名称 例子 等价于 &= 位与赋值 a&=b a=a&b |= 位或赋值 a|=b a=a|b ^= 位异或赋值 a^=b a=a^b >>= 右移赋值 a>>=b a=a>>b <<= 左移赋值 a<<=b a=a<<b 返回8.2.2目录
8.3 位段 1.位段的定义与位段中数据是引用 2.对于位段使用的几点说明 3.应用举例【例8.8】 8.3 位段 为了节省存储空间,并使处理简便,C语言提供了又一种使用结构的方法——位段(也称位域)。所谓位段就是以位为单位定义长度的结构体类型中的成员。每个位段都有一个位段名,允许在程序中引用位段中的数据进行操作。 这样就可以将若干个信息紧缩存放,仅用一个或几个字节来表示。 《 C语言程序设计》 (Visual C++ 6.0环境) 1.位段的定义与位段中数据是引用 2.对于位段使用的几点说明 3.应用举例【例8.8】 返回本章目录
1.位段的定义与位段中数据是引用 (1)位段的定义 (2)位段中数据的引用 《 C语言程序设计》 (Visual C++ 6.0环境) 返回本节目录
(1)位段的定义 《 C语言程序设计》 (Visual C++ 6.0环境) 位段的定义就是将它定义为结构体中的成员,其形式为: struct <结构体名> { <位段表列> }; struct <结构体名> <结构变量表列>; 或 struct <结构体名> }<结构变量表列>; 或 struct 在这三种形式中,位段表列的形式均为: <类型说明符> <位段名>:<位段长度>; 《 C语言程序设计》 (Visual C++ 6.0环境) 返回1目录
(1)位段的定义 a b c 《 C语言程序设计》 (Visual C++ 6.0环境) 例如:struct bs { unsigned a:8; unsigned b:2; unsigned c:6; }data; 表示data为struct bs型结构变量(也称位段变量),共占两个字节。其中位段a占8位,位段b占2位,位段c占6位,如图8-1所示。 《 C语言程序设计》 (Visual C++ 6.0环境) a b c 8 2 6 (位) 图8-1data位段变量内存示意图 返回1目录
(2)位段中数据的引用 《 C语言程序设计》 (Visual C++ 6.0环境) 位段在本质上就是一种结构体类型中的成员,只不过是按二进制位分配的。因此,位段中数据的引用可按结构变量成员的访问方法进行,形式为: <结构变量>.<位段名> 如:data.a=2;表示给位段变量data的位段a赋值为2。 《 C语言程序设计》 (Visual C++ 6.0环境) 返回1目录
2.对于位段使用的几点说明 《 C语言程序设计》 (Visual C++ 6.0环境) (1)一个位段必须存储在同一个存储单元中,不能跨跃两个单元。如一个单元所剩空间不够存放下一位段时,应从下一单元起存放该位段。 (2)可以有意使某位段从下一单元开始存放。 (3)位段中的数据不能超过其允许的最大值范围,位段的长度不能大于机器字长。例如,定义位段的长度为2位,则其中存放的最大数为3。 (4)位段可以无名位段,无名位段是不能使用的,它只用来作填充或调整位置。 (5)不能定义位段数组,也不能定义返回值为位段的函数。 (6)位段允许在数据表达式中引用,它会被系统自动地转换为整型数。 《 C语言程序设计》 (Visual C++ 6.0环境) 返回本节目录
【例8.8】实例 《 C语言程序设计》 (Visual C++ 6.0环境) 【例8.8】位段变量和指向位段的指针变量的使用。程序名为l8_8.cpp。 #include "stdio.h" 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; /*把位段变量bit的地址送给指针变量pbit*/ pbit->a=0; /*用指针方式给位段a重新赋值,赋为0*/ 《 C语言程序设计》 (Visual C++ 6.0环境) 返回本节目录
【例8.8】实例 《 C语言程序设计》 (Visual C++ 6.0环境) pbit->b&=3; pbit->c|=1; printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /*用指针方式输出了这三个位段的值*/ } 运行结果为: 1,7,15 0,3,15 《 C语言程序设计》 (Visual C++ 6.0环境) 程序演示 返回本节目录
[例8.8]程序演示 弹出运行结果窗口 输入源程序 返回例题 返回本节目录
8.4 综合实训 《 C语言程序设计》 (Visual C++ 6.0环境) [例8.9] [例8.10] 返回本章目录
【例8.9】实例 《 C语言程序设计》 (Visual C++ 6.0环境) 【例8.9】取 一个数 a从右端开始的第5至8位。程序名为l8_9.cpp。 分析: (1)先使a右移5位,见图8-4,目的是使要取出的那几位移到最右端。 (2)设置一个低4位(即8-5+1)全为1,其余的位全为0的数,即将一个全1的数左移4位,这样右端低4位为0。 (3)将上面两数按位与,将低4位保留。 《 C语言程序设计》 (Visual C++ 6.0环境) 15 9 8 5 4 3 当然,对一个数可以任意指定从其右面第m位开始向右取n位,只需将程序中的“a>>5”改成 “a>>m”,且将“~(~0<<3)”改成“~(~0<<(m-n+1))”即可。 返回本节目录
【例8.9】实例 《 C语言程序设计》 (Visual C++ 6.0环境) #include "stdio.h" main() { unsigned a,b,c,d; printf("input a number:"); scanf("%d",&a); b=a>>5; c=~(~0<<4); d=b&c; printf("a=%x\td=%x\n",a,d); } 运行情况如下: input a number: 输入:421 < CR > a=01a5 d=d 《 C语言程序设计》 (Visual C++ 6.0环境) 程序演示 返回本节目录
[例8.9]程序演示 弹出运行结果窗口 输入源程序 返回例题 返回本节目录
【例8.10】实例 《 C语言程序设计》 (Visual C++ 6.0环境) n 16-n 16-n n 【例8.10】对某个整数x进行左循环移位,移n位。程序名为l8_10.cpp。 分析:设该数用2个字节存放,则原数中右边的16-n位向左移动,原数中左边的n位移至右边,如图8-5所示。①先把x赋给中间变量a,b。 ②把a右移16-n位,使原数中左边的n位移至右边,其余各位为0。 ③再把b左移n位,使原数中右边的16-n位移至最左边,其余各位为0。 ④上述两数按位相或。 《 C语言程序设计》 (Visual C++ 6.0环境) n 16-n 16-n n 返回本节目录
【例8.10】实例 《 C语言程序设计》 (Visual C++ 6.0环境) #include "stdio.h" main() { unsigned x,a,b,c; int n; scanf("%d,%d",&x,&n); a=x>>(16-n); b=x<<n; c=a|b; printf("x=%x,c=%x",x,c); } 运行情况如下: 输入:400,3 < CR > x=190,c=c80 《 C语言程序设计》 (Visual C++ 6.0环境) 程序演示 返回本节目录
[例8.10]程序演示 弹出运行结果窗口 输入源程序 返回例题 返回本节目录