目录
数组Array
数组定义
数组是具有相同类型且长度固定的一组连续数据。在go
语言中我们可以使用如下几种方式来定义数组:
// 方式一
var arr1 = [5]int{}
// 方式二
var arr2 = [5]int{1,2,3,4,5}
// 方式三
var arr3 = [5]int{3:10}
输出以上三个变量的值如下所示:
arr1 [0 0 0 0 0]
arr2 [1 2 3 4 5]
arr3 [0 0 0 10 0]
- 方法一在声明时没有为其指定初值,所以数组内的值被初始化为类型的零值。
- 方法二使用显示的方式为数组定义初值。
- 方法三通过下标的方式为下标为
3
的位置赋上了初值10
而且在数组的定义是包含其长度的,也就是说[3]int
与[4]int
是两种不同的数据类型。
数组的基本使用
- 通过下标修改数组:
package main
import "fmt"
func main() {
var x [5]int
x[4] = 100
fmt.Println(x)
}
- 数组的长度:
var total float64 = 0
for i := 0; i < len(x); i++ {
total += x[i]
}
// total是float64,len(x)是int,所以需要显示的进行转换:
fmt.Println(total / float64(len(x)))
- 数组的中赋值后可以有一个逗号:
package main
import "fmt"
func main() {
x := [5]float64{
98,
93,
77,
82,
83,
}
var total float64 = 0
for _, value := range x {
total += value
}
fmt.Println(total / float64(len(x)))
}
- 数组可以通过下标进行访问,下标是从
0
开始,最后一个元素下标是:len-1
for i := 0; i < len(arr); i++ {
}
for index, v := range arr {
}
- 访问越界,如果下标在数组合法范围之外,则触发访问越界,会
panic
- 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
- 支持 “
==
”、"!=
" 操作符,因为内存总是被初始化过的。 - 指针数组
[n]*T
,数组指针*[n]T
。- 对于指针数组来说,就是:一个数组里面装的都是指针,在
go
语言中数组默认是值传递的,所以如果我们在函数中修改传递过来的数组对原来的数组是没有影响的。
- 对于指针数组来说,就是:一个数组里面装的都是指针,在
func main() {
var a [5]int
fmt.Println(a)
test(a)
fmt.Println(a)
}
func test(a [5]int) {
a[1] = 2
fmt.Println(a)
}
输出
[0 0 0 0 0]
[0 2 0 0 0]
[0 0 0 0 0]
- 数组指针的是指指向数组的指针,在go语言中我们可以如下操作。
func main() {
var a [5]int
var aPtr *[5]int
aPtr = &a
//这样简短定义也可以aPtr := &a
fmt.Println(aPtr)
test(aPtr)
fmt.Println(aPtr)
}
func test(aPtr *[5]int) {
aPtr[1] = 5
fmt.Println(aPtr)
}
首先定义了一个数组a
,然后定一个指向数组a
的指针aPtr
,然后将这个指针传入一个函数,在函数中我们改变了具体的值,程序的输出结果
&[0 0 0 0 0]
&[0 5 0 0 0]
&[0 5 0 0 0]
切片Slice
因为数组是固定长度的,所以在一些场合下就显得不够灵活,所以go
语言提供了一种更为便捷的数据类型叫做切片。切片操作与数组类似,但是它的长度是不固定的,可以追加元素,如果以达到当前切片容量的上限会再自动扩容。
切片定义
可以通过以下几种方式定义切片:
// 方法一
var s1 = []int{}
// 方法二
var s2 = []int{1, 2, 3}
// 方法三
var s3 = make([]int, 5)
// 方法四
var s4 = make([]int, 5, 10)
方法一声明了一个空切片,方法二声明了一个长度为3
的切片,方法三声明了一个长度为5
的空切片,方法四声明了一个长度为5
容量为10
的切片。可以看到切片的定义与数组类似,但是定义切片不需要为其指定长度。
我们可以通过len()
和cap()
这两个函数来获取切片的长度和容量,下面就来依次看下上面各切片的长度以及容量。
s1 [] 0 0
s2 [1 2 3] 3 3
s3 [0 0 0 0 0] 5 5
s4 [0 0 0 0 0] 5 10
如果我们通过这种方式定义一个切片,那么他会被赋予切片的空值nil
。
var s5 []int
切片的基本使用
我们可以通过如下的方式在数组和切片上继续获取切片
func main() {
arr := [5]int{1, 2, 3, 4, 5}
s := []int{6, 7, 8, 9, 10}
s1 := arr[2:4]
s2 := arr[:3]
s3 := arr[2:]
s4 := s[1:3]
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
fmt.Println("s3:", s3)
fmt.Println("s4:", s4)
}
程序的输出结果如下:
s1: [3 4]
s2: [1 2 3]
s3: [3 4 5]
s4: [7 8]
切片的扩充与拼接
我们之前介绍了切片的定义以及一些切片的操作,下面就来看一下如何对切片进行扩充。
func main() {
a := []int{1, 2, 3}
b := a[1:3]
b = append(b, 4)
b = append(b, 5)
b = append(b, 6)
b = append(b, 7)
fmt.Println(a)
fmt.Println(b)
}
程序输出结果如下:
[1 2 3]
[2 3 4 5 6 7]
- 想要将两个切片进行拼接可以使用如下这种方式:
func main() {
a := []int{1, 2, 3}
b := a[1:3]
fmt.Println(b)
a = append(a, b...)
fmt.Println(a)
}
- 超出原
slice.cap
限制,就会重新分配底层数组,即便原数组并未填满。
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 10: 0}
s := data[:2:3]
s = append(s, 100, 200) // 一次 append 两个值,超出 s.cap 限制。
fmt.Println(s, data) // 重新分配底层数组,与原数组无关。
fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。
}
输出结果:
[0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]
0xc4200160f0 0xc420070060
从输出结果可以看出,append
后的s
重新分配了底层数组,并复制数据。如果只追加一个值,则不会超过s.cap
限制,也就不会重新分配。 通常以2
倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的len
属性,改用索引号进行操作。及时释放不再使用的slice
对象,避免持有过期数组,造成 GC
无法回收。
- 如果我们想要将一个切片的值复制给另一个切片,
go
语言也提供了非常简便的方式
func main() {
a := []int{1, 2, 3}
b := make([]int, 3)
copy(b, a)
fmt.Println(a)
fmt.Println(b)
}
几个问题:
- 声明
b
切片时,其长度比a
切片长,复制结果是怎么样的?
剩下的长度会0
填充 - 声明
b
切片时,其长度比a
切片短,复制结果是怎么样的?
只会copy
切片长度内的数值 - 声明
b
切片时,其长度被定义为0
,那么调用copy
函数会报错吗?
不会报错,会返回空数组 - 删除处于索引
i
的元素
Go
语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。
对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用append
或copy
原地完成:
s = []int{1, 2, 3, ...}
s = append(s[:i], s[i+1:]...) // 删除中间1个元素
s = append(a[:i], s[i+N:]...) // 删除中间N个元素
s = s[:i+copy(s[i:], s[i+1:])] // 删除中间1个元素
s = s[:i+copy(s[i:], s[i+N:])] // 删除中间N个元素
- 在索引
i
的位置插入元素&
在索引i
的位置插入长度为j
的新切片
var s []int
s = append(s[:i], append([]int{x}, s[i:]...)...) // 在第i个位置插入x
s = append(s[:i], append([]int{1,2,3}, s[i:]...)...) // 在第i个位置插入切片
slice遍历
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
slice := data[:]
for index, value := range slice {
fmt.Printf("inde : %v , value : %v\n", index, value)
}
}
切片resize(调整大小)
package main
import (
"fmt"
)
func main() {
var a = []int{1, 3, 4, 5}
fmt.Printf("slice a : %v , len(a) : %v\n", a, len(a))
b := a[1:2]
fmt.Printf("slice b : %v , len(b) : %v\n", b, len(b))
c := b[0:3]
fmt.Printf("slice c : %v , len(c) : %v\n", c, len(c))
}
输出结果:
slice a : [1 3 4 5] , len(a) : 4
slice b : [3] , len(b) : 1
slice c : [3 4 5] , len(c) : 3
切片与数组的关系
对于任何一个切片来说,其都有一个底层数组与之对应,我们可以将切片看作是一个窗口,透过这个窗口可以看到底层数组的一部分元素,对应关系如下图所示。
切片是引用底层数组的,需要注意的就是小切片引用大数组的问题,如果底层的大数组一直有切片进行引用,那么垃圾回收机制就不会将其收回,造成内存的浪费,最有效的做法是copy
需要的数据后再进行操作。
字符串和切片(string and slice)
string
底层就是一个byte
的数组,因此,也可以进行切片操作。
package main
import (
"fmt"
)
func main() {
str := "hello world"
s1 := str[0:5]
fmt.Println(s1)
s2 := str[6:]
fmt.Println(s2)
}
输出结果:
hello
world
string
本身是不可变的,因此要改变string
中字符。需要如下操作: 英文字符串:
package main
import (
"fmt"
)
func main() {
str := "Hello world"
s := []byte(str) //中文字符需要用[]rune(str)
s[6] = 'G'
s = s[:8]
s = append(s, '!')
str = string(s)
fmt.Println(str)
}
输出结果:
Hello Go!
数组or切片转字符串
strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)