360 likes | 463 Views
第八章 位 运 算. 位运算概述 位运算符的使用 位运算应用举例 位 段. §8.1 位运算概述. 计算机真正执行的是由 0 和 1 信号组成的机器指令,数据也是以二进制表示的。每一个 0 和 1 的状态称为一个 " 位 "(bit) 的状态。每个字节 (Byte) 由 8 位二进制数组成。 C 语言除了具有高级语言的功能以外,其一个重要特点是具有某些低级语言的功能。例如: 调用 PC 机 BIOS 中例行程序的 int86( ) 函数; 调用系统功能的 bdos( ),intdos( ) 等函数;
E N D
第八章 位 运 算 位运算概述 位运算符的使用 位运算应用举例 位 段
§8.1 位运算概述 • 计算机真正执行的是由0和1信号组成的机器指令,数据也是以二进制表示的。每一个0和1的状态称为一个"位"(bit)的状态。每个字节(Byte)由8位二进制数组成。 • C语言除了具有高级语言的功能以外,其一个重要特点是具有某些低级语言的功能。例如: • 调用PC机BIOS中例行程序的int86( )函数; • 调用系统功能的bdos( ),intdos( )等函数; • 读写I/O端口的函数inportb( )和outportb( )等。 • 具有直接对int和char类型的数据的某些字节或位进行操作的能力。
§8.2 位运算符的使用方法 • 在进行位运算时用十进制数难以直接看出每一个二进数中各位的状态,不直观。 如:101,很难立刻知道它每一位是什么? • 在进行位运算时常用八进制数或十六进制数,它们的表示方法与每一个位的状况有直接联系。 • 一位十六进制数正好代表4位二进制数,例如十六进数8相应的二进制数为1000,9为1001。 • 一位八进制数正好代表3位二进位数,如:八进数5相应的二进制数为101,12为001010。
a= 1 0 1 0 1 1 0 1 (八进制数255) (&) b= 1 1 0 0 1 0 1 1 (八进制数313) 结果 = 1 0 0 0 1 0 0 1 (八进制数211) 按位与运算符(&) • 运算符“&”要求有两个运算量:如 a&b • 作用:将a和b中对应的各位都分别进行“与”运算,即二者都为1时结果为1,否则为0。 例8.1 main( ) { unsigned char a,b; printf("enter a and b:"); scanf("%o,%o",&a,&b); printf("a&b=%o\n",a&b); }
按位与运算符(&) • 例8.2 有一数据b,想测试其第3位是否为1,位号是从右向左数,最右一位作为第0位。 我们可以将一个十进制数8(二进制数1000)与b进行&运算,如果结果为8(不为0),则第3位为1。 main( ) { unsigned char a,b; a=8; scanf("%d",&b); prmtt("%d",a&b); }
按位与运算符(&) • 用途: • 测试某一位或几位是否为1 • 使操作数的某几位置0 • 取数中的某几位(屏蔽法) • 取高4位:a & 0xf0 • 取低4位:a & 0x0f • 取奇数位:a & 0xaa • 取偶数位:a & 0x55 屏蔽字
a= 1 0 1 0 1 1 0 1 (十六进制数AD) ( | ) b= 1 1 0 0 1 0 1 1 (十六进制数CB) 结果 = 1 1 1 0 1 1 1 1 (十六进制数EF) 按位或运算符(| ) • 按位或运算也要求两个运算量 • 这两个运算量中对应的各位进行或运算,任一个为1则该位运算结果为1,否则为0。 例8.4 main( ) { unsigned char a,b; printf("Enter two hex numbers:"); scanf("%X,%X",&a,&b); printf("a|b=%02X\n",a|b); }
a= 0 0 0 0 1 1 0 1(十六进制数0D) ( | ) b= 1 0 0 1 0 0 0 0 (十六进制数90) 结果 = 1 0 0 1 1 1 0 1(十六进制数9D) 按位或运算符( | ) • 用途: • 使操作数的某几位置1 • 可以用 | 运算符将不同字节中的某些位组合成为新的值。方法是:将不被包含到新值去的那些位置为0,然后进行或运算即可。
a= 0 0 0 1 0 0 0 0(十六进制数10) ^ b= 0 0 1 1 0 1 0 1(十六进制数35) 结果 = 0 0 1 0 0 1 0 1(十六进制数25) 按位异或运算符(^) • 按位异或运算符要求两个运算量 • 作用:判断两个数相应位的值是否“相异”(不同),若为异,则该位结果为1,否则为0。 即: 0^0==0 0^1==1 1^0==1 1^1==0 例8.5 main( ) { unsigned char a,b; printf("Enter two hex numbers:"); scanf("%X,%X",&a,&b); printf("a|b=%02X\n",a^b); }
按位异或运算符(^) • 用途:异或运算符可用于“翻转”某一位或某几位的值,也就是使0翻转为1,使1翻转为0。 • 欲使一数的某些位翻转,只需将这些位置1,其余位为0,将所得数与该数进行异或运算即可。 • 如果将某数与同一数进行两次(偶数次)异或运算,该数的值不变: b^a^a==b
按位取反运算符(~) • "~"是一个单目运算符(优先级较高),运算量写在运算符之后(结合性自右至左) • 作用:使运算量的各位都取其相反值,即0变1,1变0。 如:a=00000011 (十六进制03) ~a=11111100 (十六进制fc) 例8.5 main( ) { unsigned char x; printf("Enter a hex numbers:"); scanf("%X",&x); printf("~x=%02X\n",~x); }
左移运算符(<<) • 左移运算符“<<”需要两个运算量,左边是要左移的数据,右边是要左移的位数。如:a<<2 • 作用:将一个数中各位全部左移若干位,如a<<2表示将a中各位左移2位, a = 1 0 0 0 0 1 0 1 (十六进制数85) a<<2 = 1 0 0 0 0 1 0 1 0 0(十六进制数14) 左移后右边空出来的位置补零。 左移后左边超出范围的数位丢失。
左移运算符(<<) • 左移n位相当于乘以2n的运算:左移1位相当于乘2,左移2位相当于乘4。左移比乘法运算快得多,所以有些C编译系统自动将乘2n的运算用左移n位来实现。 • 但有一点要注意:以上结论只是在左移不出现溢出时才是正确的。如: char a=0x0f, b=0x80,aa; int c=0x80; printf("a<<4=%02x",aa=(a<<4)); /* f0 */ printf("b<<4=%02x",(char)(b<<4)); /* 00 */ printf("c<<4=%04x",c<<4); /* 0800 */
左移运算符(<<) main() { char ca='\x08', cb='\x80', cc; int ia=0x8001,ib=0x80; printf("\nca<<4=%02x",(char)(ca<<4)); /* ff80 */ printf("\ncb<<4=%02x",(char)(cb<<4)); /* 00 */ printf("\nca<<4=%04x",ca<<4); /* 0080 */ printf("\ncb<<4=%04x",cb<<4); /* f800 */ printf("\nia<<4=%04x",ia<<4); /* 0010 */ printf("\nib<<4=%04x",ib<<4); /* 0800 */ printf("\nsizeof(ca<<4)=%d",sizeof(ca<<4)); /* 2*/ printf("\nsizeof(cb<<4)=%d",sizeof(cb<<4)); /* 2 */ } 字符型数据左移,由其构成表达式的类型为int型,sizeof(ca<<4)的值为2; 改为以下程序可以输出正确结果 cc=ca<<4; printf("\nca<<4=%02x",cc);
右移运算符(>>) • 与左移相反,“>>”的作用是使一个数的各个位全部右移若干位, • 右移出去的位丢失,左端补什么呢?这点比左移时复杂一些,要区分不同情况: • 对无符号的int型或char型数据,右移时左端补零 a = 1 0 0 0 0 1 0 1 (十六进制数85) a>>2 = 0 0 1 0 0 0 0 1 0 1(十六进制数21) 右移后右边丢失。 右移后左边补0。
右移运算符(>>) • 对带符号的int和char类型数据 • 如果符号位为0(即正数),则左边也是补入0, • 如果符号位为1(即负数),则左边补入的全是1,这是为了保持数原来的符号并实现右移一位相当于除2。 a = 1 1 0 0 0 1 0 0 (十进制数-60) a>>2 = 1 1 1 1 0 0 0 1 0 0(十进制数-15) 右移后右边丢失。 右移后左边补1。
右移运算符(>>) main() { char ca='\x08', cb='\x80'; int ia=0x8001,ib=0x80; unsigned ua=0x8001,ub=0x80; printf("\nca>>4=%02x",(char)(ca>>4)); /* 00 */ printf("\ncb>>4=%02x",(char)(cb>>4)); /* fff8 */ printf("\nua>>4=%04x",ua>>4); /* 0800 */ printf("\nub>>4=%04x",ub>>4); /* 0008 */ printf("\nia>>4=%04x",ia>>4); /* f800 */ printf("\nib>>4=%04x",ib>>4); /* 0008 */ printf("\nsizeof(ca>>4)=%d",sizeof(ca>>4)); /* 1 */ printf("\nsizeof(cb>>4)=%d",sizeof(cb>>4)); /* 1 */ } 字符型数据右移,由其构成表达式的类型仍为字符型,sizeof(ca>>4)的值为1; 改为以下程序可以输出正确结果 cc=ca>>4; printf("\nca>>4=%02x",cc);
§8.3 位运算应用举例 • 例8.9 将十六进制数转换为二进制数输出。 • C语言的printf函数只提供%x,%d,%o方式输出一个整数(即16进制,10进制,8进制形式)而不能直接输出一个整数的二进制形式,需要人工转换,可以用位运算符来实现此功能。 • 例8.10 按位运算计算器 • 要求程序能实现6种位运算,即输入两个整数,以及指定进行何种运算,则程序应能实现对之位运算。 • 例8.11 循环移位
例8.9 将十六进制数转换为二进制数输出 • 方法是:对一个整数num(16位)的每一位进行测试,视其是否为1。设置一个屏蔽字与该数进行&运算,从而保留(取出)所需的一个位的状态。从高位(第15位)开始,此时设置的屏蔽字为0x8000,其最高位(15位)为1其余位为0,将它赋予一个变量mask,使mask&num,若结果不为0则说明num的15位为1,否则为0。把这个1或0存放在bit中,即 bit=(mask&num) ? 1 : 0; 这个bit的值(非1即0)就是num第15位之值。 • 接着将mask右移一位改为0x4000,处理第14位。依次进行直到处理完最后一位。
/* prints number in hex and binary */ main( ) { int j, num, bit; unsigned int mask=0X8000; printf("\nEnter your number:"); scanf("%X", &num); printf("binary of %04x is:", num); for(j=O; j<16; j++) { bit=(mask & num) ? 1:0; printf("%d",bit); if (j==7) printf("--"); mask >>= 1; } }
例8.10 按位运算计算器 • 要求程序能实现6种位运算,即输入两个整数,以及指定进行何种运算,则程序应能实现对之位运算。 • 例如: • 输入 3 & 5 则应对3和5进行按位“与”; • 输人 4 | 5 则对4和5进行按位“或”处理。
/* performs bitwise calculations */ main( ) { char op[4]; int a,b; while (1) { printf("\nEnter expression (example'ff00 & 1101'):"); scanf("%x %s %x", &a, op, &b); printf("\n"); switch (op[0]) { case '&': pr_bin(a); printf("(&)"); pr_bin(b); pline( ); pr_bin(a & b); break; case '|': pr_bin(a); printf("(|)"); pr_bin(b); pline( ); pr_bin(a | b); break; case '^': pr_bin(a); printf("(^)"); pr_bin(b); pline( ); pr_bin(a ^ b) ;break; case '>': pr_bin(a); pline(); printf("(>>%d)",b); pr_bin(a>>b); break; case '<': pr_bin(a); pline(); printf("(<<%d)",b); pr_bin(a>>b); break; case '~': pr_bin(b); pline(); printf("( ~ )"); pr_bin( ~b ); break; default: printf("not valid operator. \n"); } } } /* prints number in hex and binary */ pr_bin(int num) { int j, bit; unsigned int mask=0X8000; printf("\t%04x ", num); for(j=O; j<16; j++) { bit=(mask & num) ? 1:0; printf("%d",bit); if (j==7) printf("--"); mask >>= 1; } } /* print a line */ pline() { printf("----------------------------------\n"); }
n位 a n位 例8.11 循环移位 • 在低级语言中有直接实现循环移位的指令,C语言没有循环移位的运算符,但可以利用已有的位运算符实现循环移位。 • 所谓循环移位是指在移位时不丢失移位前原范围的位,而是将它们作为另一端的补入位。 例如:循环右移n位,指各位右移n位,原来的低n位变成高n位,见图。
例8.11 循环移位 • 为实现循环右移n位,可以用以下步骤实现: • 使a中各位左移(16-n)位,使右端的n位放到b中的高n位中。其余各位补0,可用下面语句实现(见图(a))。 b=a<<(16-n); • 将a右移n位,由于a不带符号,故左端补0。将右移后的数放在c中,可用以下语句(见图(b))。 c=a>>n; • 按位“或”具有组合两个数的某些位的作用,使b与c进行按位“或”运算,即:c = c | b;
循环右移程序 main( ) { unsigned a,b,c; int n; scanf("a=%x,n=%d", &a, %n); b=a<<(16-n); c=a>>n; c=c | b; printf("a=%x \t c=%x", a, c); } 运行时输入 a=f2d3, n=3↙ 结果显示: a=f2d3 c=7e5a f2d3= 1111 0010 1101 0011 7e5a= 0111 1110 0101 1010 /*实现循环右移的函数*/ unsigned rightrot(unsigned a,int n) { int b; b=a<<(16-n); a=a>>n; a=a | b; return (a); }
§8.4 位 段 • C语言可以用以上介绍的位运算符访问位(bit),也可以用“位段”的方法来访问字节中某些位。 • 位段(bit field)又可称为“位域”,“位字段”等,实际上它是字节中一些位的组合,因此也可认为它是“位信息组”。
位段的概念和定义方法 • 位段是一种特殊形式的结构体结构中的成员,它与以前介绍过的结构体有不同之处在于它是以位为单位来定义结构体中的成员的长度。 struct packed_data1 { unsigned int a :3; /*占3位*/ unsigned int b:1; /*占1位*/ unsigned int c :3; /*占3位*/ unsigned int d : 1; /*占1位*/ } x ; a,b,c,d 4个成员总共占1个字节。每个位段占的位数不同 在一个字节中,各位段存放的方向无统一规定,由各C版本自定。
位段的概念和定义方法 • 可以指定某一个位段从下一个字节开始存放,而不是紧接着前面的位段存放。 struct packed_data1 { unsigned int a :3; unsigned int :0; unsigned int c : 5; unsigned int : 0; unsigned int e : 6; unsigned int f : 9; } x ; a后面的5位和c后面的3位未被使用,最后在f之后也有一位多余无用。 位段a后面定义了一个“位数为0”的无名位段,它的作用是使下一个位段从另一个字节开始存放,如图。
位段的概念和定义方法 第一个成员是整型的,占2个字节;下面三个位段a,b,c,占2个字节(多余6位);最后f又占4个字节。共占8个字节。 • 在一个结构体中可以混合使用位段和通常的结构体成员。 特别强调 定义位段时必须用unsigned int或int类型,不能用char或其它类型。 有的C编译系统只允许使用unsigned型。
位段的引用方法 • 对位段的引用方法与引用结构体变量中的成员相同,即用以下形式: x.a, x.b, x.c, x.d • 允许对位段赋值,例如: x.a=2, x.b=1, x.c=7, x.d=0; 但要注意每一个位段能存储的最大值。 例如: x.c占三位,最大值为7,如果赋以8就会出现溢出,从而使x.c只取8的二进制数的低3位(000)。
位段的引用方法 • 可以用指针变量指向一个成员为位段的结构体变量,可以引用此结构体变量的地址,也可以通过指针变量来引用位段。例如: p=&x; /*使p指向结构体变量x*/ printf("%d",p->a); /*输出p所指向的结构体中的位段a */ • 但不能引用位段的地址,下面写法是不合法的: &x.a (错误) 因为地址是以字节为单位的,无法指向位。
位段的用途及优点 • 能方便地访问一个字节中的有关位,这在控制中更为需要。而在其它高级语言中无此功能的。 • 用位段可以节省存储空间,把几个数据放在同一字节中,如“真”和“假”(即0和1)这样的信息只需一位就可以存放了。 • 某些输入输出设备的接口将传输信息编码为一个字节中的某个位,程序可以访问它们,并据此作出相应的操作。 • 对位段的操作比用位运算符方便,而且可植值性好,效率高。
作业 P292 • 8.1 8.2 8.4 8.8 8.10 8.14