이 글은 Golang의 비트 연산에 대한 심층적인 이해를 제공하고, 각 연산자에 대한 자세한 사례와 사용 방법을 소개합니다. 모두에게 도움이 되기를 바랍니다!
컴퓨터 메모리가 비싸고 처리 능력이 제한되었던 옛날에는 해커 스타일의 비트 연산을 사용하여 정보를 처리하는 것이 선호되는 방법이었습니다(어떤 경우에는 유일한 방법이었습니다). 오늘날까지도 비트 연산의 직접적인 사용은 하위 수준 시스템 프로그래밍, 그래픽 처리, 암호화 등과 같은 많은 컴퓨팅 분야에서 여전히 필수적인 부분입니다. [관련 권장 사항: Go 비디오 튜토리얼]
Go 프로그래밍 언어는 다음 비트 연산자를 지원합니다.
& bitwise AND | bitwise OR ^ bitwise XOR &^ AND NOT << left shift >> right shift
이 문서의 나머지 부분에서는 각 연산자와 사용 방법에 대한 예를 자세히 설명합니다.
Go에서 &
연산자는 두 개의 정수 피연산자에 대해 비트 단위 AND
연산을 수행합니다. AND
연산에는 다음과 같은 속성이 있습니다: &
运算符在两个整型操作数中执行按位 AND
操作。AND
操作具有以下属性:
Given operands a, b AND(a, b) = 1; only if a = b = 1 else = 0
AND
运算符具有选择性的把整型数据的位清除为 0 的好的效果。 例如,我们可以使用 &
运算符去清除(设置)最后 4 个最低有效位(LSB)全部为 0 。
func main() { var x uint8 = 0xAC // x = 10101100 x = x & 0xF0 // x = 10100000 }
所有的位运算都支持简写的赋值形式。 例如,前面的例子可以重写为如下。
func main() { var x uint8 = 0xAC // x = 10101100 x &= 0xF0 // x = 10100000 }
另外一个巧妙的技巧是:你可以用 &
操作去测试一个数字是奇数还是偶数。原因是当一个数字的二进制的最低位是 1 的时候,那他就是奇数。我们可以用一个数字和 1 进行 &
操作,然后在和 1 做 AND
运算,如果的到的结果是 1 ,那么这个原始的数字就是奇数
import ( "fmt" "math/rand" ) func main() { for x := 0; x < 100; x++ { num := rand.Int() if num&1 == 1 { fmt.Printf("%d is odd\n", num) } else { fmt.Printf("%d is even\n", num) } } }
在 Playground 上运行上面的例子
|
对其整型操作数执行按位或
操作。回想一下或
操作符具备以下性质:
Given operands a, b OR(a, b) = 1; when a = 1 or b = 1 else = 0
我们可以利用按位或
操作符为给定的整数有选择地设置单个位。例如,在如下示例中我们使用按位或
将示例数(从低位到高位(MSB))中的第 3 ,第 7 和第 8 位置为 1 。
func main() { var a uint8 = 0 a |= 196 fmt.Printf("%b", a) } // 打印结果 11000100 ^^ ^
练习场中可运行范例。
在使用位掩码技术为给定的整型数字设置任意位时,或
运算非常有用。例如,我们可以扩展之前的程序为变量 a
存储的值设置更多的位。
func main() { var a uint8 = 0 a |= 196 a |= 3 fmt.Printf("%b", a) } // 打印结果 11000111
在练习场中可以运行范例。
在前面的程序里,不仅要按位设置十进制的 196,而且要设置低位上的十进制 3。我们还可以继续(或
上更多的值)设置完所有的位。
现在,回顾一下 AND(a, 1) = a 当且仅当 a = 1
。 我们可以利用这个特性去查询其设置位的值。例如,在上述代码中 a & 196
会返回 196 是因为这几位的值在 a
中确实都存在。所以我们可以结合使用 OR
和 AND
运算的方式来分别设置和读取某位的配置值。.
接下来的源码片段演示了这个操作。函数 procstr
会转换字符串的内容。它需要两个参数:第一个, str
,是将要被转换的字符串,第二个, conf
,是一个使用位掩码的方式指定多重转换配置的整数。
const ( UPPER = 1 // 大写字符串 LOWER = 2 // 小写字符串 CAP = 4 // 字符串单词首字母大写 REV = 8 // 反转字符串 ) func main() { fmt.Println(procstr("HELLO PEOPLE!", LOWER|REV|CAP)) } func procstr(str string, conf byte) string { // 反转字符串 rev := func(s string) string { runes := []rune(s) n := len(runes) for i := 0; i < n/2; i++ { runes[i], runes[n-1-i] = runes[n-1-i], runes[i] } return string(runes) } // 查询配置中的位操作 if (conf & UPPER) != 0 { str = strings.ToUpper(str) } if (conf & LOWER) != 0 { str = strings.ToLower(str) } if (conf & CAP) != 0 { str = strings.Title(str) } if (conf & REV) != 0 { str = rev(str) } return str }
在 Playground上面运行代码.
上面的 procstr("HELLO PEOPLE!", LOWER|REV|CAP)
方法会把字符串变成小写,然后反转字符串,最后把字符串里面的单词首字母变成大写。这个功能是通过设置 conf
里的第二,三,四位的值为 14 来完成的。然后代码使用连续的 if 语句块来获取这些位操作进行对应的字符串转换。
在 Go 中 按位 异或
操作是用 ^
来表示的。 异或
运算符有如下的特点:
Given operands a, b XOR(a, b) = 1; only if a != b else = 0
异或
func main() { var a uint16 = 0xCEFF a ^= 0xFF00 // same a = a ^ 0xFF00 } // a = 0xCEFF (11001110 11111111) // a ^=0xFF00 (00110001 11111111)
AND
연산자는 정수 데이터의 비트를 선택적으로 0으로 지우는 좋은 효과를 갖습니다. 예를 들어 &
연산자를 사용하여 마지막 4개의 최하위 비트(LSB)를 모두 0으로 지울 수 있습니다. 🎜func main() { a, b := -12, 25 fmt.Println("a and b have same sign?", (a ^ b) >= 0) }
func main() { var a byte = 0x0F fmt.Printf("%08b\n", a) fmt.Printf("%08b\n", ^a) } // 打印结果 00001111 // var a 11110000 // ^a
&
연산을 사용하여 숫자가 홀수인지 짝수인지 테스트할 수 있다는 것입니다. 그 이유는 숫자의 최하위 비트가 1이면 홀수이기 때문입니다. 숫자와 1을 사용하여 &
연산을 수행한 다음 1을 사용하여 AND
연산을 수행할 수 있습니다. 결과가 1이면 원래 숫자는 홀수입니다. 🎜Given operands a, b AND_NOT(a, b) = AND(a, NOT(b))
|는 정수 피연산자에 대해 비트 <code> 또는
연산을 수행합니다. 또는
연산자에는 다음과 같은 속성이 있다는 점을 기억하세요. 🎜AND_NOT(a, 1) = 0; clears a AND_NOT(a, 0) = a;
또는
연산자를 사용하여 주어진 정수에 대해 개별 비트를 선택적으로 설정할 수 있습니다. 예를 들어, 다음 예에서는 비트별 또는
를 사용하여 샘플 번호의 3번째, 7번째, 8번째 비트(낮은 비트부터 높은 비트(MSB)까지)를 1로 설정합니다. 🎜func main() { var a byte = 0xAB fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) a &amp;amp;amp;amp;amp;amp;amp;amp;^= 0x0F fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) } // 打印: 10101011 10100000
또는
연산은 비트 마스킹 기술을 사용하여 주어진 정수에 대해 임의의 비트를 설정할 때 매우 유용합니다. 예를 들어, 이전 프로그램을 확장하여 a
변수에 저장된 값에 대해 더 많은 비트를 설정할 수 있습니다. 🎜Given integer operands a and n, a &amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt; n; shifts all bits in a to the left n times a &amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;gt; n; shifts all bits in a to the right n times
또는
에 더 많은 값) 모든 비트를 설정할 수 있습니다. 🎜🎜🎜🎜🎜 비트 연산의 구성 사용법🎜🎜🎜이제 AND(a, 1) = a a = 1인 경우에만
을 검토하세요. 이 기능을 사용하여 설정 비트 값을 쿼리할 수 있습니다. 예를 들어 위 코드에서 a &amp;amp;amp;amp;amp;amp;amp; 196
은 이 비트의 값이 a
에 존재하기 때문에 196을 반환합니다. 따라서 OR
및 AND
연산을 조합하여 사용하여 각각 특정 비트의 구성 값을 설정하고 읽을 수 있습니다. .🎜🎜다음 소스 코드 조각은 이 작업을 보여줍니다. procstr
함수는 문자열의 내용을 변환합니다. 여기에는 두 개의 매개변수가 필요합니다. 첫 번째 매개변수 str
는 변환할 문자열이고 두 번째 매개변수 conf
는 구성을 변환하기 위한 여러 정수를 지정하는 비트마스크입니다. 🎜func main() { var a int8 = 3 fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt;1) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt;2) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt;3) } // 输出的结果: 00000011 00000110 00001100 00011000
procstr(&amp;amp;quot;HELLO PEOPLE!&amp;amp;quot;, LOWER|REV|CAP)
메서드는 문자열을 소문자로 변환한 다음 문자열을 반전시키고 마지막으로 문자열에 있는 단어의 첫 글자를 대문자로 표시합니다. 이 기능은 conf
의 두 번째, 세 번째, 네 번째 비트를 14 값으로 설정하여 수행됩니다. 그런 다음 코드는 연속적인 if 문 블록을 사용하여 이러한 비트 연산을 얻고 해당 문자열 변환을 수행합니다. 🎜🎜🎜🎜🎜^ 연산자 🎜🎜🎜Go에서 비트별 XOR
연산은 ^
로 표현됩니다. XOR
연산자에는 다음과 같은 특징이 있습니다. 🎜func main() { var a uint8 = 120 fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;gt;1) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;gt;2) } // 打印: 01111000 00111100 00011110
XOR
연산자의 이 기능은 이진 비트의 한 값을 다른 값으로 변경하는 데 사용할 수 있습니다. 예를 들어, 16진수 값이 주어지면 다음 코드를 사용하여 값의 처음 8비트(MSB에서 시작)를 전환할 수 있습니다. 🎜func main() { var a uint16 = 0xCEFF a ^= 0xFF00 // same a = a ^ 0xFF00 } // a = 0xCEFF (11001110 11111111) // a ^=0xFF00 (00110001 11111111)
在前面的代码片段中,与 1 进行异或的位被翻转(从 0 到 1 或从 1 到 0)。异或
运算的一个实际用途,例如,可以利用 异或
运算去比较两个数字的符号是否一样。当 (a ^ b) ≥ 0
(或相反符号的 (a ^ b) < 0
)为 true
的时候,两个整数 a,b 具有相同的符号,如下面的程序所示:
func main() { a, b := -12, 25 fmt.Println(&amp;amp;amp;amp;amp;amp;amp;quot;a and b have same sign?&amp;amp;amp;amp;amp;amp;amp;quot;, (a ^ b) &amp;amp;amp;amp;amp;amp;amp;gt;= 0) }在 Go 的 Playground运行代码。
当执行上面这个程序的时候,将会打印出:
a and b have same sign? false
。在 Go Playground 上修改程序里 a ,b 的符号,以便看到不同的结果。^ 作为取反位运算符 (非)
不像其他语言 (c/c++,Java,Python,Javascript,等), Go 没有专门的一元取反位运算符。取而代之的是,
XOR
运算符^
,也可作为一元取反运算符作用于一个数字。对于给定位 x,在 Go 中 x = 1 ^ x 可以翻转该位。在以下的代码段中我们可以看到使用^a
获取变量a
的取反值的操作。func main() { var a byte = 0x0F fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, ^a) } // 打印结果 00001111 // var a 11110000 // ^a로그인 후 복사在练习场中可以运行范例。
&amp;amp;amp;amp;amp;amp;amp;^ 操作符
&amp;amp;amp;amp;amp;amp;amp;^
操作符意为与非
,是与
和非
操作符的简写形式,它们定义如下。Given operands a, b AND_NOT(a, b) = AND(a, NOT(b))로그인 후 복사로그인 후 복사如果第二个操作数为 1 那么它则具有清除第一个操作数中的位的趣味特性。
AND_NOT(a, 1) = 0; clears a AND_NOT(a, 0) = a;로그인 후 복사로그인 후 복사接下来的代码片段使用
AND NOT
操作符,将变量值1010 1011
变为1010 0000
,清除了操作数上的低四位。func main() { var a byte = 0xAB fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) a &amp;amp;amp;amp;amp;amp;amp;amp;^= 0x0F fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) } // 打印: 10101011 10100000로그인 후 복사로그인 후 복사在练习场中运行范例。
<< 和 >> 操作符
与其他 C 的衍生语言类似, Go 使用
<<
和来表示左移运算符和右移运算符,如下所示:
Given integer operands a and n, a &amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt; n; shifts all bits in a to the left n times a &amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;gt; n; shifts all bits in a to the right n times로그인 후 복사로그인 후 복사例如,在下面的代码片段中变量
a
(00000011
)的值将会左移位运算符分别移动三次。每次输出结果都是为了说明左移的目的。func main() { var a int8 = 3 fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt;1) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt;2) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt;3) } // 输出的结果: 00000011 00000110 00001100 00011000로그인 후 복사로그인 후 복사在 Playground 运行代码
注意每次移动都会将低位右侧补零。相对应,使用右移位操作符进行运算时,每个位均向右方移动,空出的高位补零,如下示例 (有符号数除外,参考下面的算术移位注释)。
func main() { var a uint8 = 120 fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;gt;1) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;gt;2) } // 打印: 01111000 00111100 00011110로그인 후 복사로그인 후 복사在 练习场中可以运行范例。
可以利用左移和右移运算中,每次移动都表示一个数的 2 次幂这个特性,来作为某些乘法和除法运算的小技巧。例如,如下代码中,我们可以使用右移运算将
200
(存储在变量 a 中)除以 2 。func main() { a := 200 fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%d\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;amp;gt;1) } // 打印: 100로그인 후 복사在 练习场 中可以运行范例。
或是通过左移 2 位,将一个数乘以4:
func main() { a := 12 fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%d\n&amp;amp;amp;amp;amp;amp;amp;quot;, a&amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt;2) } // 打印: 48로그인 후 복사在 练习场 中可以运行范例。
位移运算符提供了有趣的方式处理二进制值中特定位置的值。例如,下列的代码中,
|
和<<
用于设置变量a
的第三个 bit 位。func main() { var a int8 = 8 fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) a = a | (1&amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt;2) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%08b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) } // prints: 00001000 00001100로그인 후 복사可以在 练习场 中运行代码示例。
或者,您可以组合位移运算符和
&amp;amp;amp;amp;amp;amp;amp;
测试是否设置了第n位,如下面示例所示:func main() { var a int8 = 12 if a&amp;amp;amp;amp;amp;amp;amp;amp;(1&amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt;2) != 0 { fmt.Println(&amp;amp;amp;amp;amp;amp;amp;quot;take action&amp;amp;amp;amp;amp;amp;amp;quot;) } } // 打印: take action로그인 후 복사在 练习场中运行代码。
使用
&amp;amp;amp;amp;amp;amp;amp;^
和位移运算符,我们可以取消设置一个值的某个位。例如,下面的示例将变量 a 的第三位置为 0 :func main() { var a int8 = 13 fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%04b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) a = a &amp;amp;amp;amp;amp;amp;amp;amp;^ (1 &amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;lt; 2) fmt.Printf(&amp;amp;amp;amp;amp;amp;amp;quot;%04b\n&amp;amp;amp;amp;amp;amp;amp;quot;, a) } // 打印: 1101 1001로그인 후 복사在 练习场 中运行代码。
关于算术位移运算的笔记
当要位移的值(左操作数)是有符号值时,Go 自动应用算术位移。在右移操作期间,复制(或扩展)二进制补码符号位以填充位移的空隙。
요약
다른 최신 연산자와 마찬가지로 Go는 모든 이진 비트 조작 연산자를 지원합니다. 이 문서에서는 이러한 연산자를 사용하여 수행할 수 있는 다양한 해킹의 예를 제공할 뿐입니다. 웹에서 많은 기사, 특히 Sean Eron Anderson의 Bit Twiddling Hacks을 찾을 수 있습니다.
트위터에서 Vladim @vladimirvivien을 팔로우하세요.
Go를 배우고 있다면 Vladimir Vivien의 Go에 관한 책 Learning Go 프로그래밍을 읽어보세요.
이 기사는 원래 Vladimir Vivien 작가가 Bit Hacking with Go라는 제목으로 Medium에 게시한 것입니다.
영어 원본 주소: https://dev.to/vladimirvivien/bit-hacking-with-go
더 많은 프로그래밍 관련 지식을 보려면 프로그래밍 비디오를 방문하세요! !
위 내용은 Golang의 비트 연산에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!