本篇文章将列出Go中的各种语法和语义例外。 这些例外中的一些属于方便编程的语法糖,一些属于内置泛型特权,一些源于历史原因或者其它各种逻辑原因。
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())
}
package main
type T struct {
x int
}
func main() {
var t T
var p = &t
p.x *= 2
// 上一行是下一行的语法糖。
(*p).x *= 2
}
*T
显式声明的方法肯定不是类型T
的方法。
*T
显式声明的方法肯定不是类型T
的方法,但是可寻址的T
值可以用做这些方法的调用的属主实参。
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() // 现在:t.x == 6
// 上一行是下一行的语法糖。
(&t).Double() // 现在:t.x == 12
}
x
的类型为一个一级具名指针类型,并且(*x).f
是一个合法的选择器,则x.f
也是合法的。
任何多级指针均不能出现在选择器语法形式中。
上述语法糖的例外:f
为字段的情况才有效,对于f
为方法的情况无效。
package main
type T struct {
x int
}
func (T) y() {
}
type P *T
type PP **T // 一个多级指针类型
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 // 合法(因为x为一个字段)
_ = (*p).y // 合法
// _ = p.y // 不合法(因为y为一个方法)
// 下面的选择器均不合法。
/*
_ = ppt.x
_ = ppt.y
_ = pp.x
_ = pp.y
*/
}
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: 函数调用是不可寻址的
_ = &f()[2] // okay
}
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} // 整体覆盖修改是可以的
}
make
和new
函数的第一个参数为一个类型。
init
类型为func()
的函数。
init
函数不可被调用。
init
函数不可被用做函数值。
package main
import "fmt"
import "unsafe"
func init() {}
func main() {
// 这两行编译没问题。
var _ = main
var _ = fmt.Println
// 下面这行编译不通过。
var _ = init
}
new
泛型函数的类型实参是包裹在一对圆括号中。
内置make
泛型函数的类型实参是和值实参混杂在一起并包裹在同一对圆括号中。
builtin
和unsafe
标准库包中的函数)调用的返回值不能被舍弃。
copy
和recover
的调用的返回值可以被舍弃。
copy
和append
的一个调用的第一个形参为一个字节切片(这时,一般来说,第二形参也应该是一个字节切片),则第二个形参可以是一个字符串,即使字符串不能被赋给一个字节切片。
(假设append
函数调用的第二个形参使用arg...
形式传递。)
package main
func main() {
var bs = []byte{1, 2, 3}
var s = "xyz"
copy(bs, s)
// 上一行是下一行的语法糖和优化。
copy(bs, []byte(s))
bs = append(bs, s...)
// 上一行是下一行的语法糖和优化。
bs = append(bs, []byte(s)...)
}
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: 函数值不可比较。
_ = f1 == nil // ok
_ = f2 == nil // ok
}
==
和!=
比较符来做比较。
T
的值可以用组合字面量表示,则T{}
表示此类型的零值。
T
,T{}
不是它的零值,它的零值使用预声明的nil
表示。
package main
import "fmt"
func main() {
// new(T)返回类型T的一个零值的地址。
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
}
range
关键字后,for-range
循环遍历出来的是容器值的各个元素。
每个容器元素对应的键值(或者索引)也将被一并遍历出来。
range
关键字后跟的是字符串时,遍历出来的是码点值,而不是字符串的各个元素byte字节值。
range
关键字后跟的是通道时,通道的元素的键值(次序)并未被一同遍历出来。
range
关键字后可以跟数组指针来遍历数组元素。
error
有一个Error() string
方法。
nil
值既没有确定的类型也没有默认类型。
iota
是一个绑定了0
的常量,但是它的值并不固定。
在一个包含多个常量描述的常量声明中,如果一个iota
的值出现在一个常量描述中,则它的值将被自动调整为此常量描述在此常量声明中的次序值,尽管此调整发生在编译时刻。
iota
只能被用于常量声明中,它不能被赋给变量。
package main
func main() {
var ok bool
var m = map[int]int{}
_, ok = m[123]
_ = m[123] // 不会产生恐慌
var c = make(chan int, 2)
c <- 123
close(c)
_, ok = <-c
_ = <-c // 不会产生恐慌
var v interface{} = "abc"
_, ok = v.(int)
_ = v.(int) // 将产生一个恐慌!
}
else
关键字后跟随另一个代码块else
关键字后必须跟随一个显式代码块{...}
。
else
关键字后可以跟随一个(隐式)if
代码块,
foo
编译没问题,但是函数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
}
}
Go101.org网站内容包括Go编程各种相关知识(比如Go基础、Go优化、Go细节、Go实战、Go测验、Go工具等)。后续将不断有新的内容加入。敬请收藏关注期待。
本丛书微信公众号(联系方式一)名称为"Go 101"。二维码在网站首页。此公众号将时不时地发表一些Go语言相关的原创短文。各位如果感兴趣,可以搜索关注一下。
《Go语言101》系列丛书项目目前托管在Github上(联系方式二)。欢迎各位在此项目中通过提交bug和PR的方式来改进完善《Go语言101》丛书中的各篇文章。我们可以在项目目录下运行go run .
来浏览和确认各种改动。
本书的twitter帐号为@Golang_101(联系方式三)。玩推的Go友可以适当关注。
你或许对本书作者老貘开发的一些App感兴趣。
sync
标准库包sync/atomic
标准库包