在计算机内存昂贵,处理能力有限的美好旧时光里,用比较黑客范的位运算方式去处理信息是首选方式(某些情况下只能如此)。时至今日,直接使用位运算仍然是很多计算领域中不可或缺的部分,例如底层系统编程,图形处理,密码学等。

Go 编程语言支持以下按位运算符:

& bitwise AND
| bitwise OR
^ bitwise XOR
&^ AND NOT
<< left shift
>> right shift
本文的余下部分详述了每个操作符以及它们如何使用的案例。

& 运算符
在 Go 中, & 运算符在两个整型操作数中执行按位 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
异或运算的这个特性可以用来把二进制位的一个值变成另外一个值。举个例子,给到一个 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("a and b have same sign?", (a ^ b) >= 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("%08b\n", a)
fmt.Printf("%08b\n", ^a)
}

// 打印结果
00001111 // var a
11110000 // ^a
在练习场中可以运行范例。

&^ 操作符
&^ 操作符意为 与非,是 与 和 非 操作符的简写形式,它们定义如下。

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("%08b\n", a)
a &^= 0x0F
fmt.Printf("%08b\n", a)
}

// 打印:
10101011
10100000
在练习场中运行范例。

<<和>> 操作符
与其他 C 的衍生语言类似, Go 使用 << 和 >> 来表示左移运算符和右移运算符,如下所示:

Given integer operands a and n,
a << n; shifts all bits in a to the left n times
a >> n; shifts all bits in a to the right n times
例如,在下面的代码片段中变量 a (00000011)的值将会左移位运算符分别移动三次。每次输出结果都是为了说明左移的目的。

func main() {
var a int8 = 3
fmt.Printf("%08b\n", a)
fmt.Printf("%08b\n", a<<1)
fmt.Printf("%08b\n", a<<2)
fmt.Printf("%08b\n", a<<3)
}

// 输出的结果:
00000011
00000110
00001100
00011000
在 Playground 运行代码

注意每次移动都会将低位右侧补零。相对应,使用右移位操作符进行运算时,每个位均向右方移动,空出的高位补零,如下示例 (有符号数除外,参考下面的算术移位注释)。

func main() {
var a uint8 = 120
fmt.Printf("%08b\n", a)
fmt.Printf("%08b\n", a>>1)
fmt.Printf("%08b\n", a>>2)
}

// 打印:
01111000
00111100
00011110
在 练习场中可以运行范例。

可以利用左移和右移运算中,每次移动都表示一个数的 2 次幂这个特性,来作为某些乘法和除法运算的小技巧。例如,如下代码中,我们可以使用右移运算将 200(存储在变量 a 中)除以 2 。

func main() {
a := 200
fmt.Printf("%d\n", a>>1)
}

// 打印:
100
在 练习场 中可以运行范例。

或是通过左移 2 位,将一个数乘以 4:

func main() {
a := 12
fmt.Printf("%d\n", a<<2)
}
// 打印:

48
在 练习场 中可以运行范例。

位移运算符提供了有趣的方式处理二进制值中特定位置的值。例如,下列的代码中,| 和 << 用于设置变量 a 的第三个 bit 位。

func main() {
var a int8 = 8
fmt.Printf("%08b\n", a)
a = a | (1<<2)
fmt.Printf("%08b\n", a)
}
// prints:
00001000
00001100
可以在 练习场 中运行代码示例。

或者,您可以组合位移运算符和 & 测试是否设置了第 n 位,如下面示例所示:

func main() {
var a int8 = 12
if a&(1<<2) != 0 {
fmt.Println("take action")
}
}

// 打印:
take action
在 练习场中运行代码。

使用 &^ 和位移运算符,我们可以取消设置一个值的某个位。例如,下面的示例将变量 a 的第三位置为 0 :

func main() {
var a int8 = 13
fmt.Printf("%04b\n", a)
a = a &^ (1 << 2)
fmt.Printf("%04b\n", a)
}

// 打印:
1101
1001
在 练习场 中运行代码。

关于算术位移运算的笔记
当要位移的值(左操作数)是有符号值时,Go 自动应用算术位移。在右移操作期间,复制(或扩展)二进制补码符号位以填充位移的空隙。

总结
与其它现代运算符一样,Go 支持所有二进制位操作运算符。这篇文章仅仅提供了可以用这些操作符完成的各种黑科技示例。你可以在网络上找到很多文章,特别是 Sean Eron Anderson 写的 Bit Twiddling Hacks 。

关注 Vladim @vladimirvivien 的 Twitter。

如果你正在学习 Go,阅读 Vladimir Vivien 关于 Go 的书,名为 Learning Go Programming 。

这篇文章开始由作者 Vladimir Vivien 发布在 Medium 上,名为 Bit Hacking with Go。

————————————————
原文作者:Richard101
转自链接:https://learnku.com/go/t/23460/bit-operation-of-go
版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。

Go 的位操作的更多相关文章

  1. 《Entity Framework 6 Recipes》中文翻译系列 (19) -----第三章 查询之使用位操作和多属性连接(join)

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-16  过滤中使用位操作 问题 你想在查询的过滤条件中使用位操作. 解决方案 假 ...

  2. 如何获取byte的各个bit值以及常见位操作

    项目中通过信号采集板的数据获取车上仪表盘指示灯的信息,将接收到的数据转成byte后,还要将每一个Byte的各个Bit值分离出来,这样才知道每个bit的值代表的具体信息.这里记录下如何获取byte的各个 ...

  3. php strtotime 在32位操作系统下的限制

    php strtotime 在32位操作系统下的限制 <?php class DateHelper{ /** * 在32位操作系统下,超过 2038-01-19 03:14:07 ,会溢出 * ...

  4. C#按位操作,直接操作INT数据类型的某一位

    /// <summary> /// 根据Int类型的值,返回用1或0(对应True或Flase)填充的数组 /// <remarks>从右侧开始向左索引(0~31)</r ...

  5. 64位操作系统 通过ODP.NET 访问ORACLE 11g

    摘要:64位操作系统部署.NET 程序访问oracle时,无法连接问题.(注意:客户端是64位系统 ,服务端是否64位 还是32位无关.) 1.到oracle 官网搜索相关版本的 ODAC网址: ht ...

  6. java位操作总结

    在计算机中所有数据都是以二进制的形式储存的. 位运算其实就是直接对在内存中的二进制数据进行操作,因此处理数据的速度非常快. 方便演示,首先写个二进制打印方法: private static void ...

  7. (原)解决.NET 32位程序运行在64位操作系统下的兼容性问题

    背景:一个第三方组件是C++.NET  32位开发的,后被C#(基于FrameWork4.0)调用并封装成组件,此二次封装的组件无法运行于64位操作系统上.        开发环境:VS2012:解决 ...

  8. (Array,位操作)137. Single Number II

    Given an array of integers, every element appears three times except for one. Find that single one. ...

  9. delphi.位操作

    位操作网上有很多介绍,请上网google/baidu,比如: 位操作技巧实例大全: http://blog.csdn.net/g_spider/article/details/5750665 位操作基 ...

  10. iis6|iis7|配置URLRewriter|64位操作系统下|.net2.0|.net4.0|配置URLRewriter|Web.config配置详情

    想必很多ASP.NET的码友们在IIS配置伪静态被严重纠结过不止一次两次,本园主经过多次站点伪静态配置,总结了一下,IIS版本:IIS 6.IIS 7,服务器:Windows Server 2003. ...

随机推荐

  1. Java面向对象之Object类

    Object类 Object类是所有Java类的根父类 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类 Object类中的功能(属性.方法)就具有 ...

  2. wpa_supplicant 交叉编译

    交叉编译 wpa_supplicant 指定交叉编译环境: CC=arm-linux-xxxxx-gcc 运行错误 :  wlan0: Unsupported driver 'nl80211' 在.c ...

  3. k8s安装metrics-server

    Kubernetes Metrics Server: Kubernetes Metrics Server 是 Cluster 的核心监控数据的聚合器,kubeadm 默认是不部署的. Metrics ...

  4. 函数:3ds max 给选择对象设置轴心点

    ------轴心点函数大全------函数名称中的字母含义:------w:西 e:东 n:北 s:南 b:底 c:中心 t:顶 m:间 如:wnb表示西北下 smt表示南中上 fn pivot_wn ...

  5. PDF.JS 预览pdf文件,中文不显示问题

    pdf.js 下载路径 http://mozilla.github.io/pdf.js/ pdf.js的使用不多说,网上都有 讲一下,.NET CORE MVC的使用遇到的问题 1. 将下载文件解压放 ...

  6. winform 中 label透明化

    label1.BackColor = Color.Transparent;//设置背景颜色为透明 label1.Parent = pictureBox1;//将pictureBox1设为标签的父控件, ...

  7. 为 windows 10 右键菜单加打开DOS窗口

    创建一个批处理文件,输入以下行,保存执行即可. echo off reg add "HKCR\*\shell\ms-dos" /ve /d 打开DOS命令 /f reg add & ...

  8. 加密算法和hash

    随着安全问题越来越被重视,公司也全面替换了HTTP为HTTPS.2015年iOS9的ATS到今年苹果更是放出话来,2017年全面支持HTTPS,不支持的App,在审核的时候可能会遇到麻烦.鉴于此,我有 ...

  9. JavaScript基础知识整理(引用类型-Function)

    Function Function类型实际上是对象,每个函数都是Function类型的实例,自然也就具有属性和方法. 定义函数通常有三种方式 使用函数声明 function sum(num1,num2 ...

  10. JBOSS环境与应用部署