Go指南笔记_03(复杂类型)

请注意,本文编写于 641 天前,最后修改于 191 天前,其中某些信息可能已经过时。

Go指南

指针

Go语言中具有指针,但是并没有类似C语言中的指针运算.

指针保存着变量的内存地址.比如: *T表示了指向T类型的值得指针,其零值为nil

&符号会生成一个指向其作用对象的指针, *符号表示取出指针指向内存地址里面存储的值.

package main

import "fmt"

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

    p := &i         // 生成了一个指向i内存地址的指针
    fmt.Println(*p) // *p表示访问p指针指向的那块内存区域中存储的值也就是i的值
    *p = 21         // 在p指针指向的内存区域(也就是i的内存区域)中写入值21 此时i的值是21
    fmt.Println(i)  
    p = &j         // 生成一个指向j内存区域的指针p(此时p指针指向了j的内存区域)
    *p = *p / 37   // 取出j内存区域的值除以37 然后再写入到p指向的内存区域中(也就是j的内存区域)     
    fmt.Println(j)  
}

结构体

一个结构体就是一个字段的集合.

结构体字段使用点号来访问.

结构体指针

结构体字段可以通过结构体指针来访问

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

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

上面代码的理解(尚不知对错,如有指教,不胜感激):p := v 此时p的内存区域被写入了结构体v,并不是 p := &v 这样p指向了v的内存地址,所以操作的不是一块内存区域,所以p.X的赋值对v本身没有影响.

结构体文法

结构体文法表示通过结构体字段的值来作为列表来分配一个结构体,使用Name: 语法可以仅列出部分字段.

package main

import "fmt"

type Vertex struct {
    X, Y int
}

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

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

数组

类型[n]T 表示一个有n个类型为T的值得数组.数组的长度是其类型的一部分,因此在Go语言中数组不能改变大小.

slice

slice(切片)是一个会指向一个序列的值,它包含了长度信息.

语句var a []string定义了一个有未知个数的sting类型值得slice.

函数len(v)提供了获取slice长度的方法.

slice中可以包含任意类型的值,包括slice

slice的零值是nil, 一个nil的slice长度和容量都是0

package main

import (
    "fmt"
    "strings"
)

func main() {
    // 创建一个存放slice的slice,被存放slice中的值得类型为string
    game := [][]string{
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
        []string{"_", "_", "_"},
    }

    // 先取slice中的slice 再按其下标赋值
    game[0][0] = "X"
    game[2][2] = "O"
    game[2][0] = "X"
    game[1][0] = "O"
    game[0][2] = "X"

    printBoard(game)
}

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

对slice切片

slice可以重新切片,创建新的slice值指向相同的数组, 使用 : 来进行切片,对slice进行切片之后不会改变原slice.

s[lo:hi] 表示从lo到hi-1的slice数组,是一个前闭后开区间. 如果省略前面的下表,表示从零开始,省略后面的则表示到len(s)处结束.

构造slice

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

a := make([]int, 5)创建一个长度为5的slice,可以在make函数的第三个值处传入指定slice容量的值.

当对slice赋值的时候超过了指定的长度和容量就会报错

向slice添加元素

Go中提供append()内建函数来实现向slice的末尾添加元素的操作.

package main

import "fmt"

func main() {
    var a []int
    printSlice("a", a)

    // append works on nil slices.
    a = append(a, 0)
    printSlice("a", a)

    // the slice grows as needed.
    a = append(a, 1)
    printSlice("a", a)

    // we can add more than one element at a time.
    a = append(a, 2, 3, 4)
    printSlice("a", a)
}

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

执行上述代码会发现,当向一个空的slice中添加一个元素的时候,其容量从0变为了2. 这是因为:append内建函数将元素追加到切片的末尾,若他又足够的容量,其目标就会重新切片以容纳新的元素.否则,就会分配一个新的基本数组.append返回更新后的数组,因此必须存储追加后的结果,通常为包含该切片自身的变量

range

Go语言中的for循环的range格式可以对slice或者map进行迭代循环.

使用for循环遍历一个slice时,每次迭代range将会返回两个值,第一个是下标,第二个是该下标对应的元素的一个拷贝.当使用range返回两个值中有不需要用的到可以通过赋值给 _ 来忽略下标或值,如果只需要下标则第二个参数值可以直接去掉不写.

map

map 是一种映射(键值一一对应的)

map在使用之前必须用make来创建.

值是nil的map是空的,并且不能对其赋值.

map文法

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

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

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


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

修改map

在map中插入或修改一个元素:

m[key] = elem

获得元素

elem = m[key]

删除元素

delete(m, key)

通过双赋值检测某个键的存在

elme, ok = m[key]

上面的语句返回了两个值,ok表示key是否在m中,如果存在,第一个是key对应的value值,否则elem是map的元素类型的零值.当从map中读取某个不存在键时,结果是map的元素类型的零值.

实现 WordCount。它应当返回一个含有 s 中每个 “词” 个数的 map。函数 wc.Test 针对这个函数执行一个测试用例,并输出成功还是失败。

package main

import (
    "golang.org/x/tour/wc"
    "strings"
)

func WordCount(s string) map[string]int {
    list := strings.Fields(s)
    m := make(map[string]int)
    for i:=0; i < len(list); i++ {
        if m[list[i]] == 0 {
            m[list[i]] = 1
        }else{
            m[list[i]]++
        }
    }
    return m
}

func main() {
    wc.Test(WordCount)
}


strings.Fields(),strings包中的Fields()函数可以返回一个字符串中用空格分割而成的list.

遍历list 如果没有list中的元素没有出现过 那么他在map中的默认值为0

函数值

在Go语言中 函数也是值, 他们可以像其他值一样传递,作为函数的参数或者返回值

函数的闭包

Go函数可以是一个闭包.闭包是一个函数值,它引用了函数体之外的变量.这个函数可以对这个引用变量进行访问和赋值,也就是这个函数被绑定在这个变量上.

package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

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

打印值为: 
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90

理解: 例子中在main函数的for循环体之外初始化了两个变量,这两个变量是两个闭包,也就是说闭包函数绑定在了这两个变量上了.变量在for循环体中执行的每一次都会记住上次执行的值.这里就可以将两个闭包想象成两个变量来理解,是想如果pos 和 neg分别为1,2,那么for循环每次执行的时候pos和neg的值是不是会相应的增加呢?

Comments

添加新评论