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 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋值其引用的变量的值,也即该函数被“绑定”在了这些变量上。

参考

@刚刚小码农

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • 出处---Go编程语言 欢迎来到 Go 编程语言指南。本指南涵盖了该语言的大部分重要特性 Go 语言的交互式简介,...
    Tuberose阅读 18,399评论 1 46
  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,424评论 3 44
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,506评论 1 51
  • 一早上起来心血来潮,我又在网上发表了一篇自己写的日记,很快就有个朋友来消息跟我说:你觉得你的文章写得很好吗?我说:...
    张竹青yes阅读 427评论 13 11
  • 我是担心在这个解不开的死结下继续让南都维持,受损害的不仅仅是业主 南都何尝不是损失者?如此下去,影响越来越差,现在...
    肖华_71ef阅读 136评论 0 0