原文釋出於個人站點: GitDiG.com, 原文連結: Go 程式設計:那些隱晦的操作符
本篇作為 Go 程式設計“邊角料”的最後一篇,主要針對 Go 語言提供的操作符進行一次總結。剛好回應上篇一位讀者關於表示式是否要加'.'的問題做個回覆。
在 Go 語言中,一共提供了47個操作符,包括標點符號。摘自官方文件,分別是:
+ & += &= && == != ( )
- | -= |= || < <= [ ]
* ^ *= ^= <- > >= { }
/ << /= <<= ++ = := , ;
% >> %= >>= -- ! ... . :
&^ &^=
複製程式碼
除以上操作符以外,在 Go 語言中還有一個特殊的符號 _
, 以及一個非 Go 語言操作符的特殊位元組?
。
刨去一些常用的操作符,對其中較隱晦操作符做個簡單的備註,方便不時之需。 就隱晦本身而言可以劃分為兩類:
- 符號本身隱晦
- 應用場景隱晦
1. 符號隱晦
上文中的 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
。
2. 場景隱晦
另一類操作符,看似非常簡單,但因其在不同應用場景下產生了不同功能效果,導致在使用上的陌生。
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 字串合適的型別值。
3. 小結
以上收集的操作符僅僅是個簡單的小結,可能更多的應用場景沒有關注到,歡迎指正。