数组和切片

  • 数组
  • 1.数组的定义
  • 2.数组的特点
  • 3.多维数组
  • 切片
  • 1.切片的定义
  • 2.切片是引用类型
  • 3.切片存在默认的上下界
  • 4.切片的长度和容量
  • 5.nil切片
  • 6.使用make创建切片
  • 7.使用append对切片扩容
  • 8.copy
  • 9. 切片“陷阱”


数组

1.数组的定义

数组是一组相同的数组类型的集合,支持随机访问,以下列出了部分定义方式(在go语言中指定了大小的一组相同类型的元素叫数组,不指定大小的一组相同类型元素叫切片)。
Go的数组是值语义。一个数组变量表示整个数组,它不是指向第一个元素的指针(不像 C 语言的数组)。 当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组,因此在函数传参时,使用的是建议使用 切片或者数组的指针,频繁使用数组进行作为参数传递会降低效率。

// 指定大小,同时可以通过下标来指定内容
    var strs [3]string = [3]string{1 : "a"}
	// 简短写法
	arrays := [5]int64{4: 1}
	// 使用...自导推到长度
	b := [...]string{"Penn", "Teller"}
	fmt.Println(strs)
	fmt.Println(arrays)
	fmt.Prinln(b)

2.数组的特点

go语言中的数组和其他语言的数组有一个特别的区别,它的数据类型是会存储数组的大小,在数组数据类型相同,数组的大小不同的情况下,是不支持强行转化的。例如以下代码:

package main

import (
	"fmt"
)

func testArray() []string {
	var arrays [5]string
	return arrays
}

func main() {
	fmt.Println(testArray())
}

运行时会报cannot use arrays (type [5]string) as type []string in return argument的错误

func testArray() [4]string {
	var arrays [5]string
	return arrays
}

cannot use arrays (type [5]string) as type [4]string in return argument

3.多维数组

[] 即可

arrays1 := [3][2]int64{
		[2]int64{1, 2},
		[2]int64{0, 3},
	}

切片

1.切片的定义

每个数组的大小都是固定的,操作起来不方便进行容量管理,因此切片就出现了。切片支持数组元素提供动态大小的、灵活的视角。切片是可以包含任何类型的,甚至被切的是切片的本身。

类型 []T 表示一个元素类型为 T 的切片。

切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
a[low : high]

它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
以下表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素:
a[1:4]

package main

import "fmt"

func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4]
	fmt.Println(s)
}

[Running] go run "/Users/lijinlong/local/go/GoProjects/go_demo/main.go"
[3 5 7]

2.切片是引用类型

官方文档中说到切片更像是数组的类型(相对于java),在c/c++语言中理解就是共享地址,修改了其中一个切片内容相当于所有切片的内容都改变了,即修改了切片会影响原来所有的的被切入的数组和切片的元素。这相当于 零拷贝 操作,而数组是拷贝操作,因此在使用数组和切片时候需要多加注意!!!

package main

import "fmt"

func main() {
	old := []int64{1, 2, 3}
	new := old[1: 3]
	new[0] = 100
	fmt.Println(old)
	fmt.Println(new)

	arrays := [6]int64{1, 2, 3, 4, 5, 6}
	silces := arrays[4 : 6]
	silces[0] = 100
	fmt.Println(arrays)
	fmt.Println(silces)
}

[Running] go run "/Users/lijinlong/local/go/GoProjects/go_demo/main.go"
[1 100 3]
[100 3]
[1 2 3 4 100 6]
[100 6]

3.切片存在默认的上下界

切片的下标是左闭右开的,切片的默认的上下界类似于python

对于数组
var a [10]int

来说,以下切片是等价的:
a[0:10]
a[:10]
a[0:]
a[:]

package main

import "fmt"

func main() {
	array := [10]int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	slice1 := array[0: 10]
	slice2 := array[0 : ]
	slice3 := array[ : 10]
	slice4 := array[ : ]
	
	fmt.Println(slice1)
	fmt.Println(slice2)
	fmt.Println(slice3)
	fmt.Println(slice4)
}

4.切片的长度和容量

切片的长度就是它所包含的元素个数。

切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。(切片的底层数据结构其实就是数组)

package main

import "fmt"

func main() {
	// 先定义一个容量为10的数组
	array := [10]int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	// 切片1,长度10 - 0 = 10,容量 10 - 0 = 10
	s := array[ : 10]
	printSlice(s)
	// 切片2,从下标2开始,那么长度7 - 2 = 5,容量10 - 2 = 8(底层数组的最大下标为10)
	s = s[2: 7]
	printSlice(s)
}

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

[Running] go run "/Users/lijinlong/local/go/GoProjects/go_demo/main.go"
len = 10, cap = 10, val = [1 2 3 4 5 6 7 8 9 10]
len = 5, cap = 8, val = [3 4 5 6 7]

5.nil切片

切片的零值是 nil。

nil 切片的长度和容量为 0 且没有底层数组。(一定是没有底层数组才行)

package main

import "fmt"

func main() {
	var nilSlice []string
	// 这是含有底层数组的,因此不是nil切片
	var noNilSlice = []string{}
	if(nilSlice == nil){
		fmt.Println("nilSlice is nil slice")
	}else{
		fmt.Println("nilSlice is not nil slice")
	}
	if(noNilSlice == nil){
		fmt.Println("noNilSlice is nil slice")
	}else{
		fmt.Println("noNilSlice is not nil slice")
	}
}

[Running] go run "/Users/lijinlong/local/go/GoProjects/go_demo/main.go"
nilSlice is nil slice
noNilSlice is not nil slice

6.使用make创建切片

使用 make([]T, len, cap) 语法可以快速的创建切片
len: 当前切片已经使用了的空间
cap - len: 切片还没使用的空间

package main

import "fmt"

func main() {
	a := make([]int64, 5)
	printSlice("a", a)

	b := make([]int64, 6, 10)
	printSlice("b", b)
}

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

[Running] go run "/Users/lijinlong/local/go/GoProjects/go_demo/main.go"
a: len=5 cap=5 [0 0 0 0 0]
b: len=6 cap=10 [0 0 0 0 0 0]

7.使用append对切片扩容

append方法相当于在原来切片的基础上,申请了一块新的内存空间,是拷贝操作,它会将原来的数组内容保存,且将新的内容追加到数组的末尾。

package main

import "fmt"

func main() {
	// 先声明一个长度为0,容量为0的切片
	s1 := make([]int64, 0)

	s2 := append(s1, 1, 2)
	s2[0] = 100

	s3 := append(s2, 3, 4, 5, 6, 7)
	s3[1] = 200
	printSlice("s1", s1)
	printSlice("s2", s2)
	printSlice("s3", s3)
}

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

[Running] go run "/Users/lijinlong/local/go/GoProjects/go_demo/main.go"
s1: len=0 cap=0 []
s2: len=2 cap=2 [100 2]
s3: len=7 cap=8 [100 200 3 4 5 6 7]

8.copy

copy 函数将源切片的元素复制到目的切片, 它返回复制元素的数目。

func copy(dst, src []T) int

copy 函数支持不同长度的切片之间的复制(它只复制较短切片的长度个元素)。 此外, copy 函数可以正确处理源和目的切片有重叠的情况。

package main

import "fmt"

func main() {
	// 先声明一个长度为0,容量为0的切片
	s := []int64{1, 2, 3}
	t := make([]int64, 2)
	fmt.Printf("copylen = %d\n", copy(t, s))
	printSlice("s", s)
	printSlice("t", t)
}

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

9. 切片“陷阱”

切片是操作是一个零拷贝的操作,在多个切片引用同一块内存的时候情况下,释放了多个切片引用,但是由于一个小引用,导致整个大片内存无法被正确释放,这是比较需要注意的点。

举一个例子:有个一个函数,他的作用是读取硬盘中比较大的文件进行扫描,寻找其中感兴趣的一小块内容返回,这时候使用了一个小切片作为了函数的返回值,只是大切片中的小块内容,但是由于这块大内存存在小切片引用,导致了内存无法释放,白白浪费了许多内存空间。
解决方法:寻找到小块感兴趣的内容之后,使用make函数重新分配一个新的切片,将感兴趣内容使用copy函数复制到新的切片上,并且将该新的切片作为函数值返回

参考文献:切片的本质和用法