2019-05-21 Go語言學(xué)習(xí)二(接上)

三、更多類型:struct、slice和映射

1.指針

  • Go 的指針保存了值的內(nèi)存地址。
  • 類型 *T 是指向 T 類型值的指針。其零值為 nil。
  • var p *int
  • 操作符& 會生成一個指向其操作數(shù)的指針。
    i := 42
    p = &i
  • 操作符* 表示指針指向的底層值。
    fmt.Println(*p) // 通過指針 p 讀取 i
    *p = 21 // 通過指針 p 設(shè)置 i
    這也就是通常所說的“間接引用”或“重定向”
    與 C 不同,Go 沒有指針運算。
package main

import "fmt"

func main() {
    i, j := 42, 2701

    p := &i         // 指向 i
    fmt.Println(*p) // 通過指針讀取 i 的值
    *p = 21         // 通過指針設(shè)置 i 的值
    fmt.Println(i)  // 查看 i 的值

    p = &j         // 指向 j
    *p = *p / 37   // 通過指針對 j 進(jìn)行除法運算
    fmt.Println(j) // 查看 j 的值
}

-------------------------------------------------------------------------------
//輸出結(jié)果
42
21
73

2.結(jié)構(gòu)體和結(jié)構(gòu)體指針

(1)結(jié)構(gòu)體
  • 一個結(jié)構(gòu)體(struct)就是一組字段(field)。
  • 結(jié)構(gòu)體字段使用點號來訪問。
package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v.X)
}

//運行結(jié)果:4
(2) 結(jié)構(gòu)體指針
  • 結(jié)構(gòu)體字段可以通過結(jié)構(gòu)體指針來訪問。 如果我們有一個指向結(jié)構(gòu)體的指針 p,那么可以通過 (*p).X 來訪問其字段 X。不過這么寫太啰嗦了,所以語言也允許我們使用隱式間接引用,直接寫 p.X 就可以。
package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    p := &v
    p.X = 1e9
    fmt.Println(v)
}

//運行結(jié)果:{1000000000 2}
(3)結(jié)構(gòu)體文法
  • 結(jié)構(gòu)體文法通過直接列出字段的值來新分配一個結(jié)構(gòu)體。
  • 使用 Name: 語法可以僅列出部分字段。(字段名的順序無關(guān)。)
  • 特殊的前綴 & 返回一個指向結(jié)構(gòu)體的指針。
package main
import "fmt"
type Vertex struct {
    X, Y int
}

var (
    v1 = Vertex{1, 2}  // 創(chuàng)建一個 Vertex 類型的結(jié)構(gòu)體
    v2 = Vertex{X: 1}  // Y:0 被隱式地賦予
    v3 = Vertex{}      // X:0 Y:0
    p  = &Vertex{1, 2} // 創(chuàng)建一個 *Vertex 類型的結(jié)構(gòu)體(指針)
)

func main() {
    fmt.Println(v1, p, v2, v3)
}

//運行結(jié)果:{1 2} &{1 2} {1 0} {0 0}

3.數(shù)組

  • 類型 [n]T 表示擁有 n 個 T 類型的值的數(shù)組。
    表達(dá)式 var a [10]int (會將變量 a 聲明為擁有 10 個整數(shù)的數(shù)組。 )
  • 數(shù)組的長度是其類型的一部分,因此數(shù)組不能改變大小。這看起來是個限制,不過沒關(guān)系,Go 提供了更加便利的方式來使用數(shù)組。
package main
import "fmt"
func main() {
    var a [2]string
    a[0] = "Hello"
    a[1] = "World"
    fmt.Println(a[0], a[1])
    fmt.Println(a)

    primes := [6]int{2, 3, 5, 7, 11, 13}
    fmt.Println(primes)
}

----------------------------------------------------------------------------
//運行結(jié)果:
Hello World
[Hello World]
[2 3 5 7 11 13]

4.切片

(1)切片介紹
  • 每個數(shù)組的大小都是固定的。而切片則為數(shù)組元素提供動態(tài)大小的、靈活的視角。在實踐中,切片比數(shù)組更常用。 類型 []T 表示一個元素類型為 T 的切片。

  • 切片通過兩個下標(biāo)來界定,即一個上界和一個下界,二者以冒號分隔: a[low : high] 它會選擇一個半開區(qū)間,包括第一個元素,但排除最后一個元素
    eg: 以下表達(dá)式創(chuàng)建了一個切片,它包含 a 中下標(biāo)從 1 到 3 的元素: a[1:4]

  • 切片并不存儲任何數(shù)據(jù),它只是描述了底層數(shù)組中的一段。

  • 更改切片的元素會修改其底層數(shù)組中對應(yīng)的元素。

  • 與它共享底層數(shù)組的切片都會觀測到這些修改。

package main

import "fmt"

func main() {
    names := [4]string{
        "John",
        "Paul",
        "George",
        "Ringo",
    }
    fmt.Println(names)

    a := names[0:2] //左閉右開
    b := names[1:3]
    fmt.Println(a, b)

    b[0] = "XXX" //更改切片的元素會修改其底層數(shù)組中對應(yīng)的元素
    fmt.Println(a, b)
    fmt.Println(names)
}

----------------------------------------------------------------------------
//運行結(jié)果:
[John Paul George Ringo]
[John Paul] [Paul George]
[John XXX] [XXX George]
[John XXX George Ringo]
(2)切片文法
  • 切片文法類似于沒有長度的數(shù)組文法。
    這是一個數(shù)組文法: [3]bool{true, true, false}
  • 下面這樣則會創(chuàng)建一個和上面相同的數(shù)組,然后構(gòu)建一個引用了它的切片: []bool{true, true, false}
func main() {
    q := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(q)

    r := []bool{true, false, true, true, false, true}
    fmt.Println(r)

    s := []struct {
        i int
        b bool
    }{
        {2, true},
        {3, false},
        {5, true},
        {7, true},
        {11, false},
        {13, true},
    }
    fmt.Println(s)
}

----------------------------------------------------------------------------
//運行結(jié)果:
[2 3 5 7 11 13]
[true false true true false true]
[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
btw 切片的默認(rèn)行為

在進(jìn)行切片時,你可以利用它的默認(rèn)行為來忽略上下界。
切片下界的默認(rèn)值為 0,上界則是該切片的長度。
對于數(shù)組
var a [10]int
來說,以下切片是等價的:
a[0:10]
a[:10]
a[0:]
a[:]

(3)切片的長度和容量

切片擁有 長度 和 容量。
切片的長度就是它所包含的元素個數(shù)。
切片的容量是從它的第一個元素開始數(shù),到其底層數(shù)組元素末尾的個數(shù)。
切片 s 的長度和容量可通過表達(dá)式 len(s) 和 cap(s) 來獲取。

package main
import "fmt"
func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    printSlice(s)

    // 截取切片使其長度為 0
    s = s[:0]
    printSlice(s)

    // 拓展其長度
    s = s[:4]
    printSlice(s)

    // 舍棄前兩個值
    s = s[2:]
    printSlice(s)
    
    //將它擴(kuò)展到超出容量范圍 len = 6 cap = 6  錯誤輸出
    //s := []int{2, 3, 5, 7, 11, 13}
    //printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

--------------------------------------------------------------------------------------------------
//運行結(jié)果:
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
(4)nil切片

切片的零值是 nil。
nil 切片的長度和容量為 0 且沒有底層數(shù)組。

    var s []int
    fmt.Println(s, len(s), cap(s))
//輸出:  [] 0 0
(5)用make創(chuàng)建切片
  • 切片可以用內(nèi)建函數(shù) make 來創(chuàng)建,這也是你創(chuàng)建動態(tài)數(shù)組的方式。
    make 函數(shù)會分配一個元素為零值的數(shù)組并返回一個引用了它的切片:
    a := make([]int, 5) // len(a)=5
    要指定它的容量,需向 make 傳入第三個參數(shù):
    b := make([]int, 0, 5) // len(b)=0, cap(b)=5

    b = b[:cap(b)] // len(b)=5, cap(b)=5
    b = b[1:] // len(b)=4, cap(b)=4

    a := make([]int, 5)   // a len=5 cap=5 [0 0 0 0 0]
    b := make([]int, 0, 5) // b len=0 cap=5 []
    c := b[:2] // c len=2 cap=5 [0 0]
    d := c[2:5] // d len=3 cap=3 [0 0 0]
(6)切片的切片

切片可包含任何類型,甚至包括其它的切片。
eg:二維數(shù)組

package main
import (
    "fmt"
    "strings"
)
func main() {
    // 創(chuàng)建一個井字板(經(jīng)典游戲)
    board := [][]string{
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
    }

    // 兩個玩家輪流打上 X 和 O
    board[0][0] = "X"
    board[2][2] = "O"
    board[1][2] = "X"
    board[1][0] = "O"
    board[0][2] = "X"

    for i := 0; i < len(board); i++ {
        fmt.Printf("%s\n", strings.Join(board[i], " "))
    }
}

-------------------------------------------------------------------------------
//運行結(jié)果:
X _ X
O _ X
_ _ O
(7)切片追加元素

為切片追加新的元素是種常用的操作,為此 Go 提供了內(nèi)建的 append 函數(shù)。內(nèi)建函數(shù)的文檔對此函數(shù)有詳細(xì)的介紹。
append 的第一個參數(shù) s 是一個元素類型為 T 的切片,其余類型為 T 的值將會追加到該切片的末尾。
append 的結(jié)果是一個包含原切片所有元素加上新添加元素的切片。
當(dāng) s 的底層數(shù)組太小,不足以容納所有給定的值時,它就會分配一個更大的數(shù)組。返回的切片會指向這個新分配的數(shù)組。

more: Go 切片:用法和本質(zhì)

package main
import "fmt"
func main() {
    var s []int
    printSlice(s)

    // 添加一個空切片
    s = append(s, 0)
    printSlice(s)

    // 這個切片會按需增長
    s = append(s, 1)
    printSlice(s)

    // 可以一次性添加多個元素
    s = append(s, 2, 3, 4)
    printSlice(s)
}
func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

------------------------------------------------------------------------------------
//運行結(jié)果:
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4]

5.Range

  • for 循環(huán)的 range 形式可遍歷切片或映射。
  • 當(dāng)使用 for 循環(huán)遍歷切片時,每次迭代都會返回兩個值。第一個值為當(dāng)前元素的下標(biāo),第二個值為該下標(biāo)所對應(yīng)元素的一份副本。
var pow = []int{1, 2, 4, 8, 16}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
}

//結(jié)果
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
range續(xù)
  • 可以將下標(biāo)或值賦予 _ 來忽略它。
    for i, _ := range pow
    for _, value := range pow
  • 若你只需要索引,忽略第二個變量即可。
    for i := range pow
package main
import "fmt"
func main() {
    pow := make([]int, 5)
    for i := range pow {
        pow[i] = 1 << uint(i) // == 2**i
    }
    for _, value := range pow {
        fmt.Printf("%d\n", value)
    }
}

---------------------------------------------------------------------------------
//運行結(jié)果:
1
2
4
8
16

* btw, 
uint(i)代表將i強轉(zhuǎn)成uint類型,也就是無符號整型。
1<<uint(0) //1左移0位是1
1<<uint(1)//1左移1位是1*2=2
1<<uint(2)//1左移2位是1*2*2=4

6.映射 ('map')

(1)基本用法
  • map映射將鍵映射到值。
    映射的零值為 nil 。nil 映射既沒有鍵,也不能添加鍵。
    make 函數(shù)會返回給定類型的映射,并將其初始化備用。
package main
import "fmt"
type Vertex struct {
    Lat, Long float64
}
var m map[string]Vertex
func main() {
    m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])
} 

--------------------------------------------------------------------------------
//運行結(jié)果:
{40.68433 -74.39967}
(2)映射的文法

映射的文法與結(jié)構(gòu)體相似,不過必須有鍵名。
若頂級類型只是一個類型名,你可以在文法的元素中省略它

package main
import "fmt"
type Vertex struct {
    Lat, Long float64
}
var m = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    }, // 也可寫成 "Bell Labs": {40.68433, -74.39967},
    "Google": Vertex{ 37.42202, -122.08408 }, // "Google":    {37.42202, -122.08408},
}

func main() {
    fmt.Println(m)
}

--------------------------------------------------------------------------------------
//運行結(jié)果:
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
(3)修改映射
  • 在映射 m 中插入或修改元素:
    m[key] = elem
  • 獲取元素:
    elem = m[key]
  • 刪除元素:
    delete(m, key)
  • 通過雙賦值檢測某個鍵是否存在:
    elem, ok = m[key]
    若 key 在 m 中,ok 為 true ;否則,ok 為 false。
    若 key 不在映射中,那么 elem 是該映射元素類型的零值。
    同樣的,當(dāng)從映射中讀取某個不存在的鍵時,結(jié)果是映射的元素類型的零值。
    注 :若 elem 或 ok 還未聲明,你可以使用短變量聲明:
    elem, ok := m[key]
package main
import "fmt"
func main() {
    m := make(map[string]int)

    m["Answer"] = 42
    fmt.Println("The value:", m["Answer"])

    m["Answer"] = 48 //修改
    fmt.Println("The value:", m["Answer"])

    delete(m, "Answer") //刪除
    fmt.Println("The value:", m["Answer"])

    v, ok := m["Answer"] 
//通過雙賦值檢測某個鍵是否存在 v,ok 僅僅只是一個表示符 也可換成其他的字母 不過記得把Println里面的v,ok換成對應(yīng)的字母就可以了。
    fmt.Println("The value:", v, "Present?", ok)
}

// 運行結(jié)果;
The value: 42
The value: 48
The value: 0
The value: 0 Present? false

7.函數(shù)值和函數(shù)閉包

(1)函數(shù)值

函數(shù)也是值。它們可以像其它值一樣傳遞。
函數(shù)值可以用作函數(shù)的參數(shù)或返回值。

package main 
import (    "fmt"   "math") 
func compute(fn func(float64, float64) float64) float64 {   
      return fn(3, 4)
      }
func main() {   
      hypot := func(x, y float64) float64 {     
      return math.Sqrt(x*x + y*y)   
       }    
fmt.Println(hypot(5, 12))   //調(diào)用hypot函數(shù),輸出13    
fmt.Println(compute(hypot))  //將hypot函數(shù)作為compute函數(shù)的參數(shù),使用3,4作為調(diào)用hypot函數(shù)的參數(shù)調(diào)用,結(jié)果為5    
fmt.Println(compute(math.Pow))  //將math.Pow函數(shù)作為compute函數(shù)的參數(shù),使用3,4作為調(diào)用math.Pow函數(shù)的參數(shù)調(diào)用,結(jié)果為81
}
(2)函數(shù)閉包

*用遞歸實現(xiàn)斐波那契數(shù)列

package main
import "fmt"
func fibonacci(n int) int {
  if n < 2 {
   return n
  }
  return fibonacci(n-2) + fibonacci(n-1)
}
func main() {
    var i int
    for i = 0; i < 10; i++ {
       fmt.Printf("%d\t", fibonacci(i))
    }
}

// 運行結(jié)果: 0    1    1    2    3    5    8    13    21    34
  • 函數(shù)閉包實現(xiàn)斐波那契額數(shù)列
package main

import "fmt"

const LIM = 10

func main() {
    f := fibonacci() //返回一個閉包函數(shù)
    var array [LIM]int
    for i := 0; i < LIM; i++ {
        array[i] = f()
    }
    fmt.Println(array)
}

func fibonacci() func() int {
    back1, back2 := 0, 1
    return func() int {
        // 重新賦值
        back1, back2 = back2, (back1 + back2)
        return back1
    }
}

//運行結(jié)果:[1 1 2 3 5 8 13 21 34 55]
(2)閉包
  • 閉包是匿名函數(shù)與匿名函數(shù)所引用環(huán)境的組合。匿名函數(shù)有動態(tài)創(chuàng)建的特性,該特性使得匿名函數(shù)不用通過參數(shù)傳遞的方式,就可以直接引用外部的變量。這就類似于常規(guī)函數(shù)直接使用全局變量一樣,個人理解為:匿名函數(shù)和它引用的變量以及環(huán)境,類似常規(guī)函數(shù)引用全局變量處于一個包的環(huán)境。
    CSDN閉包博文
func main() {
    n := 0
    f := func() int {
        n += 1
        return n
    }
    fmt.Println(f())  // 別忘記括號,不加括號相當(dāng)于地址
    fmt.Println(f())
}
/*
輸出:
1
2
*/

在上述代碼中,

n := 0
f := func() int {
    n += 1
    return n
}

就是一個閉包,類比于常規(guī)函數(shù)+全局變量+包。f不僅僅是存儲了一個函數(shù)的返回值,它同時存儲了一個閉包的狀態(tài)。

(3)閉包作為函數(shù)返回值

匿名函數(shù)作為返回值,不如理解理解為閉包作為函數(shù)的返回值,如下代碼:

func Increase() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}

func main() {
    in := Increase()
    fmt.Println(in())
    fmt.Println(in())
}
/*
輸出:
1
2
*/

閉包被返回賦予一個同類型的變量時,同時賦值的是整個閉包的狀態(tài),該狀態(tài)會一直存在外部被賦值的變量in中,直到in被銷毀,整個閉包也被銷毀。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內(nèi)容