Go 语言极速入门3 - 内建容器

内建容器

  • Array 数组
  • Slice 切片,是 Array 的一个 view
  • Map 映射

一、Array 数组

  • [10]int 和 [20]int 是不同类型,数组类型相同要长度和元素类型完全相同才可以
  • func loopArray(arr2 [3]int) 是值传递,入参会拷贝一份数组,所以如果数组很大,从内存和性能上函数传递数组值都是很大的开销,需要避免(使用指针可以实现"引用传递" func loopArray(arr2 *[3]int),调用方传入 &arr2
  • 在 Go 中一般不直接使用数组,而是使用切片
  • 数组是定长的,不可扩展,切片相当于动态数组
func defineArray() [3]int {
    // 定义数组,不赋初值(使用默认值)
    var arr1 [5]int // [0 0 0 0 0]
    // 定义数组,赋初值
    arr2 := [3]int{1, 2, 3} // [1 2 3]
    // 定义数组,由编译器来计算长度,不可写成[],不带长度或者 ... 的表示切片
    arr3 := [...]int{4, 5, 6, 7} // [4 5 6 7]
    // 创建指针数组
    arr4 := [2]*string{new(string), new(string)}
    *arr4[0] = "hello"
    *arr4[1] = "go"
    // 为指定索引位置设置值
    arr5 := [3]int{1:10} // [0,10,0]
    // 二维数组
    var grid [4][5]int // [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
    // 数组拷贝,直接复制一份 arr2 给 arr6
    arr6 := arr2

    fmt.Println(arr1, arr2, arr3, arr4, arr5, arr6, grid)// arr4 打印出来的是地址 [0xc00000e1e0 0xc00000e1f0]
    fmt.Println(*arr4[0]) // hello
    return arr2
}

// 数组是值传递,这里的入参会拷贝一份数组(使用指针可以实现"引用传递")
func loopArray(arr2 [3]int) {
    // 通用方法
    for i := 0; i < len(arr2); i++ {
        fmt.Println(arr2[i])
    }
    // 最简方法,只获取数组下标
    for i := range arr2 {
        fmt.Println(arr2[i])
    }
    // 最简方法,获取数组下标和对应的值
    for i, v := range arr2 {
        fmt.Println(i, v)
    }
    // 最简方法,只获取值,使用 _ 省略变量
    for _, v := range arr2 {
        fmt.Println(v)
    }
}

二、Slice 切片

  • 切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。切片的动态增长是通过内置函数 append 来实现的。这个函数可以快速且高效地增长切片。还可以通过对切片再次切片来缩小一个切片的大小。
  • 切片有 3 个字段分别是指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)


    image.png
  • 从切片 slice1 创建出来的切片 slice2,slice1 和 slice2 共享底层数组,一个修改了共享部分的元素,另一个也会感知

2.1、创建切片

    // 1、使用make函数创建一个字符串切片,长度和容量都是5
    slice1 := make([]string, 5)
    // 2、创建一个int切片,长度是3,容量是5
    slice2 := make([]int, 3, 5)
    // 3、使用字面量创建切片,长度是3,容量是3
    slice3 := []int{1, 2, 3} // [3]int{1, 2, 3}
    // 4、创建 nil 切片,长度为0,容量为0
    var slice4 []int
    // 5、创建空切片,长度为0,容量为0
    slice5 := make([]int, 0)
    slice6 := []int{}

    // 6、自定义底层数组,通过该底层数组创建切片
    arr := [5]int{1, 2, 3, 4, 5}
    // 数组转化为切片,左闭右开 [arr[2]~arr[4])
    slice7 := arr[2:4] // [3,4]
    slice8 := arr[2:]  // [3,4,5]
    slice9 := arr[:4]  // [1,2,3,4]
    slice10 := arr[:]   // [1,2,3,4,5]

实际上还有第七种创建切片的方式:根据切片创建切片,称为 reslice

2.2、切片基本使用

    slice1 := []int{1, 2, 3, 4, 5}
    // 1、根据索引获取切片元素
    fmt.Println(slice1[1]) // 2
    // 2、根据索引修改切片元素
    slice1[3] = 400
    fmt.Println(slice1) // [1, 2, 3, 400, 5]
    // 3、根据切片创建切片,和根据自定义数组创建切片方式相同,长度是2=3-1,容量是4=5-1
    // 但是需要格外注意,新生成的切片 slice2 和原始切片 slice1 的指针元素指向了相同的底层数组,所以修改元素要注意
    slice2 := slice1[1:3] // [2, 3]
    slice2[1] = 300
    fmt.Println(slice2) // [2, 300]
    fmt.Println(slice1) // [1, 2, 300, 400, 5] slice1也发生了变化
    // 4、拷贝 slice 中的元素
    fmt.Println("copy")
    slice3 := []int{0, 0, 0, 0, 0}
    slice4 := []int{1, 2, 3}
    copy(slice3, slice4)
    fmt.Println(slice3) // [1, 2, 3, 0, 0]
    fmt.Println(slice4) // [1, 2, 3]    
    // 5、删除 slice 中的元素,删除slice5[2]=3
    fmt.Println("delete")
    slice5 := []int{1, 2, 3, 4}
    slice5 = append(slice5[:2], slice5[3:]...)
    fmt.Println(slice5) // [1, 2, 4]

这里给出一张《Go 语言实战》的 reslice 共享底层数组的图

image.png

2.3、append 增加切片长度

    // 1、创建原始切片,长度是5,容量是5
    slice := []int{10, 20, 30, 40, 50}
    // 2、reslice 新切片,长度是2,容量是4
    newSlice := slice[1:3] // [20, 30]
    // 由于底层数组还有容量,可以直接追加元素而容量不变
    newSlice = append(newSlice, 60) // [20, 30 ,60] 长度是3,容量是4
    fmt.Println(newSlice)           // [20, 30 ,60]
    fmt.Println(slice)              // [10, 20, 30 ,60, 50]
image.png
    // 长度4,容量4
    slice := []int{10, 20, 30, 40}
    // 此时切片容量用完了,再追加需要扩容,此处会新加数组,长度为原数组的2倍,即 newSlice 的底层数组是新数组,新切片容量为8;
    // 而 slice 的底层数组是旧数组,二者互不影响
    newSlice := append(slice, 50)
    fmt.Println(slice)    // [10, 20, 30, 40]
    fmt.Println(newSlice) // [10, 20, 30, 40, 50]
    newSlice[0] = 100
    fmt.Println(slice)    // [10, 20, 30, 40]
    fmt.Println(newSlice) // [100, 20, 30, 40, 50]
image.png
  • 切片容量(而非数组长度,默认切片容量等于数组长度,也可以显示指定)用完了,再追加需要扩容,此处会新建数组,长度为原数组的2倍,然后将旧数组元素拷贝到新数组,newSlice 的底层数组是新数组,newSlice 容量为8;而 slice 的底层数组是旧数组,二者互不影响
  • slice 扩容机制:在切片的容量小于 1000 个元素时,总是会成倍地增加容量。一旦元素个数超过 1000,容量的增长因子会设为 1.25,也就是会每次增加 25% 的容量

2.4、显示设置容量

  • 在没有显示指定容量的情况下,切片容量就是其底层数组的长度
  • 如 2.3 所示,如果在创建切片时设置切片的容量和长度一样,就可以强制让新切片的第一个 append 操作创建新的底层数组,与原有的底层数组分离。新切片与原有的底层数组分离后,可以安全地进行后续修改
    source := []string{"Apple", "Orange", "Plum", "Banana", "Grape"}
    // 长度为1=3-2,容量为1=3-2  source[i:j:k] 长度=j-i 容量=k-i
    slice := source[2:3:3]
    fmt.Println(source) // ["Apple", "Orange", "Plum", "Banana", "Grape"]
    fmt.Println(slice) // ["Plum"]
    // 超出切片容量3,需要新建数组
    slice = append(slice, "Kiwi")
    fmt.Println(source) // ["Apple", "Orange", "Plum", "Banana", "Grape"]
    fmt.Println(slice) // ["Plum", "Kiwi"]

2.5、合并切片

    s1 := []int{1, 2}
    s2 := []int{3, 4}
    fmt.Println(append(s1,s2...)) // [1, 2, 3, 4]

2.6、迭代切片

    slice := []int{10, 20, 30, 40}
    // 与数组迭代一样,可以使用 for range + 普通 for 循环
    for index,value := range slice {
        fmt.Println(index, value)
    }

2.7、函数间传递切片

  • 在函数间传递切片就是要在函数间以值的方式传递切片。由于切片的尺寸很小,在函数间复制和传递切片成本也很低;而在函数间传递数组是需要拷贝整个数组的,所以内存和性能上都不好
  • 调用函数,传递一个切片副本,实际上内部还是传递了对数组的指针,所以 foo 内部的操作会影响 main 中的 slice
func foo(slice []int) []int {
    slice[0] = 100
    return slice
}

func main() {
    // 1、创建一个 slice
    slice := []int{1, 2, 3, 4, 5}
    fmt.Println(slice) // [1, 2, 3, 4, 5]
    // 2、调用函数,传递一个切片副本,实际上内部还是传递了对数组的指针,
    // 所以 foo 内部的操作会影响 main 中的 slice
    slice2 := foo(slice)
    fmt.Println(slice2) // [100, 2, 3, 4, 5]
    fmt.Println(slice) // [100, 2, 3, 4, 5]
}

三、Map 映射

  • Map 是一个存储键值对的无序集合,每次迭代映射的时候顺序也可能不一样
  • Slice、Map、function 以及包含切片的结构类型不能作为 Map 的 key
  • map在函数间传递,不会拷贝一份map,相当于是"引用传递",所以remove函数对传入的map的操作是会影响到main函数中的map的

3.1、基本用法

    // 1、使用 make 创建 map,key为string,value为int
    map1 := make(map[string]int)
    // 2、使用字面量创建 map - 最常用的姿势,key为string,value为slice,初始值中的slice可以不加 []string 定义
    map2 := map[string][]string{"hi": {"go", "c"}, "hello": []string{"java"}}
    // 3、创建空映射
    map3 := map[string]string{} // map3 := map[string]string nil映射
    fmt.Println(map1, map2, map3)

    // 4、向映射添加值
    fmt.Println("map put")
    map3["a"] = "x"
    map3["b"] = "y"
    fmt.Println(map3) // map[a:x b:y]

    // 5、获取值并判断是否存在
    value, exist := map3["c"]
    if exist {
        fmt.Println(value)
    } else {
        fmt.Println("map3[\"c\"] does not exist")
    }

    // 6、迭代
    fmt.Println("iterat")
    for key, value := range map3 {
        fmt.Println(key, value)
    }

    // 7、从 map 中删除元素
    delete(map3, "a")
    fmt.Println(map3) // map[b:y]

3.2、函数间传递映射

// map在函数间传递,不会拷贝一份map,相当于是"引用传递",所以remove函数对传入的map的操作是会影响到main函数中的map的
func remove(map4 map[int]int)  {
    delete(map4, 1)
}

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

推荐阅读更多精彩内容

  • 出处---Go编程语言 欢迎来到 Go 编程语言指南。本指南涵盖了该语言的大部分重要特性 Go 语言的交互式简介,...
    Tuberose阅读 18,306评论 1 47
  • 1.安装 https://studygolang.com/dl 2.使用vscode编辑器安装go插件 3.go语...
    go含羞草阅读 1,496评论 0 6
  • 很久没有了看朋友圈的习惯,微信于我而言仅仅只是用来“联系”的工具。偶尔想念朋友们,会特意去她们朋友圈看看近况,却发...
    __katyusha阅读 405评论 0 0
  • 1、 在茫茫的人海里,人们往往仅是凭借着一眼就放走了身边与自己擦肩而过的人,转过头去,已经是人海茫茫,再次找到那个...
    大房子613阅读 1,255评论 5 13
  • Slow is smooth, smooth is fast. 喜欢看书,看的也宽泛,总是希望在最短的时候尽可能吸...
    lei__阅读 604评论 3 0