一個(gè)Go源文件可以多次引入同一個(gè)包。但是每次的引入名稱必須不同。這些相同的包引入引用著同一個(gè)包實(shí)例。
示例:
package main
import "fmt"
import "io"
import inout "io"
func main() {
fmt.Println(&inout.EOF == &io.EOF) // true
}
示例:
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")
}
var x, y chan int
select {
case <-x:
case y <- 1:
default:
}
select {
case <-x:
default:
case y <- 1:
}
select {
default:
case <-x:
case y <- 1:
}
例如,下面的代碼在編譯時(shí)會(huì)失敗。
package main
func main() {
switch 123 {
case 123:
case 123: // error: duplicate case
}
}
但是下面的代碼在編譯時(shí)是沒問題的。
package main
func main() {
switch false {
case false:
case false:
}
}
關(guān)于原因,請閱讀這個(gè)issue。 此行為依賴于編譯器。事實(shí)上,標(biāo)準(zhǔn)編譯器同樣不允許重復(fù)的字符串case表達(dá)式,但是gccgo編譯器卻允許。
例如,在下列switch
代碼塊中的switch表達(dá)式123
被視為一個(gè)int
值,而不是一個(gè)類型不確定的整數(shù)。
package main
func main() {
switch 123 {
case int64(123): // error: 類型不匹配
case uint32(789): // error: 類型不匹配
}
}
例如,下列程序會(huì)打印出true
。
package main
import "fmt"
func main() {
switch { // <=> switch true {
case true: fmt.Println("true")
case false: fmt.Println("false")
}
}
例如:
package main
func main() {
var i = 0
Outer:
for
{ // 在這里斷行是沒問題的
switch
{ // 在這里斷行是沒問題的
case i == 5:
break Outer
default:
i++
}
}
}
下面程序的結(jié)果會(huì)打印什么?true
還是false
? 答案是true
。 關(guān)于原因請閱讀Go中的代碼斷行規(guī)則一文。
package main
import "fmt"
func False() bool {
return false
}
func main() {
switch False()
{
case true: fmt.Println("true")
case false: fmt.Println("false")
}
}
例如,下面的程序會(huì)在編譯時(shí)將失敗。
func demo(n, m int) (r int) {
switch n {
case 123:
if m > 0 {
goto End
}
r++
End: // syntax error: 標(biāo)簽后缺少語句
default:
r = 1
}
return
}
為了編譯通過,case
分支代碼塊必須改成顯式的:
func demo(n, m int) (r int) {
switch n {
case 123: {
if m > 0 {
goto End
}
r++
End:
}
default:
r = 1
}
return
}
另外,我們可以在標(biāo)簽End:
之后加一個(gè)分號,如下所示:
func demo(n, m int) (r int) {
switch n {
case 123:
if m > 0 {
goto End
}
r++
End:;
default:
r = 1
}
return
}
關(guān)于原因,請閱讀Go的代碼斷行規(guī)則一文。
package main
import "fmt"
func F() (r int) {
defer func() {
r = 789
}()
return 123 // <=> r = 123; return
}
func main() {
fmt.Println(F()) // 789
}
我們需要在正確的地方調(diào)用recover
函數(shù)。 關(guān)于細(xì)節(jié),請閱讀 在正確的位置調(diào)用內(nèi)置函數(shù)recover
一文。
我們可以通過調(diào)用os.Exit
函數(shù)從任何函數(shù)里退出一個(gè)程序。 os.Exit
函數(shù)調(diào)用接受一個(gè)int
代碼值做為參數(shù)并將此代碼返回給操作系統(tǒng)。
示例:
// exit-example.go
package main
import "os"
import "time"
func main() {
go func() {
time.Sleep(time.Second)
os.Exit(1)
}()
select{}
}
運(yùn)行:
$ go run a.go
exit status 1
$ echo $?
1
我們可以通過調(diào)用runtime.Goexit
函數(shù)退出一個(gè)goroutine。 runtime.Goexit
函數(shù)沒有參數(shù)。
在下面的示例中,文字Java
將不會(huì)被打印出來。
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
}
例如:
package main
import "fmt"
type T struct {
x int
y *int
}
func main() {
var t T
p := &t.x // <=> p := &(t.x)
fmt.Printf("%T\n", p) // *int
*p++ // <=> (*p)++
*p-- // <=> (*p)--
t.y = p
a := *t.y // <=> *(t.y)
fmt.Printf("%T\n", a) // int
}
package main
func main() {
}
const M = 2
var _ = 1.0 << M // 編譯沒問題。1.0將被推斷為一個(gè)int值。
var N = 2
var _ = 1.0 << N // 編譯失敗。1.0將被推斷為一個(gè)float64值。
關(guān)于原因請閱讀運(yùn)算操作符一文。
package main
type MyInt int64
type Ta *int64
type Tb *MyInt
func main() {
var a Ta
var b Tb
//a = Ta(b) // error: 直接轉(zhuǎn)換是不允許的。
// 但是間接轉(zhuǎn)換是允許的。
y := (*MyInt)(b)
x := (*int64)(y)
a = x // 等價(jià)于下一行
a = (*int64)(y) // 等價(jià)于下一行
a = (*int64)((*MyInt)(b))
_ = a
}
兩個(gè)零尺寸值的地址是否相等時(shí)依賴于具體編譯器實(shí)現(xiàn)以及具體編譯器版本。
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)
// 對于標(biāo)準(zhǔn)編譯器1.19版本,x、y、o和p將
// 逃逸到堆上,但是a、b、m和n則開辟在棧上。
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
}
上面代碼中所示的輸出是針對標(biāo)準(zhǔn)編譯器1.19版本的。
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))))
}
無論一個(gè)指針值的類型是具名的還是無名的,如果它的(指針)類型的基類型為一個(gè)結(jié)構(gòu)體類型,則我們可以使用此指針值來選擇它所引用著的結(jié)構(gòu)體中的字段。 但是,如果此指針的類型為一個(gè)具名類型,則我們不能使用此指針值來選擇它所引用著的結(jié)構(gòu)體中的方法。
我們總是不能使用二級以上指針來選擇結(jié)構(gòu)體字段和方法。
package main
type T struct {
x int
}
func (T) m(){} // T有一個(gè)方法m。
type P *T // P為一個(gè)具名一級指針類型。
type PP *P // PP為一個(gè)具名二級指針類型。
func main() {
var t T
var tp = &t
var tpp = &tp
var p P = tp
var pp PP = &p
tp.x = 12 // 沒問題
p.x = 34 // 沒問題
pp.x = 56 // error: 類型PP沒有名為x的字段或者方法。
tpp.x = 78 // error: 類型**T沒有名為x的字段或者方法。
tp.m() // 沒問題,因?yàn)轭愋?T也有一個(gè)m方法。
p.m() // error: 類型P沒有名為m的字段或者方法。
pp.m() // error: 類型PP沒有名為m的字段或者方法。
tpp.m() // error: 類型**T沒有名為m的字段或者方法。
}
關(guān)于細(xì)節(jié),請閱讀內(nèi)嵌組合字面量可以被簡化這一章節(jié)。
關(guān)于細(xì)節(jié),請閱讀把數(shù)組指針當(dāng)做數(shù)組來使用這一章節(jié)。
例如,函數(shù)Foo1
和Foo2
是等價(jià)的,但是函數(shù)Foo2
比函數(shù)Foo1
簡潔得多。
func Foo1(m map[string]int) int {
if m != nil {
return m["foo"]
}
return 0
}
func Foo2(m map[string]int) int {
return m["foo"]
}
例如,下面這個(gè)程序不會(huì)因?yàn)榭只哦罎ⅰ?
package main
func main() {
var m map[string]int // nil
delete(m, "foo")
}
關(guān)于細(xì)節(jié),請閱讀添加和刪除容器元素這一章節(jié)。
例如:
package main
import "fmt"
func main() {
s := make([]int, 3, 9)
fmt.Println(len(s)) // 3
s2 := s[2:7]
fmt.Println(len(s2)) // 5
}
關(guān)于細(xì)節(jié),請閱讀從數(shù)組或者切片派生切片這一章節(jié)。
例如,下面的程序在運(yùn)行時(shí)刻不會(huì)產(chǎn)生恐慌。
package main
import "fmt"
func main() {
var x []int // nil
a := x[:]
b := x[0:0]
c := x[:0:0]
// 下一行將打印出三個(gè)true。
fmt.Println(a == nil, b == nil, c == nil)
}
關(guān)于細(xì)節(jié),請閱讀從數(shù)組或者切片派生切片這一章節(jié)。
package main
func main() {
var s []int // nil
for range s {
}
var m map[string]int // nil
for range m {
}
}
例如,下面的程序會(huì)輸出01234
。
package main
import "fmt"
func main() {
var a *[5]int // nil
for i, _ := range a {
fmt.Print(i)
}
}
我們可以通過反射途徑單獨(dú)修改一個(gè)切片的長度或者容量。 關(guān)于細(xì)節(jié),請閱讀單獨(dú)修改一個(gè)切片的長度或者容量這一章節(jié)。
var k = 1
var x = [2]int{k: 1} // error: 索引必須為一個(gè)常量
var y = []int{k: 1} // error: 索引必須為一個(gè)常量
注意,映射組合字面量中的鍵值不必為常量。
// error: 重復(fù)的索引:1
var a = []bool{0: false, 1: true, 1: true}
// error: 重復(fù)的索引:0
var b = [...]string{0: "foo", 1: "bar", 0: "foo"}
// error: 重復(fù)的鍵值:"foo"
var c = map[string]int{"foo": 1, "foo": 2}
這個(gè)特性可以用于在編譯時(shí)刻斷言某些條件。
原因是一個(gè)數(shù)組值的元素和此數(shù)組存儲(chǔ)在同一個(gè)內(nèi)存塊中。 但是切片的情況大不相同。
package main
func main() {
// 組合字面量是不可尋址的。
/* 取容器元素的地址。 */
// 取不可尋址的切片的元素的地址是沒問題的
_ = &[]int{1}[0]
// error: 不能取不可尋址的數(shù)組的元素的地址
_ = &[5]int{}[0]
/* 修改元素值。 */
// 修改不可尋址的切片的元素是沒問題的
[]int{1,2,3}[1] = 9
// error: 不能修改不可尋址的數(shù)組的元素
[3]int{1,2,3}[1] = 9
}
原因和上一個(gè)細(xì)節(jié)是一樣的。
例如:
package main
func main() {
// 映射元素是不可尋址的。
// 下面幾行編譯沒問題。
_ = []int{6, 7, 8, 9}[1:3]
var ms = map[string][]int{"abc": {0, 1, 2, 3}}
_ = ms["abc"][1:3]
// 下面幾行將編譯失敗,因?yàn)椴豢蓮牟豢蓪ぶ返臄?shù)組派生切片。
/*
_ = [...]int{6, 7, 8, 9}[1:3] // error
var ma = map[string][4]int{"abc": {0, 1, 2, 3}}
_ = ma["abc"][1:3] // error
*/
}
原因是下面的另一個(gè)細(xì)節(jié)中提到的NaN != NaN
。 但是,在Go1.12之前,以NaN
作為鍵值的元素只能在for-range
循環(huán)中被找到; 從Go1.12開始,以NaN
作為鍵值的元素也可以通過類似fmt.Print
的函數(shù)打印出來。
package main
import "fmt"
import "math"
func main() {
var a = math.NaN()
fmt.Println(a) // NaN
var m = map[float64]int{}
m[a] = 123
v, present := m[a]
fmt.Println(v, present) // 0 false
m[a] = 789
v, present = m[a]
fmt.Println(v, present) // 0 false
fmt.Println(m) // map[NaN:789 NaN:123]
delete(m, a) // no-op
fmt.Println(m) // map[NaN:789 NaN:123]
for k, v := range m {
fmt.Println(k, v)
}
// the above loop outputs:
// NaN 123
// NaN 789
}
注意:在Go1.12之前,兩個(gè)fmt.Println(m)
調(diào)用均打印出map[NaN:<nil> NaN:<nil>]
。
我們不應(yīng)該假設(shè)結(jié)果切片的長度和容量總是相等的。
在下面的例子中,如果最后一個(gè)fmt.Println
行被刪除,在其前面的兩行會(huì)打印相同的值32
;否則,一個(gè)打印32
,一個(gè)打印8
(對于標(biāo)準(zhǔn)編譯器1.19版本來說)。
package main
import "fmt"
func main() {
s := "a"
x := []byte(s) // len(s) == 1
fmt.Println(cap([]byte(s))) // 32
fmt.Println(cap(x)) // 8
fmt.Println(x)
}
如果我們假設(shè)結(jié)果切片的長度和容量總是相等,就可能寫出一些有bug的代碼。
對于這兩個(gè)循環(huán),迭代變量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
}
比如下面這個(gè)例子不會(huì)無窮盡地循環(huán)下去(注意每次退出前的循環(huán)次數(shù)可能不同):
package main
import "fmt"
func f(m map[byte]byte) string {
bs := make([]byte, 0, 2*len(m))
for k, v := range m {
bs = append(bs, k, v)
}
return string(bs)
}
func main() {
m := map[byte]byte{'a':'A', 'b':'B', 'c':'C'}
s0 := f(m)
for i := 1; ; i++{
if s := f(m); s != s0 {
fmt.Println(s0)
fmt.Println(s)
fmt.Println(i)
return
}
}
}
注意:對映射進(jìn)行JSON格式化輸出中的映射條目是按照它們的鍵值排序的。 另外,從Go 1.12開始,使用fmt
標(biāo)準(zhǔn)庫包中的打印函數(shù)打印映射時(shí),輸出的映射條目也是按照它們的鍵值排序的; 而在Go 1.12之前,這些打印輸出時(shí)亂序的。
有例為證:
package main
import "fmt"
func main() {
m := map[int]int{0: 0, 1: 100, 2: 200}
r, n, i:= len(m), len(m), 0
for range m {
m[n] = n*100
n++
i++
}
fmt.Printf("新增了%d個(gè)條目,其中%d個(gè)被遍歷出來,%d個(gè)沒有。\n",
i, i - r, n - i,
)
}
感謝Valentin Deleplace提出了上面兩條細(xì)節(jié)建議。
關(guān)于細(xì)節(jié),請閱讀有返回值的函數(shù)的調(diào)用是一種表達(dá)式這一章節(jié)。
關(guān)于細(xì)節(jié),請閱讀哪些函數(shù)調(diào)用將在編譯時(shí)刻被估值?這一總結(jié)。
關(guān)于細(xì)節(jié),請閱讀每個(gè)方法對應(yīng)著一個(gè)隱式聲明的函數(shù)這一章節(jié)。
package main
func main() {
var x interface{} = []int{}
_ = x == x // panic
}
例如:
package main
type Foo interface {
foo()
}
type T int
func (T) foo() {}
func main() {
var x interface{} = T(123)
// 下面這兩行將編譯失敗。
/*
var _ Foo = x // error: interface{}類型沒有實(shí)現(xiàn)Foo類型
var _ = Foo(x) // error: interface{}類型沒有實(shí)現(xiàn)Foo類型
*/
// 但是下面這行可以編譯通過。
var _ = x.(Foo) // okay
}
如果第二個(gè)可選結(jié)果出現(xiàn)在失敗的類型斷言中,那么此類型斷言不會(huì)導(dǎo)致恐慌。否則,恐慌將產(chǎn)生。 例如:
package main
func main() {
var x interface{} = true
_, _ = x.(int) // 斷言失敗,但不會(huì)導(dǎo)致恐慌。
_ = x.(int) // 斷言失敗,并導(dǎo)致一個(gè)恐慌。
}
在編譯時(shí)刻,編譯可以發(fā)現(xiàn)某些目標(biāo)類型為接口類型的斷言是不可能成功的。比如下面這個(gè)程序中的斷言:
package main
type Ia interface {
m()
}
type Ib interface {
m() int
}
type T struct{}
func (T) m() {}
func main() {
var x Ia = T{}
_ = x.(Ib) // panic: main.T is not main.Ib
}
這樣的斷言并不會(huì)導(dǎo)致編譯失敗(但編譯后的程序?qū)⒃谶\(yùn)行時(shí)刻產(chǎn)生恐慌)。 從官方Go工具鏈1.15開始,go vet
會(huì)對對這樣的斷言做出警告。
原因是errors.New
函數(shù)會(huì)復(fù)制輸入的字符串實(shí)參至一個(gè)局部變量并取此局部變量的指針作為返回error
值的動(dòng)態(tài)值。 兩次調(diào)用會(huì)產(chǎn)生兩個(gè)不同的指針。
package main
import "fmt"
import "errors"
func main() {
notfound := "not found"
a, b := errors.New(notfound), errors.New(notfound)
fmt.Println(a == b) // false
}
例如,下面的代碼會(huì)在編譯時(shí)候失敗。
package main
func main() {
}
func foo(c <-chan int) {
close(c) // error: 不能關(guān)閉單向接收通道
}
例如,在下面的程序里,如果第二個(gè)case
分支會(huì)被選中,則在運(yùn)行時(shí)刻將產(chǎn)生一個(gè)恐慌。
package main
func main() {
var c = make(chan bool)
close(c)
select {
case <-c:
case c <- true: // panic: 向已關(guān)閉的通道發(fā)送數(shù)據(jù)
default:
}
}
類型可以聲明在函數(shù)體內(nèi)。例如,
package main
func main() {
type T struct{}
type S = []int
}
關(guān)于細(xì)節(jié),請閱讀這個(gè)FAQ條目。
此規(guī)則遵循IEEE-754標(biāo)準(zhǔn),并與大多數(shù)其它語言是一致的。
package main
import "fmt"
import "math"
func main() {
var a = math.Sqrt(-1.0)
fmt.Println(a) // NaN
fmt.Println(a == a) // false
var x = 0.0
var y = 1.0 / x
var z = 2.0 * y
fmt.Println(y, z, y == z) // +Inf +Inf true
}
例如,在包foo
中聲明了如下的類型:
package foo
type I = interface {
about() string
}
type S struct {
a string
}
func (s S) about() string {
return s.a
}
在包bar
中聲明了如下的類型:
package bar
type I = interface {
about() string
}
type S struct {
a string
}
func (s S) about() string {
return s.a
}
那么,
S
的值不能相互轉(zhuǎn)換。foo.S
沒有實(shí)現(xiàn)接口類型 bar.I
。bar.S
沒有實(shí)現(xiàn)接口類型foo.I
。package main
import "包2/foo"
import "包2/bar"
func main() {
var x foo.S
var y bar.S
var _ foo.I = x
var _ bar.I = y
// 下面這些行將編譯失敗。
x = foo.S(y)
y = bar.S(x)
var _ foo.I = y
var _ bar.I = x
}
比如,下面這個(gè)程序?qū)⒋蛴〕?code>true。
package main
import "fmt"
type T struct {
_ int
_ bool
}
func main() {
var t1 = T{123, true}
var t2 = T{789, false}
fmt.Println(t1 == t2) // true
}
例如:
package main
type T struct{x, y int}
func main() {
// 因?yàn)閧}的煩擾,下面這三行均編譯失敗。
/*
if T{} == T{123, 789} {}
if T{} == (T{123, 789}) {}
if (T{}) == T{123, 789} {}
var _ = func()(nil) // nil被認(rèn)為是一個(gè)類型
*/
// 必須加上一對小括號()才能編譯通過。
if (T{} == T{123, 789}) {}
if (T{}) == (T{123, 789}) {}
var _ = (func())(nil) // nil被認(rèn)為是一個(gè)值
}
在目前的主流Go編譯器實(shí)現(xiàn)中,棧溢出是致命錯(cuò)誤。一旦棧溢出發(fā)生,程序?qū)⒉豢苫謴?fù)地崩潰。
package main
func f() {
f()
}
func main() {
defer func() {
recover() // 無法防止程序崩潰
}()
f()
}
運(yùn)行結(jié)果:
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
runtime stack:
...
關(guān)于更多不可恢復(fù)的致命錯(cuò)誤,請參考此篇維基文章。
關(guān)于細(xì)節(jié),請閱讀表達(dá)式估值順序規(guī)則一文。
如果表達(dá)式x
和y
的類型不相同,則函數(shù)調(diào)用DeepEqual(x, y)
的結(jié)果總為false
,但x == y
的估值結(jié)果有可能為true
。
如果x
和y
為(同類型的)兩個(gè)引用著不同其它值的指針值,則x == y
的估值結(jié)果總為false
,但函數(shù)調(diào)用DeepEqual(x, y)
的結(jié)果可能為true
,因?yàn)楹瘮?shù)reflect.DeepEqual
將比較x
和y
所引用的兩個(gè)值。
第三個(gè)區(qū)別是當(dāng)x
和y
均處于某個(gè)循環(huán)引用鏈中時(shí),為了防止死循環(huán),DeepEqual
調(diào)用的結(jié)果可能為true
。
第四個(gè)區(qū)別是一個(gè)DeepEqual(x, y)
調(diào)用無論如何不應(yīng)該產(chǎn)生一個(gè)恐慌,但是如果x
和y
是兩個(gè)動(dòng)態(tài)類型相同的接口值并且它們的動(dòng)態(tài)類型是不可比較類型的時(shí)候,x == y
將產(chǎn)生一個(gè)恐慌。
一個(gè)展示了這些不同的例子:
package main
import (
"fmt"
"reflect"
)
func main() {
type Book struct {page int}
x := struct {page int}{123}
y := Book{123}
fmt.Println(reflect.DeepEqual(x, y)) // false
fmt.Println(x == y) // true
z := Book{123}
fmt.Println(reflect.DeepEqual(&z, &y)) // true
fmt.Println(&z == &y) // false
type Node struct{peer *Node}
var q, r, s Node
q.peer = &q // 形成一個(gè)循環(huán)引用鏈
r.peer = &s // 形成一個(gè)循環(huán)引用鏈
s.peer = &r
println(reflect.DeepEqual(&q, &r)) // true
fmt.Println(q == r) // false
var f1, f2 func() = nil, func(){}
fmt.Println(reflect.DeepEqual(f1, f1)) // true
fmt.Println(reflect.DeepEqual(f2, f2)) // false
var a, b interface{} = []int{1, 2}, []int{1, 2}
fmt.Println(reflect.DeepEqual(a, b)) // true
fmt.Println(a == b) // 產(chǎn)生恐慌
}
注意:如果傳遞給一個(gè)DeepEqual
調(diào)用的兩個(gè)實(shí)參均為函數(shù)類型值,則此調(diào)用只有在這兩個(gè)實(shí)參都為nil并且它們的類型相同的情況下才返回true
。 比較元素中含有函數(shù)值的容器值或者比較字段中含有函數(shù)值的結(jié)構(gòu)體值也是類似的。 另外要注意:如果兩個(gè)同類型切片共享相同的元素序列(即它們的長度相同并且它們的各對相應(yīng)元素的地址也相同),則使用DeepEqual
比較它們時(shí)返回的結(jié)果總是為true
,即使它們的元素中含有函數(shù)值。
一個(gè)例子:
package main
import (
"fmt"
"reflect"
)
func main() {
a := [1]func(){func(){}}
b := a
fmt.Println(reflect.DeepEqual(a, a)) // false
fmt.Println(reflect.DeepEqual(a[:], a[:])) // true
fmt.Println(reflect.DeepEqual(a[:], b[:])) // false
a[0], b[0] = nil, nil
fmt.Println(reflect.DeepEqual(a[:], b[:])) // true
}
假設(shè)一個(gè)自定義類型MyByte
的底層類型為內(nèi)置類型byte
,我們知道Go類型系統(tǒng)禁止切片類型[]MyByte
的值轉(zhuǎn)換為類型[]byte
。 但是,當(dāng)前的reflect.Value
類型的Bytes
方法的實(shí)現(xiàn)可以幫我們繞過這個(gè)限制。 此實(shí)現(xiàn)應(yīng)該是違反了Go類型系統(tǒng)的規(guī)則。
例子:
package main
import "bytes"
import "fmt"
import "reflect"
type MyByte byte
func main() {
var mybs = []MyByte{'a', 'b', 'c'}
var bs []byte
// bs = []byte(mybs) // this line fails to compile
v := reflect.ValueOf(mybs)
bs = v.Bytes() // okay. Violating Go type system.
fmt.Println(bytes.HasPrefix(bs, []byte{'a', 'b'})) // true
bs[1], bs[2] = 'r', 't'
fmt.Printf("%s \n", mybs) // art
}
雖然這違反了Go類型系統(tǒng)的規(guī)則,但是貌似此違反并沒有什么害處,相反,它帶來了一些好處。 比如,我們可以將bytes
標(biāo)準(zhǔn)庫包中提供的函數(shù)(間接)應(yīng)用到[]MyByte
值上,如上例所示。
注意:reflect.Value.Bytes()
方法以后可能會(huì)被移除。
使用err == os.ErrNotExist
可能漏掉一些錯(cuò)誤。
package main
import (
"fmt"
"os"
)
func main() {
_, err := os.Stat("a-nonexistent-file.abcxyz")
fmt.Println(os.IsNotExist(err)) // true
fmt.Println(err == os.ErrNotExist) // false
}
如果你的項(xiàng)目只支持Go 1.13+,則更推薦使用errors.Is(err, os.ErrNotExist)
來檢查文件是否存在。
package main
import (
"errors"
"fmt"
"os"
)
func main() {
_, err := os.Stat("a-nonexistent-file.abcxyz")
fmt.Println(errors.Is(err, os.ErrNotExist)) // true
}
傳遞程序選項(xiàng)有三種形式。
-flag
:僅適用于布爾選項(xiàng)。
-flag=x
:用于任何類型的選項(xiàng)。.
-flag x
:僅用于非布爾選項(xiàng)。
請注意,使用第一種形式的布爾選項(xiàng)將被視為最后一個(gè)選項(xiàng),其后面的所有項(xiàng)都被視為參數(shù)。
package main
import "fmt"
import "flag"
var b = flag.Bool("b", true, "一個(gè)布爾選項(xiàng)")
var i = flag.Int("i", 123, "一個(gè)整數(shù)選項(xiàng)")
var s = flag.String("s", "hi", "一個(gè)字符串選項(xiàng)")
func main() {
flag.Parse()
fmt.Print("b=", *b, ", i=", *i, ", s=", *s, "\n")
fmt.Println("arguments:", flag.Args())
}
如果我們用下面顯示的標(biāo)志和參數(shù)運(yùn)行此程序
./exampleProgram -b false -i 789 -s bye arg0 arg1
輸出結(jié)果會(huì)是:
b=true, i=123, s=hi
arguments: [false -i 789 -s bye arg0 arg1]
這個(gè)輸出顯然不是我們所期望的。
我們應(yīng)該像這樣傳遞選項(xiàng)和參數(shù):
./exampleProgram -b=false -i 789 -s bye arg0 arg1
或者
./exampleProgram -i 789 -s bye -b arg0 arg1
以獲取我們期望的輸出:
b=true, i=789, s=bye
arguments: [arg0 arg1]
下面的程序會(huì)打印coco
。
package main
import "fmt"
func main() {
// The next line prints: coco
fmt.Printf("%[2]v%[1]v%[2]v%[1]v", "o", "c")
}
更多建議: