数组和切片
- 数组
- 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函数复制到新的切片上,并且将该新的切片作为函数值返回
参考文献:切片的本质和用法