运算符小汇总

一、位运算符

参考链接:知识星球英雄算法联盟

1、位 与(&) 运算符

位与运算符是一个二元的位运算符,也就是有两个操作数,表示为 x & y。

左操作数右操作数结果
000
010
100
111
#include <stdio.h>
int main() {
   int a = 0b1010; // (1)
   int b = 0b0110; // (2)
   printf("%d\n", (a & b) ); // (3)
   return 0;
}
  1. 在C语言中,以 0b 作为前缀,表示这是一个二进制数。那么 a 的实际值就是 (1010) 。
  2. 同样的, b 的实际值就是 (0110) ;
  3. 那么这里 a & b 就是对 (1010) 和 (0110) 的每一位做表格中的 & 运算。
  4. 所以最后输出结果为:2
    因为输出的是十进制数,它的二进制表示为: (0010)_2

注意:这里的 前导零 可有可无,作者写上前导零只是为了对齐以及让读者更加清楚位与的运算方式。

前导零的概念通过一个例子解释:
例如,反转 2021 得到 1202 。反转 12300 得到 321 ,这里就不保留前导零 。

位与运算符的应用

1、奇偶性判定

对任何一个数,通过将它和 0b1 进行位与,结果为零,则必然这个数的二进制末尾位为0,根据以上表就能得出它是偶数了;否则,就是奇数。

if(5 & 1) {
     printf("5是奇数\n");
 }
 if((6 & 1) == 0) {
     printf("6是偶数\n");
 }

2、取末五位

【例题1】给定一个数,求它的二进制表示的末五位,以十进制输出即可。

  • 这个问题的核心就是:我们只需要末五位,剩下的位我们是不需要的,所以可以将给定的数 位与上0b11111,这样一来就直接得到末五位的值了。
  • 代码实现如下:
#include<stido.h>
int main() {
    int x;
    scanf(%d”, &x);
	printf("%d\n", (x & 0b11111) );
	return 0;
}

3、消除末尾五位

【例题3】给定一个 32 位整数,要求消除它的末五位。

还是根据位与的性质,消除末五位的含义,有两层:

  • 1)末五位,要全变成零;
  • 2)剩下的位不变;

那么,根据位运算的性质,我们需要数,它的高27位都为1,低五位都为 0,则这个数就是:
( 11111111111111111111111111100000 ) 2 (11111111111111111111111111100000)_2 (11111111111111111111111111100000)2
但是如果要这么写,代码不疯掉,人也会疯掉,所以一般我们把它转成十六进制,每四个二进制位可以转成一个十六进制数,所以得到十六进制数为 0xffffffe0
代码实现如下:

#include <stdio.h>
	int main() {
	int x;
	scanf("%d", &x);
	printf("%d\n", (x & 0xffffffe0) );
	return 0;
}

提示: f 代表 4 个1; e 代表 3个1,1个0; 0 代表 4 个 0 ;

4、消除末尾连续 1

【例题4】给出一个整数,现在要求将这个整数转换成二进制以后,将末尾连续的1都变成0,输出改变后的数(以十进制输出即可)。

我们知道,这个数的二进制表示形式一定是:
… 0 11 … 11 ⏟ k \dots 0 \underbrace{11\dots11}_{\text{k}} 0k 1111
如果,我们把这个二进制数加上1,得到的就是:
… 1 00 … 00 ⏟ k \dots 1 \underbrace{00\dots00}_{\text{k}} 1k 0000
我们把这两个数进行位与运算,得到:
… 0 00 … 00 ⏟ k \dots 0 \underbrace{00\dots00}_{\text{k}} 0k 0000

5、2的幂判定

【例题5】请用一句话,判断一个正数是不是2的幂。

如果一个数是 2 的幂,它的二进制表示必然为以下形式:
… 1 00 … 00 ⏟ k \dots 1 \underbrace{00\dots00}_{\text{k}} 1k 0000
这个数的十进制值为 2 的 k 次。
那么我们将它减一,即 2 的 k 次 减一 的二进制表示如下(参考二进制减法的借位):
… 0 11 … 11 ⏟ k \dots 0 \underbrace{11\dots11}_{\text{k}} 0k 1111
于是 这两个数位与的结果为零,于是我们就知道了如果一个数 x 是 2 的幂,那么 x & (x-1) 必然为零。而其他情况则不然。所以本题的答案为:
(x & (x-1)) == 0

2、右移(>>) 运算符

1、右移的二进制形态

右移运算符是一个二元的位运算符,也就是有两个操作数,表示为 x >> y。其中 x 和 y 均为整数。

x >> y 念作:“将 x 右移 y 位”,这里的位当然就是二进制位了,那么它表示的意思也就是:先将 x 用二进制表示,对于正数,右移 y 位;对于负数,右移 y 位后高位都补上 1。

举个例子:对于十进制数 87,它的二进制是 (1010111), 左移 y 位的结果就是: ( 1010 ) 2 (1010)_2 (1010)2

2、右移的执行结果

x >> y 的执行结果等价于: ⌊ x 2 y ⌋ \lfloor\frac{x}{2^y}\rfloor 2yx
⌊ ⌋ \lfloor\rfloor 这个符号代表取下整。如下代码:

#include <stdio.h>
int main() {
	int x = 0b1010111;
	int y = 3;
	printf("%d\n", x >> y);
	return 0;
}

输出结果为:
10
即: ( 1010 ) 2 (1010)_2 (1010)2
正好符合这个右移运算符的实际含义: 10 = ⌊ 87 2 3 ⌋ 10 = \lfloor\frac{87}{2^3}\rfloor 10=2387

由于除法可能造成不能整除,所以才会有 取下整 这一步运算。

3、负数右移的执行结果

所谓负数右移,就是 x >> y 中,当 x 为负数的情况,代码如下:

#include <stdio.h>
	int main() {
	printf("%d\n", -1 >> 1);
	return 0;
}

它的输出如下:
-1
同样满足式子: ⌊ x 2 y ⌋ \lfloor\frac{x}{2^y}\rfloor 2yx
这个可以用补码来解释,-1 的补码为:(补码的计算方法可参考:补码的计算
11111111111111111111111111111111 11111111 11111111 11111111 11111111 11111111111111111111111111111111
右移一位后,由于是负数,高位补上 1,得到:
11111111111111111111111111111111 11111111 11111111 11111111 11111111 11111111111111111111111111111111

可以理解成 - (x >> y)和 (-x) >> y 是等价的。

4、右移负数位是什么情况

刚才我们讨论了 x < 0 的情况,那么接下来,我们试下 y < 0 的情况会是如何?是否同样满足如下性质呢? ⌊ x 2 y ⌋ \lfloor\frac{x}{2^y}\rfloor 2yx
如果还是满足,那么两个整数的左移就有可能产生小数了。
看个例子:

#include <stdio.h>
int main() {
	printf("%d\n", 1 >> -1); // 2
	printf("%d\n", 1 >> -2); // 4
	printf("%d\n", 1 >> -3); // 8
	printf("%d\n", 1 >> -4); // 16
	printf("%d\n", 1 >> -5); // 32
	printf("%d\n", 1 >> -6); // 64
	printf("%d\n", 1 >> -7); // 128
	return 0;
}

虽然能够正常运行,但是结果好像不是我们期望的,而且会报警告如下:
[Warning] right shift count is negative [-Wshift-count-negative]

实际上,编辑器告诉我们尽量不用右移的时候用负数,但是它的执行结果不能算错误,起码例子里面对了。

右移负数位其实效果和左移对应正数数值位一致。

右移运算符的应用

1、去掉低 k 位

【例题2】给定一个数 x,去掉它的低 k 位以后进行输出。

这个问题,可以直接通过右移来完成,如下:x >> k

2、取低位连续 1

【例题3】获取一个数 x 低位连续的 1 并且输出。

对于一个数 x,假设低位有连续 k 个 1。如下:
( … 0 11 … 11 ⏟ k ) 2 (\dots 0 \underbrace{11\dots11}_{\text{k}})_2 (0k 1111)2
然后我们将它加上 1 以后,得到的就是:
( … 1 00 … 00 ⏟ k ) 2 (\dots 1 \underbrace{00\dots00}_{\text{k}})_2 (1k 0000)2
这时候将这两个数异或结果为:
( 11 … 11 ⏟ k+1 ) 2 (\underbrace{11\dots11}_{\text{k+1}})_2 (k+1 1111)2
这时候,再进行右移一位,就得到了 连续 k 个 1 的值,也正是我们所求。
所以可以用以下语句来求:(x ^ (x + 1)) >> 1

3、取第k位的值

【例题4】获取一个数 x 的第 k(0 <= k <= 30) 位的值并且输出。

对于二进制数来说,第 k 位的值一定是 0 或者 1。
而 对于 1 到 k-1 位的数字,对于我们来说是没有意义的,我们可以用右移来去掉,再用位与运算符来获取二进制的最后一位是 0 还是 1,如下:(x >> k) & 1

3、异或(^)运算符

异或运算符是一个二元的位运算符,也就是有两个操作数,表示为 x ^ y。
异或运算会对操作数的每一位按照如下表格进行运算,对于每一位只有 0 或 1 两种情况,所以组合出来总共 4 种情况。

左操作数右操作数结果
000
011
101
110

通过这个表,我们得出一些结论:

1)两个相同的十进制数异或的结果一定为零。

2)任何一个数和 0 的异或结果一定是它本身。

3)异或运算满足结合律和交换律。

#include <stdio.h>
int main() {
	int a = 0b1010; // (1)
	int b = 0b0110; // (2)
	printf("%d\n", (a ^ b) ); // (3)
	return 0;
}

(1) 在C语言中,以 0b 作为前缀,表示这是一个二进制数。那么 a 的实际值就是 (1010) 。

(2) 同样的, b 的实际值就是 (0110) ;

(3) 那么这里 a ^ b 就是对 (1010) 和 (0110) 的每一位做表格中的 ^ 运算。

所以最后输出结果为:

12
因为输出的是十进制数,它的二进制表示为: (1100) 。

异或运算符的应用

1、标记位取反

【例题1】给定一个数,将它的低位数起的第 4 位取反,0 变 1,1 变 0。

这个问题,我们很容易联想到异或。我们分析一下题目意思,如果第 4 位为 1,则让它异或上 0b1000 就能变成 0;如果第 4 位 为 0,则让它异或上 0b1000 就能变成 1,也就是无论如何都是异或上 0b1000 ,代码如下:

int main() {
	int x;
	scanf("%d", &x);
	printf("%d\n", x ^ 0b1000);
	return 0;
}

2、变量交换

【例题2】给定两个数 a 和 b,用异或运算交换它们的值。

#include <stdio.h>
int main() {
	int a, b;
	while (scanf("%d %d", &a, &b) != EOF) {
		a = a ^ b; // (1)
		b = a ^ b; // (2)
		a = a ^ b; // (3)
		printf("%d %d\n", a, b);
	}
return 0;
}

我们直接来看 (1) 和 (2) 这两句话,相当于 b 等于 a ^ b ^ b ,根据异或的几个性质,我们知道,这时候的 b 的值已经变成原先 a 的值了。

而再来看第 (3) 句话,相当于 a 等于 a ^ b ^ a,还是根据异或的几个性质,这时候,a 的值已经变成了原先 b 的值。

从而实现了变量 a 和 b 的交换。

3、出现奇数次的数

【例题3】输入 n 个数,其中只有一个数出现了奇数次,其它所有数都出现了偶数次。求这个出现了奇数次的数。

根据异或的性质,两个一样的数异或结果为零。也就是所有出现偶数次的数异或都为零,那么把这 n 个数都异或一下,得到的数就一定是一个出现奇数次的数了。

#include <stdio.h>
int main() {
	int n, x, i, ans;
	scanf("%d", &n);
	ans = 0;
	for(i = 0; i < n; ++i) {
		scanf("%d", &x);
		ans = (ans ^ x);
	}
	printf("%d\n", ans);
	return 0;
}

4、丢失的数

【例题4】给定一个 n-1 个数,分别代表 1 到 n 的其中 n-1 个,求丢失的那个数。

int missingNumber(int* nums, int numsSize){
    int i;
    int res = 0;
    for (i = 0; i < numsSize; ++i) {
        res ^= (i + 1) ^ nums[i];
    }
    return res;
}

5、简单加密

基于 两个数异或为零,任何数和零 异或 为其本身 这两个特点,异或还可以用来做简单的加密。将明文异或上一个固定的数变成密文以后,可以通过继续异或上这个数,再将密文转变成明文。

二、算术运算符

二、取余

取余,也就是求余数,使用的运算符是 %,C语言中的取余运算只能针对整数,也就是说,% 两边必须是整数,不能出现小数,否则会出现编译错误。
例如:5 % 3 = 2、7 % 2 = 1。

当然,余数可以是正数也可以是负数,由 % 左边的数决定:
1)如果 % 左边的数是正数,那么余数也是正数;
2)如果 % 左边的数是负数,那么余数也是负数;

三、幂

幂用 **表示。
其中 a**b 代表 a 的 b 次幂。如下:
当然,幂运算的右操作数,也支持负数。

四、取整数

取整除就是数学上的取下整的意思。用 // 表示,如下:
a = 10 b = 6
`` print(a//b)
输出的结果是 : 1

汉明重量

leetcode 191.编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量 )。
例如:11100011 的汉明重量是 5。

int hammingWeight(uint32_t n) {
    int c = 0;
    while(n){       //循环遍历 n 的每个二进制位
        if(n & 1){  //如果 n 的最低二进制位为 1,则给计数器 c 加上 1;
            ++c;
        }
        n >>= 1;    //将 n 的二进制位右移一位;
    }
    return c;       //返回计数器,代表 n 中二进制位的 1 的数量;
}

汉明距离

两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
leetcode 461.
C语言版本:

int hammingDistance(int x, int y){
    int z = x ^ y;  //将 x 和 y 进行异或操作,得到的结果存到 z 中;
    int c = 0;      //c 统计变量 z 中 1 的数量;
    while(z){
        if(z & 1){
            ++c;
        }
        z >>= 1;    //将 z 的二进制位右移一位;
    }
    return c;
}

python版本:

class Solution(object):
    def hammingDistance(self, x, y):
        """
        :type x: int
        :type y: int
        :rtype: int
        """
        z = x ^ y
        c = 0
        while z:
            if z & 1:
                c += 1
            z >>= 1
        return c
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值