Go語言 Go中的一些語法/語義例外

2023-02-16 17:40 更新

本篇文章將列出Go中的各種語法和語義例外。 這些例外中的一些屬于方便編程的語法糖,一些屬于內(nèi)置泛型特權(quán),一些源于歷史原因或者其它各種邏輯原因。

嵌套函數(shù)調(diào)用

基本規(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())
}

選擇結(jié)構(gòu)體字段值

基本規(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
}

方法調(diào)用的屬主實(shí)參

基本規(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} // 整體覆蓋修改是可以的
}

函數(shù)參數(shù)

基本規(guī)則:

函數(shù)的每個(gè)參數(shù)一般為某個(gè)類型一個(gè)值。

例外:

內(nèi)置?make?和?new?函數(shù)的第一個(gè)參數(shù)為一個(gè)類型。

同一個(gè)代碼包中的函數(shù)命名

基本規(guī)則:

同一個(gè)代碼包中聲明的函數(shù)的名稱不能重復(fù)。

例外:

同一個(gè)代碼包中可以聲明若干個(gè)名為?init?類型為?func()?的函數(shù)。

函數(shù)調(diào)用

基本規(guī)則:

名稱為非空標(biāo)識(shí)符的函數(shù)可以被調(diào)用。

例外:

?init?函數(shù)不可被調(diào)用。

函數(shù)值

基本規(guī)則:

聲明的函數(shù)可以被用做函數(shù)值。

例外:

?init?函數(shù)不可被用做函數(shù)值。

例子:

package main

import "fmt"
import "unsafe"

func init() {}

func main() {
	// 這兩行編譯沒問題。
	var _ = main
	var _ = fmt.Println

	// 下面這行編譯不通過。
	var _ = init
}

泛型類型實(shí)參的傳遞方式

基本規(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)中。

舍棄函數(shù)調(dià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ù)組元素。

內(nèi)置類型的方法

基本規(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?只能被用于常量聲明中,它不能被賦給變量。

舍棄表達(dá)式中可選的結(jié)果值對(duì)程序行為的影響

基本規(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è)恐慌!
}

else關(guā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
	}
}


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)