位运算符及其相关操作详解

位运算符详解

前言:由于位运算符是直接对二进制数操作,因此对二进制、八进制、十六进制不甚了解的小伙伴建议先看这篇二进制、八进制、十六进制与十进制的相互关系,这样阅读本篇时将事半功倍

总览

  • 位运算是对计算机存储的二进制序列的相应位进行操作

  • 位运算的操作数必须是整数型或字符型

运算符 示例
按位与 & a & b
按位或 | a | b
按位异或 ^ a ^ b
按位取反 ~ ~ a
左移 << a << b
右移 >> a >> b

按位与 &

  • 按位与是指:参加运算的两个操作数,按二进制对应的位进行“与”运算。如果两个相应的二进制位都为1,则得到的结果为1,否则位0。

  • 0 & 0 = 0,0 & 1 = 0,1 & 1 = 1,1 & 0 = 0,即 全一为1,有零则零

    	int a = 15;
    	//15的二进制序列为 0000 0000 0000 0000 0000 0000 0000 1111
    	int b = 17;
    	//16的二进制序列为 0000 0000 0000 0000 0000 0000 0001 0001
    	int c = a & b;
    	//a和b按位与,那么得到的二进制序列应该是 0000 0000 0000 0000 0000 0000 0000 0001
    	//即十进制的1
    	printf("c = %d\n", c);
    

    位运算符及其相关操作详解_第1张图片

按位或 |

  • 两个相应的二进制位中只要有一个为1,则得到的结果为1。

  • 0 | 0 = 0,0 | 1 = 1,1 | 0 = 1,1 | 1 = 1,即 有一为1,全零为0

    int a = 15;
    //15的二进制序列为 0000 0000 0000 0000 0000 0000 0000 1111
    int b = 17;
    //16的二进制序列为 0000 0000 0000 0000 0000 0000 0001 0001
    int c = a | b;
    //a和b按位与,那么得到的二进制序列应该是 0000 0000 0000 0000 0000 0000 0001 1111
    //即十进制的31
    printf("c = %d\n", c);
    

    位运算符及其相关操作详解_第2张图片

按位异或 ^

  • 参加运算的两个二进制位,如果相同则为0,不同则为1

  • 1 ^ 1 = 0,1 ^ 0 = 1,0 ^ 1 = 1,0 ^ 0 = 0,即 不同为1,相同为0

    int a = 15;
    //15的二进制序列为 0000 0000 0000 0000 0000 0000 0000 1111
    int b = 17;
    //16的二进制序列为 0000 0000 0000 0000 0000 0000 0001 0001
    int c = a ^ b;
    //a和b按位与,那么得到的二进制序列应该是 0000 0000 0000 0000 0000 0000 0001 1110
    //即十进制的30
    printf("c = %d\n", c);
    

    位运算符及其相关操作详解_第3张图片

按位取反 ~

  • 将二进制中的每个位的 0变成1,1变成0

  • 如 ~025就是对八进制数25(即二进制数000 010 101)进行按位取反,得到752(111 101 010)

    int a = 15;
    //15的二进制序列为 0000 0000 0000 0000 0000 0000 0000 1111
    int b = ~ a;
    //a按位取反,二进制序列为 1111 1111 1111 1111 1111 1111 1111 0000
    // 由于在计算机内部是按补码储存,那么转换为反码 1111 1111 1111 1111 1111 1111 1110 1111
    // 转化为原码 1000 0000 0000 0000 0000 0000 0001 0000
    //即十进制的-16
    printf("b = %d\n", b);
    
    int c = -1;
    //-1的二进制序列为 1111 1111 1111 1111 1111 1111 1111 1111
    int d = ~c;
    //c按位取反,二进制序列为 0000 0000 0000 0000 0000 0000 0000 0000
    //即十进制的0
    printf("d = %d\n", d);
    

    位运算符及其相关操作详解_第4张图片

左移 <<

  • 左移运算符是用来将一个数的各二进制位全部向左移动若干位,右边的空位补零

  • 例如 a = 3(10) = 011(2),那么 a << 1的结果就是110(2)=6(10)

    int a = 17;
    //a的二进制序列为 0000 0000 0000 0000 0000 0000 0001 0001
    int b = a << 2;
    //b为所有二进制为左移两位得到 0000 0000 0000 0000 0000 0000 0100 0100
    //即十进制数 68
    printf("b = %d\n", b);
    

    位运算符及其相关操作详解_第5张图片

右移 >>

  • 右移运算符是用来将一个数的各二进制位全部向右移动若干位
  • 逻辑右移:右移之后的空位无论原符号位是什么,统一补零
  • 算术右移:如果原符号位位1,那么空位全部补1,反之如果符号位为0,那么空位补0
  • 一般来说,编译器采用的是算术右移
	int a = 17;
	//a的二进制序列为 0000 0000 0000 0000 0000 0000 0001 0001
	int b = a >> 2;
	//b为所有二进制为左移两位得到 0000 0000 0000 0000 0000 0000 0000 0100
	//即十进制数4
	int c = -15;
	//c的二进制原码序列为 1000 0000 0000 0000 0000 0000 0000 1111
	//反码 1111 1111 1111 1111 1111 1111 1111 0000
	//补码 1111 1111 1111 1111 1111 1111 1111 0001
	int d = c >> 2;
	//如果是逻辑右移
	//d的二进制序列为 0011 1111 1111 1111 11111 1111 1111 1100
	//如果是算术右移
	//d的二进制序列为 1111 1111 1111 1111 1111 1111 1111 1100
	//反码 1111 1111 1111 1111 1111 1111 1111 1011
	//原码 1000 0000 0000 0000 0000 0000 0000 0100
	//即十进制数-4
	printf("b = %d\n", b);
	printf("d = %d\n", d);

位运算符及其相关操作详解_第6张图片

位运算的运用

判断一个数是否为偶数

  • Tips:利用位运算对一个数进行奇偶判断的速度要快于if(num % 2 == 0){……}这一操作

  • 我们知道二进制数每一位的权重为2,因此要判断一个数是否为偶数,只需要判断它二进制位的第一位是1还是0

    • 如果第一位是1,那么十进制值就会加上20 = 1这个奇数,而偶数加奇数一定为奇数,因此该数一定是奇数
    • 如果第一位是0,那么这个1就不会加上,该数就一定是偶数
  • 那我们如何得到二进制数的第一位呢?将这个数和1进行按位与操作就可以了,因为1的二进制位只有第一位为1,这样我们就可以单独对二进制的第一位进行判断,从而判断该数的奇偶性

    #include
    int main()
    {
    	int a;
    	while (scanf_s("%d", &a) != EOF)
    	{
            //注意:关系运算符的优先级高于&,|,^操作符
    		if ((a & 1) == 0)
    			printf("%d是偶数\n",a);
    		else
    			printf("%d是奇数\n", a);
    	}
    	return 0;
    }
    

    位运算符及其相关操作详解_第7张图片

统计二进制数中1的个数

方法一

  • 我们知道如果我们要获得十进制数的每一位,我们可以对这个数不断进行模10除以10操作

    • 例如要获得十进制数139的每一位,139 % 10 = 9,得到各位9,139 / 10 = 13,13 % 10 = 3,得到十位3,13 / 10 = 1,1 % 10 = 1,得到百位1
  • 那么对二进制数也可以这样操作,只是将模10除以10改为模2除以2

    int One_Count(unsigned int num)	//参数定义为无符号型是为了确保对负数运算的正确
    {
    	int count = 0;
    	while (num)
    	{
    		if (num % 2 == 1)
    			count++;
    		num /= 2;
    	}
    	return count;
    }
    

方法二

  • 可以利用 & 获得二进制的每一位

    int One_Count(int num)
    {
    	int count = 0;
    	for (int i = 0; i < 32; i++)
    	{
    		if (((num >> i) & 1) == 1)
    			count++;
    	}
    	return count;
    }
    

方法三(巧解)

  • 可能有小伙伴会认为上面的方法二中的循环固定要循环32次,当计算小数字二进制中1的个数时会浪费时间,那么还有没有更加极致的方法呢?

  • 有!我们来看一个表达式:n = n & (n - 1)

    • 举个例子,n = 15(10) = 1111(2), n - 1 = 14(10) = 1110(2), n = n & (n-1) = 1110(2)

      ​ n = 1110(2), n - 1 = 1101(2), n = n & (n-1) = 1100(2)

      ​ n = 1100(2), n - 1 = 1011(2), n = n & (n-1) = 1000(2)

      ​ n = 1000(2), n - 1 = 0111(2), n = n & (n-1) = 0000(2)

  • 我们可以发现,这个表达式每一次计算,都可以将数n二进制位中最右边的1变成0,因此我们可以创建一个循环,n = n & (n - 1)这个表达式可以运行的次数就是数n二进制位中含1的个数

int One_Count(int num)
{
	int count = 0;
	while (num)
	{
		num = num & (num - 1);
		count++;
	}
	return count;
}

统计两个二进制数中不同位的个数

  • 根据按位异或 ^ 的规律,两个二进制位如果不同,则结果为1,相同结果为0,因此我们可以将这两个数先按位异或,在统计结果二进制位中1的个数

    #include
    int One_Count(int num)
    {
    	int count = 0;
    	while (num)
    	{
    		num = num & (num - 1);
    		count++;
    	}
    	return count;
    }
    int main()
    {
    	int a, b, c;
    	scanf_s("%d %d", &a, &b);
    	c = a ^ b;
    	printf("%d和%d二进制不同位有%d个\n", a,b,One_Count(c));
    	return 0;
    }
    

将一个数用二进制形式打印(左边为高位,右边为地位)

#include
void Print(int num)
{
	unsigned int temp = 0x80000000;
   //即二进制数 1000 0000 0000 0000 0000 0000 0000 0000
   //第一位不看成符号位
	while (temp)
	{
		if ((num & temp))
			printf("1 ");
		else
			printf("0 ");
		temp >>= 1;
	}
}
int main()
{
	int a;
	scanf_s("%d", &a);
	Print(a);
	printf("\n");
	return 0;
}

不创建临时变量交换两个数

  • 这里我们要用到按位异或 ^

  • 这种方法的运行效率不如创建临时变量

    void exchange(int *a, int *b)
    {
        int temp = *a;
        *a = *b;
        *b = temp;
    }
    
  • 这种方法仅作了解即可

    #include
    int main()
    {
    	int a = 2, b = 3;
    	a = a ^ b;
    	b = a ^ b;
    	a = a ^ b;
    	return 0;
    }
    

你可能感兴趣的:(C语言,c语言)