PPC的C/C++和人工智能学习笔记
每一篇学习笔记,都只是为了更好地掌握和理解

C语言基础(16)_位运算

今天学习C语言的位运算。后面给出了一些位运算的例题。

 

C语言中的很多运算基本是以字节为单位进行的,但有时为了节约内存空间,或很多系统程序中要求在比特位级别进行运算处理(比如嵌入式),C语言提供了六种位运算符:

&(按位与)|(按位或)~(按位取反)^(按位异或)<<(左移)>>(右移)

要注意, && 与 &, || 与 |, !与 ~的区别,前者为逻辑的与、或、非,求得的结果不是真就是假(非0和0),用来判断逻辑真假的概念,而后者是要从比特位的角度一位一位的进行操作。

一个字节通常由8个二进制位来组成。位操作,就是把某个变量的值用二进制进行表示之后(就是其本来在计算机中的实际存储格式),再进行操作。二进制数的表示,只有0和1组成:

&,按位与:1&1=1,1&0=0,0&1=0,0&0=0;有0就为0

|,按位或:1|1=1,1|0=1,0|1=1,0|0=0;有1就为1

~,按位非:~1=0,~0=1;取反,0为1,1为0

^:按位异或:1^1=0,1^0=1,0^1=1,0^0=0;相异为1,相同为0

<<,左移:0010 << 1 = 0100

>>,右移:0010 >> 1 = 0001

 

接下来,仔细来分析一下移位操作(<<和>>)

正数,右移正数位(右移位数小于所定义类型的位数,int类型就是<32):

int a = 3;

int result = a>>2; //结果result = 0

分析:a是一个整形,占4个字节,一共32个比特位,则a=3可表示为:0000 0000 0000 0000 0000 0000 0000 0011,当a执行右移操作时,右边移动的位数溢出丢弃,而左边补齐0,总之,不管怎么移动,a一共是32个比特位,所以,在移动之后a就表示为: 0000 0000 0000 0000 0000 0000 0000 0000,其中左边的两个0为补充的,而右边的两个1已经右移溢出丢弃,最终a的结果的为0.

正数,右移正数位(右移位数所定义类型的位数,int类型就是>=32):

int a = 3;

int result = a>>33; //结果是1,相当于右移33-32=1位,所以右移32位等于没执行操作

分析:从理论上来说,移位的位数是不能超过所定义类型的位数的,如果一个正数右移位数超过了所定义类型的位数,其真实移动的位数为所移动的位数%类型位数,例如,整形a右移33,其实就是移动33%32=1 位。所以,a在右移1个比特位后,其结果为1。相当于移动前为: 0000 0000 0000 0000 0000 0000 0000 0011,而移动后为: 0000 0000 0000 0000 0000 0000 0000 0001,右边溢出移位,左边补齐移位。

正数,右移负数位(注意,右移还是右移,不是因为负就相反方向移动,而是换算为正数再移位)

int a = 3;

int result = a>>-31; //结果是右移 -31 +32 =1位,所以是1

a = 3;

int result = a>>-33;//结果是右移 -33 +32*2 = 31位,所以是0

 

正数,左移正数位(左移位数小于所定义类型的位数,int类型就是<32):

int a = 3;

int result = a<<2; //结果是12

分析:正数的左移是其右移的一个逆向过程,左边移动溢出丢弃,右边补齐,因此,a移动前为: 0000 0000 0000 0000 0000 0000 0000 0011,左移2为之后为:0000 0000 0000 0000 0000 0000 0000 1100,所以移动后的结果为12。

 

正数,左移正数位(左移位数小于所定义类型的位数,int类型就是>=32):

int a = 3;

int result = a<<33; //相当于左移33%32=1位,结果是6

分析:一个正数在左移超过所定义类型位数时,其真实移动的位数也会限定在其类型位数的范围之类,所以移动33位,实则移动33%32=1位,因此最终的结果为6。

 

综述:正数的左移右移,相当于乘2和除2的操作,移动n位(有效范围内),相当于乘以2^n和除以2^n(仅限于正数)。

 

负数,右移正数位

int a = -3;

int result = a>>2; //结果是-1

分析:负数的右移法则跟正数的右移法则基本一样,唯一不同的是正数右移其左边补的是0,而负数右移左边补的是1,即为符号位,因此,有人把负数的移位称为符号移位,其原理就是补齐的是符号位,例如:a = -3,由于是负数,在计算机中采用补码的形式进行表示,1111 1111 1111 1111 1111 1111 1111 1101(符号位不变,原码取反加1),因此在右移两位之后为: 1111 1111 1111 1111 1111 1111 1111 1111,左边的两个1为补齐的符号位,将其补码还原原码之后结果为:1000 0000 0000 0000 0000 0000 0000 0001,所以最终的结果为-1。

同样的,假如右移33位,也是相当于右移33%32=1位。

 

负数,左移正数位

int a = -3;

int result = a<<2; //结果是-12

分析:负数的左移跟正数的左移一样,左边溢出丢弃,右边补0,移动前为:1111 1111 1111 1111 1111 1111 1111 1101,移动后: 1111 1111 1111 1111 1111 1111 1111 0100,转换原码后为-12。

同样的,假如左移33位,也是相当于左移33%32=1位。

 

对于负数的移动负数位,一样要把移动的位数换算为正数,比如-1就是 -1+32=31,比如-31就是-31+32=1,-33就是-33+32*2=31,移动的方向是不变的。

 

位运算应用与例子:

位运算例题1:用两种方法快速找出一个32位整数的二进制表达里有多少个”1″?

答案1:for(n=0; b; b >>= 1) if (b & 1) n++; //n是结果,循环32次,(关于数字位数线性):

答案2:for(n=0; b; n++) b &= b-1; //n是结果,有几个1就循环几次,(关于”1″的个数线性)

 

位运算例题2: 给出一行C语言表达式,判断给定的正整数是否是一个2的幂。

答案:(b & (b-1)) == 0。2的幂,就是只有1位是1,其他位全是0,比如16,8,4,2,1等等。

 

位运算例题3:一个4字节的unsigned int a,求其低16位和高16位的值。

答案:低16位= a & 0x0000FFFF; 高16位=(a>>16)& 0x0000FFFF。

 

位运算例题4:循环移位,32位的无符号整数,求其循环(右/左)移n位后的值。(循环右移,就是将移出的低位作为高位的移入位)。

答案:循环右移n位,y = (x>>n) | (x<<(32-n));

循环左移n位,y = (x<<n) | (x>>(32-n));

 

位运算例题5:将int类型整数在电脑中实际存储的的二进制数写入一个字符串中。

答案:先将n按位与00000000 00000000 00000000 00000001(就是1),得到最低位,存入字符串最后,然后n右移1次,重复操作得到倒数第2位,第3位,…第32位。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
 int n = 0,mask=1;
 char str[33] = { 0 }; //32位数,1位字符串结束符
 scanf("%d", &n);
 int m = n;
 for (int i = 31; i >= 0; i--) {
 str[i] = "01"[(n&mask) % 2]; //也可以写 (n&mask)+'0';
 n >>= 1;
 }
 printf("%d的二进制是:%s\n",m, str);
 getchar();
 return 0;
}

(2017-03-01 www.vsppc.com)

学习笔记未经允许不得转载:PPC的C/C++和人工智能学习笔记 » C语言基础(16)_位运算

分享到:更多 ()

评论 73

评论前必须登录!