Home > Article > Backend Development > What are slices and arrays in go language
In the Go language, an array is a sequence of fixed-length elements of a specific type. It is a collection of elements of the same data type. An array can be composed of zero or more elements. The type corresponding to an array is Slice. A slice is a reference to a continuous fragment of an array, so a slice is a reference type. This fragment can be the entire array or some items identified by the start and end indexes. subset, it should be noted that the items identified by the terminating index are not included in the slice.
The operating environment of this tutorial: Windows 7 system, GO version 1.18, Dell G3 computer.
An array is a collection of elements of the same data type. In the Go language, the array is determined
when it is declared. The array members can be modified during use, but the
array size cannot be changed . Basic syntax:
// 定义一个长度为3元素类型为int的数组a var a [3]intThe length of the array must be a constant, and the
length is part of the array type. Once defined, the length cannot be changed
var testArray [3]int // 定义数组时,会初始化int类型为零值 var cityArray = [3]string{"北京", "上海", "深圳"} // 使用指定的初始值完成初始化Generally, we can let the compiler infer the length of the array based on the number of initial values
var cityArray = [...]string{"北京", "上海", "深圳"}We can also initialize the array by specifying the index value, for example:
func main() { a := [...]int{1: 1, 3: 5} fmt.Println(a) // [0 1 0 5] fmt.Printf("type of a:%T\n", a) //type of a:[4]int }
func main() { var a = [...]string{"北京", "上海", "深圳"} // 方法1:for循环遍历 for i := 0; i < len(a); i++ { fmt.Println(a[i]) } // 方法2:for range遍历 for index, value := range a { fmt.Println(index, value) } }
(1) Definition of two-dimensional array
func main() { a := [3][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]] fmt.Println(a[2][1]) //支持索引取值:重庆 }
(2) Traversal of two-dimensional array
func main() { a := [3][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, } for _, v1 := range a { for _, v2 := range v1 { fmt.Printf("%s\t", v2) } fmt.Println() } }
Note: Multi-dimensional arrays Only the first level can use
... to let the compiler deduce the array length. For example:
a := [...][2]string{ {"北京", "上海"}, {"广州", "深圳"}, {"成都", "重庆"}, }
Changing the value of the copy will not change the value of itself.
func modifyArray(x [3]int) { x[0] = 100 } func modifyArray2(x [3][2]int) { x[2][0] = 100 } func main() { a := [3]int{10, 20, 30} modifyArray(a) //在modify中修改的是a的副本x fmt.Println(a) //[10 20 30] b := [3][2]int{ {1, 1}, {1, 1}, {1, 1}, } modifyArray2(b) //在modify中修改的是b的副本x fmt.Println(b) //[[1 1] [1 1] [1 1]] }
Note:
represents an array of pointers (this is an
array, the elements inside are pointers one by one)
represents an array pointer (this is a
pointer, which stores the memory address of an array)
variable-length sequence of elements of the same type. It is
a layer of encapsulation based on the array type. It is very flexible and
supports automatic expansion.
reference type, its internal structure contains address,
length and
capacity. Slices are generally used to quickly operate on a collection of data.
The internal structure of slices in the Go language includes address, size and capacity. Slices are generally used to quickly operate a data set. If a data set is compared to cutting a cake, a slice is the "piece" you want. , the cutting process includes where to start (the starting position of the slice) and how big to cut (the size of the slice). The capacity can be understood as the size of the pocket holding the slice.
var name []T // name:表示变量名 // T:表示切片中的元素类型For example:
func main() { // 声明切片类型 var a []string //声明一个字符串切片 var b = []int{} //声明一个整型切片并初始化 var c = []bool{false, true} //声明一个布尔切片并初始化 var d = []bool{false, true} //声明一个布尔切片并初始化 fmt.Println(a == nil) //true fmt.Println(b == nil) //false fmt.Println(c == nil) //false // fmt.Println(c == d) //切片是引用类型,不支持直接比较,只能和nil比较 }
Construct subcharacters from strings, arrays, pointers to arrays or slices String or slice . It has two variants: a simple form
that specifies the low and high index limit values , and the other is a complete form that
also specifies the capacity in addition to the low and high index limit values. form.
Full slicing expressions are useless, here we only talk about simple slicing expressions!
// 简单切片表达式 func main() { a := [5]int{1, 2, 3, 4, 5} s := a[1:3] // s := a[low:high] fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s)) }Running results:
s:[2 3] len(s):2 cap(s):4
(1) Use the make() function to construct slices
We all created them based on arrays If you need to create a slice dynamically, we need to use the built-in make() function, the format is as follows:make([]T, size, cap)
举个栗子:
func main() { a := make([]int, 2, 10) fmt.Println(a) //[0 0] fmt.Println(len(a)) //2 fmt.Println(cap(a)) //10 }
上面代码中a
的内部存储空间已经分配了10个,但实际上只用了2个。 容量并不会影响当前元素的个数,所以len(a)
返回2,cap(a)
则返回该切片的容量。
切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中
。
切片的本质 就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)
。
举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。
切片s2 := a[3:6],相应示意图如下:
如果你懂了切片的本质,那么试试下面这个题吧!
func main() { a := [5]int{1, 2, 3, 4, 5} s := a[1:3] // s := a[low:high] fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s)) s2 := s[3:4] // 索引的上限是cap(s)而不是len(s),可能认为cap是2?切片是从原数组中元素2开始切走的 fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2)) }
运行结果:
s:[2 3] len(s):2 cap(s):4 s2:[5] len(s2):1 cap(s2):1
s2什么鬼?[2 3][3:4]
这个能运行?如果有这样的疑惑,说明你并没有认识到切片的本质,下面我们来看一个图:
注意切片的本质是一个指向底层数组的起点的指针
,切片len有效长度
,以及cap容量
。
上面是切片s生成的过程,现在又要切片取[3:4],从s的起点开始数
,我们可以很容易看出来[3:4]是5。
切片之间是不能比较的
,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较
。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0
。但是我们不能说一个长度和容量都是0的切片一定是nil
,例如下面的示例:
var s1 []int //len(s1)=0;cap(s1)=0;s1==nil s2 := []int{} //len(s2)=0;cap(s2)=0;s2!=nil s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil
所以要判断一个切片是否是空的,要是用len(s) == 0
来判断,不应该使用s == nil
来判断。
注意:nil和空不是一个概念,nil的判断是有无底层数组,s2、s3初始化了的,其实是有底层数组的,s1只是声明,因此没有底层数组为nil。是否为空,则len是否为0为唯一判断条件。
下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容
,这点需要特别注意。
func main() { s1 := make([]int, 3) //[0 0 0] s2 := s1 //将s1直接赋值给s2,s1和s2共用一个底层数组 s2[0] = 100 fmt.Println(s1) //[100 0 0] fmt.Println(s2) //[100 0 0] }
切片的遍历方式和数组是一致的,支持索引遍历
和for range
遍历。
func main() { s := []int{1, 3, 5} for i := 0; i < len(s); i++ { fmt.Println(i, s[i]) } for index, value := range s { fmt.Println(index, value) } }
Go语言的内建函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。
func main(){ var s []int s = append(s, 1) // [1] s = append(s, 2, 3, 4) // [1 2 3 4] s2 := []int{5, 6, 7} s = append(s, s2...) // [1 2 3 4 5 6 7] } // 这个...类似于python中的*args打散列表
注意: 通过var声明的零值切片可以在append()函数直接使用,无需初始化。
var s []int s = append(s, 1, 2, 3)
没有必要像下面的代码一样初始化一个切片再传入append()函数使用
s := []int{} // 没有必要初始化 s = append(s, 1, 2, 3) var s = make([]int) // 没有必要初始化 s = append(s, 1, 2, 3)
每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”
,此时该切片指向的底层数组就会更换
。“扩容”操作往往发生在append()函数调用时,所以
我们通常都需要用原变量接收append函数的返回值
。
可以通过查看$GOROOT/src/runtime/slice.go
源码,其中扩容相关代码如下:
newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } }
func main() { a := []int{1, 2, 3, 4, 5} b := a fmt.Println(a) //[1 2 3 4 5] fmt.Println(b) //[1 2 3 4 5] b[0] = 1000 fmt.Println(a) //[1000 2 3 4 5] fmt.Println(b) //[1000 2 3 4 5] }
由于切片是引用类型
,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化
。
Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中
,copy()函数的使用方法如下:
func main() { // copy()复制切片 a := []int{1, 2, 3, 4, 5} c := make([]int, 5, 5) copy(c, a) //使用copy()函数将切片a中的元素复制到切片c fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[1 2 3 4 5] c[0] = 1000 fmt.Println(a) //[1 2 3 4 5] fmt.Println(c) //[1000 2 3 4 5] // 再对切片c操作,就不会影响a了 }
Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:
func main() { // 从切片中删除元素 a := []int{30, 31, 32, 33, 34, 35, 36, 37} // 要删除索引为2的元素 a = append(a[:2], a[3:]...) // 把index=2之后的切片和index=2之前的切片拼接在一起 fmt.Println(a) //[30 31 33 34 35 36 37] }
切片a中删除索引为index
的元素,操作方法是a = append(a[:index], a[index+1:]...)
切片持有对底层数组的引用。只要切片在内存中,数组就不能被垃圾回收
。在内存管理方面,这是需要注意的。让我们假设我们有一个非常大的数组,我们只想处理它的一小部分
。然后,我们由这个数组创建一个切片,并开始处理切片。这里需要重点注意的是,在切片引用时数组仍然存在内存中。
一种解决方法是使用上面的copy函数,根据切片生成一个一模一样的新切片
。这样我们可以使用新的切片,原始数组可以被垃圾回收。
package mainimport ( "fmt")func countries() []string { a := []string{1, 2, 3, 4, 5} b := a[:len(a)-2] c := make([]string, len(b)) copy(c, b) // 将b的内容copy给c return c}func main() { d := countries() fmt.Println(d) }
b := a[:len(a)-2]
创建一个去掉a的尾部 2 个元素的切片 b,在上述程序的 11 行,将 切片b 复制到 切片c。同时在函数的下一行返回 切片c。现在 a 数组可以被垃圾回收, 因为数组a不再被引用。
Go 数组与像 C/C++等语言中数组略有不同:
1. Go 中的数组是值类型,换句话说,如果你将一个数组赋值给另外一个数组,那么,实际上就是将整个数组拷贝一份。因此,在 Go 中如果将数组作为函数的参数传递的话,那效率就肯定没有传递指针高了。
2. 数组的长度也是类型的一部分,这就说明[10]int和[20]int不是同一种数据类型。并且Go 语言中数组的长度是固定的,且不同长度的数组是不同类型,这样的限制带来不少局限性。
3. 而切片则不同,切片(slice)是一个拥有相同类型元素的可变长序列,可以方便地进行扩容和传递,实际使用时比数组更加灵活,这也正是切片存在的意义。而且切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。
The above is the detailed content of What are slices and arrays in go language. For more information, please follow other related articles on the PHP Chinese website!