Golang中的位操作技巧与实践
Bit Hacking with Go - Learning the Go Programming Language - Medium
在计算机的旧日美好时光里,内存昂贵,处理能力稀缺,直接进行位操作是首选方法…
阅读时间:8 分钟
func main() {
var x uint8 = 0xAC // x = 10101100
x &= 0xF0 // x = 10100000
}
什么是 uint8? 什么是 0xAC? 10101100 …172? 什么是 &=? 什么是 0xF0? 10100000…160?
AND 和 & 是一样的吗?
https://play.golang.org/p/1OU2kehp5EW 输出中的数字是从哪里来的?
| 和 OR 是一样的吗?
什么是 MSB?
我不想停下!但我必须停下来了。回头见!
更多关于Golang中的位操作技巧与实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html
func main() {
fmt.Println("hello world")
}
但这意味着什么?
如果可能的话,请与我小学时学的乘法进行比较/对比
非常有趣!感谢指教!
那么,是那个额外的1让它变成负数的吗?
这个问题并非Go语言特有。这是一个关于位、二进制数及其运算的通用问题。
是的。我目前正在学习它(自己抽时间学,时间有限)。过程很慢。
cherilexvold1974:
010 1 (十进制 5) AND 001 1 (十进制 3) = 000 1 (十进制 1)"
5 x 3 怎么会等于 1?
这就像按位对它们进行乘法运算,正如引用的例子所示。
我说 -(MIN + 1) == MAX,这可以重写为 MIN == -MAX - 1,这基本上意味着,对于任何有符号整数,可表示的最小值等于可表示的最大值的负值减一……
有意思。我很想了解更多关于“可表示的最小值”和“可表示的最大值”的信息。
使用4位有符号数,可以表示从 7 到 -8 的值。
有趣。
你已经从维基百科引用了它。
你有数字5和3,它们的二进制表示(4位)分别是 0101 和 0011。
现在你逐位相乘这些二进制表示(0 * 0 = 0,1 * 0 = 0,0 * 1 = 0 以及 1 * 1 = 1)。然后将它们组合回一个二进制数 0001,其十进制表示为 1。
cherilexvold1974:
什么是最有效位?
https://en.wikipedia.org/wiki/Bit_numbering#Most_significant_bit
现在,请尝试理解这一点,并找出是哪一位负责标记符号。
hollowaykeanho:
“嘿,让我们使用最高有效位(在我们的例子中是
0b10000000)作为符号标志”。这也意味着一个8位所能表示的有效数字现在只剩下7位,使得有符号数的范围为:
0b11111111 == -127
0b10000000 == 0b00000000 = 0
0b01111111 == 127
让我看看我是否正确理解了你的意思。
“二进制补码”基本上意味着对每一位取反然后加 1。
0 -> 000 -> 111
我在这里停一下。所以在 000 中,你对每个 0 取反并加 1,因此得到 111?
我不理解第四列。
我想我理解了 1 的前三列。
我不理解 2 的第三列。不应该是 011 吗?
-4 -> 100 -> 011 -> 100 // 警告!4 在 3 位有符号表示中是不可表示的!
为什么会这样?
我不理解 -2 和 -2 的第三列。
很抱歉我的知识如此欠缺。这背后有原因。感谢你的耐心。
cherilexvold1974:
What is == MAX?
这只是等式的一半。
我说的是 -(MIN + 1) == MAX,它可以重写为 MIN == -MAX - 1,这基本上意味着,对于任何有符号整数,可表示的最小值等于可表示的最大值的负数减一……
cherilexvold1974:
Is there a way to differentiate 4 from -4 ?
嗯,如果你的有符号整数位数超过3位,那么你当然可以表示 4,因此可以轻松地将 -4 与之区分开来。我们考虑4位有符号整数:
4 -> 0100 -> 1011 -> 1100
-4 -> 1100 -> 0011 -> 0100
使用4位有符号整数,你可以表示从 7 到 -8 的值。
“取反”一个比特位意味着将 0 变为 1,反之亦然,并且对每一位都进行此操作。
谢谢 🙏 非常有帮助。
不,我是将每个 0 取反为 1,这样我得到 111,然后 我加上 1,由于进位和溢出,结果得到 000。
明白了 😊 酷。
当我真正理解时,我感到非常开心。
我仔细查看了图表,现在理解了。
因为,正如你从表中看到的,4 和 -4 具有相同的表示形式。这就是为什么在任何有符号表示中 -(MIN + 1) == MAX。
不用担心最后一个问题,因为你所说的已经回答了那个问题。
出于某种原因,我还没完全理解这一点。我确实看到 4 和 -4 有相同的表示形式。我知道有符号表示是什么。== MAX 是什么意思?有没有办法区分 4 和 -4?
“二进制补码”基本上意味着将每一位取反,然后加 1。
以下将再次假设为3位:
十进制 -> 二进制表示 -> 所有位取反 -> 加一(等同于乘以十进制的-1)
0 -> 000 -> 111 -> 000
1 -> 001 -> 110 -> 111
2 -> 010 -> 101 -> 110
3 -> 011 -> 100 -> 101
-4 -> 100 -> 011 -> 100 // 注意!4无法用3位有符号数表示!
-3 -> 101 -> 010 -> 011
-2 -> 110 -> 001 -> 010
-1 -> 111 -> 000 -> 001
(2^m - n)_2 这种表示法让很多没有数学背景的人感到困惑,因此在我的计算机科学学位第一学期甚至没有涉及,直到第三或第四学期才粗略地提到,尽管二进制补码作为基础内容在第一学期就已经介绍了。
它是以我在此解释的两步方式引入的。对于至少理解二进制数和单比特取反基础知识的人来说,这通常是普遍可以理解的。
为了准确回答您的问题:
uint8 在 Go 语言规范中定义于此:https://golang.org/ref/spec#Numeric_types
0xAC 是一个十六进制数字字面量,同样在语言规范中定义于此:https://golang.org/ref/spec#Integer_literals
&= 是一个赋值运算符,在规范中定义于此:https://golang.org/ref/spec#Assignments
单个与符号 & 对其操作数执行按位与操作,在算术运算符部分定义:https://golang.org/ref/spec#Arithmetic_operators
按位或运算符 | 在同一部分定义。
综上所述,我建议您查阅维基百科以获取更多解释:https://en.wikipedia.org/wiki/Bitwise_operation。我经常使用维基百科和 YouTube 来熟悉新概念,因为这些平台通常有图表可以直观地演示正在发生的事情。
最后,关于您提出的示例中数字来源的问题,在我回答之前,您能先说明一下示例中哪部分对您来说不清楚吗?
cherilexvold1974:
我很想了解更多关于“最小可表示值”和“最大可表示值”的信息。
具体是指什么?
核心在于,如果你拥有的数字位数是有限的,那么你能表示的值也是有限的。这就像你的汽车里程表一样。你有6位数字来表示里程,从0开始,最大值将是999999。之后,它会重新归零。二进制数也是类似的。唯一的例外是,我们只有0和1作为数字,而不是0到9。
另一件事是,我们没有 - 号来表示一个值的负性。但一些工程师们约定,在有符号整数类型中,将最高有效位作为“负号”。他们还约定使用二进制补码,而不是简单地反转每一位,因为使用二进制补码可以直接进行正负数的加法运算:
让我们用3位有符号整数计算 `-2 + 3`
十进制 二进制补码 普通反转
-2 110 101
+ 3 011 011
=== === ===
1 (1)001 (1)000
如你所见,在二进制补码表示法中,我们自然地得到了 1 的二进制表示,而在普通反转法中,我们需要跟踪我们越过了 0 的边界这一事实,并且必须手动再加一个 1 来修正结果。
“取反”一个比特位意味着将 0 变为 1,反之亦然,并且对每一位都进行此操作。所以这还和加法没有任何关系,它只是构建二进制补码的两个步骤中的第一步。只有在所有位都翻转之后,你才加上 1,并处理进位。
cherilexvold1974: 我在这里停一下。那么在 000 中,你对每个 0 取反并加 1,因此得到 111?
不,我将每个 0 取反为 1,这样我得到 111,然后我加上 1,由于进位和溢出,结果得到 000。
cherilexvold1974: 我不明白 -2 的第三列。不应该是 011 吗?
不,那只是直接 加 一,而没有先对所有位取反。
cherilexvold1974: -4 -> 100 -> 011 -> 100 // 警告!4 无法用 3 位有符号数表示! 为什么?
因为,正如你从表中看到的,4 和 -4 有相同的表示形式。这就是为什么在任何有符号表示中 -(MIN + 1) == MAX。
cherilexvold1974: 我不明白 -2 和 -2 的第三列
我想你指的是 -2 和 2 吧?
你具体哪里不明白?还是因为你误解了“对每位取反”的意思?或者是其他原因?
uint8在 Go 语言规范中定义于此:https://golang.org/ref/spec#Numeric_types
感谢为我指明了正确的方向!我点击了链接并将其保存到文件中。我之前读过它,但这次我理解得更好了。
在十六进制字面量中,字母 a-f 和 A-F 代表值 10 到 15……哦。我之前接触过这个,但它对我来说太新了,以至于我没有认出来。
https://golang.org/ref/spec#Assignments 这对我来说简直是天书。我的老师教过 & 是指向一个整数的指针,它显示一个内存地址。我的理解非常基础。所以我不知道 &= 是什么意思。
单个的与符号 &,对其操作数执行按位与运算,定义在算术运算符下:https://golang.org/ref/spec#Arithmetic_operators 开始明白了。按位的概念对我来说是新的。
感谢提供算术运算符的链接。我实际上一直在找这个。我不知道我怎么错过了它。
正在阅读这个:https://en.wikipedia.org/wiki/Bitwise_operation。 “在简单的低成本处理器上,通常,按位运算比除法快得多,比乘法快几倍,有时甚至比加法快得多。” 这很酷。难怪人们会使用它。 我明天继续(希望如此)
从维基百科的文章中……"按位与是一种二元运算,它接受两个等长的二进制表示,并对每一对相应的位执行逻辑与操作,这相当于将它们相乘。因此,如果比较位置的两个位都是 1,则结果二进制表示中的该位是 1 (1 × 1 = 1);否则,结果为 0 (1 × 0 = 0 和 0 × 0 = 0)。例如:
010 1 (十进制 5) AND 001 1 (十进制 3) = 000 1 (十进制 1)"
5 x 3 怎么会等于 1?
位操作核心概念解析
1. 基础类型与表示
var x uint8 = 0xAC // uint8是无符号8位整数,范围0-255
// 0xAC是十六进制表示:
// A = 10(十进制) = 1010(二进制)
// C = 12(十进制) = 1100(二进制)
// 0xAC = 10101100(二进制) = 172(十进制)
2. 位运算符详解
// & 是按位AND运算符
x &= 0xF0 // 等价于 x = x & 0xF0
// 0xF0 = 11110000(二进制) = 240(十进制)
// 计算过程:
// 10101100 (x = 0xAC)
// & 11110000 (0xF0)
// ---------
// 10100000 = 0xA0 = 160(十进制)
3. 完整示例演示
package main
import "fmt"
func main() {
var x uint8 = 0xAC // 10101100
fmt.Printf("原始值: %08b = 0x%X = %d\n", x, x, x)
// AND操作
x &= 0xF0
fmt.Printf("AND 0xF0后: %08b = 0x%X = %d\n", x, x, x)
// OR操作示例
var y uint8 = 0x0A // 00001010
y |= 0xF0 // OR操作
fmt.Printf("OR 0xF0后: %08b = 0x%X = %d\n", y, y, y)
// XOR操作示例
var z uint8 = 0xAA // 10101010
z ^= 0xFF // XOR操作
fmt.Printf("XOR 0xFF后: %08b = 0x%X = %d\n", z, z, z)
}
4. 常用位操作技巧
// 1. 检查特定位是否设置
func isBitSet(num, pos uint8) bool {
return (num & (1 << pos)) != 0
}
// 2. 设置特定位
func setBit(num, pos uint8) uint8 {
return num | (1 << pos)
}
// 3. 清除特定位
func clearBit(num, pos uint8) uint8 {
return num & ^(1 << pos)
}
// 4. 切换特定位
func toggleBit(num, pos uint8) uint8 {
return num ^ (1 << pos)
}
// 5. 提取低位字节
func getLowByte(value uint16) uint8 {
return uint8(value & 0xFF)
}
// 6. 判断奇偶(最低位检查)
func isEven(num uint8) bool {
return num&1 == 0
}
5. 实际应用场景
// 标志位管理
const (
FlagRead = 1 << iota // 00000001
FlagWrite // 00000010
FlagExecute // 00000100
)
func main() {
var permissions uint8 = 0
// 设置权限
permissions |= FlagRead | FlagWrite // 00000011
// 检查权限
canRead := permissions&FlagRead != 0 // true
canExecute := permissions&FlagExecute != 0 // false
// 移除权限
permissions &^= FlagWrite // 清除写权限
fmt.Printf("权限位: %08b\n", permissions)
}
6. MSB(最高有效位)操作
// 获取MSB(第7位,从0开始计数)
func getMSB(num uint8) uint8 {
return (num >> 7) & 1
}
// 检查是否为负数(对于有符号数)
func isNegative(num int8) bool {
return num&0x80 != 0 // 检查符号位
}
7. 性能优化示例
// 使用位操作实现快速乘除
func multiplyByPowerOfTwo(num, power uint) uint {
return num << power // 相当于 num * 2^power
}
func divideByPowerOfTwo(num, power uint) uint {
return num >> power // 相当于 num / 2^power
}
// 交换两个变量(不使用临时变量)
func swap(a, b *int) {
*a ^= *b
*b ^= *a
*a ^= *b
}
Go中的位操作直接对应CPU指令,执行效率高。&对应AND,|对应OR,^对应XOR,&^对应AND NOT。这些操作在底层编程、协议解析、性能优化等场景中非常实用。



