工作中常用到的二进制运算
进制转换表
| 二进制 | 八进制 | 十进制 | 十六进制 |
|---|---|---|---|
| 0000 | 0 | 0 | 0 |
| 0001 | 1 | 1 | 1 |
| 0010 | 2 | 2 | 2 |
| 0011 | 3 | 3 | 3 |
| 0100 | 4 | 4 | 4 |
| 0101 | 5 | 5 | 5 |
| 0110 | 6 | 6 | 6 |
| 0111 | 7 | 7 | 7 |
| 1000 | 10 | 8 | 8 |
| 1001 | 11 | 9 | 9 |
| 1010 | 12 | 10 | A |
| 1011 | 13 | 11 | B |
| 1100 | 14 | 12 | C |
| 1101 | 15 | 13 | D |
| 1110 | 16 | 14 | E |
| 1111 | 17 | 15 | F |
Go语言中的进制表示
在Go语言中,可以使用不同前缀表示不同进制的数:
go
package main
import "fmt"
func main() {
// 二进制表示 (0b 或 0B 开头)
binaryNum := 0b1010 // 十进制 10
binaryNum2 := 0B1100 // 十进制 12
// 八进制表示 (0o, 0O 或 0 开头)
octalNum := 0o12 // 十进制 10
octalNum2 := 0O16 // 十进制 14
octalNum3 := 012 // 传统写法,十进制 10
// 十六进制表示 (0x 或 0X 开头)
hexNum := 0xA // 十进制 10
hexNum2 := 0xFF // 十进制 255
fmt.Printf("二进制 0b1010 = %d\n", binaryNum)
fmt.Printf("八进制 0o12 = %d\n", octalNum)
fmt.Printf("十六进制 0xA = %d\n", hexNum)
}
二进制运算基础
1. 位运算(Bitwise Operations)
与运算(&):两个位都为1时,结果才为1
go
func andOperation() {
a := 0b1010 // 10
b := 0b1100 // 12
result := a & b // 0b1000 = 8
fmt.Printf("%b & %b = %b (十进制: %d)\n", a, b, result, result)
// 输出: 1010 & 1100 = 1000 (十进制: 8)
}
或运算(|):两个位有一个为1时,结果就为1
go
func orOperation() {
a := 0b1010 // 10
b := 0b1100 // 12
result := a | b // 0b1110 = 14
fmt.Printf("%b | %b = %b (十进制: %d)\n", a, b, result, result)
// 输出: 1010 | 1100 = 1110 (十进制: 14)
}
异或运算(^):两个位不同时,结果为1
go
func xorOperation() {
a := 0b1010 // 10
b := 0b1100 // 12
result := a ^ b // 0b0110 = 6
fmt.Printf("%b ^ %b = %b (十进制: %d)\n", a, b, result, result)
// 输出: 1010 ^ 1100 = 110 (十进制: 6)
}
非运算(^ 或 ^=):在Go中,一元^是按位取反
go
func notOperation() {
a := 0b1010 // 10
result := ^a // 在64位系统中: -11
// 要只取低4位,可以使用掩码
maskedResult := result & 0b1111 // 0b0101 = 5
fmt.Printf("^%b = %b (完整: %064b)\n", a, maskedResult, result)
}
清除位运算(&^):Go语言特有的位清空运算符
go
func andNotOperation() {
a := 0b1011 // 11
b := 0b1100 // 12
result := a &^ b // 0b0011 = 3
fmt.Printf("%b &^ %b = %b (十进制: %d)\n", a, b, result, result)
// 输出: 1011 &^ 1100 = 11 (十进制: 3)
// 解释: 在b为1的位置,将a的对应位清零
}
2. 移位运算(Shift Operations)
左移(<<):向左移动指定位数,低位补0
go
func leftShift() {
a := 0b1010 // 10
result := a << 2 // 0b101000 = 40
fmt.Printf("%b << 2 = %b (十进制: %d)\n", a, result, result)
// 输出: 1010 << 2 = 101000 (十进制: 40)
}
右移(>>):向右移动指定位数
go
func rightShift() {
a := 0b1010 // 10
result := a >> 2 // 0b0010 = 2
fmt.Printf("%b >> 2 = %b (十进制: %d)\n", a, result, result)
// 输出: 1010 >> 2 = 10 (十进制: 2)
}
实际应用场景
1. 权限控制
使用位掩码进行权限管理:
go
package main
import "fmt"
const (
Read = 1 << iota // 0b0001 = 1
Write // 0b0010 = 2
Execute // 0b0100 = 4
Admin // 0b1000 = 8
)
type User struct {
name string
permissions int
}
func (u *User) addPermission(perm int) {
u.permissions |= perm
}
func (u *User) removePermission(perm int) {
u.permissions &^= perm
}
func (u *User) hasPermission(perm int) bool {
return (u.permissions & perm) != 0
}
func main() {
user := User{name: "Alice", permissions: 0}
// 添加权限
user.addPermission(Read | Write)
// 检查权限
fmt.Printf("%s 有读取权限: %v\n", user.name, user.hasPermission(Read))
fmt.Printf("%s 有执行权限: %v\n", user.name, user.hasPermission(Execute))
// 权限二进制表示
fmt.Printf("权限位: %04b\n", user.permissions)
}
2. 标志位处理
go
package main
import "fmt"
// 使用 iota 自动生成位标志
const (
FlagA = 1 << iota // 0b0001
FlagB // 0b0010
FlagC // 0b0100
FlagD // 0b1000
)
func processFlags(flags int) {
if flags & FlagA != 0 {
fmt.Println("FlagA 被设置")
}
if flags & FlagB != 0 {
fmt.Println("FlagB 被设置")
}
// 检查多个标志
if flags & (FlagA | FlagB) == (FlagA | FlagB) {
fmt.Println("同时设置了FlagA和FlagB")
}
}
func main() {
flags := FlagA | FlagC
fmt.Printf("当前标志: %04b\n", flags)
processFlags(flags)
}
3. 快速乘除法运算
go
func fastMath() {
num := 0b1010 // 10
// 快速乘以2^n
multiplyBy4 := num << 2 // 10 * 4 = 40
multiplyBy8 := num << 3 // 10 * 8 = 80
// 快速除以2^n
divideBy2 := num >> 1 // 10 / 2 = 5
divideBy4 := num >> 2 // 10 / 4 = 2
fmt.Printf("10 * 4 = %d\n", multiplyBy4)
fmt.Printf("10 / 2 = %d\n", divideBy2)
}
4. 奇偶判断
go
func isEvenOdd() {
numbers := []int{0b1010, 0b1011, 0b1100, 0b1101}
for _, n := range numbers {
// 与 0b1 进行与运算判断奇偶
if n&1 == 0 {
fmt.Printf("%d (%b) 是偶数\n", n, n)
} else {
fmt.Printf("%d (%b) 是奇数\n", n, n)
}
}
}
进阶技巧
1. 获取最低位的1
dart
func getLowestOneBit() {
num := 0b101100 // 十进制 44
// 获取最低位的1
// 原理: -num 是 num 的二进制补码
// 例如: num = 44 (0b101100)
// -num = -44 (二进制补码: ...1111010100)
// num & -num 会得到只保留最低位1的结果
lowestBit := num & -num
fmt.Printf("数字: %d (%b)\n", num, num)
fmt.Printf("最低位的1: %d (%b)\n", lowestBit, lowestBit)
// 输出: 数字: 44 (101100)
// 最低位的1: 4 (100)
}
2. 判断是否为2的幂
go
func isPowerOfTwo() {
numbers := []int{8, 9, 16, 32, 33} // 二进制: 1000, 1001, 10000, 100000, 100001
for _, n := range numbers {
// 2的幂的特征: 二进制表示中只有一个1
// 原理: 如果n是2的幂,那么n-1的所有低位都是1
// 例如: 8(1000) & 7(0111) = 0
if n > 0 && (n & (n-1)) == 0 {
fmt.Printf("%d (%b) 是2的幂\n", n, n)
} else {
fmt.Printf("%d (%b) 不是2的幂\n", n, n)
}
}
}
3. 统计1的个数(位计数)
go
func countOnes() {
num := 0b101101 // 45,二进制有4个1
// 方法1: 循环计数
count := 0
temp := num
for temp != 0 {
count++
// 关键技巧: n & (n-1) 会清除最低位的1
// 每次循环清除一个1,直到所有1都被清除
temp &= temp - 1
}
fmt.Printf("数字 %d (%b) 有 %d 个1\n", num, num, count)
// 方法2: 使用内置函数(Go 1.9+)
// import "math/bits"
// count = bits.OnesCount(uint(num))
}
4. 反转二进制位
go
func reverseBits() {
num := 0b10100111 // 167
reversed := 0
bits := 8 // 假设处理8位
for i := 0; i < bits; i++ {
// 1. 将结果左移1位,为最低位腾出空间
reversed <<= 1
// 2. 获取原数的最低位
// 3. 将原数的最低位放到结果的最低位
reversed |= (num & 1)
// 4. 原数右移1位,处理下一位
num >>= 1
}
fmt.Printf("原始: %08b\n", 0b10100111)
fmt.Printf("反转: %08b\n", reversed)
// 输出: 原始: 10100111
// 反转: 11100101
}
5. 提取连续位
go
func extractBits() {
num := 0b11011010 // 218
// 提取第3-5位(从右向左,从0开始计数)
start := 3
end := 5
// 原理:
// 1. 创建掩码: (1 << (end-start+1)) - 1
// 例如提取3位: (1 << 3) - 1 = 0b111
// 2. 右移start位,将目标位移动到最低位
// 3. 与掩码进行与运算,只保留需要的位
mask := (1 << (end - start + 1)) - 1
result := (num >> start) & mask
fmt.Printf("原始数字: %08b\n", num)
fmt.Printf("提取第%d-%d位: %03b (十进制: %d)\n", start, end, result, result)
// 输出: 提取第3-5位: 101 (十进制: 5)
}
6. 检查特定位是否被设置
go
func checkBit() {
num := 0b10110110 // 182
// 检查第4位(从0开始计数)
pos := 4
// 方法1: 右移后检查最低位
bit1 := (num >> pos) & 1
// 方法2: 创建掩码后检查
mask := 1 << pos
bit2 := (num & mask) != 0
fmt.Printf("数字: %08b\n", num)
fmt.Printf("第%d位: %d (方法1)\n", pos, bit1)
fmt.Printf("第%d位: %v (方法2)\n", pos, bit2)
}
实用工具函数
go
package main
import (
"fmt"
"strings"
)
// 格式化显示二进制
func formatBinary(num int, bits int) string {
return fmt.Sprintf("%0*b", bits, num)
}
// 设置特定位
func setBit(num int, pos int) int {
return num | (1 << pos)
}
// 清除特定位
func clearBit(num int, pos int) int {
return num &^ (1 << pos)
}
// 切换特定位(0变1,1变0)
func toggleBit(num int, pos int) int {
return num ^ (1 << pos)
}
// 检查特定位是否被设置
func isBitSet(num int, pos int) bool {
return (num & (1 << pos)) != 0
}
// 获取最低位1的位置
func getLowestBitPosition(num int) int {
if num == 0 {
return -1
}
position := 0
for (num & 1) == 0 {
num >>= 1
position++
}
return position
}
func main() {
num := 0b11011010
fmt.Printf("原始数字: %s\n", formatBinary(num, 8))
fmt.Printf("设置第2位: %s\n", formatBinary(setBit(num, 2), 8))
fmt.Printf("清除第3位: %s\n", formatBinary(clearBit(num, 3), 8))
fmt.Printf("切换第5位: %s\n", formatBinary(toggleBit(num, 5), 8))
fmt.Printf("第4位是否设置: %v\n", isBitSet(num, 4))
fmt.Printf("最低位1的位置: %d\n", getLowestBitPosition(num))
}
性能优势
Go语言中的位运算在性能上有显著优势:
- 编译时优化:Go编译器能识别位运算模式并进行优化
- CPU原生支持:现代CPU都有专门的位运算指令
- 内存高效:多个布尔标志可以打包到一个整型中
css
// 性能对比示例
package main
import (
"fmt"
"time"
)
func main() {
const iterations = 1000000000
// 使用乘法
start := time.Now()
for i := 0; i < iterations; i++ {
_ = i * 2
}
multiplyTime := time.Since(start)
// 使用位移
start = time.Now()
for i := 0; i < iterations; i++ {
_ = i << 1
}
shiftTime := time.Since(start)
fmt.Printf("乘法用时: %v\n", multiplyTime)
fmt.Printf("位移用时: %v\n", shiftTime)
fmt.Printf("位移比乘法快: %.2f%%\n",
float64(multiplyTime.Nanoseconds()-shiftTime.Nanoseconds())/float64(multiplyTime.Nanoseconds())*100)
}
总结
在Go开发中,二进制运算不仅是底层编程的基础,也是编写高效代码的重要工具。通过合理使用:
- 权限和标志位管理:使用位掩码提高存储效率
- 数学优化:位移替代乘除提升性能
- 算法实现:在算法竞赛和性能敏感场景中广泛应用
- 位操作技巧:解决特定问题的高效方案
掌握这些技巧能让你的Go代码更加高效、优雅。建议在实际项目中多尝试使用位运算,特别是在处理标志、权限、状态等场景时。