文章

【GESP】C++三级考试大纲知识点梳理, (3)位运算

GESP C++三级官方考试大纲中,共有8条考点,本文针对C++(3)号知识点进行总结梳理。

(3)掌握位运算:与(&)、或(|)、非(~)、异或(^)、左移(«)、右移(»)的基本使用方法及原理。

位运算是对二进制数据的直接操作,通常用于高效的数值计算和操作,尤其在嵌入式系统和性能优化中非常有用。理解位运算时,掌握它们的二进制原理是非常重要的。以下是从二进制的角度,对各个位运算符的详细说明:

一、与(&)运算符

1. (&)运算规则

原理:按位与运算符对两个数字的每一位进行比较,只有当 两个操作数的相应位都为 1 时,结果的相应位才为 1,否则为 0。

  • 0 & 0 = 0
  • 0 & 1 = 0
  • 1 & 0 = 0
  • 1 & 1 = 1

示例

1
2
3
int a = 5;  // 二进制:0101
int b = 3;  // 二进制:0011
int result = a & b;  // 运算过程:0101 & 0011 = 0001

过程

1
2
3
4
  0101  (5)
& 0011  (3)
-------
  0001  (结果:1)
  • 第1位:0 & 0 = 0
  • 第2位:1 & 0 = 0
  • 第3位:0 & 1 = 0
  • 第4位:1 & 1 = 1

2. (&)应用场景

掩码操作,清除或检查某些特定位。掩码操作常用于清除数值中的某些位,或检查某些特定的位是否为 1。例如,清除某些低位、检查某一位的状态。

示例:检查一个数是否为偶数(通过与操作检查最低位)

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;

int main() {
    int num = 5; // 二进制:0101
    if (num & 1) {
        cout << "奇数" << endl;  // 结果是奇数
    } else {
        cout << "偶数" << endl;
    }
    return 0;
}

在这个例子中,num & 1 用来检查最低位。如果最低位是 1(即数值为奇数),结果为 true,否则为 false


二、或(|)运算符

1. (|)运算规则

按位或运算符对两个数字的每一位进行比较,当 两个操作数的相应位有一个为 1 时,结果的相应位就为 1,否则为 0。

  • 0 | 0 = 0
  • 0 | 1 = 1
  • 1 | 0 = 1
  • 1 | 1 = 1

示例

1
2
3
int a = 5;  // 二进制:0101
int b = 3;  // 二进制:0011
int result = a | b;  // 运算过程:0101 | 0011 = 0111

过程

1
2
3
4
  0101  (5)
| 0011  (3)
-------
  0111  (结果:7)
  • 第1位:0 | 0 = 0
  • 第2位:1 | 0 = 1
  • 第3位:0 | 1 = 1
  • 第4位:1 | 1 = 1

2. (|)应用场景

设置某些特定位为 1,| 操作符可以用于将特定的位设置为 1,常见于标志位的设置。

示例:设置第 3 位为 1

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include<bitset>
using namespace std;

int main() {
    int num = 0b1010; // 二进制:1010
    num = num | 0b0100  // 设置第 3 位为 1
    cout << "结果: " << std::bitset<8>(num) << endl;  // 输出结果:00001110
    return 0;
}

输出

1
结果: 00001110

在这个例子中,我们使用 | 操作符和左移操作符将第 3 位设置为 1。1 << 3 产生一个只有第 3 位为 1 的掩码。

三、非(~)运算符

1. (~)运算规则

按位非(取反)运算符对数字的每一位进行反转,将 1 变为 0,0 变为 1。在二进制表示中,通常涉及到补码的概念,之前提到过,在计算机中整数通常是以补码表示的。

示例

1
2
int a = 5;  // 二进制:0101
int result = ~a;  // 运算过程:~0101 = 1010

过程

1
2
3
4
原数:
  0101  (5)
取反:
  1010  (-6,补码表示)
  • 注意:~a 的结果不仅是二进制位的逐位取反,还会涉及到补码转化,导致负数的表示。

2.(~)应用场景

清除某一特定的位(设置为0)~ 操作符用于取反,可以结合 & 操作符来将特定位置设为0,其他位不变。

示例:第3位置为0

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include<bitset>
using namespace std;

int main() {
    int num2 = 0b1110;  // 二进制:1110
    num2 = num2 & ~0b0100;  // 清除第 3 位
    cout << "结果: " << std::bitset<8>(num2) << endl;  // 输出结果:00001010
    return 0;
}

在这个例子中,我们先使用 1 << 3 将第 3 位移动到相应位置,再通过 ~ 运算取反得到掩码,最后通过 & 运算清除该位。

四、异或(^)运算符

1. (^)运算规则

按位异或运算符对两个数字的每一位进行比较,只有当 两个操作数的相应位不相同 时,结果的相应位才为 1,否则为 0。

运算规则

  • 0 ^ 0 = 0
  • 0 ^ 1 = 1
  • 1 ^ 0 = 1
  • 1 ^ 1 = 0

示例

1
2
3
int a = 5;  // 二进制:0101
int b = 3;  // 二进制:0011
int result = a ^ b;  // 运算过程:0101 ^ 0011 = 0110

过程

1
2
3
4
5
6
7
8
9
10
  0101  (5)
^ 0011  (3)
-------
  0110  (结果:6)

```console
- 第1位:0 ^ 0 = 0
- 第2位:1 ^ 0 = 1
- 第3位:0 ^ 1 = 1
- 第4位:1 ^ 1 = 0

2. (^)应用场景

切换某一位(^ 异或操作符):切换位的状态(从 0 到 1 或从 1 到 0)

^ 异或运算符常用于切换某一位的值。如果该位是 0,异或后变成 1;如果该位是 1,异或后变成 0。

示例:切换第 2 位

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include<bitset>
using namespace std;

int main() {
    int num3 = 0b1100;       // 二进制:1100
    num3 = num3 ^ 0b0010;    // 切换第 2 位
    cout << "切换结果: " << std::bitset<8>(num3) << endl;  // 输出结果:00001110
    int num4 = 0b1110;       // 二进制:1110
    num4 = num4 ^ 0b0010;    // 切换第 2 位
    cout << "切换结果: " << std::bitset<8>(num4) << endl;  // 输出结果:00001100
}

输出

1
2
切换结果: 00001110
切换结果: 00001100

在这个例子中,我们使用 ^ 来切换第 2 位的值。如果该位原来是 1,则变为 0,反之亦然。

实现加密算法(异或):简单加密算法(异或加密)

异或操作被广泛用于简单的加密和解密算法中,特别是在没有要求强加密的情况下,它具有对称性,即 A ^ B ^ B = A

示例:使用异或实现加密和解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

int main() {
    int key = 0x5A;  // 加密密钥
    int data = 123;  // 明文数据
    
    // 加密:数据与密钥异或
    int encrypted = data ^ key;
    cout << "加密结果: " << encrypted << endl;
    
    // 解密:加密结果与密钥异或
    int decrypted = encrypted ^ key;
    cout << "解密结果: " << decrypted << endl;
    
    return 0;
}

输出

1
2
加密结果: 33
解密结果: 123

在这个例子中,data ^ key 实现了数据的加密,而 encrypted ^ key 则是解密过程。由于异或具有对称性,解密过程与加密过程相同。


五、左移(«)运算符

1. («)运算规则

左移运算符将数字的二进制位向 左移动指定的位数,右侧用零填充。每移动一位,相当于将数字乘以 2(移位数等于乘以 2 的幂)。

示例

1
2
3
4
    int a = 5;            // 二进制:0101
    int result2 = a << 1;  // 运算过程:0101 << 1 = 1010
    cout << bitset<4>(result2) << endl;
    cout << result2 << endl;

输出

1
2
1010
10

过程

1
2
3
  0101  (5)
<< 1 位
  1010  (结果:10)
  • 左移 1 位,相当于 5 * 2 = 10。

扩展

  • 左移 2 位,相当于 5 * 4 = 20(即乘以 2 的平方)。
  • 左移 3 位,相当于 5 * 8 = 40(即乘以 2 的立方)。

2.(«)应用场景

快速的乘法运算,左移运算符 << 实现乘以 2 的幂。这些运算通常比乘法运算更高效,特别是在需要优化性能时。

六、右移(»)运算符

1. (»)运算规则

右移运算符将数字的二进制位向 右移动指定的位数。对于有符号整数,符号位(最高位)会填充到左侧;对于负数,补充的是 1(补码表示)。

示例

1
2
3
4
5
    int b = 5;            // 二进制:0101
    int result3 = b >> 1;  // 运算过程:0101 >> 1 = 0010
    cout << bitset<4>(b) << endl;
    cout << bitset<4>(result3) << endl;
    cout << result3 << endl;

输出

1
2
3
0101
0010
2

过程

1
2
3
  0101  (5)
>> 1 位
  0010  (结果:2)
  • 右移 1 位,相当于 5 / 2 = 2(向下取整)。

负数的右移

负数右移操作涉及 补码表示符号扩展 的概念。在计算机中,负数是通过补码表示的。右移时,符号位(最高位)会根据操作数的符号进行扩展。下面详细介绍负数右移的操作和示例。

例如,-5 的补码表示过程如下:

  • 正数 5 的二进制表示是:0000000000000101(假设为 16 位)。
  • 对其取反:1111111111111010
  • 加 1 得到补码:1111111111111011,这就是 -5 的补码表示。

有符号右移(»):右移时,符号位会扩展,即高位填充符号位(1 或 0)。对于负数,符号位扩展为 1。

示例

1
2
3
4
5
int c = -5;  // -5 的二进制是 1111111111111011
int result4 = c >> 1;  // 右移 1 位
cout << bitset<16>(c) << endl;
cout << bitset<16>(result4) << endl;
cout << result4 << endl;   

输出

1
2
3
1111111111111011
1111111111111101
-3

运算过程

1
2
原数: 1111111111111011 (-5)
右移 1 位:1111111111111101

结果

  • 右移 1 位后,符号位(1)被扩展,得到的结果是 1111111111111101,即 -3(补码表示)。
  • 因此,-5 >> 1 的结果是 -3

右移 2 位(有符号右移 >>

1
2
int num = -5;  // -5 的二进制是 1111111111111011
int result = num >> 2;  // 右移 2 位

运算过程

1
2
原数: 1111111111111011 (-5)
右移 2 位:1111111111111110

结果

  • 右移 2 位后,符号位(1)继续扩展,得到的结果是 1111111111111110,即 -2(补码表示)。
  • 因此,-5 >> 2 的结果是 -2

2.(»)应用场景

快速的除法运算,左移运算符 >> 实现除以 2 的幂。这些运算通常比除法运算更高效,特别是在需要优化性能时。


七、总结

  1. 与(&):两个相应位都为 1 时结果为 1,否则为 0。
  2. 或(|):两个相应位只要有一个为 1,结果就是 1。
  3. 非(~):逐位取反,将 1 变为 0,0 变为 1。
  4. 异或(^):两个相应位不同时结果为 1,相同为 0。
  5. 左移(«):每左移一位,等于乘以 2。
  6. 右移(»):每右移一位,等于除以 2。

这些操作都基于二进制运算,因此理解它们的原理对编程中高效处理数据非常有帮助。


所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

GESP各级别考纲要点、知识拓展和练习题目清单详见C++学习项目主页

luogu-”系列题目已加入洛谷Java、C++初学团队作业清单,可在线评测,团队名额有限,欢迎加入。

bcqm-”系列题目可在编程启蒙题库进行在线评测。

本文由作者按照 CC BY 4.0 进行授权