久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

關(guān)于Go,你可能不注意的7件事 | Golang中文社區(qū)(Go語(yǔ)言構(gòu)建) | Go語(yǔ)言中文網(wǎng) | Go語(yǔ)言學(xué)習(xí)園地

 dazheng 2015-09-18

Go以簡(jiǎn)潔著稱,,但簡(jiǎn)潔中不乏值得玩味的小細(xì)節(jié),。這些小細(xì)節(jié)不如goroutine,、interface和channel那樣"高大上",,"屌 絲"得可能不經(jīng)常被人注意到,,但它們卻對(duì)理解Go語(yǔ)言有著重要的作用,。這里想挑出一些和大家一起通過(guò)詳實(shí)的例子來(lái)逐一展開(kāi)和理解,。本文內(nèi)容較為基礎(chǔ),適合初學(xué)者,,高手可飄過(guò):)

一,、源文件字符集和字符集編碼

Go源碼文件默認(rèn)采用Unicode字符集,Unicode碼點(diǎn)(code point)和內(nèi)存中字節(jié)序列(byte sequence)的變換實(shí)現(xiàn)使用了UTF-8:一種變長(zhǎng)多字節(jié)編碼,,同時(shí)也是一種事實(shí)字符集編碼標(biāo)準(zhǔn),,為L(zhǎng)inux、MacOSX 上的默認(rèn)字符集編碼,,因此使用Linux或MacOSX進(jìn)行Go程序開(kāi)發(fā),,你會(huì)省去很多字符集轉(zhuǎn)換方面的煩惱,。但如果你是在Windows上使用 默認(rèn)編輯器編輯Go源碼文本,當(dāng)你編譯以下代碼時(shí)會(huì)遇到編譯錯(cuò)誤:

//hello.go
package main

import "fmt"

func main() {
    fmt.Println("中國(guó)人")
}

$ go build hello.go
# command-line-arguments
hello.go:6 illegal UTF-8 sequence d6 d0
hello.go:6 illegal UTF-8 sequence b9
hello.go:6 illegal UTF-8 sequence fa c8
hello.go:6 illegal UTF-8 sequence cb 22
hello.go:6 newline in string
hello.go:7 syntax error: unexpected }, expected )

這是因?yàn)閃indows默認(rèn)采用的是CP936字符集編碼,,也就是GBK編碼,,“中國(guó)人”三個(gè)字的內(nèi)存字節(jié)序列為:

“d0d6    fab9    cbc8    000a” (通過(guò)iconv轉(zhuǎn)換,然后用od -x查看)

這個(gè)字節(jié)序列并非utf-8字節(jié)序列,,Go編譯器因此無(wú)法識(shí)別,。要想通過(guò)編譯,需要將該源文件轉(zhuǎn)換為UTF-8編碼格式,。

字符集編碼對(duì)字符和字符串字面值(Literal)影響最大,,在Go中對(duì)于字符串我們可以有三種寫法:

1) 字面值

var s = "中國(guó)人"

2) 碼點(diǎn)表示法

var s1 = "\u4e2d\u56fd\u4eba"

or

var s2 = "\U00004e2d\U000056fd\U00004eba"

3) 字節(jié)序列表示法(二進(jìn)制表示法)

var s3 = "\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba"

這三種表示法中,除字面值轉(zhuǎn)換為字節(jié)序列存儲(chǔ)時(shí)根據(jù)編輯器保存的源碼文件編碼格式之外,,其他兩種均不受編碼格式影響,。我們可以通過(guò)逐字節(jié)輸出來(lái)查 看字節(jié)序列的內(nèi)容:

    fmt.Println("s byte sequence:")
    for i := 0; i < len(s); i++ {
        fmt.Printf("0x%x ", s[i])
    }
    fmt.Println("")

二,、續(xù)行

良好的代碼style一般會(huì)要求代碼中不能有太long的代碼行,否則會(huì)影響代碼閱讀者的體驗(yàn)。在C中有續(xù)行符"\"專門用于代碼續(xù)行處理,;但在 Go中沒(méi)有專屬續(xù)行符,,如何續(xù)行需要依據(jù)Go的語(yǔ)法規(guī)則(參見(jiàn)Go spec),。

Go與C一樣,,都是以分號(hào)(";")作為語(yǔ)句結(jié)束的標(biāo)識(shí)。不過(guò)大多數(shù)情況下,,分號(hào)無(wú)需程序員手工輸入,,而是由編譯器自動(dòng)識(shí)別語(yǔ)句結(jié)束位置,并插入 分號(hào),。因此續(xù)行要選擇合法的位置,。下面代碼展示了一些合法的續(xù)行位置:(別嫌太丑,這里僅僅是展示合法位置的demo)

//details-in-go/2/newline.go
… …
var (
    s = "This is an example about code newline," +
        "for string as right value"
    d = 5 + 4 + 7 +
        4
    a = [...]int{5, 6, 7,
        8}
    m = make(map[string]int,
        100)
    c struct {
        m1     string
        m2, m3 int
        m4     *float64
    }

    f func(int,
        float32) (int,
        error)
)

func foo(int, int) (string, error) {
    return "",
        nil
}

func main() {
    if i := d; i >
        100 {
    }

    var sum int
    for i := 0; i < 100; i = i +
        1 {
        sum += i
    }

    foo(1,
        6)

    var i int
    fmt.Printf("%s, %d\n",
        "this is a demo"+
            " of fmt Printf",
        i)
}

實(shí)際編碼中,,我們可能經(jīng)常遇到的是fmt.Printf系列方法中format string太長(zhǎng)的情況,,但由于Go不支持相鄰字符串自動(dòng)連接(concatenate),只能通過(guò)+來(lái)連接fmt字符串,,且+必須放在前一行末尾,。另外Gofmt工具會(huì)自動(dòng)調(diào)整一些不合理的續(xù)行處理,主要針對(duì) for, if等控制語(yǔ)句,。

三,、Method Set

Method Set是Go語(yǔ)法中一個(gè)重要的隱式概念,在為interface變量做動(dòng)態(tài)類型賦值,、embeding struct/interface,、type alias、method expression時(shí)都會(huì)用到Method Set這個(gè)重要概念。

1,、interface的Method Set

根據(jù)Go spec,,interface類型的Method Set就是其interface(An interface type specifies a method set called its interface)。

type I interface {
    Method1()
    Method2()
}

I的Method Set包含的就是其literal中的兩個(gè)方法:Method1和Method2,。我們可以通過(guò)reflect來(lái)獲取interface類型的 Method Set:

//details-in-go/3/interfacemethodset.go
package main

import (
    "fmt"
    "reflect"
)

type I interface {
    Method1()
    Method2()
}

func main() {
    var i *I
    elemType := reflect.TypeOf(i).Elem()
    n := elemType.NumMethod()
    for i := 0; i < n; i++ {
        fmt.Println(elemType.Method(i).Name)
    }
}

運(yùn)行結(jié)果:
$go run interfacemethodset.go
Method1
Method2

2,、除interface type外的類型的Method Set

對(duì)于非interface type的類型T,其Method Set為所有receiver為T類型的方法組成,;而類型*T的Method Set則包含所有receiver為T和*T類型的方法。

// details-in-go/3/othertypemethodset.go
package main

import "./utils"

type T struct {
}

func (t T) Method1() {
}

func (t *T) Method2() {
}

func (t *T) Method3() {
}

func main() {
    var t T
    utils.DumpMethodSet(&t)

    var pt *T
    utils.DumpMethodSet(&pt)
}

我們要dump出T和*T各自的Method Set,,運(yùn)行結(jié)果如下:

$go run othertypemethodset.go
main.T's method sets:
     Method1

*main.T's method sets:
     Method1
     Method2
     Method3

可以看出類型T的Method set僅包含一個(gè)receiver類型為T的方法:Method1,,而*T的Method Set則包含了T的Method Set以及所有receiver類型為*T的Method。

如果此時(shí)我們有一個(gè)interface type如下:

type I interface {
    Method1()
    Method2()
}

那下面哪個(gè)賦值語(yǔ)句合法呢,?合不合法完全依賴于右值類型是否實(shí)現(xiàn)了interface type I的所有方法,,即右值類型的Method Set是否包含了I的 所有方法。

var t T
var pt *T

var i I = t

or

var i I = pt

編譯錯(cuò)誤告訴我們:

     var i I = t // cannot use t (type T) as type I in assignment:
                  T does not implement I (Method2 method has pointer receiver)

T的Method Set中只有Method1一個(gè)方法,,沒(méi)有實(shí)現(xiàn)I接口中的 Method2,,因此不能用t賦值給i;而*T實(shí)現(xiàn)了I的所有接口,,賦值合 法,。不過(guò)Method set校驗(yàn)僅限于在賦值給interface變量時(shí)進(jìn)行,無(wú)論是T還是*T類型的方法集中的方法,,對(duì)于T或*T類型變量都是可見(jiàn)且可以調(diào)用的,,如下面代碼 都是合法的:

    pt.Method1()
    t.Method3()

因?yàn)镚o編譯器會(huì)自動(dòng)為你的代碼做receiver轉(zhuǎn)換:

    pt.Method1() <=> (*pt).Method1()
    t.Method3() <=> (&t).Method3()

很多人糾結(jié)于method定義時(shí)receiver的類型(T or *T),個(gè)人覺(jué)得有兩點(diǎn)考慮:

1) 效率
   Go方法調(diào)用receiver是以傳值的形式傳入方法中的,。如果類型size較大,,以value形式傳入消耗較大,這時(shí)指針類型就是首選,。

2) 是否賦值給interface變量,、以什么形式賦值
   就像本節(jié)所描述的,由于T和*T的Method Set可能不同,,我們?cè)谠O(shè)計(jì)Method receiver type時(shí)需要考慮在interface賦值時(shí)通過(guò)對(duì)Method set的校驗(yàn),。

3、embeding type的Method Set

interface embeding

我們先來(lái)看看interface類型embeding,。例子如下:

//details-in-go/3/embedinginterface.go
package main

import "./utils"

type I1 interface {
    I1Method1()
    I1Method2()
}
type I2 interface {
    I2Method()
}

type I3 interface {
    I1
    I2
}

func main() {
    utils.DumpMethodSet((*I1)(nil))
    utils.DumpMethodSet((*I2)(nil))
    utils.DumpMethodSet((*I3)(nil))
}

$go run embedinginterface.go
main.I1's method sets:
     I1Method1
     I1Method2

main.I2's method sets:
     I2Method

main.I3's method sets:
     I1Method1
     I1Method2
     I2Method

可以看出嵌入interface type的interface type I3的Method Set包含了被嵌入的interface type:I1I2的Method Set,。很多情況下,我們Go的interface type中僅包含有少量方法,,常常僅是一個(gè)Method,,通過(guò)interface type embeding來(lái)定義一個(gè)新interface,這是Go的一個(gè)慣用法,比如我們常用的io包中的Reader, Writer以及ReadWriter接口:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}

【struct embeding interface】

在struct中嵌入interface type后,,struct的Method Set中將包含interface的Method Set:

type T struct {
    I1
}

func (T) Method1() {

}

… …
func main() {
    … …
    var t T
    utils.DumpMethodSet(&t)
    var pt = &T{
        I1: I1Impl{},
    }
    utils.DumpMethodSet(&pt)

}

輸出結(jié)果與預(yù)期一致:

main.T's method sets:
     I1Method1
     I1Method2
     Method1

*main.T's method sets:
     I1Method1
     I1Method2
     Method1

【struct embeding struct】

在struct中embeding struct提供了一種“繼承”的手段,,外部的Struct可以“繼承”嵌入struct的所有方法(無(wú)論receiver是T還是*T類型)實(shí)現(xiàn),但 Method Set可能會(huì)略有不同,??聪旅胬樱?/p>

//details-in-go/3/embedingstructinstruct.go
package main

import "./utils"

type T struct {
}

func (T) InstMethod1OfT() {

}

func (T) InstMethod2OfT() {

}

func (*T) PtrMethodOfT() {

}

type S struct {
}

func (S) InstMethodOfS() {

}

func (*S) PtrMethodOfS() {
}

type C struct {
    T
    *S
}

func main() {
    var c = C{S: &S{}}
    utils.DumpMethodSet(&c)
    var pc = &C{S: &S{}}
    utils.DumpMethodSet(&pc)

    c.InstMethod1OfT()
    c.PtrMethodOfT()
    c.InstMethodOfS()
    c.PtrMethodOfS()
    pc.InstMethod1OfT()
    pc.PtrMethodOfT()
    pc.InstMethodOfS()
    pc.PtrMethodOfS()
}

$go run embedingstructinstruct.go
main.C's method sets:
     InstMethod1OfT
     InstMethod2OfT
     InstMethodOfS
     PtrMethodOfS

*main.C's method sets:
     InstMethod1OfT
     InstMethod2OfT
     InstMethodOfS
     PtrMethodOfS
     PtrMethodOfT

可以看出:
類型C的Method Set = T的Method Set + *S的Method Set
類型*C的Method Set = *T的Method Set + *S的Method Set

同時(shí)通過(guò)例子可以看出,無(wú)論是T還是*S的方法,,C或*C類型變量均可調(diào)用(編譯器甜頭),,不會(huì)被局限在Method Set中。

4,、alias type的Method Set

Go支持為已有類型定義alias type,,如:

type MyInterface I
type Mystruct T

對(duì)于alias type, Method Set是如何定義的呢?我們看下面例子:

//details-in-go/3/aliastypemethodset.go
package main

import "./utils"

type I interface {
    IMethod1()
    IMethod2()
}

type T struct {
}

func (T) InstMethod() {

}
func (*T) PtrMethod() {

}

type MyInterface I
type MyStruct T

func main() {
    utils.DumpMethodSet((*I)(nil))

    var t T
    utils.DumpMethodSet(&t)
    var pt = &T{}
    utils.DumpMethodSet(&pt)

    utils.DumpMethodSet((*MyInterface)(nil))

    var m MyStruct
    utils.DumpMethodSet(&m)
    var pm = &MyStruct{}
    utils.DumpMethodSet(&pm)
}

$go run aliastypemethodset.go
main.I's method sets:
     IMethod1
     IMethod2

main.T's method sets:
     InstMethod

*main.T's method sets:
     InstMethod
     PtrMethod

main.MyInterface's method sets:
     IMethod1
     IMethod2

main.MyStruct's method set is empty!
*main.MyStruct's method set is empty!

從例子的結(jié)果上來(lái)看,,Go對(duì)于interface和struct的alias type給出了“不一致”的結(jié)果:

MyInterface的Method Set與接口類型I Method Set一致,;
而MyStruct并未得到T的哪怕一個(gè)Method,MyStruct的Method Set為空,。

四,、Method Type、Method Expression,、Method Value

Go中沒(méi)有class,,方法與對(duì)象通過(guò)receiver聯(lián)系在一起,我們可以為任何非builtin類型定義method:

type T struct {
    a int
}

func (t T) Get() int       { return t.a }
func (t *T) Set(a int) int { t.a = a; return t.a }

在C++等OO語(yǔ)言中,,對(duì)象在調(diào)用方法時(shí),,編譯器會(huì)自動(dòng)在方法的第一個(gè)參數(shù)中傳入this/self指針,而對(duì)于Go來(lái) 說(shuō),,receiver也是同樣道理,,將T的method轉(zhuǎn)換為普通function定義:

func Get(t T) int       { return t.a }
func Set(t *T, a int) int { t.a = a; return t.a }

這種function形式被稱為Method Type,也可以稱為Method的signature,。

Method的一般使用方式如下:

var t T
t.Get()
t.Set(1)

不過(guò)我們也可以像普通function那樣使用它,,根據(jù)上面的Method Type定義:

var t T
T.Get(t)
(*T).Set(&t, 1)

這種以直接以類型名T調(diào)用方法M的表達(dá)方法稱為Method Expression。類型T只能調(diào)用T的Method Set中的方法,;同理*T只能調(diào)用*T的Method Set中的方法,。上述例子中T的Method Set中只有Get,因此T.Get是合法的,。但T.Set則不合法:

    T.Set(2) //invalid method expression T.Set (needs pointer receiver: (*T).Set)

我們只能使用(*T).Set(&t, 11),。

這樣看來(lái)Method Expression有些類似于C++中的static方法(以該類的某個(gè)對(duì)象實(shí)例作為第一個(gè)參數(shù))。

另外Method express自身類型就是一個(gè)普通function,,可以作為右值賦值給一個(gè)函數(shù)類型的變量:

    f1 := (*T).Set //函數(shù)類型:func (t *T, int)int
    f2 := T.Get //函數(shù)類型:func(t T)int
    f1(&t, 3)
    fmt.Println(f2(t))

Go中還定義了一種與Method有關(guān)的語(yǔ)法:如果一個(gè)表達(dá)式t具有靜態(tài)類型T,,M是T的Method Set中的一個(gè)方法,那么t.M即為Method Value。注意這里是t.M而不是T.M,。

    f3 := (&t).Set //函數(shù)類型:func(int)int
    f3(4)
    f4 := t.Get
//函數(shù)類型:func()int   
    fmt.Println(f4())

可以看出,,Method value與Method Expression不同之處在于,Method value綁定了T對(duì)象實(shí)例,,它的函數(shù)原型并不包含Method Expression函數(shù)原型中的第一個(gè)參數(shù),。完整例子參見(jiàn):details-in-go/4/methodexpressionandmethodvalue.go

五,、for range“坑”大閱兵

for range的引入提升了Go的表達(dá)能力,,但for range顯然不是”免費(fèi)的午餐“,在享用這個(gè)美味前,,需要搞清楚for range的一些坑,。

1、iteration variable重用

for range的idiomatic的使用方式是使用short variable declaration(:=)形式在for expression中聲明iteration variable,,但需要注意的是這些variable在每次循環(huán)體中都會(huì)被重用,而不是重新聲明,。

//details-in-go/5/iterationvariable.go
… …
    var m = [...]int{1, 2, 3, 4, 5}

    for i, v := range m {
        go func() {
            time.Sleep(time.Second * 3)
            fmt.Println(i, v)
        }()
    }

    time.Sleep(time.Second * 10)
… …

在我的Mac上,,輸出結(jié)果如下:

$go run iterationvariable.go
4 5
4 5
4 5
4 5
4 5

各個(gè)goroutine中輸出的i,v值都是for range循環(huán)結(jié)束后的i, v最終值,而不是各個(gè)goroutine啟動(dòng)時(shí)的i, v值,。一個(gè)可行的fix方法:

    for i, v := range m {
        go func(i, v int) {
            time.Sleep(time.Second * 3)
            fmt.Println(i, v)
        }(i, v)
    }

2,、range expression副本參與iteration

range后面接受的表達(dá)式的類型包括:array, pointer to array, slice, string, map和channel(有讀權(quán)限的)。我們以array為例來(lái)看一個(gè)簡(jiǎn)單的例子:

//details-in-go/5/arrayrangeexpression.go
func arrayRangeExpression() {
    var a = [5]int{1, 2, 3, 4, 5}
    var r [5]int

    fmt.Println("a = ", a)

    for i, v := range a {
        if i == 0 {
            a[1] = 12
            a[2] = 13
        }
        r[i] = v
    }

    fmt.Println("r = ", r)
}

我們期待輸出結(jié)果:

a =  [1 2 3 4 5]
r =  [1 12 13 4 5]

a =  [1 12 13 4 5]

但實(shí)際輸出結(jié)果卻是:

a =  [1 2 3 4 5]
r =  [1 2 3 4 5]
a =  [1 12 13 4 5]

我們?cè)詾樵诘谝淮蝘teration,,也就是i = 0時(shí),,我們對(duì)a的修改(a[1] = 12,a[2] = 13)會(huì)在第二次,、第三次循環(huán)中被v取出,,但結(jié)果卻是v取出的依舊是a被修改前的值:2和3。這就是for range的一個(gè)不大不小的坑:range expression副本參與循環(huán),。也就是說(shuō)在上面這個(gè)例子里,,真正參與循環(huán)的是a的副本,而不是真正的a,,偽代碼如 下:

    for i, v := range a' {//a' is copy from a
        if i == 0 {
            a[1] = 12
            a[2] = 13
        }
        r[i] = v
    }

Go中的數(shù)組在內(nèi)部表示為連續(xù)的字節(jié)序列,,雖然長(zhǎng)度是Go數(shù)組類型的一部分,但長(zhǎng)度并不包含的數(shù)組的內(nèi)部表示中,,而是由編譯器在編譯期計(jì)算出 來(lái),。這個(gè)例子中,對(duì)range表達(dá)式的拷貝,,即對(duì)一個(gè)數(shù)組的拷貝,,a'則是Go臨時(shí)分配的連續(xù)字節(jié)序列,與a完全不是一塊內(nèi)存。因此無(wú)論a被 如何修改,,其副本a'依舊保持原值,,并且參與循環(huán)的是a',因此v從a'中取出的仍舊是a的原值,,而非修改后的值,。

我們?cè)賮?lái)試試pointer to array:

func pointerToArrayRangeExpression() {
    var a = [5]int{1, 2, 3, 4, 5}
    var r [5]int

    fmt.Println("pointerToArrayRangeExpression result:")
    fmt.Println("a = ", a)

    for i, v := range &a {
        if i == 0 {
            a[1] = 12
            a[2] = 13
        }

        r[i] = v
    }

    fmt.Println("r = ", r)
    fmt.Println("a = ", a)
    fmt.Println("")
}

這回的輸出結(jié)果如下:

pointerToArrayRangeExpression result:
a =  [1 2 3 4 5]
r =  [1 12 13 4 5]
a =  [1 12 13 4 5]

我們看到這次r數(shù)組的值與最終a被修改后的值一致了。這個(gè)例子中我們使用了*[5]int作為range表達(dá)式,,其副本依舊是一個(gè)指向原數(shù)組 a的指針,,因此后續(xù)所有循環(huán)中均是&a指向的原數(shù)組親自參與的,因此v能從&a指向的原數(shù)組中取出a修改后的值,。

idiomatic go建議我們盡可能的用slice替換掉array的使用,,這里用slice能否實(shí)現(xiàn)預(yù)期的目標(biāo)呢?我們來(lái)試試:

func sliceRangeExpression() {
    var a = [5]int{1, 2, 3, 4, 5}
    var r [5]int

    fmt.Println("sliceRangeExpression result:")
    fmt.Println("a = ", a)

    for i, v := range a[:] {
        if i == 0 {
            a[1] = 12
            a[2] = 13
        }

        r[i] = v
    }

    fmt.Println("r = ", r)
    fmt.Println("a = ", a)
    fmt.Println("")
}

pointerToArrayRangeExpression result:
a =  [1 2 3 4 5]
r =  [1 12 13 4 5]
a =  [1 12 13 4 5]

顯然用slice也能實(shí)現(xiàn)預(yù)期要求,。我們可以分析一下slice是如何做到的,。slice在go的內(nèi)部表示為一個(gè)struct,由(*T, len, cap)組成,,其中*T指向slice對(duì)應(yīng)的underlying array的指針,,len是slice當(dāng)前長(zhǎng)度,cap為slice的最大容量,。當(dāng)range進(jìn)行expression復(fù)制時(shí),,它實(shí)際上復(fù)制的是一個(gè) slice,也就是那個(gè)struct,。副本struct中的*T依舊指向原slice對(duì)應(yīng)的array,,為此對(duì)slice的修改都反映到 underlying array a上去了,v從副本struct中*T指向的underlying array中獲取數(shù)組元素,,也就得到了被修改后的元素值,。

slice與array還有一個(gè)不同點(diǎn),就是其len在運(yùn)行時(shí)可以被改變,,而array的len是一個(gè)常量,,不可改變。那么len變化的 slice對(duì)for range有何影響呢,?我們繼續(xù)看一個(gè)例子:

func sliceLenChangeRangeExpression() {
    var a = []int{1, 2, 3, 4, 5}
    var r = make([]int, 0)

    fmt.Println("sliceLenChangeRangeExpression result:")
    fmt.Println("a = ", a)

    for i, v := range a {
        if i == 0 {
            a = append(a, 6, 7)
        }

        r = append(r, v)
    }

    fmt.Println("r = ", r)
    fmt.Println("a = ", a)
}

輸出結(jié)果:

a =  [1 2 3 4 5]
r =  [1 2 3 4 5]
a =  [1 2 3 4 5 6 7]

在這個(gè)例子中,,原slice a在for range過(guò)程中被附加了兩個(gè)元素6和7,其len由5增加到7,,但這對(duì)于r卻沒(méi)有產(chǎn)生影響,。這里的原因就在于a的副本a'的內(nèi)部表示struct中的 len字段并沒(méi)有改變,依舊是5,,因此for range只會(huì)循環(huán)5次,,也就只獲取a對(duì)應(yīng)的underlying數(shù)組的前5個(gè)元素,。

range的副本行為會(huì)帶來(lái)一些性能上的消耗,尤其是當(dāng)range expression的類型為數(shù)組時(shí),,range需要復(fù)制整個(gè)數(shù)組,;而當(dāng)range expression類型為pointer to array或slice時(shí),這個(gè)消耗將小得多,,僅僅需要復(fù)制一個(gè)指針或一個(gè)slice的內(nèi)部表示(一個(gè)struct)即可,。我們可以通過(guò) benchmark test來(lái)看一下三種情況的消耗情況對(duì)比:

對(duì)于元素個(gè)數(shù)為100的int數(shù)組或slice,測(cè)試結(jié)果如下:

//details-in-go/5/arraybenchmark
go test -bench=.
testing: warning: no tests to run
PASS
BenchmarkArrayRangeLoop-4             20000000           116 ns/op
BenchmarkPointerToArrayRangeLoop-4    20000000            64.5 ns/op
BenchmarkSliceRangeLoop-4             20000000            70.9 ns/op

可以看到range expression類型為slice或pointer to array的性能相近,,消耗都近乎是數(shù)組類型的1/2,。

3、其他range expression類型

對(duì)于range后面的其他表達(dá)式類型,,比如string, map, channel,,for range依舊會(huì)制作副本。

【string】
對(duì)string來(lái)說(shuō),,由于string的內(nèi)部表示為struct {*byte, len),,并且string本身是immutable的,因此其行為和消耗和slice expression類似,。不過(guò)for range對(duì)于string來(lái)說(shuō),,每次循環(huán)的單位是rune(code point的值),而不是byte,,index為迭代字符碼點(diǎn)的第一個(gè)字節(jié)的position:

    var s = "中國(guó)人"

    for i, v := range s {
        fmt.Printf("%d %s 0x%x\n", i, string(v), v)
    }

輸出結(jié)果:
0 中 0x4e2d
3 國(guó) 0x56fd
6 人 0x4eba

如果s中存在非法utf8字節(jié)序列,,那么v將返回0xFFFD這個(gè)特殊值,,并且在接下來(lái)一輪循環(huán)中,,v將僅前進(jìn)一個(gè)字節(jié):

//byte sequence of s: 0xe4 0xb8 0xad 0xe5 0x9b 0xbd 0xe4 0xba 0xba
    var sl = []byte{0xe4, 0xb8, 0xad, 0xe5, 0x9b, 0xbd, 0xe4, 0xba, 0xba}
    for _, v := range sl {
        fmt.Printf("0x%x ", v)
    }
    fmt.Println("\n")

    sl[3] = 0xd0
    sl[4] = 0xd6
    sl[5] = 0xb9

    for i, v := range string(sl) {
        fmt.Printf("%d %x\n", i, v)
    }

輸出結(jié)果:

0xe4 0xb8 0xad 0xe5 0x9b 0xbd 0xe4 0xba 0xba

0 4e2d
3 fffd
4 5b9
6 4eba

以上例子源碼在details-in-go/5/stringrangeexpression.go中可以找到。

map

對(duì)于map來(lái)說(shuō),,map內(nèi)部表示為一個(gè)指針,,指針副本也指向真實(shí)map,因此for range操作均操作的是源map,。

for range不保證每次迭代的元素次序,,對(duì)于下面代碼:

 var m = map[string]int{
        "tony": 21,
        "tom":  22,
        "jim":  23,
    }

    for k, v := range m {
        fmt.Println(k, v)
    }

輸出結(jié)果可能是:

tom 22
jim 23
tony 21

也可能是:

tony 21
tom 22
jim 23

或其他可能。

如果map中的某項(xiàng)在循環(huán)到達(dá)前被在循環(huán)體中刪除了,,那么它將不會(huì)被iteration variable獲取到,。
    counter := 0
    for k, v := range m {
        if counter == 0 {
            delete(m, "tony")
        }
        counter++
        fmt.Println(k, v)
    }
    fmt.Println("counter is ", counter)

反復(fù)運(yùn)行多次,我們得到的兩個(gè)結(jié)果:

tony 21
tom 22
jim 23
counter is  3

tom 22
jim 23
counter is  2

如果在循環(huán)體中新創(chuàng)建一個(gè)map元素項(xiàng),,那該項(xiàng)元素可能出現(xiàn)在后續(xù)循環(huán)中,,也可能不出現(xiàn):

    m["tony"] = 21
    counter = 0

    for k, v := range m {
        if counter == 0 {
            m["lucy"] = 24
        }
        counter++
        fmt.Println(k, v)
    }
    fmt.Println("counter is ", counter)

執(zhí)行結(jié)果:

tony 21
tom 22
jim 23
lucy 24
counter is  4

or

tony 21
tom 22
jim 23
counter is  3

以上代碼可以在details-in-go/5/maprangeexpression.go中可以找到。

【channel】

對(duì)于channel來(lái)說(shuō),,channel內(nèi)部表示為一個(gè)指針,,channel的指針副本也指向真實(shí)channel,。

for range最終以阻塞讀的方式阻塞在channel expression上(即便是buffered channel,當(dāng)channel中無(wú)數(shù)據(jù)時(shí),,for range也會(huì)阻塞在channel上),,直到channel關(guān)閉:

//details-in-go/5/channelrangeexpression.go
func main() {
    var c = make(chan int)

    go func() {
        time.Sleep(time.Second * 3)
        c <- 1
        c <- 2
        c <- 3
        close(c)
    }()

    for v := range c {
        fmt.Println(v)
    }
}

運(yùn)行結(jié)果:

1
2
3

如果channel變量為nil,則for range將永遠(yuǎn)阻塞,。

六,、select求值 

golang引入的select為我們提供了一種在多個(gè)channel間實(shí)現(xiàn)“多路復(fù)用”的一種機(jī)制。select的運(yùn)行機(jī)制這里不贅述,,但select的case expression的求值順序我們倒是要通過(guò)一個(gè)例子來(lái)了解一下:

// details-in-go/6/select.go

func takeARecvChannel() chan int {
    fmt.Println("invoke takeARecvChannel")
    c := make(chan int)

    go func() {
        time.Sleep(3 * time.Second)
        c <- 1
    }()

    return c
}

func getAStorageArr() *[5]int {
    fmt.Println("invoke getAStorageArr")
    var a [5]int
    return &a
}

func takeASendChannel() chan int {
    fmt.Println("invoke takeASendChannel")
    return make(chan int)
}

func getANumToChannel() int {
    fmt.Println("invoke getANumToChannel")
    return 2
}

func main() {
    select {
    //recv channels
    case (getAStorageArr())[0] = <-takeARecvChannel():
        fmt.Println("recv something from a recv channel")

        //send channels
    case takeASendChannel() <- getANumToChannel():
        fmt.Println("send something to a send channel")
    }
}

運(yùn)行結(jié)果:

$go run select.go
invoke takeARecvChannel
invoke takeASendChannel
invoke getANumToChannel

invoke getAStorageArr
recv something from a recv channel

通過(guò)例子我們可以看出:
1) select執(zhí)行開(kāi)始時(shí),,首先所有case expression的表達(dá)式都會(huì)被求值一遍,按語(yǔ)法先后次序,。

invoke takeARecvChannel
invoke takeASendChannel
invoke getANumToChannel

例外的是recv channel的位于賦值等號(hào)左邊的表達(dá)式(這里是:(getAStorageArr())[0])不會(huì)被求值,。

2) 如果選擇要執(zhí)行的case是一個(gè)recv channel,那么它的賦值等號(hào)左邊的表達(dá)式會(huì)被求值:如例子中當(dāng)goroutine 3s后向recvchan寫入一個(gè)int值后,,select選擇了recv channel執(zhí)行,,此時(shí)對(duì)=左側(cè)的表達(dá)式 (getAStorageArr())[0] 開(kāi)始求值,輸出“invoke getAStorageArr”,。

七,、panic的recover過(guò)程

Go沒(méi)有提供“try-catch-finally”這樣的異常處理設(shè)施,而僅僅提供了panic和recover,,其中recover還要結(jié)合 defer使用,。最初這也是被一些人詬病的點(diǎn)。但和錯(cuò)誤碼返回值一樣,,漸漸的大家似乎適應(yīng)了這些,,征討之聲漸稀,即便有也是排在“缺少generics” 之后了,。

【panicking】

在沒(méi)有recover的時(shí)候,,一旦panic發(fā)生,panic會(huì)按既定順序結(jié)束當(dāng)前進(jìn)程,,這一過(guò)程成為panicking,。下面的例子模擬了這一過(guò)程:

//details-in-go/7/panicking.go
… …
func foo() {
    defer func() {
        fmt.Println("foo defer func invoked")
    }()
    fmt.Println("foo invoked")

    bar()
    fmt.Println("do something after bar in foo")
}

func bar() {
    defer func() {
        fmt.Println("bar defer func invoked")
    }()
    fmt.Println("bar invoked")

    zoo()
    fmt.Println("do something after zoo in bar")
}

func zoo() {
    defer func() {
        fmt.Println("zoo defer func invoked")
    }()

    fmt.Println("zoo invoked")
    panic("runtime exception")
}

func main() {
    foo()
}

執(zhí)行結(jié)果:

$go run panicking.go
foo invoked
bar invoked
zoo invoked
zoo defer func invoked
bar defer func invoked
foo defer func invoked
panic: runtime exception

goroutine 1 [running]:
… …
exit status 2

從結(jié)果可以看出:
    panic在zoo中發(fā)生,在zoo真正退出前,,zoo中注冊(cè)的defer函數(shù)會(huì)被逐一執(zhí)行(FILO),,由于zoo defer中沒(méi)有捕捉panic,因此panic被拋向其caller:bar,。
    這時(shí)對(duì)于bar而言,,其函數(shù)體中的zoo的調(diào)用就好像變成了panic調(diào)用似的,zoo有些類似于“黑客帝國(guó)3”中里奧被史密斯(panic)感 染似的,,也變成了史密斯(panic),。panic在bar中擴(kuò)展開(kāi)來(lái),,bar中的defer也沒(méi)有捕捉和recover panic,因此在bar中的defer func執(zhí)行完畢后,,panic繼續(xù)拋給bar的caller: foo,;
    這時(shí)對(duì)于foo而言,bar就變成了panic,,同理,,最終foo將panic拋給了main
    main與上述函數(shù)一樣,沒(méi)有recover,,直接異常返回,,導(dǎo)致進(jìn)程異常退出。
 

【recover】

recover只有在defer函數(shù)中調(diào)用才能起到recover的作用,,這樣recover就和defer函數(shù)有了緊密聯(lián)系,。我們?cè)趜oo的defer函數(shù)中捕捉并recover這個(gè)panic:

//details-in-go/7/recover.go
… …
func zoo() {
    defer func() {
        fmt.Println("zoo defer func1 invoked")
    }()

    defer func() {
        if x := recover(); x != nil {
            log.Printf("recover panic: %v in zoo recover defer func", x)
        }
    }()

    defer func() {
        fmt.Println("zoo defer func2 invoked")
    }()

    fmt.Println("zoo invoked")
    panic("zoo runtime exception")
}

… …

這回的執(zhí)行結(jié)果如下:

$go run recover.go
foo invoked
bar invoked
zoo invoked
zoo defer func2 invoked
2015/09/17 16:28:00 recover panic: zoo runtime exception in zoo recover defer func
zoo defer func1 invoked
do something after zoo in bar
bar defer func invoked
do something after bar in foo
foo defer func invoked

由于zoo在defer里恢復(fù)了panic,這樣在zoo返回后,,bar不會(huì)感知到任何異常,,將按正常邏輯輸出函數(shù)執(zhí)行內(nèi)容,比如:“do something after zoo in bar”,以此類推,。

但若如果在zoo defer func中recover panic后,,又raise another panic,那么zoo對(duì)于bar來(lái)說(shuō)就又會(huì)變成panic了,。

Last,、參考資料

1、The Go Programming Language Specification (Version of August 5, 2015,,Go 1.5),;
2、Effective Go (Go 1.5),;
3,、Rob Pike: Go Course Day 1~3,。

本文實(shí)驗(yàn)環(huán)境:Go 1.5 darwin_amd64,。示例代碼在這里可以下載。

我就是這樣一種人:對(duì)任何自己感興趣且有極大熱情去做的事情都喜歡刨根問(wèn)底,,徹底全面地了解其中細(xì)節(jié),,否則我就會(huì)有一種“不安全 感”。我不知道在心理學(xué)范疇這樣的我屬于那種類別^_^,。

2015, bigwhite. 版權(quán)所有.

0

Related posts:

  1. Go程序設(shè)計(jì)語(yǔ)言(二)
  2. Go中的系統(tǒng)Signal處理
  3. Go程序設(shè)計(jì)語(yǔ)言(三)
  4. Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)概覽
  5. Golang的演化歷程

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買等信息,,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多