GO专栏 (三) ——— 数组、切片和映射

Go-In-Action

@author 鲁伟林
记录《Go语言实战》中各章节学习过程,写下一些自己的思考和总结。希望维护管理此仓库,记录学习过程中部分心得,以与其他同行参考。

本博客中涉及的完整代码:
GitHub地址: https://github.com/thinkingfioa/go-learning
本人博客地址: https://blog.csdn.net/thinking_fioa
文中如若有任何错误,欢迎指出。个人邮箱: thinking_fioa@163.com

第4章 数组、切片和映射

Go语言有3种数据结构来管理集合:数组、切片和映射

4.1 数组的内部实现和基础功能

数组是一组长度固定的数据类型,用于存储一段具有相同类型的元素的连续块。数组的每个元素类型都是相同,又是连续分配,可通过索引快速查找到对应的数据。

  • 数组创建完成后,存储的数据类型和数组长度就不再允许改变。

4.1.2 声明和初始化

4.1.2.1 var声明

使用var变量声明类型时,总是会用对应类型的零值对变量进行初始化。

// 数组array中每个元素都初始化为0
var array [5]int

4.1.2.2 数组字面量

  1. 直接创建并初始化数组
  2. 可以使用...来替代数组长度
  3. 声明数组并指定特定索引位置的值
array := [5]int {1,2,3,4,5}
// 使用...来替代数组长度
array := [...]int {1,2,3,4,5}
// 声明数组并指定特定元素的值。只有下标为1的值为10,下标为4的值为40
array := [5]int {1:10 ,4: 40}

4.1.3 使用数组

使用[]运算符来访问数组里的某个单独元素

array := [5]int {10, 20, 30, 40, 50}
array[2]=35

4.1.3.1 指针数组

声明一个指针数组,使用*运算符访问元素

array := [5]*int{0: new(int), 1: new(int)}
*array[0]=10
*array[1]=20

4.1.3.2 数组赋值操作

  1. 数组长度和元素类型一样,可直接赋值操作。赋值后两个数组之间拥有各自的底层数据,修改相互不影响
  2. 复制指针数组,只会复制指针的地址,而不会复制指针所指向的值。赋值后两个数组指针共享底层数据,修改相互影响
package main

import "fmt"

func main() {
    var array1 [3]*string

    array2 := [3]*string{new(string), new(string), new(string)}
    *array2[0] = "Red"
    *array2[1] = "Blue"
    *array2[2] = "Green"

    array1 = array2

    *array1[0] = "Yellow"
    // 遍历
    for index := range array1 {
        fmt.Println(*array1[index])
    }

    // 遍历
    for index := range array2 {
        fmt.Println(*array1[index])
    }
}

4.1.4 多维数组

多维数组的特性与一维数组特性完全一致。包括初始化、遍历,赋值等特性

array := [4][2]int{{10,11},{20,21},{30,31},{40,41}}

4.1.5 在函数间传递数组

Go语言函数之间传递变量,总是以值的方式传递。如果这个变量是数组,不管都长,都会完整复制一份。所以数组作为函数参数传递,将严重影响程序的内存和性能。

推荐的方式是传入指向数组的指针,但是要提醒的是:如果传递的是指针,改变指针指向的值,会改变共享的内存,对原数组有影响,请慎重处理。

4.2 切片的内部实现和基础功能

切片是围绕着动态数组,切片的底层内存也是在连续块中分配。但其与数组在内部实现和基础功能存在较多不同点。类似于Netty的ByteBuf中的slice(切片)概念。

切片与数组最大的不同点是:切片为共享而生。基于一个切片创建新的切片,新的切片会和原有的切片共享底层数组。

4.2.1 内部实现

切片由3个字段组成,分别是:执行底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。如下图

 

4.2.2 创建和初始化

Go语言中有几种方法可以创建和初始化切片

4.2.2.1 make函数

内置的make函数创建切片,需要传入一个参数,制定切片的长度。也可以同时指定长度和容量。

// 创建字符串切片,其长度和容量都是5个元素
slice := make([]string, 5)
// 创建一个整型切片,其长度为3,容量为5
slice := make([]string, 3, 5)

4.2.2.2 切片字面量

另一种常见的创建切片的方法是使用切片字面量

// 创建字符串切片
slice := []string {"Red", "Blue", "Yellow"}
// 创建长度和容量都为100的整型切片
slice := []int {100: 1000}

提醒

使用切面量创建数组和切片非常相似。如果[]运算符里指定了一个值,那么创建的就是数组而不是切片

// 创建3个元素的整型数组
array := [3]int {1,2,3}
// 创建长度和容量都是3的切片
array := []int{1,2,3}

4.2.2.3 nil和空切片

创建nil的切片 ----- val slice []int 

创建空切片

// 使用make创建空的整型切片
slice := make([]int, 0)
// 使用make创建空的整型切片
slice := []int {}

4.2.3 使用切片

4.2.3.1 赋值和切片

对切片里某个索引执行的元素赋值和对数组里的某个索引指向的元素赋值的方法完全一样

slice := []int {1,2,3}
slice[2] = 4

基于一个切片创建出新的切片,新的切片和原来的切片共享一个底层数组,如下代码和图

// 如上图所示
slice := []int {10, 20, 30, 40, 50}
// 创建一个新的切片
newSlice := slice[1:3]

计算:

  1. 如何计算新切片的长度和容量. slice[i:j] ----- 长度=j-i, 容量=k-i,其中k是原切片容量
  2. slice[i:j:k] ----- 长度=j-i, 容量=k-i。推荐使用该方式

4.2.3.2 切片增长 ----- append函数

相对于数组,使用切片的一个好处是:可按需增加切片的容量。Go语言里使用append函数来实现,append函数可同时追加多个值

函数append总是会增加新切片的长度,当容量不够时,append函数会创建一个新的底层数组,将原有的值复制进去,再追加新的值。与其他语言的可变长度数组扩容逻辑基本类似。所以一次性分配足够的容量,可有效减少扩容时间。

package main

import "fmt"

func main() {
    slice := []int{10, 20, 30, 40, 50}

    newSlice := slice[2:5:5]

    newSlice = append(newSlice, 60)
    fmt.Println("Slice :")
    for index, value := range slice {
        fmt.Println("Index %d, Value: %d", index, value)
    }
    fmt.Println("NewSlice :")
    for index, value := range newSlice {
        fmt.Println("Index %d, Value: %d", index, value)
    }
}

4.2.3.3 创建切片时的3个索引

推荐创建切片时,使用三个值。如:slice[i:j:k]。第三个值k是用来控制新切片的容量,其目的是限制容量,防止误操作改变了原切片的底层数据。

slice[i:j:k] ----- 长度=j-i, 容量=k-i。k(容量)和j(长度)设置成一样,可防止底层误操作。当新切片容量不够时,append函数会自动进行扩容,与原切片底层分离,而不会修改原切片的值。

append函数可同时追加多个值,如果使用...运算符,可将一个切片的所有元素追加到另一个切片里。eg: newSlice = append(slice1, slice2...)

4.2.3.4 迭代切片

关键字range迭代。关键字range会返回两个值,第一个是索引的位置(可用下划线忽略索引值),第二个是该位置对应元素值的一份副本。

for index, value := range slice {
    fmt.Println("index %d, value: %d", index, value)
}

传统的for循环方式迭代

for index:0; index < len(slice); index++ {
    fmt.Println("index %d, value %d", index, slice[index]);
}

注:对于切片,函数len返回切片的长度,函数cap返回切片的容量

4.2.4 多维切片

切片是一维的,也可以创建多维切片。eg: slice := [][]int {{10}, {100, 200}}

append函数同样适用于多维切片。eg: slice[0] = append(slice[0], 2)

4.2.5 在函数间传递切片

函数间传递切片,只是切片自身值(切片的3个字段)被复制一份,不会涉及底层数组。在函数间传递切片代价非常小。

4.3 映射的内部实现和基础功能

映射是一种数据结构,用于存储一系列无序的键值对。映射是一种无序的集合,意味着没有办法预测键值对被返回的顺序

4.3.2 创建和初始化

映射的创建和初始化与数组、切片的方式一样,第一种:使用内置的make函数,第二种:使用映射字面量

映射的键可以是任何值。但是切片、函数以及包含切片的结构类型都具有引用语义,不能作为映射的键

4.3.2.1 make函数

如下代码生声明一个键的类型是string,值的类型是int

// 声明一个键的类型是string,值的类型是int
dict := make(map[string]int)

4.3.2.2 使用映射字面量

// 声明一个键的类型是string,值的类型是string
dict := map[string]string {"red": "1", "blue": "2", "yellow": "3"}

4.3.3 使用映射

映射取值有两种选择。

第一种选择是:可以同时获得值,以及一个表示这个键是否存在的标志

// 第一种方法
ageValue, exist := dict["age"]
if exist {
    fmt.Println("age is  %s", ageValue)
}

第二种选择是:只返回对应的值,然后再通过判断这个值是不是对应类型的零值来确定键是否存在

// 第二中方法
phoneValue := dict["phone"]
if "" != phoneValue {
    fmt.Println("phone is %d", phoneValue)
}

删除映射中的键

如果想把一个键值对从映射中删除,可使用内置的delete函数, eg: delete(dict, "age")

遍历映射

遍历映射,使用range关键字。关键字range返回的不是索引和值,而是键值对

// 遍历
for key, value := range dict {
    fmt.Println("key is %s, value is %s", key, value)
}

4.3.4 在函数间传递映射

在函数之间传递映射,并不会制造出该映射的一个副本。实际上传递映射给一个函数时,在函数中如果对该映射做了修改,所有对这个映射的引用都会觉察到这个修改。该特性与切片类似,保证使用最小的成本来复制映射。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值