第 11章 位运算
,C语言程序设计,
课程讲义
2006年 4月
上一章节课程回顾
关系运算符、逻辑运算符及其表达式
关系运算符及逻辑运算符的优先次序
逻辑运算的值
if语句的三种形式
if语句的嵌套
条件运算符和条件表达式
switch语句
第 11章 位运算
11.2 位域(位段)
11.1 概 述
11.1 位运算符六种位运算符
C语言是为描述系统而设计的,因此它应当具有汇编
语言所能完成的一些功能。第九章介绍的指针运算和本章
将介绍的位运算就很适合于编写系统软件的需要。
所谓位运算是指进行二进制位的运算。在系统软件中,
常要处理二进位的问题。例如,将一个存储单元中的各二
进位左移或右移一位,两个数按位相加等。c语言提供位
运算的功能,
为了使没有学过汇编语言的读者对二进制运算能有较
好的理解,先介绍有关位的知识。
11.1 概 述
一、字节和位
大多数计算机系统(包含 IBM-PC系列)的内存储器
是由许许多多被称为“字节”(by te)的单元组成的。
每一个字节有一个地址。一个字节由若干个二进制位(bi
t) 组成。若干个字节组成一个存储单元,称为“字”(w
ord)。每一个存储单元存放一个数据或一条指令。
一个字节一般由8个二进位组成,其中最右边的一位称为“最低有
效位”或“最低位”,最左面的一位称为“最高有效位”或“最高位”,
每一个二进位的值是0或1。
在微型机中一般以4个字节存放一个实数,以2个字节
存放一个整数。最左边的一位(最高位)用作数的符号位。
为了表示数值,可以采用不同的方法,一般有:原码、
反码和补码。
二、原码
只将最高位作符号位(以0代表正,1代表负),其余
各位代表数值本身的绝对值(以二进制表示)。如,
11.1 概 述
+7的原码为,00000111
|
代表 '正 '
一7的原码为,1000 0 1 1 1
|
代表,'负 '
二进制的111代表十进制的7,为简化起见,我们只用
一个字节存放一个整数,如果用两个
字节存放一个整数,情况是一样的,无非把+7表示成0
0000000 00000111而已。
十0的原码为 00000000
一0的原码为 10000000
显然,+0和一0表示的是同一个数0,而在内存中却有
两个不同的表示。也就是说,0的表示
不唯一,这不适合于计算机的运算。
11.1 概 述
三、反码
一个数如果值为正,则它的反码与原码相同,如:+
7的反码为00000111。
一个数的值如为负,则符号位为1,其余各位是对原
码取反。如,
一7的反码为:11111000
十0的反码为:00000000
一0的反码为:11111111
同样,o的表示不唯一。用反码表示的最大值为12
7,最小值为 -127。
127的反码为,01111111
一127的反码为,10000000
用反码表示数,现已不多用。
四、补码
原码和反码都不便于计算机内的运算,因为在运算中
要单独处理其符号。
11.1 概 述
例如,对以原码表示的+7和一7相加,必须先判断各自
的符号位,然后对后7位进行相应的处理,很不方便。
因此,最好能做到将符号位和其它位统一处理。对减
法也按加法来处理。这就是“补码”。
,补码”的原理可以用时钟来说明,见图11.1。
如果要将时针从 9点拨到4点,可以向前拨,也可以向后
拨,其表示如下,
12
11 1
10 2
9 ------- 3
8 4
7 5
6
11.1 概 述
9一5=4 (向后拨5个字)
9+7=16(向前拨7个字)
从图上可见,向前拨7个字也能指向4。这是由于钟是
圆的,12点的下一个小时是1点。时钟是12进制的
,可以把12点看成0点,13点就是 1点,其实是进位
后得到了十二进制数11,其中第一个 1是进位,即高位
,第二个 1是低位。高位不保留,只保留低位,因此,1
6点用十二进制数表示为14,高位不保留,在时钟上
就是4点,用十进制数可表示为:16一12=4。
对十进制数,如果想从9得到结果值5,可以用减
法,
9一4=5
已知4的补数为10一4=6,即4与6互补。因此9
一4可以改写为加法,
9 +6 = 15
11.1 概 述
再去掉高位1,得5。
在计算机中,以一个有限长度的二进位作为数的模,
如果用 1个字节表示一个数,一个字节为8位,模为 256。
因为逢2 56就进1,在内存中情况为
------------
1 |00000000|
-------------
进位被丢弃。
补码是这样规定的,
正数:其原码、反码、补码相同。例如,+7的补
码也是00000111。
负数:最高位为1,其余各位为原码的相应位取反
,然后对整个数加1。例如,
一7的原码,10000111
一7的补码:第①步,11111000
11.1 概 述
+1
第②步, -----------
11111001
即对十7各位取反加1。也可以这样做:①将该负数(不包
括0)先加1;②然后将其绝对值以
二进制表示;③再对其求反。例如,一7先加1得-6,。
对6以二进制表示为0000 110,再取反得111100
1,它就是一7的补码,见表11.1。
-------------------------------------------------
| 数值 | 原码 | 反码 | 补码 |
|---------|------------|-------------|------------|
| +7 |00000111|00000111 |00000111|
-------------------------------------------------
-7 10000111 11111000 11111001
(表 11.1)
11.1 概 述
因此一0的补码也是00000000。可知,+0和
一0的补码表示是相同的。或者说0的补码是唯一的。
用补码进行运算,减法可以用加法来实现,如十7
一6应得1。可以将十7的补码和一6的
补码相加,就得到结果值的补码。
十7的补码,000001 1 1
一6的补码,11111010
-----------------------------------------------
( 相 加) 1 0 0 0 0 0 0 1
进位被舍去。后面8位00000001就是 1的补码
如果将一7+6,同样,
一7的补码,11111001
十6的补码,00000110
-----------------------------------------------
(相加) 11111111
11.1 概 述
11111111是一 1的补码。
表11.2是以补码表示的数的一些例子

表 11.2
-------------------------------------------------------
数值 | 补码
----------------------|---------------------------------
0 | 00000000
-1 | 11111111
-2 | 11111110
--------------------------------------------------------
11.1 概 述
-------------------------------------------------------------
-3 | 11111101
-4 | 11111100
, |,
, |, (往下不断减 1)
, |,
-127 | 10000001
-128 | 10000000
---------------------------|----------------------------------
1 | 00000001
2 | 00000010
, |,
, |,(往下不断加 1 )
126 | 01111110
127 | 01111111
--------------------------------------------------------------
11.1 概 述
& 按位与
| 按位或
^ 按位异或
~ 取反
<< 左移
>> 右移
第 11章 位运算
11.1 位运算符的六种位运算符,
按位与运算符 "&"是双目运算符 。 其
功能是参与运算的两数各对应的二进位
相与 。 只有对应的两个二进位均为 1时,
结果位才为 1,否则为 0。 参与运算的数
以补码方式出现 。
第 11章 位运算
11.1.1 按位与运算
例如,9&5可写算式如下,
00001001 (9的二进制补码 )
&00000101 (5的二进制补码 )
00000001 (1的二进制补码 )
可见 9&5=1。
按位与运算通常用来对某些位清 0或保留某些
位 。 例如把 a 的高八位清 0, 保留低八位,
可作 a&255 运算 ( 255 的 二 进 制 数 为
0000000011111111)。
11.1.1 按位与运算
【 例 11.1】
main()
{
inta=9,b=5,c;
c=a&b;
printf("a=%d\nb=%d\nc=%d\n",a,b,c);
}
11.1.1 按位与运算
11.1.2 按位或运算
? 按位或运算符, |” 是双目运算符 。 其功能
是参与运算的两数各对应的二进位相或 。
只要对应的二个二进位有一个为 1时, 结果
位就为 1。 参与运算的两个数均以补码出现 。
例如,9|5可写算式如下,
00001001
|00000101
00001101 (十进制为 13)可见
9|5=13
11.1.2 按位或运算
【 例 11.2】
main(){
inta=9,b=5,c;
c=a|b;
printf("a=%d\nb=%d\nc=%d\n",a,b,c);
}
11.1.2 按位或运算
位异或运算符, ^” 是双目运算符 。
其功能是参与运算的两数各对应的二进
位相异或, 当两对应的二进位相异时,
结果为 1。 参与运算数仍以补码出现, 例
如 9^5可写成算式如下,
00001001
^00000101
00001100 (十进制为 11)
11.1.3 按位异或运算
【 例 11.3】
main(){
int a=9;
a=a^5;
printf("a=%d\n",a);
11.1.3 按位异或运算
求反运算符~为单目运算符, 具有右结合
性 。 其功能是对参与运算的数的各二进位按位
求反 。
例如 ~ 9的运算为,
~(0000000000001001)=?
结果,1111111111110110
?11.1.4 求反运算
? 左移运算符, <<” 是双目运算符 。 其功
能把, <<, 左边的运算数的各二进位全
部左移若干位, 由, <<” 右边的数指定
移动的位数, 高位丢弃, 低位补 0。
? 例如,a<<4
? 指把 a的各二进位向左移动 4位 。 如
a=00000011(十进制 3),左移 4位后为
00110000(十进制 48)。
11.1.5 左移运算
右移运算符, >>” 是双目运算符 。 其功能是把
,>>, 左边的运算数的各二进位全部右移若
干位,, >>” 右边的数指定移动的位数 。
例如:设 a=15,a>>2
表示把 000001111右移为 00000011(十进制 3)。
应该说明的是, 对于有符号数, 在右移时, 符
号位将随同移动 。 当为正数时, 最高位补 0,
而为负数时, 符号位为 1,最高位是补 0或是补
1 取决于编译系统的规定 。 Turbo C和很多系
统规定为补 1。
11.1.6 右移运算
【 例 11.4】
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);
}
11.1.6 右移运算
有些信息在存储时, 并不需要占用一个完整的字
节, 而只需占几个或一个二进制位 。 例如在存放
一个开关量时, 只有 0和 1两种状态, 用一位二进
位即可 。 为了节省存储空间, 并使处理简便, C
语言又提供了一种数据结构, 称为, 位域, 或
,位段, 。
所谓, 位域, 是把一个字节中的二进位划分为几
个不同的区域, 并说明每个区域的位数 。 每个域
有一个域名, 允许在程序中按域名进行操作 。 这
样就可以把几个不同的对象用一个字节的二进制
位域来表示 。
11.2 位域 ( 位段 )
1,位域的定义和位域变量的说明
位域定义与结构定义相仿, 其形式为,
struct 位域结构名
{ 位域列表 };
其中位域列表的形式为,
类型说明符 位域名:位域长度
例如,struct bs
{
int a:8;
int b:2; int c:6;
};
11.2 位域 ( 位段 )
? 例如,
? struct packed-- data
? {unsigned a:2;
? unsigned b:6;
? unsigned c:4;
? unsigned d:4;
? int I
? }data;
? 其中a,b,c,d,分别占2位、6位,
11.2 位域 ( 位段 )
? 4位、4位。i为整型。共占4个字节。也可
以使各个位段不恰好占满一个字节。如,
? struct packed-data
? {unsigned a:2;
? unsigned b:3;
? unsigned c:4;
? int I;
? }
? stuct packed— data data ;
11.2 位域 ( 位段 )
? 其中a,b.c共占9位,占1个字节多,不到
2个字节。它的后面为 int 型,占2个字节,
在a,b.c之后的7位空闲,i从另一字节开
头起存放。
? 注意,在存储单元中位段的空间分配方向,
因机器而异。在PDP中,由右到左分配,
? 对位段中的数据引用的方法。如,
? data.a =2;
? data.b =7;
? data.c =9;
? 注意位段允许的最大值范围。如果写
11.2 位域 ( 位段 )
? data.a=8
? 就错了。因为它只占2位,最大值为3。在此
情况下,自动取赋予它的数的低位。例如,8
的二进制数形式为1000,而 data, a 只有
2位,取 1000 的低 2位,data, a 得值0。
? 关于位段的定义和引用,有几点要说明,
? 1.若某一位段要从另一个字开始存放。可
以用以下形式定义,unsigned a:1;
一个存储单元
11.2 位域 ( 位段 )
? unsigned b:2;
? unsigned,0;
? unsigned c:3; ( 另一单元)
? 本来a,b,c应连续存放在一个存储单
元(字)中,由于用了长度为0的位段,其作
用是使下一个位段从下一个存储单元开始存放。
因此,现在只将a、b存储在一个存储单元中,
c另存放在下一个单元。
? 2,一个位段必须存储在同一存储单元中,
不能跨两个单元。
11.2 位域 ( 位段 )
? 如果第一个单元空间不能容纳下一个位段,则
该空间不用,而从下一个单元起存放该位段。
? 3.可以定义无名位段。如,
? unsigned a,l;
? unsigned,2;(这两位空
间不用)
? unsigned b:3 ;
? unsigned c:4;
? 在 a后面的是无名位段,空间不用。
11.2 位域 ( 位段 )
? 4.位段的长度不能大于存储单元的长度,也不
能定义位段数组。
? 5.位段可以用整型格式符输出。如,
printf(, %d,%d,%d,,dat
a,a,data,b,data, c),
? 当然,也可以用% u、% o、% x等格式符输出。
? 6 ·位段可以在数值表达式中引用,它会被系统
自动地转换成整型数。如
? data.a+5/data.b
? 是合法的。
11.2 位域 ( 位段 )
2.位域的使用
位域的使用和结构成员的使用相同,
其一般形式为,
位域变量名 ·位域名
位域允许用各种格式输出 。
11.2 位域 ( 位段 )
本章小结
1.位运算是C语言的一种特殊运算功能,它
是以二进制位为单位进行运算的。位运算符只
有逻辑运算和移位运算两类。位运算符可以与
赋值符一起组成复合赋值符。如
&=,|=,^=,>>=,<<=等。
2.利用位运算可以完成汇编语言的某些功能,
如置位,位清零,移位等。还可进行数据的压
缩存储和并行运算。高级语言中实现数据的压
缩,节省了存储空间,同时也提高了程序的效
率。
3.位域在本质上也是结构类型,不过
它的成员按二进制位分配内存。其定义、
说明及使用的方式都与结构相同。
4.位域提供了一种手段,使得可在高
级语言中实现数据的压缩,节省了存储
空间,同时也提高了程序的效率。
课堂同步,做做与练练
1.~ 9的运算为,
~(0000000000001001) = ________
2.设 a=15,a>>2 = ______
课堂同步,做做与练练
2.以下程序输出的结果是,______
{ unsigned a,b;
printf("input a number,");
scanf("%d",&a);
b=a>>5; b=b&15;
printf("a=%d\tb=%d\n",a,b);
}
课堂同步,课后练习与作业
1.以下程序输出的结果是,______
{ chara='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);
}