本篇文章將列出Go中的各種語法和語義例外。 這些例外中的一些屬于方便編程的語法糖,一些屬于內(nèi)置泛型特權(quán),一些源于歷史原因或者其它各種邏輯原因。
基本規(guī)則:
如果一個(gè)函數(shù)(包括方法)調(diào)用的返回值個(gè)數(shù)不為零,并且它的返回值列表可以用做另一個(gè)函數(shù)調(diào)用的實(shí)參列表,則此前者調(diào)用可以被內(nèi)嵌在后者調(diào)用之中,但是此前者調(diào)用不可和其它的實(shí)參混合出現(xiàn)在后者調(diào)用的實(shí)參列表中。
語法糖:
如果一個(gè)函數(shù)調(diào)用剛好返回一個(gè)結(jié)果,則此函數(shù)調(diào)用總是可以被當(dāng)作一個(gè)單值實(shí)參用在其它函數(shù)調(diào)用中,并且此函數(shù)調(diào)用可以和其它實(shí)參混合出現(xiàn)在其它函數(shù)調(diào)用的實(shí)參列表中。
例子:
package main
import (
"fmt"
)
func f0() float64 {return 1}
func f1() (float64, float64) {return 1, 2}
func f2(float64, float64) {}
func f3(float64, float64, float64) {}
func f4()(x, y []int) {return}
func f5()(x map[int]int, y int) {return}
type I interface {m()(float64, float64)}
type T struct{}
func (T) m()(float64, float64) {return 1, 2}
func main() {
// 這些行編譯沒問題。
f2(f0(), 123)
f2(f1())
fmt.Println(f1())
_ = complex(f1())
_ = complex(T{}.m())
f2(I(T{}).m())
// 這些行編譯不通過。
/*
f3(123, f1())
f3(f1(), 123)
*/
// 此行從Go官方工具鏈1.15開始才能夠編譯通過。
println(f1())
// 下面這三行從Go官方工具鏈1.13開始才能夠編譯通過。
copy(f4())
delete(f5())
_ = complex(I(T{}).m())
}
基本規(guī)則:
指針類型和值沒有字段。
語法糖:
我們可以通過一個(gè)結(jié)構(gòu)體值的指針來選擇此結(jié)構(gòu)體的字段。
例子:
package main
type T struct {
x int
}
func main() {
var t T
var p = &t
p.x *= 2
// 上一行是下一行的語法糖。
(*p).x *= 2
}
基本規(guī)則:
為類型?
*T
?顯式聲明的方法肯定不是類型?T
?的方法。
語法糖:
盡管為類型?
*T
?顯式聲明的方法肯定不是類型?T
?的方法,但是可尋址的?T
?值可以用做這些方法的調(diào)用的屬主實(shí)參。
例子:
package main
type T struct {
x int
}
func (pt *T) Double() {
pt.x *= 2
}
func main() {
// T{3}.Double() // error: T值沒有Double方法。
var t = T{3}
t.Double() // 現(xiàn)在:t.x == 6
// 上一行是下一行的語法糖。
(&t).Double() // 現(xiàn)在:t.x == 12
}
基本規(guī)則:
組合字面量是不可尋址的,并且是不可尋址的值是不能被取地址的。
語法糖:
盡管組合字面量是不可尋址的,它們?nèi)钥梢员伙@式地取地址。
請(qǐng)閱讀結(jié)構(gòu)體和內(nèi)置容器類型兩篇文章獲取詳情。
基本規(guī)則:
一般說來,具名的指針類型的值不能使用在選擇器語法形式中。
語法糖:
如果值?
x
?的類型為一個(gè)一級(jí)具名指針類型,并且?(*x).f
?是一個(gè)合法的選擇器,則?x.f
?也是合法的。
任何多級(jí)指針均不能出現(xiàn)在選擇器語法形式中。
上述語法糖的例外:
上述語法糖只對(duì)?
f
?為字段的情況才有效,對(duì)于?f
?為方法的情況無效。
例子:
package main
type T struct {
x int
}
func (T) y() {
}
type P *T
type PP **T // 一個(gè)多級(jí)指針類型
func main() {
var t T
var p P = &t
var pt = &t // pt的類型為*T
var ppt = &pt // ppt的類型為**T
var pp PP = ppt
_ = pp
_ = (*p).x // 合法
_ = p.x // 合法(因?yàn)閤為一個(gè)字段)
_ = (*p).y // 合法
// _ = p.y // 不合法(因?yàn)閥為一個(gè)方法)
// 下面的選擇器均不合法。
/*
_ = ppt.x
_ = ppt.y
_ = pp.x
_ = pp.y
*/
}
基本規(guī)則:
如果一個(gè)容器值是可尋址的,則它的元素也是可尋址的。
例外:
一個(gè)映射值的元素總是不可尋址的,即使此映射本身是可尋址的。
語法糖:
一個(gè)切片值的元素總是可尋址的,即使此切片值本身是不可尋址的。
例子:
package main
func main() {
var m = map[string]int{"abc": 123}
_ = &m // okay
// 例外。
// p = &m["abc"] // error: 映射元素不可尋址
// 語法糖。
f := func() []int {
return []int{0, 1, 2}
}
// _ = &f() // error: 函數(shù)調(diào)用是不可尋址的
_ = &f()[2] // okay
}
基本規(guī)則:
不可尋址的值不可修改。
例外:
盡管映射元素是不可尋址的,但是它們可以被修改(但是它們必須被整個(gè)覆蓋修改)。
例子:
package main
func main() {
type T struct {
x int
}
var mt = map[string]T{"abc": {123}}
// _ = &mt["abc"] // 映射元素是不可尋址的
// mt["abc"].x = 456 // 部分修改是不允許的
mt["abc"] = T{x: 789} // 整體覆蓋修改是可以的
}
基本規(guī)則:
函數(shù)的每個(gè)參數(shù)一般為某個(gè)類型一個(gè)值。
例外:
內(nèi)置?
make
?和?new
?函數(shù)的第一個(gè)參數(shù)為一個(gè)類型。
基本規(guī)則:
同一個(gè)代碼包中聲明的函數(shù)的名稱不能重復(fù)。
例外:
同一個(gè)代碼包中可以聲明若干個(gè)名為?
init
?類型為?func()
?的函數(shù)。
基本規(guī)則:
名稱為非空標(biāo)識(shí)符的函數(shù)可以被調(diào)用。
例外:
?
init
?函數(shù)不可被調(diào)用。
基本規(guī)則:
聲明的函數(shù)可以被用做函數(shù)值。
例外:
?
init
?函數(shù)不可被用做函數(shù)值。
例子:
package main
import "fmt"
import "unsafe"
func init() {}
func main() {
// 這兩行編譯沒問題。
var _ = main
var _ = fmt.Println
// 下面這行編譯不通過。
var _ = init
}
基本規(guī)則:
在泛型類型實(shí)參列表中,所有實(shí)參均包裹在同一對(duì)方括號(hào)中,各個(gè)實(shí)參之間使用逗號(hào)分開
例外:
內(nèi)置泛型類型的類型實(shí)參傳遞形態(tài)各異。映射類型的鍵值類型實(shí)參單獨(dú)包裹在一對(duì)方括號(hào)中,其它實(shí)參并沒有被包裹。 內(nèi)置?
new
?泛型函數(shù)的類型實(shí)參是包裹在一對(duì)圓括號(hào)中。 內(nèi)置?make
?泛型函數(shù)的類型實(shí)參是和值實(shí)參混雜在一起并包裹在同一對(duì)圓括號(hào)中。
基本規(guī)則:
一個(gè)函數(shù)調(diào)用的所有返回值可以被一并忽略舍棄。
例外:
內(nèi)置函數(shù)(展示在?
builtin
?和?unsafe
?標(biāo)準(zhǔn)庫包中的函數(shù))調(diào)用的返回值不能被舍棄。
例外中的例外:
內(nèi)置函數(shù)?
copy
?和?recover
?的調(diào)用的返回值可以被舍棄。
基本規(guī)則:
聲明的變量總是可尋址的。
例外:
預(yù)聲明的nil變量是不可尋址的。
所以,預(yù)聲明的nil
是一個(gè)不可更改的變量。
基本規(guī)則:
當(dāng)一個(gè)實(shí)參被傳遞給對(duì)應(yīng)的形參時(shí),此實(shí)參必須能夠賦值給此形參類型。
語法糖:
如果內(nèi)置函數(shù)?
copy
?和?append
?的一個(gè)調(diào)用的第一個(gè)形參為一個(gè)字節(jié)切片(這時(shí),一般來說,第二形參也應(yīng)該是一個(gè)字節(jié)切片),則第二個(gè)形參可以是一個(gè)字符串,即使字符串不能被賦給一個(gè)字節(jié)切片。 (假設(shè)?append
?函數(shù)調(diào)用的第二個(gè)形參使用?arg...
?形式傳遞。)
例子:
package main
func main() {
var bs = []byte{1, 2, 3}
var s = "xyz"
copy(bs, s)
// 上一行是下一行的語法糖和優(yōu)化。
copy(bs, []byte(s))
bs = append(bs, s...)
// 上一行是下一行的語法糖和優(yōu)化。
bs = append(bs, []byte(s)...)
}
基本規(guī)則:
映射、切片和函數(shù)類型是不支持比較的。
例外:
映射、切片和函數(shù)值可以和預(yù)聲明標(biāo)識(shí)符?
nil
?比較。
例子:
package main
func main() {
var s1 = []int{1, 2, 3}
var s2 = []int{7, 8, 9}
//_ = s1 == s2 // error: 切片值不可比較。
_ = s1 == nil // ok
_ = s2 == nil // ok
var m1 = map[string]int{}
var m2 = m1
// _ = m1 == m2 // error: 映射值不可比較。
_ = m1 == nil // ok
_ = m2 == nil // ok
var f1 = func(){}
var f2 = f1
// _ = f1 == f2 // error: 函數(shù)值不可比較。
_ = f1 == nil // ok
_ = f2 == nil // ok
}
基本規(guī)則:
如果一個(gè)值可以隱式轉(zhuǎn)換為一個(gè)可比較類型,則這此值和此可比較類型的值可以用?
==
?和?!=
?比較符來做比較。
例外:
一個(gè)不可比較類型(一定是一個(gè)非接口類型)的值不能和一個(gè)接口類型的值比較,即使此不可比較類型實(shí)現(xiàn)了此接口類型(從而此不可比較類型的值可以被隱式轉(zhuǎn)換為此接口類型)。
請(qǐng)閱讀值比較規(guī)則獲取詳情。
基本規(guī)則:
如果一個(gè)類型?
T
?的值可以用組合字面量表示,則?T{}
?表示此類型的零值。
例外:
對(duì)于一個(gè)映射或者切片類型?
T
?,?T{}
?不是它的零值,它的零值使用預(yù)聲明的?nil
?表示。
例子:
package main
import "fmt"
func main() {
// new(T)返回類型T的一個(gè)零值的地址。
type T0 struct {
x int
}
fmt.Println( T0{} == *new(T0) ) // true
type T1 [5]int
fmt.Println( T1{} == *new(T1) ) // true
type T2 []int
fmt.Println( T2{} == nil ) // false
type T3 map[int]int
fmt.Println( T3{} == nil ) // false
}
基本規(guī)則:
只有容器值可以跟在?
range
?關(guān)鍵字后,?for-range
?循環(huán)遍歷出來的是容器值的各個(gè)元素。 每個(gè)容器元素對(duì)應(yīng)的鍵值(或者索引)也將被一并遍歷出來。
例外1:
當(dāng)?
range
?關(guān)鍵字后跟的是字符串時(shí),遍歷出來的是碼點(diǎn)值,而不是字符串的各個(gè)元素byte字節(jié)值。
例外2:
當(dāng)?
range
?關(guān)鍵字后跟的是通道時(shí),通道的元素的鍵值(次序)并未被一同遍歷出來。
語法糖:
盡管數(shù)組指針不屬于容器,但是?
range
?關(guān)鍵字后可以跟數(shù)組指針來遍歷數(shù)組元素。
基本規(guī)則:
內(nèi)置類型都沒有方法。
例外:
內(nèi)置類型?
error
?有一個(gè)?Error() string
?方法。
基本規(guī)則:
每個(gè)值要么有一個(gè)確定的類型要么有一個(gè)默認(rèn)類型。
例外:
類型不確定的?
nil
?值既沒有確定的類型也沒有默認(rèn)類型。
基本規(guī)則:
常量值的值固定不變。常量值可以被賦給變量值。
例外1:
預(yù)聲明的?
iota
?是一個(gè)綁定了?0
?的常量,但是它的值并不固定。 在一個(gè)包含多個(gè)常量描述的常量聲明中,如果一個(gè)?iota
?的值出現(xiàn)在一個(gè)常量描述中,則它的值將被自動(dòng)調(diào)整為此常量描述在此常量聲明中的次序值,盡管此調(diào)整發(fā)生在編譯時(shí)刻。
例外2:
?
iota
?只能被用于常量聲明中,它不能被賦給變量。
基本規(guī)則:
表達(dá)式中可選的結(jié)果值是否被舍棄不會(huì)對(duì)程序行為產(chǎn)生影響。
例外:
當(dāng)一個(gè)失敗的類型斷言表達(dá)式的可選的第二個(gè)結(jié)果值被舍棄時(shí),當(dāng)前協(xié)程將產(chǎn)生一個(gè)恐慌。
例子:
package main
func main() {
var ok bool
var m = map[int]int{}
_, ok = m[123]
_ = m[123] // 不會(huì)產(chǎn)生恐慌
var c = make(chan int, 2)
c <- 123
close(c)
_, ok = <-c
_ = <-c // 不會(huì)產(chǎn)生恐慌
var v interface{} = "abc"
_, ok = v.(int)
_ = v.(int) // 將產(chǎn)生一個(gè)恐慌!
}
基本規(guī)則:
?
else
?關(guān)鍵字后必須跟隨一個(gè)顯式代碼塊?{...}
?。
語法糖:
?
else
?關(guān)鍵字后可以跟隨一個(gè)(隱式)?if
?代碼塊,
比如,在下面這個(gè)例子中,函數(shù)foo
編譯沒問題,但是函數(shù)bar
編譯不過。
func f() []int {
return nil
}
func foo() {
if vs := f(); len(vs) == 0 {
} else if len(vs) == 1 {
}
if vs := f(); len(vs) == 0 {
} else {
switch {
case len(vs) == 1:
default:
}
}
if vs := f(); len(vs) == 0 {
} else {
for _, _ = range vs {
}
}
}
func bar() {
if vs := f(); len(vs) == 0 {
} else switch { // error
case len(vs) == 1:
default:
}
if vs := f(); len(vs) == 0 {
} else for _, _ = range vs { // error
}
}
更多建議: