第十二章 位运算

原创
01/07 20:07
阅读数 181

章节结构

  • 12.1 位运算和位运算符
    • 12.1.1 “按位与”运算
      • 按位与运算的应用
    • 12.1.2 "按位或”运算
      • 按位或运算的应用
    • 12.1.3 “异或”运算
      • 异或运算的应用
    • 12.1.4 “取反”运算
      • 取反运算的应用
    • 12.1.5 左移运算
      • 左移运算的应用
    • 12.1.6 右移运算
      • 右移运算的应用
    • 12.1.7 位运算赋值运算符
    • 12.1.8 不同长度的数据进行位运算
  • 12.2 位运算举例
  • 12.3 位段

位运算是C语言的重要特色,是其他计算机高级语言所没有的。
位运算,是指以二进制位为对象的运算。
指针运算和位运算往往是编写系统软件所需要的。在计算机用于检测和控制领域中也要用到位运算的知识。

12.1 位运算和位运算符(6种)

6种位运算符
&(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移

  • (1)位运算符中除"~"以外,均为二目(元)运算符,及要求两侧各有一个运算量(或称为运算对象)
  • (2)参加位运算的对象只能是整型或字符型的数据,不能为是实型数据

12.1.1 “按位与”运算

参加运算的两个数据按二进制位进行”与“运算。

  • (1)如果两个相应的二进制位都为1,则该位的结果值1;否则为0。
  • (2)如果参加的&运算是负数,则以补码形式表示为二进制数,然后按位进行”与“运算符

0 & 0 = 0,0 & 1 = 0,1 & 0 = 0,1 & 1 = 1

“按位与”运算的示例

7&5=5

分析:7的二进制形式为0000 0111
5的二进制形式为0000 101
根据按位与运算的规则,得到 0000 0101,即5

-7&-5,

则以补码形式表示为二进制数,然后按位进行“与”运算

注意“按位与”和逻辑与运算的区别

&&时逻辑与运算符,7&&5的值为1,因为非0的数值按“真”处理,逻辑与的结果是“真”,以1表示。
而&是按位与,7&5的结果是5。

按位与运算的一些特殊用途(3个)

  • (1)清零
    如果想将一个单元清零,即 使其全部二进制位为0,
    只要找一个二进制数,其各个位符合以下条件:
    原来的数中为1的位,新数中相应位为0。

然后这两个数进行&运算,即可达到清零的目的。

例如,原有数为00101011,另找一个数,设它为10010100,它符合以上条件,即在原数1的位置上,它的位置均为0。
将两个数进行&运算,得到的结果就是00000000
当然也可以使用其他数(如01000100)也可以,只要符合上述条件即可。

  • (2)取一个数中某些指定位 如有一个整数a(为方便起见,以2个字节表示),

  • 如果想要其中的低字节。只须将a与(377)<sub>8</sub>按位与即可。

  • 如果想取两个字节中的高字节,只须用`a&0177400(0177499表示八进制数的177400),

  • (3)要想将哪一位保留下来,就与一个数进行&运算,此数在该位取1 注意:这里此数在该位取1的意思是表示,原来的那个数,和取一个数在要保留的哪一位上必须取1的。
    即若原数保留的位上的数值是1,则取一个数在这个位上是0
    原数保留的位上的数值是0,则取一个数在这个位上是1

例如,有一数01010100,想把其中左面第3,4,5,7,8位保留下来,可以这样运算:让0101 0100与0011 1011进行按位与运算,得到的结果是00010000(16)

12.1.2 “按位或”运算

按位或运算的规则:两个对应的二进制位中只要有一个为1,该位的结果值为1。
即: 0|0=0,0|1=1,1|0=1,1|1=1

例如: 060 | 017
将八进制数60与八进制数17进行按位或运算。

低4位全为1.如果想使一个数a的低4位改为1,只须将a与017进行按位或运算即可。
按位或运算常用来对一个数据的某些位定值为1。

例如,a是一个整数,有表达式:a|0377,则第8位全置为1,高8位保留原样。

12.1.3 “异或”运算

异或运算符“^”也称为XOR运算符。它的规则是:若参加运算的两个二进制位异号,则得到1(真),若同好,则结果为0(假)。 即 0^0=0,0^1=1,1^0=1,1^1=0

例如:0011 1001(十进制数57,八进制数071)^0010 1010(十进制数42,八进制数052)
得到结果为0001 0011(十进制数19,八进制数023)
071^052,结果为023(八进制数)。

“异或”的意思是判断两个相应的位值是否为’异“。为”异“(值不同)就取真(1);否则为假(0)。

异或运算的应用

  • (1) 使特定位翻转
    假设有0111 1010,想使其42位翻转,即1变为0,0变为1.
    可以将它与0000 1111进行异或(^)运算,即 结果是0111 0101。
    结果值得第4位正好是原数低4位的翻转。
    要使哪几位翻转就将与其进行^运算的数在该几位置为1(其他位置为0)即可。
    这是因为原数中值为1的位与1进行^运算得0,原数中位值0与1进行^运算的结果得1

  • (2)与0相^,保留原值
    例如:012^00=012
    即结果为 0000 1010
    因为原数中的1与0进行^运算得1,0^0得0故保留原数

  • (3)交换两个值,不用临时变量
    假如a=3,b=4想将a和b的值互换,可以用一下复制语句实现

a = a ^ b;
b = b ^ a;
a = a ^ b;

即等效于以下两步: (a)执行前面两个赋值语句:a=a^b;b=b^a;相当于b=b^(a^b)
b^a^b等于a^b^bb^b的结果为0,因为同一个数与本身相^,结果为0.
因此b的值等于a^0,即a,其值为3 (b)再执行第3个赋值语句:a=a^b。由于a的值等于(a^b),b的值等于(b^a^b)
因此,相当于a=a^b^b^a^b,即a的值等于a^a^b^b^b,等于b

12.1.4 “取反”运算

~是一个单目(元)运算符,用来对一个二进制数按位取反,即将0变1,将1变0.
例如,~025是对八进制数25(即二进制数0001 0101)按位求反。

八进制数25按位求反得到八进制数177752

~运算符的应用

若一个整数a为16位,想使最低一位为0,可以用a=a&0177776
177776即二进制数1111 1111 1111 1110,如果a的值为八进制数75,a&0177776的运算可以表示如下

0000 0000 0011 1101
1111 1111 1111 1110
&
0000 0000 0011 1100

a的最后面一个二进制位变成0。
但如果一个整数a为32位,想将最后一位变成0就不能用a&0177776
应改用a&0377 7777 7776

这种方法不易记忆,可以改用a=a&~1
它对以16位和以32位存放一个整数的情况都使用,不必作修改。
因为在以两个字节存储一个整数时,1的二进制形式为0000 0000 0000 0001~11111 1111 1111 1110(注意~1不等于-1)。
在以4个字节存储一个整数时,~11111 1111 1111 1111 1111 1111 1111 1110

~运算符的优先级别比算数运算符、关系运算符、逻辑运算符和其他位运算符都高, 例如~a&b
先进行~a运算,然后进行&运算

12.1.5 左移运算

<<用来将一个数的各二进制位全部左移若干位。

例如a=a<<2
将a的二进制位数左移2位,右补0。
若a=15,即二进制数0000 1111,左移2位得0011 1100,即得到十进制数60
高位左移后溢出,舍弃。

用8位二进制数表示十进制数15,如果用16位或32位二进制数表示,结果都是一样的

左移1位相当于该数乘以2,左移2位相当于该数乘以2的2次方=4
15<<2,得到的结果是60,即乘了4。
但此结论只适用于该书左移时被溢出舍弃的高位中不包含1的情况。

例如,假设以一个字节(8位)存一个整数, 若a位无符号整型变量,则a=64时,左移一位时(相当于乘2)溢出的是0
而左移2位时(相当于乘以4),溢出的高位中包含1。

左移比乘法运算快得多,有些C编译程序自动将乘2的运算用左移一位来实现,
将乘2的n次方的幂运算处理为左移n位

12.1.6 右移运算

a>>2表示将a的各二进制位右移2位,移到右端的低位被舍弃,对无符号数,高位补0.

例如,a=017时,a的值用二进制形式表示为0000 1111

右移一位相当于除以2,右移n位相当于除以2
在右移位时,需要注意符号位问题

  • 对无符号数,右移时左边高位补0;
  • 对于有符号的数,
    • 如果原来符号为0(该数为正),则左边也补0。
    • 如果符号为原来为1(即负数),则左边移入0还是1,要取决于所用的计算机系统。

有的系统补0,有的系统补1。
补0的称为“逻辑右移”,即简单右移,不考虑数的符号问题
补1的称为“算术右移”(保持原有的符号)

Visual C++和其他一些C编译采用的算术右移,即对有符号数右移时,如果符号位原来为1,左面补入高位的是1

12.1.7 位运算赋值运算符

位运算符与赋值运算符可以组合成符合赋值运算符

例如a&=b相当于a=a&ba<<=2相当于a=a<<2

12.1.8 不同长度的数据进行位运算

规则:如果两个数据长度不同进行位运算时系统会将二者按右端对齐

  • (1)如果a为正数,则左侧16位补满0;
  • (2)若a为负数,左端应补满1;
  • (3)如果a为无符号整数型,则左侧添满0

例如,int的a和short的b进行按位与运算,系统会将二者按右端对齐。

12.3 位段

之前对内存中信息的存取一般以字节为单位。
实际上,有时存储一个信息不必用一个或多个字节,例如,“真”或“假”用0或1表示,只需1个二进位即可。
在计算机用于过程控制、参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进制位,常常在一个字节中放几个信息。

怎样向一个字节中的一个或几个二进制位赋值和改变它的值?
两种方法。

  • (1)人为地将一个整型变量data分为几段。
    例如a,b,c,d分别占2位、6位、4位、4位。如果想将c段的值变为12(设c原来为0)
    可以这样: (a)将整数12左移4位(执行12<<4),使1100成为右面起第4~7位
    (b)将data与12<<4进行按位或运算,即可使c的值变成12
    如果c的原值不为0,应先使之为0。可以使用下面方法: data=data&0177417(0177417的最左边的0表示177417是八进制数)的二进制数表示为 11 111111 0000 1111
    也就是使第4~7位全为0,其他位全为1。它与data进行&运算,使第4~7位为0,其余各位保留data的原状。

这个177417称为“屏蔽字,即把c以外的信息屏蔽起来,不受影响,只使c改变为0。 但要找出和记住177417这个数比较麻烦。可以使用data=data&~(15<<4)

15是c的最大值(c共占4位,最大值为1111,即15)。15<<4是将1111左移到以右侧开始4~7位,即c段的位置,再取反,就使4~7位变成0,其余位全是1,以上可以示意为

15: 0000 0000 0000 1111
15<<4: 0000 0000 1111 0000 ~(15<<4): 1111 1111 0000 1111

这样可以实现对c清零,而不必计算屏蔽码
将上面几步结合起来得到data = data&~(15<<4)|(n&15)<<4;
n是应赋给c的值。n&15的作用是只取n的右端4位的值,其余各位置0,即把n放到最后4位上,(n&15)<<4就是将n置在4~7上。 过程可以看出,data的其他位保留原状未改变,而第4~7位改变为12(即1100)了。 这种方法给一个字节中某几位赋值过于麻烦。还可以用另一种方法,即位段结构体的方法来处理这类问题。

  • (2)使用位段 C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为位段位域
    利用位段能够用较少的位数存储数据。
struct Packed_data
{
	unsigned a:2;
	unsigned b:2;
	unsigned c:2;
	unsigned d:2;
	short i;
}data;

其中a,b,c,d段分别占2位、6位、4位、4位,i为short型,以上共占4个字节。
也可以使各个位段不恰好占满一个字节

struct Packed_data
{
	unsigned a:2;
	unsigned b:3;
	unsigned c:4;
	short i;
}
struct Packed_data data;

其中a,b,c共占9位,占1个字节多1位,不到2个字节,它的后面位short型,占两个字节。
在a,b,c之后7位空间闲置不用,i从另一字节开头起存放。

位段的空间分配方向因机器而异。一般是由右到左进行分配的。

可以直接对位段进行操作。例如可以直接对位段赋值:

data.a =2;
data.b =7;
data.c = 9;

注意位段的最大值范围。
如果写成data.a=8错了。因为data.a只占两位,最大值为3。
在此情况下,系统会自动取赋予它的数的低位。
例如,8的二进制数形式为1000,而data.a只有2位,取1000的低2位,故data.a得值0。

关于位段的定义和引用的几点说明

  • (1) 声明位段的一般格式为类型名 [成员名]:宽度
    位段成员的类型可以指定为unsigned int或int型。
    ”宽度“应是一个整型常量表达式,其值应是非负的,且必须小于或等于指令类型的位长
  • (2)对位段组(例如上面的结构体变量data在内存中存放时,至少占一个存储单元(即一个机器字,4个字节),即使实际长度只占一个字节,但也分配4个字节。
    如果想 指定某一位段从下一个存储单元(字)存放。可以用以下定义
//这两句表示的是一个存储单元
unsigned a:1;
unsigned b: 2;   


unsigned: 0 ;  // 表示本存储单元不再存放数据。
unsigned:    3;  //另一存储单元  

本来a,b,c应连续存放在一个存储单元(字)中,由于用了长度为0的位置,其作用是使下一个位段从下一个存储单元开始存放。 存储单元:可能是一个字节,也可能是两个字节,视不同的编译系统而异.。

  • (3)一个位段必须存储在同一存储单元中,不能跨两个单元。如果第1个单元空间能容纳下一个位段,则该空间不用,而从下一个单元起存放该位段
  • (4)可以定义无名位段。
unsigned a:1;
unsigned :2; //这两位空间不用
unsigned b:3;
unsigned c:4;  

在a后面的是无名位段,该空间不用

  • (5)位段的长度不能大于存储单元的长度,也不能定义位段数组
  • (6)位段中的数可以用整型格式输出。因此也可以用%u、%o、%x等格式输出 printf("%d,%d,%d",data.a,data.b,data.c);
  • (7)位段可以在数值表达式中引用,它会被系统自动地转换成整型数。
    data.a+5/data.b是合法的。

本章中简要介绍了有关位运算的知识,可以从中了解为什么说C语言是贴近机器的语言,以及C语言是怎样对二进位操作的。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部