第 12章 位运算为了节省内存空间,在系统软件中常将多个标志状态简单地组合在一起,存储到一个字节
(或字)中。C语言是为研制系统软件而设计的,
所以她提供了实现将标志状态从标志字节中分离出来的位运算功能。
所谓位运算是指,按二进制位进行的运算。
12.1 数值在计算机中的表示
12.2 位运算
12.3 位段
12.1 数值在计算机中的表示
1.二进制位与字节计算机系统的内存储器,是由许多称为字节的单元组成的,1个字节由 8个二进制位( bit)构成,每位的取值为
0/1。最右端的那 1位称为“最低位”,编号为 0;最左端的那 1位称为“最高位”,而且从最低位到最高位顺序,依次编号。图 11-1是 1个字节各二进制位的编号。
图 12-1 1个字节各二进制位的编号
2.数值的原码表示数值的原码表示是指,将最高位用作符号位( 0表示正数,1表示负数),其余各位代表数值本身的绝对值(以二进制形式表示)的表示形式。为简化描述起见,本节约定用 1个字节表示 1个整数 。
7 6 5 4 3 2 1 0
例如,+9的原码是 00001001
└→ 符号位上的 0表示正数
-9的原码是 10001001。
└→ 符号位上的 1表示负数
3.数值的反码表示数值的反码表示分两种情况:
( 1) 正数的反码:与原码相同 。
例如,+9的反码是 00001001。
( 2) 负数的反码:符号位为 1,其余各位为该数绝对值的原码按位取反 ( 1变 0,0变 1) 。
例如,-9的反码:因为是负数,则符号位为,1”;
其余 7位为 -9的绝对值 +9的原码 0001001 按位取反为
1110110,所以 -9的反码是 11110110。
4.数值的补码表示数值的补码表示也分两种情况:
( 1) 正数的补码:与原码相同 。
例如,+9的补码是 00001001。
( 2) 负数的补码:符号位为 1,其余位为该数绝对值的原码按位取反;然后整个数加 1。
例如,-9的补码:因为是负数,则符号位为,1”;其余 7位为 -9的绝对值 +9的原码 0001001按位取反为 1110110;
再加 1,所以 -9的补码是 11110111。
已知一个数的补码,求原码的操作分两种情况:
( 1) 如果补码的符号位为,0”,表示是一个正数,所以补码就是该数的原码 。
( 2) 如果补码的符号位为,1”,表示是一个负数,求原码的操作可以是:符号位不变,其余各位取反,然后再整个数加 1。
例如,已知一个补码为 11111001,则原码是
10000111( -7),因为符号位为,1”,表示是一个负数,所以该位不变,仍为,1”;其余 7位
1111001 取反后为 0000110 ;再加 1,所 以 是
10000111。
5.数值在计算机中的表示 ──补码在计算机系统中,数值一律用补码表示 ( 存储 ),原因在于:使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理 。
另外,两个用补码表示的数相加时,如果最高位
( 符号位 ) 有进位,则进位被舍弃 。
12.2 位 运 算
12.2.1 位运算及其运算符
1.按位与 ──&
(1)格式,x&y
(2)规则:对应位均为 1时才为 1,否则为 0,3&9=1。
例如,3&9=1,0011
& 1001
────
0001=1
(3)主要用途:取 (或保留 )1个数的某 (些 )位,其余各位置 0。
2.按位或 ──|
(1)格式,x|y
(2)规则:对应位均为 0时才为 0,否则为 1,3|9=11。
例如,3|9=11,0011
| 1001
────
1011=11
(3)主要用途:将 1个数的某 (些 )位置 1,其余各位不变。
3,按位异或 ──^
(1)格式,x^y
(2)规则:对应位相同时为 0,不同时为 1,3^9=10。
(3)主要用途:使 1个数的某 (些 )位翻转 (即原来为 1的位变为 0,为 0的变为 1),其余各位不变 。
4,按位取反 ──~
(1)格式,~x
(2)规则:各位翻转,即原来为 1的位变成 0,原来为 0的位变成 1:在 IBM-PC机中,~0= 0xffff,~9=0xfff6。
(3)主要用途:间接地构造一个数,以增强程序的可移植性 。
5,按位左移 ──<<
(1)格式,x<< 位数
(2)规则:使操作数的各位左移,低位补 0,高位溢出:
5<<2=20。
6,按位右移 ──>>
(1)格式,x>>位数
(2)规则:使操作数的各位右移,移出的低位舍弃;
高位:
1)对无符号数和有符号中的正数,补 0;
2)有符号数中的负数,取决于所使用的系统:补 0的称为,逻辑右移,,补 1的称为,算术右移,。 例如,20
>> 2=5。
说明,
( 1) x,y和,位数,等操作数,都只能是整型或字符型数据 。 除按位取反为单目运算符外,其余均为双目运算符 。
( 2) 参与运算时,操作数 x和 y,都必须首先转换成二进制形式,然后再执行相应的按位运算 。
例如,5<<2=20,0101 → 10100,20 >> 2=5,10100
→ 00101。
( 3) 实现 &,|,^运算主要用途的方法
1) 构造 1个整数:该数在要取 ( 或保留 ) 的位,或要置 1的位,或要翻转的位上为 1,其余均为 0。
2) 进行按位与,或按位或,或按位异或操作 。
( 4) 实现按位取反主要用途的方法
1) 求 ~0,间接地构造一个全 1的数;
2) 按需要进行左移或右移操作,构造出所需要的数 。
例如,直接构造一个全 1的数,在 IBM-PC机中为
0xffff( 2字节 ),而在 VAX-11/780上,却是 0xffffffff( 4
字节 ) 。 如果用 ~0来构造,系统可以自动适应 。 具体应用,请参见 [案例 11.1]。
12.2.2 应用举例
[例 12.1] 从键盘上输入 1个正整数给 int变量 num,输出由 8~ 11位构成的数 ( 从低位,0号开始编号 ) 。
基本思路,
( 1) 使变量 num右移 8位,将 8~ 11位移到低 4位上 。
( 2) 构造 1个低 4位为 1,其余各位为 0的整数 。
( 3) 与 num进行按位与运算 。
/*案例代码文件名,AL11_1.C*/
/*程序功能:输出一个整数中由 8~ 11位构成的数 */
main()
{ int num,mask;
printf("Input a integer number,");
scanf("%d",&num);
num >>= 8; /*右移 8位,将 8~ 11位移到低 4位上 */
mask = ~ ( ~0 << 4); /*间接构造 1个低 4位为 1,其余各位为 0的整数 */
printf("result=0x%x\n",num & mask);
} [程序演示 ]
程序运行情况:
Input a integer number:1000 ←┘
result=0x3
程序说明,~ ( ~0 << 4)
按位取 0的反,为全 1;左移 4位后,其低 4位为 0,其余各位为 1;再按位取反,则其低 4位为 1,其余各位为 0。
这个整数正是我们所需要的 。
[例 12.2] 从键盘上输入 1个正整数给 int变量 num,按二进制位输出该数 。
/*案例代码文件名,AL11_2.C*/
/*程序功能:按二进制位输出一个整数 */
#include "stdio.h"
main()
{ int num,mask,i;
printf("Input a integer number,");
scanf("%d",&num);
mask = 1<<15; /*构造 1个最高位为 1,其余各位为 0的整数 (屏蔽字 )*/
printf("%d=",num);
for(i=1; i<=16; i++)
{ putchar(num&mask? ’1’,‘0’); /*输出最高位的值 (1/0)*/
num <<= 1; /*将次高位移到最高位上 */
if( i%4==0 ) putchar(‘,’); /*四位一组,用逗号分开 */
}
printf("\bB\n");
} [程序演示 ]
程序运行情况:
Input a integer number:1000 ←┘
1000=0000,0011,1110,1000B
12.2.3 说明
1.复合赋值运算符除按位取反运算外,其余 5个位运算符均可与赋值运算符一起,构成复合赋值运算符,&=,|+,^=,<<=,>>=
2.不同长度数据间的位运算 ──低字节对齐,短数的高字节按最高位补位:
( 1) 对无符号数和有符号中的正数,补 0;
( 2) 有符号数中的负数,补 1。
[Return]
12.3 位段简介有时,存储 1个信息不必占用 1个字节,只需二进制的 1个(或多个)位就够用。如果仍然使用结构类型,则造成内存空间的浪费。为此,C语言引入了位段类型。
1,位段的概念与定义所谓位段类型,是一种特殊的结构类型,其所有成员均以二进制位为单位定义长度,并称成员为位段。
例如,CPU的状态寄存器,按位段类型定义如下:
struct status
{ unsigned sign,1; /*符号标志 */
unsigned zero,1; /*零标志 */
unsigned carry,1; /*进位标志 */
unsigned parity,1; /*奇偶 /溢出标志 */
unsigned half_carry,1; /*半进位标志 */
unsigned negative,1; /*减标志 */
} flags;
显然,对 CPU的状态寄存器而言,使用位段类型
( 仅需 1个字节 ),比使用结构类型 ( 需要 6个字节 )
节省了 5个字节 。
2.说明
( 1) 因为位段类型是一种结构类型,所以位段类型和位段变量的定义,以及对位段 ( 即位段类型中的成员 ) 的引用,均与结构类型和结构变量一样 。
( 2) 对位段赋值时,要注意取置范围 。 一般地说,
长度为 n的位段,其取值范围是,0~ ( 2n-1) 。
( 3) 使用长度为 0的无名位段,可使其后续位段从下 1个字节开始存储 。
例如,
struct status
{ unsigned sign,1; /*符号标志 */
unsigned zero,1; /*零标志 */
unsigned carry,1; /*进位标志 */
unsigned,0; /*长度为 0的无名位段 */
unsigned parity,1; /*奇偶 /溢出标志 */
unsigned half_carry,1; /*半进位标志 */
unsigned negative,1; /*减标志 */
} flags;
原本 6个标志位是连续存储在 1个字节中的 。 由于加入了 1个长度为 0的无名位段,所以其后的 3个位段,从下 1
个字节开始存储,一共占用 2个字节 。
( 4) 1个位段必须存储在 1个存储单元 ( 通常为 1字节 )
中,不能跨 2个 。 如果本单元不够容纳某位段,则从下 1
个单元开始存储该位段 。
( 5) 可以用 %d,%x,%u和 %o等格式字符,以整数形式输出位段 。
( 6) 在数值表达式中引用位段时,系统自动将位段转换为整型数 。
(或字)中。C语言是为研制系统软件而设计的,
所以她提供了实现将标志状态从标志字节中分离出来的位运算功能。
所谓位运算是指,按二进制位进行的运算。
12.1 数值在计算机中的表示
12.2 位运算
12.3 位段
12.1 数值在计算机中的表示
1.二进制位与字节计算机系统的内存储器,是由许多称为字节的单元组成的,1个字节由 8个二进制位( bit)构成,每位的取值为
0/1。最右端的那 1位称为“最低位”,编号为 0;最左端的那 1位称为“最高位”,而且从最低位到最高位顺序,依次编号。图 11-1是 1个字节各二进制位的编号。
图 12-1 1个字节各二进制位的编号
2.数值的原码表示数值的原码表示是指,将最高位用作符号位( 0表示正数,1表示负数),其余各位代表数值本身的绝对值(以二进制形式表示)的表示形式。为简化描述起见,本节约定用 1个字节表示 1个整数 。
7 6 5 4 3 2 1 0
例如,+9的原码是 00001001
└→ 符号位上的 0表示正数
-9的原码是 10001001。
└→ 符号位上的 1表示负数
3.数值的反码表示数值的反码表示分两种情况:
( 1) 正数的反码:与原码相同 。
例如,+9的反码是 00001001。
( 2) 负数的反码:符号位为 1,其余各位为该数绝对值的原码按位取反 ( 1变 0,0变 1) 。
例如,-9的反码:因为是负数,则符号位为,1”;
其余 7位为 -9的绝对值 +9的原码 0001001 按位取反为
1110110,所以 -9的反码是 11110110。
4.数值的补码表示数值的补码表示也分两种情况:
( 1) 正数的补码:与原码相同 。
例如,+9的补码是 00001001。
( 2) 负数的补码:符号位为 1,其余位为该数绝对值的原码按位取反;然后整个数加 1。
例如,-9的补码:因为是负数,则符号位为,1”;其余 7位为 -9的绝对值 +9的原码 0001001按位取反为 1110110;
再加 1,所以 -9的补码是 11110111。
已知一个数的补码,求原码的操作分两种情况:
( 1) 如果补码的符号位为,0”,表示是一个正数,所以补码就是该数的原码 。
( 2) 如果补码的符号位为,1”,表示是一个负数,求原码的操作可以是:符号位不变,其余各位取反,然后再整个数加 1。
例如,已知一个补码为 11111001,则原码是
10000111( -7),因为符号位为,1”,表示是一个负数,所以该位不变,仍为,1”;其余 7位
1111001 取反后为 0000110 ;再加 1,所 以 是
10000111。
5.数值在计算机中的表示 ──补码在计算机系统中,数值一律用补码表示 ( 存储 ),原因在于:使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理 。
另外,两个用补码表示的数相加时,如果最高位
( 符号位 ) 有进位,则进位被舍弃 。
12.2 位 运 算
12.2.1 位运算及其运算符
1.按位与 ──&
(1)格式,x&y
(2)规则:对应位均为 1时才为 1,否则为 0,3&9=1。
例如,3&9=1,0011
& 1001
────
0001=1
(3)主要用途:取 (或保留 )1个数的某 (些 )位,其余各位置 0。
2.按位或 ──|
(1)格式,x|y
(2)规则:对应位均为 0时才为 0,否则为 1,3|9=11。
例如,3|9=11,0011
| 1001
────
1011=11
(3)主要用途:将 1个数的某 (些 )位置 1,其余各位不变。
3,按位异或 ──^
(1)格式,x^y
(2)规则:对应位相同时为 0,不同时为 1,3^9=10。
(3)主要用途:使 1个数的某 (些 )位翻转 (即原来为 1的位变为 0,为 0的变为 1),其余各位不变 。
4,按位取反 ──~
(1)格式,~x
(2)规则:各位翻转,即原来为 1的位变成 0,原来为 0的位变成 1:在 IBM-PC机中,~0= 0xffff,~9=0xfff6。
(3)主要用途:间接地构造一个数,以增强程序的可移植性 。
5,按位左移 ──<<
(1)格式,x<< 位数
(2)规则:使操作数的各位左移,低位补 0,高位溢出:
5<<2=20。
6,按位右移 ──>>
(1)格式,x>>位数
(2)规则:使操作数的各位右移,移出的低位舍弃;
高位:
1)对无符号数和有符号中的正数,补 0;
2)有符号数中的负数,取决于所使用的系统:补 0的称为,逻辑右移,,补 1的称为,算术右移,。 例如,20
>> 2=5。
说明,
( 1) x,y和,位数,等操作数,都只能是整型或字符型数据 。 除按位取反为单目运算符外,其余均为双目运算符 。
( 2) 参与运算时,操作数 x和 y,都必须首先转换成二进制形式,然后再执行相应的按位运算 。
例如,5<<2=20,0101 → 10100,20 >> 2=5,10100
→ 00101。
( 3) 实现 &,|,^运算主要用途的方法
1) 构造 1个整数:该数在要取 ( 或保留 ) 的位,或要置 1的位,或要翻转的位上为 1,其余均为 0。
2) 进行按位与,或按位或,或按位异或操作 。
( 4) 实现按位取反主要用途的方法
1) 求 ~0,间接地构造一个全 1的数;
2) 按需要进行左移或右移操作,构造出所需要的数 。
例如,直接构造一个全 1的数,在 IBM-PC机中为
0xffff( 2字节 ),而在 VAX-11/780上,却是 0xffffffff( 4
字节 ) 。 如果用 ~0来构造,系统可以自动适应 。 具体应用,请参见 [案例 11.1]。
12.2.2 应用举例
[例 12.1] 从键盘上输入 1个正整数给 int变量 num,输出由 8~ 11位构成的数 ( 从低位,0号开始编号 ) 。
基本思路,
( 1) 使变量 num右移 8位,将 8~ 11位移到低 4位上 。
( 2) 构造 1个低 4位为 1,其余各位为 0的整数 。
( 3) 与 num进行按位与运算 。
/*案例代码文件名,AL11_1.C*/
/*程序功能:输出一个整数中由 8~ 11位构成的数 */
main()
{ int num,mask;
printf("Input a integer number,");
scanf("%d",&num);
num >>= 8; /*右移 8位,将 8~ 11位移到低 4位上 */
mask = ~ ( ~0 << 4); /*间接构造 1个低 4位为 1,其余各位为 0的整数 */
printf("result=0x%x\n",num & mask);
} [程序演示 ]
程序运行情况:
Input a integer number:1000 ←┘
result=0x3
程序说明,~ ( ~0 << 4)
按位取 0的反,为全 1;左移 4位后,其低 4位为 0,其余各位为 1;再按位取反,则其低 4位为 1,其余各位为 0。
这个整数正是我们所需要的 。
[例 12.2] 从键盘上输入 1个正整数给 int变量 num,按二进制位输出该数 。
/*案例代码文件名,AL11_2.C*/
/*程序功能:按二进制位输出一个整数 */
#include "stdio.h"
main()
{ int num,mask,i;
printf("Input a integer number,");
scanf("%d",&num);
mask = 1<<15; /*构造 1个最高位为 1,其余各位为 0的整数 (屏蔽字 )*/
printf("%d=",num);
for(i=1; i<=16; i++)
{ putchar(num&mask? ’1’,‘0’); /*输出最高位的值 (1/0)*/
num <<= 1; /*将次高位移到最高位上 */
if( i%4==0 ) putchar(‘,’); /*四位一组,用逗号分开 */
}
printf("\bB\n");
} [程序演示 ]
程序运行情况:
Input a integer number:1000 ←┘
1000=0000,0011,1110,1000B
12.2.3 说明
1.复合赋值运算符除按位取反运算外,其余 5个位运算符均可与赋值运算符一起,构成复合赋值运算符,&=,|+,^=,<<=,>>=
2.不同长度数据间的位运算 ──低字节对齐,短数的高字节按最高位补位:
( 1) 对无符号数和有符号中的正数,补 0;
( 2) 有符号数中的负数,补 1。
[Return]
12.3 位段简介有时,存储 1个信息不必占用 1个字节,只需二进制的 1个(或多个)位就够用。如果仍然使用结构类型,则造成内存空间的浪费。为此,C语言引入了位段类型。
1,位段的概念与定义所谓位段类型,是一种特殊的结构类型,其所有成员均以二进制位为单位定义长度,并称成员为位段。
例如,CPU的状态寄存器,按位段类型定义如下:
struct status
{ unsigned sign,1; /*符号标志 */
unsigned zero,1; /*零标志 */
unsigned carry,1; /*进位标志 */
unsigned parity,1; /*奇偶 /溢出标志 */
unsigned half_carry,1; /*半进位标志 */
unsigned negative,1; /*减标志 */
} flags;
显然,对 CPU的状态寄存器而言,使用位段类型
( 仅需 1个字节 ),比使用结构类型 ( 需要 6个字节 )
节省了 5个字节 。
2.说明
( 1) 因为位段类型是一种结构类型,所以位段类型和位段变量的定义,以及对位段 ( 即位段类型中的成员 ) 的引用,均与结构类型和结构变量一样 。
( 2) 对位段赋值时,要注意取置范围 。 一般地说,
长度为 n的位段,其取值范围是,0~ ( 2n-1) 。
( 3) 使用长度为 0的无名位段,可使其后续位段从下 1个字节开始存储 。
例如,
struct status
{ unsigned sign,1; /*符号标志 */
unsigned zero,1; /*零标志 */
unsigned carry,1; /*进位标志 */
unsigned,0; /*长度为 0的无名位段 */
unsigned parity,1; /*奇偶 /溢出标志 */
unsigned half_carry,1; /*半进位标志 */
unsigned negative,1; /*减标志 */
} flags;
原本 6个标志位是连续存储在 1个字节中的 。 由于加入了 1个长度为 0的无名位段,所以其后的 3个位段,从下 1
个字节开始存储,一共占用 2个字节 。
( 4) 1个位段必须存储在 1个存储单元 ( 通常为 1字节 )
中,不能跨 2个 。 如果本单元不够容纳某位段,则从下 1
个单元开始存储该位段 。
( 5) 可以用 %d,%x,%u和 %o等格式字符,以整数形式输出位段 。
( 6) 在数值表达式中引用位段时,系统自动将位段转换为整型数 。