go语言中数组、切片、map | 豆包MarsCode AI刷题

66 阅读5分钟

常用包管理命令

构建包:go build 包名 导入包:import "包名"

如何使用包中定义的函数?

导入需要的包,利用包名进行调用。

包中所有的函数都能被外界调用吗?

  • 大写开头的函数能才能被调用。
  • 小写开头的函数只能在函数里被调用。

构建包有什么用?

构建包后可以得到同名的可执行文件,可以直接运行这个可执行文件。效果和go run pkg.go一样。

数组的创建以及作为参数传递时的方式

  • 可以var、短变量的方式创建
  • 值传递的方式,也就是会进行拷贝,大数组拷贝会损失性能
package main  
  
import "fmt"  
  
func main() {  
    arr1 := [5]int{1, 2, 3, 4, 5} // 短变量初始化方式  
    var arr2 [5]int = [5]int{6, 7, 8, 9} // 指定元素个数  
    var arr3 = [...]int{1, 2, 3, 4, 5, 6} // 不指定个数,自己判断元素个数  
    var arr4 = [...]string{2: "i", 3: "am", 5: "jack"} // 通过冒号指定元素初始化  
    fmt.Println(arr1, arr2, arr3, arr4)  
}

数组类型的缺点

值传递,会进行只拷贝,对于大数组会损失性能 解决办法:使用切片

1. 切片的创建和数组创建的区别

切片的创建不用指定数值大小。 创建方式:

  • var或者:=

  • make方式

  • 为什么切片的容量为8?

    • 好像是跟append有关系

2. 切片类型如果不初始化,声明后直接使用会怎么样?

如果不初始化,切片变量就没有分配内存空间,容量为0。此时的切片变量等于nil空。 如果不初始化直接使用,会报数组访问越界的panic。

如何避免切片未初始化的风险?

检查切片变量是否为空,为空则进行初始化。 两种方式:

  1. 直接make初始化。
  2. 使用append添加元素,会对切片进行扩容初始化

3. 切片的本质

切片底层指向的是一个数组,使用切片其实就是数组的引用。 切片底层维护着三个属性:

  • 指针(执行一个数组)
  • 长度len
  • 容量cap

对切片进行切片化的本质

对切片化得到的不同切片,底层其实指向的是同一个数组,只是指针指向的数组位置不同。 从以下代码中可以看出两个切片其实指向同一个数组。

package main  
  
import "fmt"  
  
func main() {  
    slice1 := []int{1, 2, 3, 4, 5}  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice2 := slice1[1:3]  // 切片化
    fmt.Println(slice2, len(slice2), cap(slice2))  
    slice2[0] = 100  // 更改元素是否会影响到slice1
    fmt.Println(slice1, len(slice1), cap(slice1))  
    fmt.Println(slice2, len(slice2), cap(slice2))  
}
/*
[1 2 3 4 5] 5 5
[2 3] 2 4
[1 100 3 4 5] 5 5
[100 3] 2 4
*/

4. 对切片进行动态扩容为什么会改变底层原数组

动态扩容后会发生什么?

  • 容量翻倍增长
  • 更改底层数组(底层数组替换为新的数组)
package main  
  
import "fmt"  
  
func main() {  
    var slice1 []int // 定义一个切片,但不初始化  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice1 = append(slice1, 1)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice1 = append(slice1, 2)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice1 = append(slice1, 3)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice1 = append(slice1, 4)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice1 = append(slice1, 5)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
}
/*
[] 0 0
[1] 1 1
[1 2] 2 2
[1 2 3] 3 4
[1 2 3 4] 4 4
[1 2 3 4 5] 5 8
*/

可以看到容量是翻倍扩容的。

package main  
  
import "fmt"  
  
func main() {  
    var slice1 []int  
    slice1 = append(slice1, 1)  
    slice1 = append(slice1, 2)  
    slice1 = append(slice1, 3)  
    slice1 = append(slice1, 4)  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    slice2 := slice1[1:3]  
    slice2[0] = 100  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    fmt.Println(slice2, len(slice2), cap(slice2))  
    slice1 = append(slice1, 5)  // 此时slice1容量扩充,是否会改变底层数组呢?
    slice2[0] = 99  
    fmt.Println(slice1, len(slice1), cap(slice1))  
    fmt.Println(slice2, len(slice2), cap(slice2))  
  
}
/*
[1 2 3 4] 4 4
[1 100 3 4] 4 4
[100 3] 2 3
[1 100 3 4 5] 5 8
[99 3] 2 3
*/

可以看到扩容后,改变slice2元素,slice1不受影响,说明底层这两个切片已经不指向同一个数组了。

5. 切片在实际应用中,怎么用更好,为什么?

  • 函数参数用切片,而不要用数组。
    • 切片其实就是引用传值,不涉及拷贝,性能更好。
  • 如果不知道切片长度,初始化时尽量给定一个容量:make([]int, 0, 20)
    • 避免频繁扩容影响性能。
  • 如果知道切片长度,初始化可以只指定长度:make([]int, 20)
    • 指定len后,cap和len相同。

如何初始化一个n×n的多维数组?

make([][]int, n)

arr := make([][]int, n)
for i := 0;i < n;i ++ {
	arr[i] = make([]int, n)
}

map类型的变量为什么必须初始化才能使用?

因为不初始化就没有分配内存空间,此时的map变量就为nil空,无法对空变量进行操作。

如何进行初始化?

  1. make初始化
  2. 字面量初始化
func main() {  
    mp1 := make(map[int]string)  
    mp1[1] = "bob"  
    mp1[2] = "jack"  
    mp1[3] = "lisa"  
    fmt.Println(mp1)  
    var mp2 = map[int]int{  
       1: 10,  
       2: 19,  
       3: 20,  
    }  
    fmt.Println(mp2)  
    var mp3 map[string]int  
    // mp3["app"] = 2 // 报错,未初始化的map无法使用  
    if mp3 == nil {  
       mp3 = make(map[string]int)  
    }  
    fmt.Println(len(mp3))  
}
/*
map[1:bob 2:jack 3:lisa]
map[1:10 2:19 3:20]
0
*/

go中的map是无序的

因此每次打印map变量,键值对的顺序可能不一样。

go中的map是引用类型,这意味着什么?

意味着将map作为函数类型传递时,传递的是变量的引用,而不是值拷贝。