位运算整理
最近看到了位运算相关的东西,也趁机会复习一下~
特别是操作寄存器的某些位的时候需要熟练使用 主要就是构建掩码,熟练使用几个运算符~
文章目录
1. 将x的某些位取出来
需求 :从无符号整数 x 中,提取从第 p 位开始(向右数)的 n 位字段。
实现原理
- 将目标字段移动到最右侧:执行
x >> (p - n + 1)。 - 构造掩码(Mask):
~(~0 << n)可以生成一个右起n位全为 1,其余为 0 的掩码。 - 相与提取:利用
&运算过滤出目标位。
c
unsigned int getbits(unsigned int x, int p, int n) {
return (x >> (p - n + 1)) & ~(~0 << n);
}
2. 将x的某些位 设置为y的某些位
需求 :将 x 中从第 p 位开始的 n 个位,替换为 y 中最右边的 n 位,其余位保持不变。
实现思路
- 清理现场 :构造一个
x_mask,将x的目标区间清零。 - 对齐数据 :将
y的低n位移动到第p位对齐。 - 合并结果 :使用按位或
|运算。
c
unsigned int setbits(unsigned int x , int p,int n,unsigned int y){
unsigned int x_mask=(~((~0)<<n))<<(p-n+1);
unsigned int y_mask=~((~0)<<n)
return ((~x_mask)&x) | (y<<(p-n+1) &mask);
}
3. 位翻转:invert 函数
需求 :将 x 中从第 p 位开始的 n 位求反(0 变 1,1 变 0)。
编写一个函数 invert(x, p, n),该函数返回对 x 执行下列操作后的结果值:将 x 中从第 p 位开始的 n 个(二进制)位求反(即,1 变成 0,0 变成 1),x 的其余各位保持不变
核心技巧:异或(XOR)
异或运算 ^ 具有如下神奇特性:
any ^ 0 = 保持不变any ^ 1 = 翻转
c
unsigned int invert(unsigned int x, int p, int n) {
// 构造掩码:目标区间为 1,其余为 0
unsigned int mask = (~(~0u << n)) << (p + 1 - n);
// 利用异或翻转目标位
return x ^ mask;
}
4. 循环移位:rightrot 函数
需求:实现循环右移,即从右端移出的位不再丢失,而是从左端重新移入。
编写一个函数 rightrot(x, n),该函数返回将 x 循环右移(即从最右端移出的位将从最左端移入)n(二进制)位后所得到的值
实现步骤
- 获取位数 :通过
sizeof(unsigned int) * 8适配不同系统的字长。 - 提取掉落位 :获取即将移出的低
n位,并将其左移到顶端。 - 整体右移:将原数正常右移。
- 合并:将"掉落位"与"移位结果"进行或运算。
c
unsigned int rightrot(unsigned int x, int n) {
int wordsize = sizeof(unsigned int) * 8;
n = n % wordsize; // 处理 n 大于字长的情况
if (n == 0) return x;
unsigned int dropped_bits = (x & ~(~0u << n)) << (wordsize - n);
return (x >> n) | dropped_bits;
}
5. x & (x - 1)
经典技巧,它的核心作用是:消除二进制表示中从右往左数的第一位 1。
为什么有效?
当我们执行 x - 1 时:
- 最右侧的
1变成0。 - 该位右侧的所有
0变成1。 - 该位左侧保持不变。 此时执行
&运算,原数中最右侧的1及其右边部分都会被抵消。
经典应用:统计 1 的个数
相比于逐位遍历 32 次,该算法的效率取决于 1 的个数。
c
int count_ones(unsigned int x) {
int count = 0;
while (x > 0) {
x &= (x - 1); // 每次执行都干掉一个 1
count++;
}
return count;
}
总结
位运算的本质是对**掩码(Mask)和移位(Shift)**的灵活组合。
一般用~0 进行位置移动,移动后取反 当作掩码
翻转常用^