Go 之旅三: 復雜類型

原文鏈接 http://ironxu.com/709

本文是學習 A Tour of Go (中文參考 Go 之旅中文 ) 整理的筆記,介紹Go 語言的指針,結構體,數組,切片,映射和閉包的基本概念和使用。

1. 指針

$GOPATH/src/go_note/gotour/advancetype/pointer/pointer.go 源碼如下:

/**
 * go 語言指針
 */

package main

import (
    "fmt"
)

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

    p := &i
    fmt.Println(*p) // 42
    *p = 21
    fmt.Println(i) // 21

    p = &j
    *p = *p / 4
    fmt.Println(j) // 9
}

Go 具有指針。 指針保存了變量的內存地址。類型 *T 是指向 T 類型值的指針,其零值為 nil 。

var p *int

& 操作符會生成一個指向其操作數的指針。

i := 42
p = &i

* 操作符表示指針指向的底層值。

fmt.Println(*p) // 通過指針 p 讀取 i
*p = 21         // 通過指針 p 設置 i

2. 結構體

$GOPATH/src/go_note/gotour/advancetype/struct/struct.go 源碼如下:

package main

import (
    "fmt"
)

type Point struct {
    X int
    Y int
}

var (
    p1  = Point{1, 2}
    p2  = Point{X: 1}
    p3  = Point{}
    pt1 = &Point{1, 2}
)

func main() {
    fmt.Println(Point{1, 2})

    p := Point{3, 4}
    p.X = 4 // 結構體字段使用點號來訪問
    fmt.Println(p, p.Y)

    pt := &p
    pt.X = 5 // 使用隱式間接引用,直接寫 `pt.X`
    (*pt).Y = 6 // 通過 `(*pt).Y` 來訪問其字段 `Y`
    fmt.Println(p)
}

一個結構體( struct )就是一個字段的集合,結構體字段使用點號來訪問。

2.1 結構體指針

結構體字段可以通過結構體指針來訪問。

pt := &p
pt.X = 5
(*pt).Y = 6

如果我們有一個指向結構體的指針 pt ,那么可以通過 (*pt).X 來訪問其字段 X,也可以使用隱式間接引用,直接寫 pt.X

2.2 結構體語法

結構體文法通過直接列出字段的值來新分配一個結構體。

使用 Name: 語法可以僅列出部分字段。(字段名的順序無關。)

特殊的前綴 & 返回一個指向結構體的指針。

var (
    p1  = Point{1, 2}
    p2  = Point{X: 1}
    p3  = Point{}
    pt1 = &Point{1, 2}
)

3. 數組

$GOPATH/src/go_note/gotour/advancetype/array/array.go 源碼如下:

/**
 * go 語言數組
 */

package main

import (
    "fmt"
)

func main() {
    var a [2]string
    a[0] = "hello"
    a[1] = "world"
    fmt.Println(a, a[0])

    list := [10]int{1, 2, 3, 4}
    fmt.Println(list)
}

類型 [n]T 表示擁有 nT 類型的值的數組。

表達式

var a [10]int // 變量 a 聲明為擁有 10 個整數的數組

數組的長度是其類型的一部分,因此數組不能改變大小

4. 切片

$GOPATH/src/go_note/gotour/advancetype/slice/slice.go 源碼如下:

/**
 * go 語言 slice
 */
package main

import (
    "fmt"
    "strings"
)

func main() {
    primes := [6]int{1, 2, 3, 4, 5, 6}
    var s []int = primes[1:4]
    fmt.Println(s) // [2 3 4]

    names := [4]string{"john", "paul", "george", "ringo"}
    fmt.Println(names) // [john paul george ringo]

    a := names[0:2]
    b := names[1:3]
    fmt.Println(a, b) // [john paul] [paul george]

    b[0] = "xxx"             // 更改切片的元素會修改其底層數組中對應的元素,并且與它共享底層數組的切片都會觀測到這些修改
    fmt.Println(a, b, names) // [john xxx] [xxx george] [john xxx george ringo]

    // 默認分片上下界
    i := []int{1, 2, 3, 4}
    fmt.Println(i[0:4]) // [1 2 3 4]
    fmt.Println(i[:4])  // [1 2 3 4]
    fmt.Println(i[0:])  // [1 2 3 4]
    fmt.Println(i[:])   // [1 2 3 4]

    // 長度與容量
    fmt.Printf("len=%d, cap=%d, %v\n", len(i[:4]), cap(i[:4]), i[:4])    // len=4, cap=4, [1 2 3 4]
    fmt.Printf("len=%d, cap=%d, %v\n", len(i[2:4]), cap(i[2:4]), i[2:4]) // len=2, cap=2, [3 4]

    // nil 切片
    var j []int
    fmt.Printf("len=%d, cap=%d, %v\n", len(j), cap(j), j) // len=0, cap=0, []

    // make 創建切片
    a_m := make([]int, 5)
    b_m := make([]int, 0, 5)
    fmt.Printf("len=%d, cap=%d, %v\n", len(a_m), cap(a_m), a_m) // len=5, cap=5, [0 0 0 0 0]
    fmt.Printf("len=%d, cap=%d, %v\n", len(b_m), cap(b_m), b_m) // len=0, cap=5, []

    // 包含切片的切片
    board := [][]string{
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
    }
    for i := 0; i < len(board); i++ {
        fmt.Printf("%s\n", strings.Join(board[i], " "))
    }

    // 向切片追加元素
    var s_p []int
    fmt.Printf("len=%d, cap=%d, %v\n", len(s_p), cap(s_p), s_p) // len=0, cap=5, []

    s_p = append(s_p, 1)
    s_p = append(s_p, 2)
    fmt.Printf("len=%d, cap=%d, %v\n", len(s_p), cap(s_p), s_p) // len=0, cap=5, []
}

每個數組的大小都是固定的,而切片則提供動態數組,類型 []T 表示一個元素類型為 T 的切片。切片類似不限定長度的數組。

4.1 切片是數組的引用

切片并不存儲任何數據,它只是描述了底層數組中的一段。更改切片的元素會修改其底層數組中對應的元素,并且與它共享底層數組的切片都會觀測到這些修改。

如下創建一個切片時,會先創建數組,然后構建一個引用了它的切片

[]bool{true, true, false}

4.2 切片的默認行為

在進行切片時,切片有默認上下界。切片下界的默認值為 0 ,上界則是該切片的長度。

對于數組

var a [10]int

來說,以下切片是等價的:

a[0:10]
a[:10]
a[0:]
a[:]

4.3 切片的長度與容量

切片擁有 長度容量

  • 長度就是它所包含的元素個數。
  • 容量是從它的第一個元素開始數,到其底層數組元素末尾的個數。

切片 s 的長度和容量可通過表達式 len(s)cap(s) 來獲取。

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

4.4 make 創建切片

切片可以用內建函數 make 來創建,這也是你創建動態數組的方式。make 函數會分配一個元素為零值的數組并返回一個引用了它的切片:

a := make([]int, 5)  // len(a)=5

要指定它的容量,需向 make 傳入第三個參數:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

4.5 切片的切片

切片可包含任何類型,甚至包括其它的切片

board := [][]string{
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
}

4.6 append 向切片追加元素

為切片追加新的元素是種常用的操作,為此 Go 提供了內建的 append 函數

func append(s []T, vs ...T) []T

append 的第一個參數 s 是一個元素類型為 T 的切片, 其余類型為 T 的值將會追加到該切片的末尾。append 的結果是一個包含原切片所有元素加上新添加元素的切片。當 s 的底層數組太小,不足以容納所有給定的值時,它就會分配一個更大的數組。 返回的切片會指向這個新分配的數組。

5. range

$GOPATH/src/go_note/gotour/advancetype/range/range.go 源碼如下:

/**
 * go 語言 range用法
 */
package main
import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
    // 2**0 = 1
    // 2**1 = 2
    // 2**2 = 4
    // 2**3 = 8
    // 2**4 = 16
    // 2**5 = 32

    s := make([]int, 5)
    for i := range s {
        s[i] = 1 << uint(i) // == 2**i
    }
    for _, v := range s {
        fmt.Printf("%d\n", v)
    }
}

for 循環的 range 形式可遍歷切片或映射。

當使用 for 循環遍歷切片時,每次迭代都會返回兩個值。 第一個值為當前元素的下標,第二個值為該下標所對應元素的一份副本。

可以將下標或值賦予 _ 來忽略它, 若你只需要索引,去掉 , value 的部分即可。

6. 映射

$GOPATH/src/go_note/gotour/advancetype/map/map.go 源碼如下:

/**
 * go 語言映射
 */
package main
import (
    "fmt"
)

type Vertex struct {
    Lit, Log float64
}

var m map[string]Vertex

var m1 = map[string]Vertex{ // 定義并初始化一個映射
    "Bell Labs": Vertex{
        40.123, -74.123,
    },
    "Google": Vertex{
        37.123, -122.123,
    },
}

var m2 = map[string]Vertex{ // 當頂級域名只是一個類型名時,可以在定義語句中省略它
    "Bell Labs": {40.123, -74.123},
    "Google": {37.123, -74.123},
}

func main() {
    m = make(map[string]Vertex) // make 返回給定類型的映射,并將其初始化備用
    m["Bell Labs"] = Vertex{40.123, -74.123}
    fmt.Println(m["Bell Labs"], m2)

    user := make(map[string]int)
    user["Age"] = 42
    fmt.Println(user["Age"])

    delete(user, "Age")
    fmt.Println(user["Age"])

    v, ok := user["Age"]
    fmt.Println("The Age:", v, "Presen?", ok)
}

映射將鍵映射到值, 映射的零值為 nil, nil 映射既沒有鍵,也不能添加鍵。make 函數會返回給定類型的映射,并將其初始化備用。

6.1 修改映射

在映射 m 中插入或修改元素:

m[key] = elem

獲取元素:

elem = m[key]

刪除元素:

delete(m, key)

通過雙賦值檢測某個鍵是否存在:

elem, ok = m[key]

keym 中, oktrue ;否則, okfalse

key 不在映射中,那么 elem 是該映射元素類型的零值。同樣的,當從映射中讀取某個不存在的鍵時,結果是映射的元素類型的零值。

7. 閉包

$GOPATH/src/go_note/gotour/advancetype/closure/closure.go 源碼如下:

/**
 * go 語言閉包
 */
package main

import (
    "fmt"
    "math"
)

func compute(fn func(float64, float64) float64) float64{
    return fn(3, 4)
}

// adder 返回一個閉包, 每個閉包都被綁定在其各自的 sum 變量上。
func adder() func (int) int {
    sum := 0
    return func (x int) int {
        sum += x
        return sum
    }
}

func main() {
    hypot := func (x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }

    fmt.Println(hypot(5, 12)) // 13
    fmt.Println(compute(hypot)) // 5
    fmt.Println(compute(math.Pow)) // 81

    pos, neg := adder(), adder()
    fmt.Printf("%v\n", pos)

    for i := 0; i < 10; i++ {
        fmt.Println(i, pos(i), -2*i, neg(-2*i))
    }
}

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

Go 函數可以是一個閉包。閉包是一個函數值,它引用了其函數體之外的變量。該函數可以訪問并賦值其引用的變量的值,也即該函數被“綁定”在了這些變量上。

參考

@剛剛小碼農

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 出處---Go編程語言 歡迎來到 Go 編程語言指南。本指南涵蓋了該語言的大部分重要特性 Go 語言的交互式簡介,...
    Tuberose閱讀 18,501評論 1 46
  • 指針是C語言中廣泛使用的一種數據類型。 運用指針編程是C語言最主要的風格之一。利用指針變量可以表示各種數據結構; ...
    朱森閱讀 3,473評論 3 44
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,536評論 1 51
  • 一早上起來心血來潮,我又在網上發表了一篇自己寫的日記,很快就有個朋友來消息跟我說:你覺得你的文章寫得很好嗎?我說:...
    張竹青yes閱讀 442評論 13 11
  • 我是擔心在這個解不開的死結下繼續讓南都維持,受損害的不僅僅是業主 南都何嘗不是損失者?如此下去,影響越來越差,現在...
    肖華_71ef閱讀 221評論 0 0