Go语言:简单的理解操作符

Go语言:简单的理解操作符

1.什么是位运算

程序中的所有数在计算机内存中都是以二进制的形式储存的.位运算就是直接对整数在内存中的二进制位进行操作.比如,and运算本来是一个逻辑运算符,但整数与整数之间也可以进行and运算.举个例子,6的二进制是110,11的二进制是1011,那么6 and 11的结果就是2,它是二进制对应位进行逻辑运算的结果(0表示False,1表示True,空位都当0处理).

感觉位运算操作符虽然在平时用得并不多,但是在涉及到底层性能优化或者使用某些trick的时候还是比较有意思.

&(AND) |(OR) 就不提了最常用的东西 会编程就会.

&操作的话是当 两个数需要同时为1的时候才会保留. 例如 0000 0100 & 0000 1111 => 0000 0100 => 4

操作的话是当 两个数同时为1或者1个为1一个不为1的时候会保留. 例如 0000 0100 | 0000 1111 => 0000 1111 => 15

本篇作为 Go 编程“边角料”的最后一篇,主要针对 Go 语言提供的操作符进行一次总结.刚好回应上篇一位读者关于表达式是否要加’.’的问题做个回复.

Go语言的操作符

在 Go 语言中,一共提供了47个操作符,包括标点符号.摘自官方文档,分别是:

+    &     +=    &=     &&    ==    !=    (    )
-    |     -=    |=     ||    <     <=    [    ]
*    ^     *=    ^=     <-    >     >=    {    }
/    <<    /=    <<=    ++    =     :=    ,    ;
%    >>    %=    >>=    --    !     ...   .    :
     &^          &^=

除以上操作符以外,在 Go 语言中还有一个特殊的符号 _, 以及一个非 Go 语言操作符的特殊字节?.

刨去一些常用的操作符,对其中较隐晦操作符做个简单的备注,方便不时之需.

就隐晦本身而言可以划分为两类:

  • 符号本身隐晦
  • 应用场景隐晦

2.符号隐晦

上文中的 47 个操作符,一个个看下来,真正隐晦的符号基本上都是位运算操作符或相关操作符.

之所以隐晦,因为位运算在大部分开发人员的日常开发中属于非常规操作,因为运用得少,而增加了其陌生感.不妨简单罗列一下:

&    bitwise AND            integers     
|    bitwise OR             integers    
^    bitwise XOR            integers    
&^   bit clear (AND NOT)    integers  

<<   left shift             integer << unsigned integer
>>   right shift            integer >> unsigned integer

写个简单的例子, 强化记忆:

package main

import "fmt"

func main(){
  fmt.Printf("AND: a(%b) & b(%b) = (%b)\n", 4, 5, (4 & 5))
  fmt.Printf("OR:  a(%b) | b(%b) = (%b)\n", 4, 5, (4 | 5))
  fmt.Printf("XOR: a(%b) ^ b(%b) = (%b)\n", 4, 5, (4 ^ 5))
  fmt.Printf("AND NOT: a(%b) &^ b(%b) = (%b)\n", 4, 5, (4 &^ 5))

  fmt.Printf("Left Shift:  a(%b) << 1 = (%b)\n", 5, (5 << 1))
  fmt.Printf("Right Shift: a(%b) >> 1 = (%b)\n", 5, (5 >> 1))
}

输出的结果是:

AND: a(100) & b(101) = (100)
OR:  a(100) | b(101) = (101)
XOR: a(100) ^ b(101) = (1)
AND NOT: a(100) &^ b(101) = (0)
Left Shift:  a(101) << 1 = (1010)
Right Shift: a(101) >> 1 = (10)

位操作符并不难,之所以隐晦,主要是实际运用的少导致的.其中,XOR 运算有个特点:如果对一个值连续做两次 XOR,会返回这个值本身.XOR 的这个特点,使得它可以用于信息的加密.阮一峰这篇文章XOR 加密简介很好读.

与位运算符相关的符号,有:

<<=    >>=    &=    ^=   |=

其功能与+=是一样的,即 a += 1 等同于 a = a + 1.

3.场景隐晦

另一类操作符,看似非常简单,但因其在不同应用场景下产生了不同功能效果,导致在使用上的陌生.

2.1 符号 ‘_’

符号 ‘_’, 又称为空标识符(Blank identifier).它有两种使用场景,不同场景提供的功能是不同的.

  • 作为匿名变量赋值使用 此时符号 ‘_’, 功能与 /dev/null 类似,只负责接收值并直接丢弃,无法取回.
ar := [10]int{1,2,3,4,5,6,7,8,9,0}
for _, v := range ar {
    println(v)
}
  • 在包引用时使用 常规情况下,包引用格式是这样的:
package YourPackage

import   "lib/math"         //math.Sin
import m "lib/math"         //m.Sin
import . "lib/math"         //Sin

具体语法意义不解释了.现在看看 ‘_’ 在包引入中的功能.

import _ "the/third/pkg"

此时引入的第三方包"the/third/pkg",如果引入的结果是一个空标识符’_‘.按其空标识符的原始意义,就是对于使用方而言,没有任何意义,因为无法使用被引入包中任何变量或是函数.

但是,这种引用有一个副作用,就是:会对第三方包进行编译并且执行初始化func init()操作.这一功能,对于某些引用方就非常有用.

所以当我们研究一些开源代码时,看到类似的引用import _ "the/third/pkg"时,直接跳到引入包的init函数,就可以建立起内在逻辑.不妨看一下github.com/golang/protobuf/protoc-gen-go/link_grpc.go的代码, 这就是grpc插件注册到protoc-gen-go的地方.

package main

import _ "github.com/golang/protobuf/protoc-gen-go/grpc"

2.2 符号 ‘.’

符号 ‘.’ 常规情况下是作为选择器的在使用.如:

//直接选择属性名或函数名
x.FieldName
x.FunctionName

还可以做为包引用使用,如上节.

import . "lib/math"         //Sin

它的作用有点类似当前目录符’.’的意思了,简化掉了包引用的相对路径.

还有一个用法,即类型断言(type assertion).

//类型断言: 类型必须用'()'括起来
v, ok := x.(T) 

作为类型断言时,类型必须用’()’括起来,防止和选择器功能混淆.类型断言类型转换需要区分一下.

//类型转换: 变量必须用'()'括起来
v := T(x)

区别:

  • 类型转换中,待转换的变量x只要是一个可以转换成目标类型的变量即可.失败时代码无法编译通过.
  • 类型断言中,待断言的变量x必须与目标类型一致.如果失败,返回bool参数标识.

    2.3 符号 ‘…’

符号 ‘…’ 主要用于不定参数与切片打散功能.非常简单,备注一下.

不定参数

import "fmt"

func Foo(args ...interface{}) {
  for _, arg := range args {
    fmt.Println(arg)
  }
}

切片打散

args := []interface{}{1, false, "hello"}
Foo(args...)

数组长度

[...]int{1,2,4}

2.4 符号 ‘?’, 非 Go 语言操作符

很多语言都支持符号 ‘?’, 但是在 Go 语言中并它不属于系统操作符, 虽然在 Go 代码中经常会碰到符号 ‘?’.在语言级别符号 ‘?’ 没有任何语法意义,只是一个常规的字节.

常见使用场景是做为 SQL 语句的替换符使用.如:

  import "database/sql"

    id := 47
    result, err := db.ExecContext(ctx, "UPDATE balances SET balance = balance + 10 WHERE user_id = ?", id)
    if err != nil {
        log.Fatal(err)
    }

其中的符号 ‘?’ 仅仅与依赖包database/sql有关,与 Go 语言本身无关.在database/sql包中,字符 ‘?’ 可以将任意类型参数变量替换转义成 SQL 字符串合适的类型值.

4.更好的心智模型

人类善于用抽象符号表达想法,01序列就是其中的一种抽象符号,不过是设计给机器用的.既然谈到位运算符,免不了要与01序列打交道,于是我们开始祭出人类一大法宝:映射.01序列每一位,要么是0,要么是1,可辨识性太低,人类看久了都会打瞌睡.那么我们在这里尝试做一些转换,把01序列变成序号集合,这个序号集合从0开始,顺序从右到左.例如 00100011 转换为序号集合就是 {5, 1, 0} .

关于集合的操作您是不是想起一些东西来,没错,集合可以合并,可以求交集,可以求差集等等操作.想到集合操作,我很容易想到两个平面圆在脑海中交织,互相争夺领地,而每次集合操作的结果可以被看做是被划分出来的某些区域,这里应该很有画面感.

继续回到原先的问题上,转变模型,重新解释如下:

A & B  -> 求交集.将两个集合共有的元素提取出来.
A | B  -> 求并集.将两个集合合并.
A ^ B  -> 求并集的补集.排除两个集合共有元素,合并两个集合剩下的元素.
^A     -> 求补集.A全集排除A集合之外的元素.这里存在求全集问题,A集合的全集就是所有序号的集合.
A &^ B -> 求差集.A集合排除B集合元素.
A << N -> A集合中的每一个序号元素加N.
A >> N -> A集合中的每一个序号元素减N.

是不是立刻感觉到世界变得友好了,不再充满恶意了,生活本该如此!

简单总结就是一句话,01序列可以当做序号集合对待.这算是一个小小的奇淫技巧吧!

目录