Contents

golang-学习笔记-数组与切片

go官方文档 go语言参考

在写过很多go代码之后,感觉自己并没有完全掌握go语言,还有很多知识盲区,所以有了这个go学习笔记系列,本系列是作者跟着电子书重新复习go语言相关内容的笔记

数组

概念

注意事项 如果我们想让数组元素类型为任意类型的话可以使用空接口作为类型。当使用值时我们必须先做一个类型判断。

数组元素可以通过 索引(位置)来读取(或者修改),索引从 0 开始。数组长度最大为 2Gb。

数组的最大长度主要受两个因素影响:

  • 可用内存:如果你的系统只有1GB的内存,你无法创建一个2GB的数组。因此,实际可用内存是限制数组长度的关键因素之一
  • Go语言规范:Go语言规范中规定,数组的长度是一个非负整数,其上限是编译器实现定义的,但必须至少为2GB

最后,值得注意的是,Go在内部实际上使用int表示数组长度和索引,对于32位系统,这意味着最大值是2GB。这可能是Go限制数组长度为2GB的原因

对于数组的指针也可以进行索引,go会自动解引用,但是对于切片的指针是不允许的

1
var identifier [len]type

数组是 可变的。

通过 for 遍历数组:

1
2
3
4
5
6
7
for i:=0; i < len(arr1); i++{
    arr1[i] = ...
}

for i,_:= range arr1 {
...
}

Go 语言中的数组是一种 值类型(不像 C/C++ 中是指向首元素的指针),可以通过 new() 来创建: var arr1 = new([5]int)。

那么这种方式和 var arr2 [5]int 的区别是什么呢?arr1 的类型是 *[5]int,而 arr2的类型是 [5]int。

初始化

注意可以通过声明索引来初始化部分值,声明索引后面的元素从索引开始往后计数,即[6]int{4:1,5}中索引为5的元素为5

1
2
3
4
5
var arrAge = [5]int{18, 20, 15, 22, 16}

var arrLazy = [...]int{5, 6, 7, 8, 22}

var arrKeyValue = [5]string{3: "Chris", 4: "Ron"}

多维数组

1
[3][5]int

将数组传递给函数

把一个大数组传递给函数会消耗很多内存。有两种方法可以避免这种现象:

  • 传递数组的指针
  • 使用数组的切片(推荐)

切片

概念

切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型。

切片是一个 长度可变的数组。

切片是可索引的,并且可以由 len() 函数获取长度。

切片提供了计算容量的函数 cap() 可以测量切片最长可以达到多少:它等于切片的长度 + 数组除切片之外的长度

多个切片如果表示同一个数组的片段,它们可以共享数据

1
var identifier []type

与数组的不同是不写明长度

一个切片在未初始化之前默认为 nil,长度为 0。

初始化格式

1
var slice1 []type = arr1[start:end]

start:end 被称为 slice 表达式,start省略表示0,end省略表示len函数

一个切片 s 可以这样扩展到它的大小上限:s = s[:cap(s)]

对于每一个切片(包括 string),以下状态总是成立的:

1
2
s == s[:i] + s[i:] // i是一个整数且: 0 <= i <= len(s)
len(s) <= cap(s)

切片也可以用类似数组的方式初始化:var x = []int{2, 3, 5, 7, 11}。这样就创建了一个长度为 5 的数组并且创建了一个相关切片。

切片在内存中的组织方式实际上是一个有 3 个域的结构体:指向相关数组的指针,切片 长度以及切片容量。

将切片传递给函数

用 make() 创建一个切片

1
2
3
4
5
var slice1 []type = make([]type, len)

slice1 := make([]type, len)

slice1 := make([]type, len, cap)

下面两种方法可以生成相同的切片:

1
2
make([]int, 50, 100)
new([100]int)[0:50]

new() 和 make() 的区别

  • new(T) 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体;它相当于 &T{}。
  • make(T) 返回一个类型为 T 的初始值,它只适用于3种内建的引用类型:切片、map 和 channel。make返回的还是这三个引用类型本身;而new返回的是指向类型的指针。

多维 切片

bytes 包 Buffer

Buffer 可以这样定义:

  • var buffer bytes.Buffer
  • var r *bytes.Buffer = new(bytes.Buffer)
  • func NewBuffer(buf []byte) *Buffer

通过 buffer 串联字符串:buffer.WriteString(s)

输出内容:buffer.String()

这种实现方式比使用 += 要更节省内存和 CPU,尤其是要串联的字符串数目特别多的时候。

strings.Builder和bytes.Buffer的用法一模一样

切片底层 type slice struct

属性

  • array unsafe.Pointer:指向底层数组
  • len int:长度
  • cap int:底层数组长度

For-range 结构

1
2
3
for ix, value := range slice1 {
    ...
}

ix,value是仅在 for 循环内部可见的局部变量。value 只是 slice1 某个索引位置的值的一个拷贝。

如果你只需要索引,你可以忽略第二个变量

多维切片下的 for-range:

1
2
3
4
5
for row := range screen {
    for column := range screen[row] {
        screen[row][column] = 1
    }
}

对于确定初始索引的正向顺序遍历都可以使用range结构

1
2
3
for ix, value := range slice1[k:] {
    ...
}

动态遍历,go专家编程

1
2
3
4
5
6
func main() {
    v := []int{1, 2, 3}
    for i:= range v {
        v = append(v, i)
    }
}

range 循环次数在循环开始时就确定了,不会无限循环

切片的最大长度远大于数组

对于Go来说,切片和数组是两种不同的数据类型,尽管切片底层基于数组实现,但是它们在长度上有不同的限制。

数组的长度是固定的,且在声明时就需要确定,其长度最大为2GB。而切片的长度是可以动态变化的,因此它的长度可以在运行时增加或减小。Go语言通过int类型来存储切片的长度和容量,所以切片的最大长度理论上应该是int类型的最大值,这个值远大于数组的长度限制。

但是在实际应用中,切片的最大长度往往受到可用内存的限制,因为当你尝试分配大量内存来扩大切片时,如果系统没有足够的可用内存,那么内存分配就会失败。

总的来说,尽管切片底层基于数组实现,但是由于它们的机制不同,所以在长度上,切片的限制更大且灵活

切片重组(reslice)

改变切片长度的过程称之为切片重组 reslicing。

简单版重切片 a[low:high]

1
2
3
slice1 = slice1[0:end]
//将切片扩展 1 位
sl = sl[0:len(sl)+1]

其中 end 是新的末尾索引(即长度)

完整版重切片 a[low:high:max]

第三个max表示cap为max-low

切片的复制与追加

想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来

append内置函数将 0 个或多个具有相同类型 s 的元素追加到切片后面并且返回新的切片。底层数组容量不够时创建新数组并拷贝切片元素。append 方法总是返回成功,除非系统内存耗尽了。

将切片 y 追加到切片 x 后面:x = append(x, y...)

func copy(dst, src []T) int 将类型为 T 的切片从源地址 src 拷贝到目标地址 dst,覆盖 dst 的相关元素,并且返回拷贝的元素个数。拷贝内容不能超过源地址内存大小。

字符串、数组和切片的应用

从字符串生成字节切片

1
2
3
c := []byte(s)

copy(dst []byte, src string)

byte可以直接转化为rune

获取字符串的某一部分

字符串使用索引按字节数计算相对位置

字符串和切片的内存结构

在内存中,一个字符串实际上是一个双字结构,即一个指向实际数据的指针和记录字符串长度的整数。

修改字符串中的某个字符

Go 语言中的字符串是不可变的,str[index] 这样的表达式是不可以被放在等号左侧的。

因此,您必须先将字符串转换成字节数组,然后再通过修改数组中的元素值来达到修改字符串的目的,最后将字节数组转换回字符串格式。

s := "hello"
c := []byte(s)
c[0] = ’c’
s2 := string(c) // s2 == "cello"

搜索及排序切片和数组

sort.Ints(arri)

sort.IntsAreSorted(a []int) bool

append 函数常见操作

  1. 将切片 b 的元素追加到切片 a 之后:a = append(a, b...)
  2. 复制切片 a 的元素到新的切片 b 上:
b = make([]T, len(a))
copy(b, a)
  1. 删除位于索引 i 的元素:a = append(a[:i], a[i+1:]...)
  2. 切除切片 a 中从索引 i 至 j 位置的元素:a = append(a[:i], a[j:]...)
  3. 为切片 a 扩展 j 个元素长度:a = append(a, make([]T, j)...)
  4. 在索引 i 的位置插入元素 x:a = append(a[:i], append([]T{x}, a[i:]...)...)
  5. 在索引 i 的位置插入长度为 j 的新切片:a = append(a[:i], append(make([]T, j), a[i:]...)...)
  6. 在索引 i 的位置插入切片 b 的所有元素:a = append(a[:i], append(b, a[i:]...)...)
  7. 取出位于切片 a 最末尾的元素 x:x, a = a[len(a)-1], a[:len(a)-1]
  8. 将元素 x 追加到切片 a:a = append(a, x)

切片和垃圾回收

切片的底层指向一个数组,该数组的实际容量可能要大于切片所定义的容量。在没有任何切片指向的时候,底层的数组内存才会被释放,这种特性有时会导致程序占用多余的内存。

想要避免这个问题,可以通过拷贝我们需要的部分到一个新的切片中。

多维数组或切片初始化

可以直接在初始化语句里面初始化元素

1
dirs:=[][]int{{0,1},{1,0},{0,-1},{-1,0}}

深拷贝

博客园BeifangCc

slice,map,channel都是浅拷贝。

深拷贝可以使用for循环复制或者使用copy,copy时需要注意被拷贝对象必须容量大于等于拷贝对象

copy返回结果为一个 int 型值,表示 copy 从原切片src复制到目的切片的长度

1
2
to:=make([]int,len(from))
copy(to,from)

数组相等比较

数组元素可以进行相等比较时,数组也可以进行相等比较,这在做算法题时很好用,因为map不支持相等比较

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

import "fmt"

type kk struct {
	a, b int
}

func main() {
	a, b := [1]kk{kk{1, 2}}, [1]kk{kk{1, 2}}
	fmt.Println(a == b)
}

定义数组切片

1
a:=make([][n]int,m)

等价于

1
2
3
4
a:=make([][]int,m)
for i:=range a{
  a[i]=make([]int,n)
}

且效率更高,写的代码更少

注意,这种方法只适用于n为常数

数组扩容

  1. 256个元素以内,翻两倍
  2. 大于256则逐步减少倍数,1024时为1.5倍
 |