• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Fork me on GitHub
            隨筆 - 215  文章 - 13  trackbacks - 0
            <2017年2月>
            2930311234
            567891011
            12131415161718
            19202122232425
            2627281234
            567891011


            專注即時(shí)通訊及網(wǎng)游服務(wù)端編程
            ------------------------------------
            Openresty 官方模塊
            Openresty 標(biāo)準(zhǔn)模塊(Opm)
            Openresty 三方模塊
            ------------------------------------
            本博收藏大部分文章為轉(zhuǎn)載,并在文章開(kāi)頭給出了原文出處,如有再轉(zhuǎn),敬請(qǐng)保留相關(guān)信息,這是大家對(duì)原創(chuàng)作者勞動(dòng)成果的自覺(jué)尊重!!如為您帶來(lái)不便,請(qǐng)于本博下留言,謝謝配合。

            常用鏈接

            留言簿(1)

            隨筆分類

            隨筆檔案

            相冊(cè)

            Awesome

            Blog

            Book

            GitHub

            Link

            搜索

            •  

            積分與排名

            • 積分 - 215511
            • 排名 - 118

            最新評(píng)論

            閱讀排行榜

             

            http://studygolang.com/articles/9357
            本文來(lái)自:鳥窩

            感謝作者:smallnest

            查看原文:[]T 還是 []*T, 這是一個(gè)問(wèn)題 全面分析Go語(yǔ)言中的類型和類型指針的抉擇


            在編程語(yǔ)言深入討論中,經(jīng)常被大家提起也是爭(zhēng)論最多的討論之一就是按值(by value)還是按引用傳遞(by reference, by pointer),你可以在C/C++或者Java的社區(qū)經(jīng)常看到這樣的討論,也會(huì)看到很多這樣的面試題。

            對(duì)于Go語(yǔ)言,嚴(yán)格意義上來(lái)講,只有一種傳遞,也就是按值傳遞(by value)。當(dāng)一個(gè)變量當(dāng)作參數(shù)傳遞的時(shí)候,會(huì)創(chuàng)建一個(gè)變量的副本,然后傳遞給函數(shù)或者方法,你可以看到這個(gè)副本的地址和變量的地址是不一樣的。

            當(dāng)變量當(dāng)做指針被傳遞的時(shí)候,一個(gè)新的指針被創(chuàng)建,它指向變量指向的同樣的內(nèi)存地址,所以你可以將這個(gè)指針看成原始變量指針的副本。當(dāng)這樣理解的時(shí)候,我們就可以理解成Go總是創(chuàng)建一個(gè)副本按值轉(zhuǎn)遞,只不過(guò)這個(gè)副本有時(shí)候是變量的副本,有時(shí)候是變量指針的副本。

            這是Go語(yǔ)言中你理解后續(xù)問(wèn)題的基礎(chǔ)。

            但是Go語(yǔ)言的情況比較復(fù)雜,我們什么時(shí)候選擇 T 作為參數(shù)類型,什么時(shí)候選擇 *T作為參數(shù)類型? []T是傳遞的指針還是值?選擇[]T還是[]*T? 哪些類型復(fù)制和傳遞的時(shí)候會(huì)創(chuàng)建副本?什么情況下會(huì)發(fā)生副本創(chuàng)建?

            本文將詳細(xì)介紹Go語(yǔ)言的變量的副本創(chuàng)建還是變量指針的副本創(chuàng)建的case以及各種類型在這些case的情況。

            副本的創(chuàng)建

            前面已經(jīng)講到,T類型的變量和*T類型的變量在當(dāng)做函數(shù)或者方法的參數(shù)時(shí)會(huì)傳遞它的副本。我們先看看例子。

            T的副本創(chuàng)建

            首先看一下 參數(shù)類型為T的函數(shù)調(diào)用的情況:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            package main
            import "fmt"
            type Bird struct {
            Age int
            Name string
            }
            func passV(b Bird) {
            b.Age++
            b.Name = "Great" + b.Name
            fmt.Printf("傳入修改后的Bird:\t %+v, \t內(nèi)存地址:%p\n", b, &b)
            }
            func main() {
            parrot := Bird{Age: 1, Name: "Blue"}
            fmt.Printf("原始的Bird:\t\t %+v, \t\t內(nèi)存地址:%p\n", parrot, &parrot)
            passV(parrot)
            fmt.Printf("調(diào)用后原始的Bird:\t %+v, \t\t內(nèi)存地址:%p\n", parrot, &parrot)
            }

            運(yùn)行后輸入結(jié)果(每次運(yùn)行指針的值可能不同):

            1
            2
            3
            原始的Bird: {Age:1 Name:Blue}, 內(nèi)存地址:0xc420012260
            傳入修改后的Bird: {Age:2 Name:GreatBlue}, 內(nèi)存地址:0xc4200122c0
            調(diào)用后原始的Bird: {Age:1 Name:Blue}, 內(nèi)存地址:0xc420012260

            可以看到,在T類型作為參數(shù)的時(shí)候,傳遞的參數(shù)parrot會(huì)將它的副本(內(nèi)存地址0xc4200122c0)傳遞給函數(shù)passV,在這個(gè)函數(shù)內(nèi)對(duì)參數(shù)的改變不會(huì)影響原始的對(duì)象。

            *T的副本創(chuàng)建

            修改上面的例子,將函數(shù)的參數(shù)類型由T改為*T:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            package main
            import "fmt"
            type Bird struct {
            Age int
            Name string
            }
            func passP(b *Bird) {
            b.Age++
            b.Name = "Great" + b.Name
            fmt.Printf("傳入修改后的Bird:\t %+v, \t內(nèi)存地址:%p, 指針的內(nèi)存地址: %p\n", *b, b, &b)
            }
            func main() {
            parrot := &Bird{Age: 1, Name: "Blue"}
            fmt.Printf("原始的Bird:\t\t %+v, \t\t內(nèi)存地址:%p, 指針的內(nèi)存地址: %p\n", *parrot, parrot, &parrot)
            passP(parrot)
            fmt.Printf("調(diào)用后原始的Bird:\t %+v, \t內(nèi)存地址:%p, 指針的內(nèi)存地址: %p\n", *parrot, parrot, &parrot)
            }

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

            1
            2
            3
            原始的Bird: {Age:1 Name:Blue}, 內(nèi)存地址:0xc420076000, 指針的內(nèi)存地址: 0xc420074000
            傳入修改后的Bird: {Age:2 Name:GreatBlue}, 內(nèi)存地址:0xc420076000, 指針的內(nèi)存地址: 0xc420074010
            調(diào)用后原始的Bird: {Age:2 Name:GreatBlue}, 內(nèi)存地址:0xc420076000, 指針的內(nèi)存地址: 0xc420074000

            可以看到在函數(shù)passP中,參數(shù)p是一個(gè)指向Bird的指針,傳遞參數(shù)給它的時(shí)候會(huì)創(chuàng)建指針的副本(0xc420074010),只不過(guò)指針0xc4200740000xc420074010都指向內(nèi)存地址0xc420076000。 函數(shù)內(nèi)對(duì)*T的改變顯然會(huì)影響原始的對(duì)象,因?yàn)樗菍?duì)同一個(gè)對(duì)象的操作。

            當(dāng)然,一位對(duì)Go有深入了解的讀者都已經(jīng)對(duì)這個(gè)知識(shí)有所了解,也明白了T*T作為參數(shù)的時(shí)候副本創(chuàng)建的不同。

            如何選擇 T*T

            在定義函數(shù)和方法的時(shí)候,作為一位資深的Go開(kāi)發(fā)人員,一定會(huì)對(duì)函數(shù)的參數(shù)和返回值定義成T*T深思熟慮,有些情況下可能還會(huì)有些苦惱。
            那么什么時(shí)候才應(yīng)該把參數(shù)定義成類型T,什么情況下定義成類型*T呢。

            一般的判斷標(biāo)準(zhǔn)是看副本創(chuàng)建的成本和需求。

            1. 不想變量被修改。 如果你不想變量被函數(shù)和方法所修改,那么選擇類型T。相反,如果想修改原始的變量,則選擇*T
            2. 如果變量是一個(gè)的struct或者數(shù)組,則副本的創(chuàng)建相對(duì)會(huì)影響性能,這個(gè)時(shí)候考慮使用*T,只創(chuàng)建新的指針,這個(gè)區(qū)別是巨大的
            3. (不針對(duì)函數(shù)參數(shù),只針對(duì)本地變量/本地變量)對(duì)于函數(shù)作用域內(nèi)的參數(shù),如果定義成T,Go編譯器盡量將對(duì)象分配到棧上,而*T很可能會(huì)分配到對(duì)象上,這對(duì)垃圾回收會(huì)有影響

            什么時(shí)候發(fā)生副本創(chuàng)建

            上面舉的例子都是作為函數(shù)參數(shù)時(shí)發(fā)生的副本的創(chuàng)建,還有很多情況下會(huì)發(fā)生副本的創(chuàng)建,甚至有些“隱蔽”的情況。
            編程的時(shí)候如何小心這些情況呢,一條原則就是:

            A go assignment is a copy of the value itself
            賦值的時(shí)候就會(huì)創(chuàng)建對(duì)象副本

            Assignment的語(yǔ)法表達(dá)式如下:

            Assignment = ExpressionList assign_op ExpressionList .
            assign_op = [ add_op | mul_op ] "=" .

            Each left-hand side operand must be addressable, a map index expression, or (for = assignments only) the blank identifier. Operands may be parenthesized.

            最常見(jiàn)的case

            最常見(jiàn)的賦值的例子是對(duì)變量的賦值,包括函數(shù)內(nèi)和函數(shù)外:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            package main
            import "fmt"
            type Bird struct {
            Age int
            Name string
            }
            type Parrot struct {
            Age int
            Name string
            }
            var parrot1 = Bird{Age: 1, Name: "Blue"}
            var parrot2 = parrot1
            func main() {
            fmt.Printf("parrot1:\t\t %+v, \t\t內(nèi)存地址:%p\n", parrot1, &parrot1)
            fmt.Printf("parrot2:\t\t %+v, \t\t內(nèi)存地址:%p\n", parrot2, &parrot2)
            parrot3 := parrot1
            fmt.Printf("parrot2:\t\t %+v, \t\t內(nèi)存地址:%p\n", parrot3, &parrot3)
            parrot4 := Parrot(parrot1)
            fmt.Printf("parrot4:\t\t %+v, \t\t內(nèi)存地址:%p\n", parrot4, &parrot4)
            }

            輸出結(jié)果:

            1
            2
            3
            4
            parrot1: {Age:1 Name:Blue}, 內(nèi)存地址:0xfa0a0
            parrot2: {Age:1 Name:Blue}, 內(nèi)存地址:0xfa0c0
            parrot2: {Age:1 Name:Blue}, 內(nèi)存地址:0xc42007e0c0
            parrot4: {Age:1 Name:Blue}, 內(nèi)存地址:0xc42007e100

            可以看到這幾個(gè)變量的內(nèi)存地址都不相同,說(shuō)明發(fā)生了賦值。

            map、slice和數(shù)組

            slice,map和數(shù)組在初始化和按索引設(shè)置的時(shí)候也會(huì)創(chuàng)建副本:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            31
            32
            33
            34
            35
            36
            37
            38
            39
            40
            41
            package main
            import "fmt"
            type Bird struct {
            Age int
            Name string
            }
            var parrot1 = Bird{Age: 1, Name: "Blue"}
            func main() {
            fmt.Printf("parrot1:\t\t %+v, \t\t內(nèi)存地址:%p\n", parrot1, &parrot1)
            //slice
            s := []Bird{parrot1}
            s = append(s, parrot1)
            parrot1.Age = 3
            fmt.Printf("parrot2:\t\t %+v, \t\t內(nèi)存地址:%p\n", s[0], &(s[0]))
            fmt.Printf("parrot3:\t\t %+v, \t\t內(nèi)存地址:%p\n", s[1], &(s[1]))
            parrot1.Age = 1
            //map
            m := make(map[int]Bird)
            m[0] = parrot1
            parrot1.Age = 4
            fmt.Printf("parrot4:\t\t %+v\n", m[0])
            parrot1.Age = 5
            parrot5 := m[0]
            fmt.Printf("parrot5:\t\t %+v, \t\t內(nèi)存地址:%p\n", parrot5, &parrot5)
            parrot1.Age = 1
            //array
            a := [2]Bird{parrot1}
            parrot1.Age = 6
            fmt.Printf("parrot6:\t\t %+v, \t\t內(nèi)存地址:%p\n", a[0], &a[0])
            parrot1.Age = 1
            a[1] = parrot1
            parrot1.Age = 7
            fmt.Printf("parrot7:\t\t %+v, \t\t內(nèi)存地址:%p\n", a[1], &a[1])
            }

            輸出結(jié)果

            1
            2
            3
            4
            5
            6
            7
            parrot1: {Age:1 Name:Blue}, 內(nèi)存地址:0xfa0a0
            parrot2: {Age:1 Name:Blue}, 內(nèi)存地址:0xc4200160f0
            parrot3: {Age:1 Name:Blue}, 內(nèi)存地址:0xc420016108
            parrot4: {Age:1 Name:Blue}
            parrot5: {Age:1 Name:Blue}, 內(nèi)存地址:0xc420012320
            parrot6: {Age:1 Name:Blue}, 內(nèi)存地址:0xc420016120
            parrot7: {Age:1 Name:Blue}, 內(nèi)存地址:0xc420016138

            可以看到 slice/map/數(shù)組 的元素全是原始變量的副本, 副本

            for-range循環(huán)

            for-range循環(huán)也是將元素的副本賦值給循環(huán)變量,所以變量得到的是集合元素的副本。

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            31
            32
            33
            34
            35
            36
            37
            38
            39
            40
            41
            42
            43
            44
            45
            46
            47
            48
            49
            package main
            import "fmt"
            type Bird struct {
            Age int
            Name string
            }
            var parrot1 = Bird{Age: 1, Name: "Blue"}
            func main() {
            fmt.Printf("parrot1:\t\t %+v, \t\t內(nèi)存地址:%p\n", parrot1, &parrot1)
            //slice
            s := []Bird{parrot1, parrot1, parrot1}
            s[0].Age = 1
            s[1].Age = 2
            s[2].Age = 3
            parrot1.Age = 4
            for i, p := range s {
            fmt.Printf("parrot%d:\t\t %+v, \t\t內(nèi)存地址:%p\n", (i + 2), p, &p)
            }
            parrot1.Age = 1
            //map
            m := make(map[int]Bird)
            parrot1.Age = 1
            m[0] = parrot1
            parrot1.Age = 2
            m[1] = parrot1
            parrot1.Age = 3
            m[2] = parrot1
            parrot1.Age = 4
            for k, v := range m {
            fmt.Printf("parrot%d:\t\t %+v, \t\t內(nèi)存地址:%p\n", (k + 2), v, &v)
            }
            parrot1.Age = 4
            //array
            a := [...]Bird{parrot1, parrot1, parrot1}
            a[0].Age = 1
            a[1].Age = 2
            a[2].Age = 3
            parrot1.Age = 4
            for i, p := range a {
            fmt.Printf("parrot%d:\t\t %+v, \t\t內(nèi)存地址:%p\n", (i + 2), p, &p)
            }
            }

            輸出結(jié)果

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            parrot1: {Age:1 Name:Blue}, 內(nèi)存地址:0xfb0a0
            parrot2: {Age:1 Name:Blue}, 內(nèi)存地址:0xc4200122a0
            parrot3: {Age:2 Name:Blue}, 內(nèi)存地址:0xc4200122a0
            parrot4: {Age:3 Name:Blue}, 內(nèi)存地址:0xc4200122a0
            parrot2: {Age:1 Name:Blue}, 內(nèi)存地址:0xc420012320
            parrot3: {Age:2 Name:Blue}, 內(nèi)存地址:0xc420012320
            parrot4: {Age:3 Name:Blue}, 內(nèi)存地址:0xc420012320
            parrot2: {Age:1 Name:Blue}, 內(nèi)存地址:0xc4200123a0
            parrot3: {Age:2 Name:Blue}, 內(nèi)存地址:0xc4200123a0
            parrot4: {Age:3 Name:Blue}, 內(nèi)存地址:0xc4200123a0

            注意循環(huán)變量是重用的,所以你看到它們的地址是相同的。

            channel

            往channel中send對(duì)象的時(shí)候也會(huì)創(chuàng)建對(duì)象的副本:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            package main
            import "fmt"
            type Bird struct {
            Age int
            Name string
            }
            var parrot1 = Bird{Age: 1, Name: "Blue"}
            func main() {
            ch := make(chan Bird, 3)
            fmt.Printf("parrot1:\t\t %+v, \t\t內(nèi)存地址:%p\n", parrot1, &parrot1)
            ch <- parrot1
            parrot1.Age = 2
            ch <- parrot1
            parrot1.Age = 3
            ch <- parrot1
            parrot1.Age = 4
            p := <-ch
            fmt.Printf("parrot%d:\t\t %+v, \t\t內(nèi)存地址:%p\n", 2, p, &p)
            p = <-ch
            fmt.Printf("parrot%d:\t\t %+v, \t\t內(nèi)存地址:%p\n", 3, p, &p)
            p = <-ch
            fmt.Printf("parrot%d:\t\t %+v, \t\t內(nèi)存地址:%p\n", 4, p, &p)
            }

            輸出結(jié)果:

            1
            2
            3
            4
            parrot1: {Age:1 Name:Blue}, 內(nèi)存地址:0xfa0a0
            parrot2: {Age:1 Name:Blue}, 內(nèi)存地址:0xc4200122a0
            parrot3: {Age:2 Name:Blue}, 內(nèi)存地址:0xc4200122a0
            parrot4: {Age:3 Name:Blue}, 內(nèi)存地址:0xc4200122a0

            函數(shù)參數(shù)和返回值

            將變量作為參數(shù)傳遞給函數(shù)和方法會(huì)發(fā)生副本的創(chuàng)建。
            對(duì)于返回值,將返回值賦值給其它變量或者傳遞給其它的函數(shù)和方法,就會(huì)創(chuàng)建副本。

            Method Receiver

            因?yàn)榉椒?method)最終會(huì)產(chǎn)生一個(gè)receiver作為第一個(gè)參數(shù)的函數(shù)(參看規(guī)范),所以就比較好理解method receiver的副本創(chuàng)建的規(guī)則了。
            當(dāng)receiver為T類型時(shí),會(huì)發(fā)生創(chuàng)建副本,調(diào)用副本上的方法。
            當(dāng)receiver為*T類型時(shí),只是會(huì)創(chuàng)建對(duì)象的指針,不創(chuàng)建對(duì)象的副本,方法內(nèi)對(duì)receiver的改動(dòng)會(huì)影響原始值。

            不同類型的副本創(chuàng)建

            bool,數(shù)值和指針

            bool和數(shù)值類型一般不必考慮指針類型,原因在于這些對(duì)象很小,創(chuàng)建副本的開(kāi)銷可以忽略。只有你在想修改同一個(gè)變量的值的時(shí)候才考慮它們的指針。

            指針類型就不用多說(shuō)了,和數(shù)值類型類似。

            數(shù)組

            數(shù)組是值類型,賦值的時(shí)候會(huì)發(fā)生原始數(shù)組的復(fù)制,所以對(duì)于大的數(shù)組的參數(shù)傳遞和賦值,一定要慎重。

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            package main
            import "fmt"
            func main() {
            a1 := [3]int{1, 2, 3}
            fmt.Printf("a1:\t\t %+v, \t\t內(nèi)存地址:%p\n", a1, &a1)
            a2 := a1
            a1[0] = 4
            a1[1] = 5
            a1[2] = 6
            fmt.Printf("a2:\t\t %+v, \t\t內(nèi)存地址:%p\n", a2, &a2)
            }

            輸出

            1
            2
            a1: [1 2 3], 內(nèi)存地址:0xc420012260
            a2: [1 2 3], 內(nèi)存地址:0xc4200122c0

            對(duì)于[...]T[...]*T的區(qū)別,我想你也應(yīng)該清楚了,[...]*T創(chuàng)建的副本的元素時(shí)元數(shù)組元素指針的副本。

            map、slice 和 channel

            網(wǎng)上一般說(shuō), 這三種類型都是指向指針類型,指向一個(gè)底層的數(shù)據(jù)結(jié)構(gòu)。
            因此呢,在定義類型的時(shí)候就不必定義成*T了。

            當(dāng)然你可以這么認(rèn)為,不過(guò)我認(rèn)為這是不準(zhǔn)確的,比如slice,其實(shí)你可以看成是SliceHeader對(duì)象,只不過(guò)它的數(shù)據(jù)Data是一個(gè)指針,所以它的副本的創(chuàng)建對(duì)性能的影響可以忽略。

            字符串

            string類型類似slice,它等價(jià)StringHeader。所以很多情況下會(huì)用`unsafe.Pointer`與[]byte類型進(jìn)行更有效的轉(zhuǎn)換,因?yàn)橹苯舆M(jìn)行類型轉(zhuǎn)換string([]byte)會(huì)發(fā)生數(shù)據(jù)的復(fù)制。

            字符串比較特殊,它的值不能修改,任何想對(duì)字符串的值做修改都會(huì)生成新的字符串。

            大部分情況下你不需要定義成*string。唯一的例外你需要 nil值的時(shí)候。我們知道,類型string的空值/缺省值為"",但是如果你需要nil,你就必須定義*string。舉個(gè)例子,在對(duì)象序列化的時(shí)候""nil表示的意義是不一樣的,""表示字段存在,只不過(guò)字符串是空值,而nil表示字段不存在。

            函數(shù)

            函數(shù)也是一個(gè)指針類型,對(duì)函數(shù)對(duì)象的賦值只是又創(chuàng)建了一個(gè)對(duì)次函數(shù)對(duì)象的指針。

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            package main
            import "fmt"
            func main() {
            f1 := func(i int) {}
            fmt.Printf("f1:\t\t %+v, \t\t內(nèi)存地址:%p\n", f1, &f1)
            f2 := f1
            fmt.Printf("f2:\t\t %+v, \t\t內(nèi)存地址:%p\n", f2, &f2)
            }

            輸出結(jié)果:

            1
            2
            f1: 0x2200, 內(nèi)存地址:0xc420028020
            f2: 0x2200, 內(nèi)存地址:0xc420028030

            參考文檔

            1. https://www.reddit.com/r/golang/comments/5lheyg/returning_t_vs_t/?
            2. https://github.com/google/go-github/issues/180
            3. http://openmymind.net/Things-I-Wish-Someone-Had-Told-Me-About-Go/
            4. http://goinbigdata.com/golang-pass-by-pointer-vs-pass-by-value/
            5. https://groups.google.com/forum/#!topic/golang-nuts/__BPVgK8LN0
            6. https://golang.org/ref/spec
            7. https://golang.org/doc/faq
            8. https://golang.org/doc/effective_go.html
            9. https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/
            10. https://dhdersch.github.io/golang/2016/01/23/golang-when-to-use-string-pointers.html
            11. https://dave.cheney.net/2016/03/19/should-methods-be-declared-on-t-or-t
            12. http://colobu.com/2016/10/28/When-are-function-parameters-passed-by-value/
            posted on 2017-02-09 09:26 思月行云 閱讀(241) 評(píng)論(0)  編輯 收藏 引用 所屬分類: Golang
            国产成人精品久久| 国产免费久久精品99久久| 亚洲国产精品一区二区久久hs| 久久久黄色大片| 热re99久久精品国99热| 久久人人爽人人爽人人片av高请| 精品久久无码中文字幕| 精品久久久久久国产免费了| 国内精品伊人久久久影院 | 2021久久精品免费观看| 无码精品久久久天天影视| 中文字幕亚洲综合久久| 99久久香蕉国产线看观香| 久久久久久国产精品免费无码| 久久国产精品免费一区二区三区 | 99久久亚洲综合精品网站| 一本色道久久88综合日韩精品 | 国产成人精品久久免费动漫| 久久久久无码中| 久久久久久久亚洲Av无码| 老司机午夜网站国内精品久久久久久久久 | 久久99精品久久久久久| 无码乱码观看精品久久| 成人久久精品一区二区三区| 2021久久精品免费观看| 国产成人香蕉久久久久| 久久AV高清无码| 国产香蕉久久精品综合网| 国产精品九九久久免费视频 | 久久精品国产亚洲AV无码偷窥| 欧美麻豆久久久久久中文| 久久91综合国产91久久精品| 国产精品亚洲综合久久| 久久精品国产欧美日韩| 精品免费tv久久久久久久| 亚洲av日韩精品久久久久久a | 伊人久久综在合线亚洲2019| 久久亚洲精精品中文字幕| 久久综合鬼色88久久精品综合自在自线噜噜 | 久久无码人妻精品一区二区三区 | 久久99热这里只有精品66|