UP | HOME

复杂类型

Table of Contents

指针

指针保存了变量的内存地址

类型 *T 是指向类型 T 的值的指针,其原始值是nil

var p *int

&符号会生成一个指向其作用对象的指针

i := 42
p = &i

*符号表示指针指向的底层的值, 这也就是通常所说的“间接引用”或“非直接引用”

fmt.Println(*p) // 通过指针 p 读取 i
*p = 21         // 通过指针 p 设置 i

注意:go没有指针算术运算!

结构体

struct 就是一个字段的集合

type Vertex struct {
        X int
        Y int
}

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

结构体字段

结构体字段使用 . 来访问

type Vertex struct {
        X int
        Y int
}

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

结构体指针

结构体字段可以通过结构体指针来访问,通过指针间接的访问是透明的。

注意:和C语言不一样,通过结构体指针访问字段依然用 . 不是用->

type Vertex struct {
        X int
        Y int
}

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

结构体文法

通过结构体字段的值作为列表来定义一个结构体。使用 Name: 语法可以仅列出部分字段。特殊的前缀 & 返回一个指向结构体的指针

type Vertex struct {
        X, Y int
}

var (
        v1 = Vertex{1, 2}  // 类型为 Vertex
        v2 = Vertex{X: 1}  // Y:0 被省略
        v3 = Vertex{}      // X:0 和 Y:0
        p  = &Vertex{1, 2} // 类型为 *Vertex
)

数组

类型 [n]T 是一个有 n 个类型为 T 的值的数组

//定义变量 a 是一个有十个整数的数组 
  var a [10]int

数组的长度是其类型的一部分,因此数组不能改变大小!

slice

slice会指向一个列表的值,并且包含了长度信息

//一个元素类型为 T 的 slice
var a []T s
//返回 slice s 的长度
len(s)

slice 可以包含任意的类型,包括另一个 slice

// Create a tic-tac-toe board.
game := [][]string{
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
}

// The players take turns.
// X _ X
// O _ _
// X _ O
game[0][0] = "X"
game[2][2] = "O"
game[2][0] = "X"
game[1][0] = "O"
game[0][2] = "X"

构造切片

slice 由函数 make 创建。这会分配一个全是零值的数组并且返回一个 slice 指向这个数组:

//slice a的长度是5,cap是无限
a := make([]int, 5)  // len(a)=5, cap(a)=5

//为了指定容量,可传递第三个参数到 make:
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

重新切片

slice 可以重新切片,创建一个新的 slice 值指向相同的数组

//表示从 lo 到 hi-1 的 slice 元素,含前端,不包含后端
s[lo:hi]

//是空的
s[lo:lo]

//包含s[lo]这个元素
s[lo: lo + 1]

空切片

未初始化的slice 值是 nil ,一个 nil 的 slice 的长度和容量是 0

var z []int
//[] 0 0
fmt.Println(z, len(z), cap(z))
//nil!
if z == nil {
        fmt.Println("nil!")
}

向切片添加元素

内建函数 append :向slice末尾添加元素

func append(s []T, vs ...T) []T
  • s 是一个元素类型为 T 的 slice ,其余类型为 T 的值将会附加到该 slice 的末尾
  • 返回是一个包含原 slice 所有元素加上新添加的元素的 slice
  • 如果 s 的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。 返回的 slice 会指向这个新分配的数组

range

for 循环的 range 格式可以对 slice 或者 map 进行迭代循环

当使用 for 循环遍历一个 slice 时,每次迭代 range 将返回两个值。 第一个是当前下标(序号),第二个是该下标所对应元素的一个拷贝!

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
// 2**0 = 1
// 2**1 = 2
// 2**2 = 4
// 2**3 = 8
// 2**4 = 16
// 2**5 = 32
// 2**6 = 64
// 2**7 = 128
func main() {
        //i是当前下标,v是当前下标对应元素的一个拷贝
        for i, v := range pow {
                fmt.Printf("2**%d = %d\n", i, v)
        }
}

可以通过赋值给 _ 来忽略序号和值,如果只需要索引值,去掉 “ , value ” 的部分即可

pow := make([]int, 10)
//如果只需要索引值,去掉 “ , value ” 的部分
for i := range pow {
        pow[i] = 1 << uint(i)
}
//通过赋值给 _ 来忽略序号和值
for _, value := range pow {
        fmt.Printf("%d\n", value)
}

map

map 在使用之前必须用 make 来创建。值为 nil 的 map 是空的,并且不能对其赋值!

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"])
}

map文法

map 的文法跟结构体文法相似,不过必须有键名

type Vertex struct {
        Lat, Long float64
}

var m = map[string]Vertex{
        "Bell Labs": Vertex{
                40.68433, -74.39967,
        },
        "Google": Vertex{
                37.42202, -122.08408,
        },
}

若顶级类型只是一个类型名,你可以在文法的元素中省略它

type Vertex struct {
        Lat, Long float64
}

var m = map[string]Vertex{
        "Bell Labs": {40.68433, -74.39967},
        "Google":    {37.42202, -122.08408},
}

修改map

  • 在 map m 中插入或修改一个元素
m[key] = elem
  • 获得元素
elem = m[key]
  • 删除元素
    delete(m, key)
    
  • 通过双赋值检测某个键是否存在
//如果 key 在 m 中, ok 为 true。否则, ok 为 false,并且 elem 是 map 的元素类型的零值 
elem, ok = m[key]
m := make(map[string]int)
m["Answer"] = 42
//The value: 42
fmt.Println("The value:", m["Answer"])

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

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

v, ok := m["Answer"]
//The value: 0 Present? false
fmt.Println("The value:", v, "Present?", ok)

函数指针

函数也是值,可以像其他值一样传递,比如,函数值可以作为函数的参数或者返回值

注意:不需要向C一样显示声明函数指针,在Go中这是透明的

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)
        }
        //13 调用hypot函数:sqrt(5 * 5 + 12 * 12)
        fmt.Println(hypot(5, 12))
        //5 把hypot函数作为参数传递给compute: sqrt(3*3 + 4 * 4)
        fmt.Println(compute(hypot))
        //81 把Math.Pow作为参数传递给compute: pow(3, 4)
        fmt.Println(compute(math.Pow))
}

函数闭包

函数可以是一个闭包。闭包是一个函数值,它引用了函数体之外的变量。 函数可以对这个引用的变量进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。

//函数 adder 返回一个闭包,每个返回的闭包都被绑定到其各自的 sum 变量上
func adder() func(int) int {
        sum := 0
        //这个匿名函数可以对sum变量进行访问和赋值,这个函数就像是被绑定在sum变量上
        return func(x int) int {
                sum += x
                return sum
        }
}

func main() {
        pos, neg := adder(), adder()
        // 0 0
        // 1 -2
        // 3 -6
        // 6 -12
        // 10 -20
        for i := 0; i < 5; i++ {
                fmt.Println(
                        pos(i),
                        neg(-2*i),
                )
        }
}

Next:接口和方法

Previous:流程控制

Home:目录