本文译自 Go Details 101 版权@归原文所有.
代码包
##这些md还有一个问题,就是 markdown 的标记,如 ## ,### 一个包可以在源文件中多次导入
Go 源文件可以多次导入相同的包, 但导入名称必须不同. 这些相同的包导入引用相同的包实例.
例如:
package main
import "fmt"
import "io"
import inout "io"
func main() {
fmt.Println(&inout.EOF == &io.EOF) // true
}
package mypkg 后的注释 // import “x.y.z.mypkg” 对于 Go 编译器是有意义的
例如, 当导入此包的源文件由标准 Go 编译器编译时, 以下包的导入路径必须为 “x.y.z.mypkg”.
package mypkg // import "x.y.z.mypkg"
...
流程控制
swith 和 select 中的 default 分支可以放在所有 case 分支之前, 之后, 或者之间.
与许多其他语言相比, 一个明显的区别在于 switch-case 控制流程块中默认分支的顺序可以是任意的. 例如, 以下三个 switch-case 控制流程块彼此等效.
switch n := rand.Intn(3); n {
case 0: fmt.Println("n == 0")
case 1: fmt.Println("n == 1")
default: fmt.Println("n == 2")
}
switch n := rand.Intn(3); n {
default: fmt.Println("n == 2")
case 0: fmt.Println("n == 0")
case 1: fmt.Println("n == 1")
}
switch n := rand.Intn(3); n {
case 0: fmt.Println("n == 0")
default: fmt.Println("n == 2")
case 1: fmt.Println("n == 1")
}
defer 匿名函数可以修改嵌套函数的命名返回结果
例如:
package main
import "fmt"
func Triple(n int) (r int) {
defer func() {
r += n // modify the return value
}()
return n + n // <=> r = n + n; return
}
func main() {
fmt.Println(Triple(5)) // 15
}
对于切片 s, 循环 for i = range s {…} 不等于 for i = 0; i < len(s); i++
对于两个循环, 迭代变量 i 的相应最终值可能不同.
package main
import "fmt"
var i int
func fa(s []int, n int) int {
i = n
for i = 0; i < len(s); i++ {}
return i
}
func fb(s []int, n int) int {
i = n
for i = range s {}
return i
}
func main() {
s := []int{2, 3, 5, 7, 11, 13}
fmt.Println(fa(s, -1), fb(s, -1)) // 6 5
s = nil
fmt.Println(fa(s, -1), fb(s, -1)) // 0 -1
}
使用 os.Exit 函数退出程序并使用 runtime.Goexit 函数退出 goroutine
我们可以通过调用 os.Exit 函数从任何函数中退出程序. os.Exit 函数调用取一个 int 码作为参数并且将该码返回给操作系统.
例如:
// exit-example.go
package main
import "os"
import "time"
func main() {
go func() {
time.Sleep(time.Second)
os.Exit(1)
}()
select{}
}
运行:
$ go run a.go
exit status 1
$ echo $?
1
我们可以通过调用 runtime.Goexit 函数来退出一个 goroutine. runtime.Goexit 函数没有参数.
一个例子:
package main
import "fmt"
import "runtime"
func main() {
c := make(chan int)
go func() {
defer func() {c <- 1}()
defer fmt.Println("Go")
func() {
defer fmt.Println("C")
runtime.Goexit()
}()
fmt.Println("Java")
}()
<-c
}
指针
对于数值指针 p, 表达式 *p++ 相当于 (*p)++. 如果 p 不是数值指针, 则 *p++ 编译不过
在 Go 中, 指针不能进行算术运算. 对于指针值 p, 语句 p++ 在 Go 中是非法的. 如果 p 是一个指向数值的指针, 那么 Go 编译器会将 *v++ 视为 (*v)++.
例如:
package main
import "fmt"
func main() {
a := int64(5)
p := &a
// The following two lines don't compile.
/*
p++
p = (&a) + 8
*/
*p++
fmt.Println(*p, a) // 6 6
fmt.Println(p == &a) // true
}
如果两种类型的基本类型相同, 则两个命名指针类型的值可以相互转换. 两种指针类型的底层类型可能不同.
给定非接口值 x 和非接口类型 T, 假定 x 的类型为 Tx,
- 如果 Tx 和 T 共享相同的底层类型(忽略结构体 tags), 那么 x 可以显示地转换为 T, 尤其当 Tx 或者 T 不是一个定义类型并且它们的底层类型相同(考虑结构体 tags), 那么 x 可以隐式地转换为 T.
- 如果 Tx 和 T 具有不同的底层类型, 但 Tx 和 T 都是非定义的指针类型, 并且它们的基类型共享相同的底层类型(忽略结构体 tags), 则 x 可以(并且必须)可以显示地转换为 T.
一般性例子:
package main
func main() {
// []int, IntSlice and MySlice share
// the same underlying type: []int
type IntSlice []int
type MySlice []int
var s = []int{}
var is = IntSlice{}
var ms = MySlice{}
var x struct{n int `foo`}
var y struct{n int `bar`}
// The two lines both fail to compile.
/*
is = ms
ms = is
*/
// Must use explicit conversions here.
is = IntSlice(ms)
ms = MySlice(is)
x = struct{n int `foo`}(y)
y = struct{n int `bar`}(x)
// Implicit conversions are okay here.
s = is
is = s
s = ms
ms = s
}
指针的例子:
package main
func main() {
type MyInt int
type IntPtr *int
type MyIntPtr *MyInt
var pi = new(int) // type is *int
var ip IntPtr = pi // ok, same underlying type
// var _ *MyInt = pi // can't convert implicitly
var _ = (*MyInt)(pi) // ok, must explicitly
// var _ MyIntPtr = pi // can't convert implicitly
// var _ = MyIntPtr(pi) // can't convert explicitly
var _ MyIntPtr = (*MyInt)(pi) // ok
var _ = MyIntPtr((*MyInt)(pi)) // ok
// var _ MyIntPtr = ip // can't convert implicitly
// var _ = MyIntPtr(ip) // can't convert explicitly
var _ MyIntPtr = (*MyInt)((*int)(ip)) // ok
var _ = MyIntPtr((*MyInt)((*int)(ip))) // ok
}
不同零大小的值的地址可能相同也可能不同.
两个零大小值的地址是否相等取决于编译器和编译器版本.
package main
import "fmt"
func main() {
a := struct{}{}
b := struct{}{}
x := struct{}{}
y := struct{}{}
m := [10]struct{}{}
n := [10]struct{}{}
o := [10]struct{}{}
p := [10]struct{}{}
fmt.Println(&x, &y, &o, &p)
// For the standard Go compiler (1.10),
// x, y, o and p escape to heap,
// but a, b, m and n are allocated on stack.
fmt.Println(&a == &b) // false
fmt.Println(&x == &y) // true
fmt.Println(&a == &x) // false
fmt.Println(&m == &n) // false
fmt.Println(&o == &p) // true
fmt.Println(&n == &p) // false
}
上面代码中的输出用于标准 Go 编译器 1.10.
指针类型的基类型可能是指针类型本身
一个例子:
package main
func main() {
type P *P
var p P
p = &p
p = **************p
}
同样,
- 切片类型的元素类型可以是切片类型本身,
- 显示类型的元素类型可以是映射类型本身,
- 管道类型的元素类型可以是管道类型本身,
- 函数类型的参数和结果类型可以是函数类型本身.
package main
func main() {
type S []S
type M map[string]M
type C chan C
type F func(F) F
s := S{0:nil}
s[0] = s
m := M{"Go": nil}
m["Go"] = m
c := make(C, 3)
c <- c; c <- c; c <- c
var f F
f = func(F)F {return f}
_ = s[0][0][0][0][0][0][0][0]
_ = m["Go"]["Go"]["Go"]["Go"]
<-<-<-c
f(f(f(f(f))))
}
关于选择器的细节
对于一个指针值, 不管其类型是否定义, 如果它的(指针)类型的基类型是一个结构体类型, 那么指针值可以访问它引用的结构体值的字段. 但是, 如果指针值的类型是定义的类型, 则该值无法访问其引用的值的方法.
package main
type T struct {
x int
}
func (T) m(){} // T has one method.
type P *T // a defined one-level pointer type.
type PP *P // a defined two-level pointer type.
func main() {
var t T
var tp = &t
var tpp = &tp
var p P = tp
var pp PP = &p
tp.x = 12 // okay
p.x = 34 // okay
pp.x = 56 // error: type PP has no field or method x
tpp.x = 78 // error: type **T has no field or method x)
tp.m() // okay. Type *T also has a "m" method.
p.m() // error: type P has no field or method m
pp.m() // error: type PP has no field or method m
tpp.m() // error: type **T has no field or method m
}
容器
嵌套复合字面量可以简化
如果复合字面量嵌套了许多其他复合字面量, 那么这些嵌套复合字面量可以简化为 {…} 形式.
例如, 切片值字面量:
// A slcie value of a type whose element type is *[4]byte.
// The element type is a pointer type whose base type is [4]byte.
// The pointer base type is an array type whose element type is byte.
var heads = []*[4]byte{
&[4]byte{'P', 'N', 'G', ' '},
&[4]byte{'G', 'I', 'F', ' '},
&[4]byte{'J', 'P', 'E', 'G'},
}
可以简化为:
var heads = []*[4]byte{
{'P', 'N', 'G', ' '},
{'G', 'I', 'F', ' '},
{'J', 'P', 'E', 'G'},
}
如下例子中的数组字面量:
type language struct {
name string
year int
}
var _ = [...]language{
language{"C", 1972},
language{"Python", 1991},
language{"Go", 2009},
}
可以简化为:
var _ = [...]language{
{"C", 1972},
{"Python", 1991},
{"Go", 2009},
}
如下是映射值字面量的一个例子:
type LangCategory struct {
dynamic bool
strong bool
}
// A value of map type whose key type is a struct type and
// whose element type is another map type "map[string]int".
var _ = map[LangCategory]map[string]int{
LangCategory{true, true}: map[string]int{
"Python": 1991,
"Erlang": 1986,
},
LangCategory{true, false}: map[string]int{
"JavaScript": 1995,
},
LangCategory{false, true}: map[string]int{
"Go": 2009,
"Rust": 2010,
},
LangCategory{false, false}: map[string]int{
"C": 1972,
},
}
可以简化为:
var _ = map[LangCategory]map[string]int{
{true, true}: {
"Python": 1991,
"Erlang": 1986,
},
{true, false}: {
"JavaScript": 1995,
},
{false, true}: {
"Go": 2009,
"Rust": 2010,
},
{false, false}: {
"C": 1972,
},
}
请注意, 在上面的几个例子中, 每个复合字面量中最后一项之后的逗号不能省略.
在某些情况下, 可以使用数组指针作为数组
在许多情况下, 我们可以使用指向数组的指针作为数组.
我们可以通过指向数组的指针来迭代数组的元素. 对于长度较大的数组, 这种方法效率更高, 因为复制指针比复制大数组效率更高. 在下面的例子中, 两个循环块是等价的, 并且都是高效的.
package main
import "fmt"
func main() {
var a [100]int
for i, n := range &a { // copying a pointer is cheap
fmt.Println(i, n)
}
for i, n := range a[:] { // copying a slice is cheap
fmt.Println(i, n)
}
}
如果第二个迭代参数既不被忽略(用 _ )也不被省略(不写), 那么覆盖零数组指针的范围将会 panic. 在以下示例中, 前两个循环块中的每一个都会打印五个索引, 但最后一个会产生 panic.
package main
import "fmt"
func main() {
var p *[5]int // nil
for i, _ := range p { // okay
fmt.Println(i)
}
for i := range p { // okay
fmt.Println(i)
}
for i, n := range p { // panic
fmt.Println(i, n)
}
}
数组指针也可以用来索引数组元素. 通过 nil 数组指针索引数组元素会产生 panic.
package main
import "fmt"
func main() {
a := [5]int{2, 3, 5, 7, 11}
p := &a
p[0], p[1] = 17, 19
fmt.Println(a) // [17 19 5 7 11]
p = nil
p[0] = 31 // panic
}
我们也可以从数组指针派生切片. 从 nil 数组指针派生切片会产生 panic.
package main
import "fmt"
func main() {
pa := &[5]int{2, 3, 5, 7, 11}
s := pa[1:3]
fmt.Println(s) // [3 5]
pa = nil
s = pa[1:3] // panic
}
我们也可以传递数组指针作为内置的 len 和 cap 函数的参数. 传入 nil 数组指针参数给这两个函数不会产生 panic.
var pa *[5]int // nil
fmt.Println(len(pa), cap(pa)) // 5 5