位运算:带带孩子吧,孩子很强的!

快速进制

在聊到位运算之前,不妨先简单过一遍二进制的东西。熟悉二进制和十进制的快速转换确实是掌握位运算的基础,因为位运算直接在二进制位上进行操作。如果不熟悉二进制表示,很难直观理解位运算的效果。

这里主要涉及二进制和十进制之间的互相转换。


十进制转二进制

十进制转二进制可以使用常见的 除2取余法 进行。每次将十进制除以2并记录所得余数,直到商为0,然后再将记录的余数 从下往上排列即可得到对应的二进制。

将十进制 13 转为二进制:

  • 13 ÷ 2 = 6 余 1 13 ÷ 2 = 6 余 1 13÷2=6余1
  • 6 ÷ 2 = 3 余 0 6 ÷ 2 = 3 余 0 6÷2=3余0
  • 3 ÷ 2 = 1 余 1 3 ÷ 2 = 1 余 1 3÷2=1余1
  • 1 ÷ 2 = 0 余 1 1 ÷ 2 = 0 余 1 1÷2=0余1

将上面的余数从下往上排得到1101就是13对应的二进制了;是不是so easy too happy!


二进制转十进制

从右往左每一位乘以对应2的幂,然后求和即可;

将二进制1011 转为对应十进制;

  • 1 × 2³ = 8
    0 × 2² = 0
  • 1 × 2¹ = 2
  • 1 × 2⁰ = 1

求和:8 + 0 + 2 + 1 = 1111即为1011的十进制表示;

这些是常规的计算方法,可能大部分人在开始学习的时候多少都接触过,但我们的诉求是尽可能的通过心算的方式快速进行二进制十进制之间的转换,这样才能在使用位运算时得心应手。下面就介绍一下如何练习以及一些常用的技巧;


1248原则

1248原则 是一个快速进行二进制到十进制转换的技巧,主要用于帮助我们快速计算二进制数对应的十进制值。这一原则来源于二进制的基本权值,也就是每一位二进制数字代表的2的幂次方值。

从右向左,二进制的各个位的值分别对应着:

  • 第一位:2⁰ = 1
  • 第二位:2¹ = 2
  • 第三位:2² = 4
  • 第四位:2³ = 8
  • ...

继续向左,依次是16、32、64、128等。

所以1248 代表了二进制数前四位的权重:1、2、4、8

当你看到一个二进制数时,可以直接用"1248"来快速确定前几位的权重值,从而进行快速的心算。

例如,转换二进制数 1011 到十进制:

  1. 首先使用

    1248

    原则标记每一位的权值:

    • 最右边一位(1):代表 1
    • 第二位(1):代表 2
    • 第三位(0):代表 4
    • 第四位(1):代表 8
  2. 然后将这些有值的位加起来:1 + 2 + 8 = 11 因此,二进制 1011 对应的十进制数是 11

再举一个例子: 二进制数 11010

  1. 应用 1248原则,最右边开始的前四位的权值是

    1、2、4、8

    ,第五位是16:

    • 最右边第一位是0,代表的值是0
    • 第二位是1,代表的值是2
    • 第三位是0,代表的值是0
    • 第四位是1,代表的值是8
    • 第五位是1,代表的值是16
  2. 将这些有值的位加起来: 16 + 8 + 2 = 26

所以,二进制 11010 对应的十进制数是 26


其他常用技巧

  • 熟记常见的2的幂 ,这样可以帮你快速推断数值,如(2⁰ = 1,2¹ = 2,2² = 4,2³ = 8,2⁴ = 16,2⁵ = 32...),这可以帮助你快速判断十进制数字在哪个幂次范围内,并由此快速推导出二进制。
  • 熟记小范围内的特殊值,记住一些常见的二进制值,可以进行快速映射。例如:

1111 = 15, 1010 = 10, 1001 = 9, 0111 = 7 等常用值。如果遇到类似的值,可以直接推导,而不需要逐位计算。

  • 如果数字接近2的幂次方 ,可以快速得到前几位。比如 14 接近 2⁴ = 16,因此前面部分是 1110,只需要从16减去14推导出最后两位的变化。

  • 对于10、100、1000等特殊数字的二进制:这些数字的二进制可以直接记忆,心算时直接转换。

    • 10 的二进制:1010
    • 100 的二进制:1100100
    • 1000 的二进制:1111101000
  • 当遇到大数字时,可以通过分解法来加快心算速度。例如,将一个大数分成几个较小的数字,每个数字转换成二进制后再合并。

    • 例如:75 转为二进制,可以将其分为64(2⁶)+ 8(2³)+ 2(2¹)+ 1(2⁰):
      • 64 的二进制是 1000000
      • 8 的二进制是 1000
      • 2 的二进制是 10
      • 1 的二进制是 1
      • 最终结果为 1001011

位运算

使用位运算之前,务必先熟悉下面的几个运算规则:

与运算(&):将两个数按位进行"与"操作,只有对应位都是1时结果才为1,否则为0。

或运算(|):将两个数按位进行"或"操作,任意一位为1时结果为1,否则为0。

异或运算(^):将两个数按位进行"异或"操作,相同为0,不同为1。

非运算(~):对数值按位取反,0变1,1变0。

左移(<<):将二进制位向左移动n位,相当于乘以2^n。

右移(>>):将二进制位向右移动n位,相当于除以2^n(有符号时保留符号位)。


对应上面的规则,下面通过简单的示例体验一下:

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int A = 12;  // 二进制: 1100
    int B = 10;  // 二进制: 1010

    // 1. 与运算(&)
    int andResult = A & B;  // 结果: 8  (二进制: 1000)
    cout << "A & B = " << andResult << endl;

    // 2. 或运算(|)
    int orResult = A | B;  // 结果: 14 (二进制: 1110)
    cout << "A | B = " << orResult << endl;

    // 3. 异或运算(^)
    int xorResult = A ^ B;  // 结果: 6  (二进制: 0110)
    cout << "A ^ B = " << xorResult << endl;

    // 4. 非运算(~)
    int notResult = ~A;  // 结果: -13 (按位取反后的二进制表示: 11111111111111111111111111110011)
    cout << "~A = " << notResult << endl;

    // 5. 左移运算(<<)
    int leftShift = A << 2;  // A 向左移 2 位,结果: 48  (二进制: 110000)
    cout << "A << 2 = " << leftShift << endl;

    // 6. 右移运算(>>)
    int rightShift = A >> 2;  // A 向右移 2 位,结果: 3  (二进制: 0011)
    cout << "A >> 2 = " << rightShift << endl;

    return 0;
}

实际应用

为什么会突然想到写这么一篇文章也不是突发奇想。主要是最近在游戏开发过程中遇到了一个常见的需求,比如游戏界面的红点系统。由于判定一个界面是否需要显示红点,这些逻辑需要后端去处理,然后返回一个是否显示的标记给前端进行具体的渲染流程。

实现这个需求的方法很多,比较常见的做法是后端处理完之后将红点状态以表的形式存起来再通过请求发送到前端,但是这样做会加大前后端交互过程中数据传输的数据量,可能会导致数据传输超上限、服务器压力增加、后端计算成本提高等潜在的问题,毕竟一个游戏功能的界面的红点数是有可能多达几十上百个的,而且每次都需要后端不断的进行存表读表的操作,确实不够优雅。

所以才想到基于二进制特有的01标记来作为状态标志位,使用位运算实现一个高效简洁的红点效果。

通过使用位运算,我们只需要将每个位置对应的红点位置为1,不显示红点时置为0即可,而在向前端传输红点状态数据时就不需要来传表结构的数据了,只需要一个简单的十进制阿拉伯数字即可,将该传输传到前端结果位运算简单的解析之后即可获得红点状态信息。

上面只是一部分应用,其实在实际开发中,还有很多地方可以用到位运算,或者是使用位运算提高性能,简化代码的地方。比如

状态标志位

在嵌入式开发、游戏开发、网络通信等场景中,经常需要用位表示多个状态(如开关、错误标志)。通过位运算可以快速检查和设置这些状态。比如上面的红点系统。

cpp 复制代码
const int FLAG_A = 1 << 0;  // 0001
const int FLAG_B = 1 << 1;  // 0010
const int FLAG_C = 1 << 2;  // 0100

int flags = FLAG_A | FLAG_C;  // 具有 FLAG_A 和 FLAG_C 的状态

// 检查 FLAG_B 是否设置
if (flags & FLAG_B) {
    cout << "FLAG_B is set" << endl;
} else {
    cout << "FLAG_B is not set" << endl;
}

应用场景:设备状态监控、通信协议标志。


数据压缩

位运算用于将多个小数值打包在一个更大的数据类型中,节省空间。例如,在图像处理、视频编码等领域,经常通过位运算来压缩颜色、透明度等多个通道的数据。

cpp 复制代码
unsigned int color = (255 << 24) | (128 << 16) | (64 << 8) | 32;
// 将 RGBA 值分别存储在 32 位整数的不同段

哈希运算和校验

在哈希表实现、数据加密、CRC 校验中,位运算可以加速特定运算,避免浮点数操作,提高性能。

示例:
cpp 复制代码
cppCopy codeunsigned int hashFunction(unsigned int x) {
    return x ^ (x >> 16);  // 异或和右移结合生成哈希
}

位图/布隆过滤器

位运算用于表示集合或快速查找元素是否存在。位图通过每一位表示一个元素是否存在,布隆过滤器则使用多个哈希函数来进行快速查找。

cpp 复制代码
const int SIZE = 100;
int bitmap[SIZE / 32] = {0};  // 位图数组,每32位存储一个int

void setBit(int index) {
    bitmap[index / 32] |= (1 << (index % 32));  // 设置位
}

bool getBit(int index) {
    return bitmap[index / 32] & (1 << (index % 32));  // 检查位
}

高效计算(高频常用)

位运算可以代替乘法、除法、模运算,奇偶性判断等操作,提高效率,尤其在性能敏感的嵌入式系统、图形编程中广泛使用。

cpp 复制代码
int x = 16;
int y = x << 1;  // x * 2
int z = x >> 1;  // x / 2

// 判断奇偶性
number & 1 == 0 // 偶数
number & 1 == 1 // 奇数    

循环队列/环形缓冲区

使用位运算来快速处理循环队列的索引更新,尤其在队列的大小是2的幂时,可以通过与运算快速获取索引的值。

cpp 复制代码
const int SIZE = 8;  // 大小为2的幂
int buffer[SIZE];
int head = 0;
int tail = 0;

void enqueue(int value) {
    buffer[tail] = value;
    tail = (tail + 1) & (SIZE - 1);  // 位运算获取下一个索引
}

小结

可以的话,多用用位运算吧,这孩子还是很强的!

相关推荐
白子寰19 分钟前
【C++打怪之路Lv13】- “继承“篇
开发语言·c++
王俊山IT36 分钟前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(1)
开发语言·c++·笔记·学习
weixin_5375904540 分钟前
《C++ Primer Plus》中文版第二章习题
开发语言·c++·visual studio
很透彻1 小时前
【网络】传输层协议TCP(下)
网络·c++·网络协议·tcp/ip
1 9 J1 小时前
数据结构 C/C++(实验三:队列)
c语言·数据结构·c++·算法
正在敲代码中2 小时前
成绩排序c++
开发语言·c++·算法·函数·结构体排序
gma9992 小时前
【Effective C++】阅读笔记3
c++·笔记
vir023 小时前
找出目标值在数组中的开始和结束位置(二分查找)
数据结构·c++·算法·leetcode
薄荷冰ovo3 小时前
C++中的提供的四种类型转换方式;
数据结构·c++
chusheng18403 小时前
Python 爬取大量数据如何并发抓取与性能优化
开发语言·python·性能优化