切片
append函数
append主要用于给某个切片(slice)追加元素,只要slice定义后就可以使用append,不需要先初始化.
- 如果该切片存储空间(cap)足够,就直接追加,长度(len)变长
- 如果空间不足,就会重新开辟内存,并将之前的元素和新的元素一同拷贝进去
第一个参数为切片,后面是该切片存储元素类型的可变参数
1
2
3
4
5
6
7
8
|
var a []int
a = append(a, 1) // 追加1个元素,向未初始化的a添加一个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包
当map的key不存在时,也可以在不存在的key中进行append添加元素
a := map[string][]string{}
a["a"] = append(a["a"], "a")
|
注意append函数返回值必须有变量接收,不然编译器会报错
追加单个元素
1
|
s3 := append(s2, 7) //append一个元素
|
追加多个元素(合并切片)
1
|
s4 := append(s2, s1...) //append 一个切片所有的元素
|
也可以将字符串当作[]byte类型作为第二个参数传入
1
|
bytes := append([]byte("hello"),"world"...)
|
删除单个元素
1
|
ss=append(ss[:index],ss[index+1:]...)//删除index元素
|
插入单个元素
1
2
3
4
5
|
//在切片中间插入元素insert element at index;
//注意:保存后部剩余元素,必须新建一个临时切片
rear:=append([]string{},ss[index:]...)
ss=append(ss[0:index],"inserted")
ss=append(ss,rear...)
|
复制slice
我们来看下面代码
1
2
3
4
5
6
7
8
9
10
11
12
|
package main
import "fmt"
func main() {
aSlice := []int{1, 2, 3}
bSlice := []int{4, 5, 6, 7, 8, 9}
copy(bSlice, aSlice)
fmt.Println(aSlice, bSlice)//[1 2 3] [1 2 3 7 8 9]
//如果是 copy( aSlice, bSlice) 则结果是 [4 5 6]
}
|
slice位置替换
从头开始替换
1
2
3
4
|
s1 := []int{1, 2, 3, 4, 5, 6}
s2 := []int{7, 8, 9, 10, 1, 1, 1, 1, 1, 1}
copy(s2, s1[1:3])
fmt.Println(s2)
|
输出
从指定位置开始替换
1
2
3
4
|
s1 := []int{1, 2, 3, 4, 5, 6}
s2 := []int{7, 8, 9, 10, 1, 1, 1, 1, 1, 1}
copy(s2[2:4], s1[1:3])
fmt.Println(s2)
|
输出
传 slice 和 slice 指针有什么区别
slice 其实是一个结构体,包含了三个成员:len, cap, array。分别表示切片长度,容量,底层数据的地址。
当 slice 作为函数参数时,就是一个普通的结构体。其实很好理解:若直接传 slice,在调用者看来,实参 slice 并不会被函数中的操作改变;若传的是 slice 的指针,在调用者看来,是会被改变原 slice 的。
值的注意的是,不管传的是 slice 还是 slice 指针,如果改变了 slice 底层数组的数据,会反应到实参 slice 的底层数据。为什么能改变底层数组的数据?很好理解:底层数据在 slice 结构体里是一个指针,仅管 slice 结构体自身不会被改变,也就是说底层数据地址不会被改变。 但是通过指向底层数据的指针,可以改变切片的底层数据,没有问题。
通过 slice 的 array 字段就可以拿到数组的地址。在代码里,是直接通过类似 s[i]=10
这种操作改变 slice 底层数组元素值。
来看一个代码片段:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package main
func main() {
s := []int{1, 1, 1}
f(s)
fmt.Println(s)
}
func f(s []int) {
for i := range s {
s[i] += 1
}
}
|
运行一下,程序输出:
果真改变了原始 slice 的底层数据。这里传递的是一个 slice 的副本,在 f 函数中,s 只是 main 函数中 s 的一个拷贝。在f 函数内部,对 s 的作用并不会改变外层 main 函数的 s。
要想真的改变外层 slice,只有将返回的新的 slice 赋值到原始 slice,或者向函数传递一个指向 slice 的指针。我们再来看一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package main
import "fmt"
func myAppend(s []int) []int {
// 这里 s 虽然改变了,但并不会影响外层函数的 s
s = append(s, 100)
return s
}
func myAppendPtr(s *[]int) {
// 会改变外层 s 本身
*s = append(*s, 100)
return
}
func main() {
s := []int{1, 1, 1}
newS := myAppend(s)
fmt.Println(s)
fmt.Println(newS)
s = newS
myAppendPtr(&s)
fmt.Println(s)
}
|
运行结果:
1
2
3
|
[1 1 1]
[1 1 1 100]
[1 1 1 100 100]
|
myAppend 函数里,虽然改变了 s,但它只是一个值传递,并不会影响外层的 s,因此第一行打印出来的结果仍然是 [1 1 1]
。
而 newS 是一个新的 slice,它是基于 s 得到的。因此它打印的是追加了一个 100 之后的结果: [1 1 1 100]
。
最后,将 newS 赋值给了 s,s 这时才真正变成了一个新的slice。之后,再给 myAppendPtr 函数传入一个 s 指针,这回它真的被改变了:[1 1 1 100 100]
。
我们再来看一段程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package main
import "fmt"
func main() {
slice := make([]int, 0, 10)
slice = append(slice, 1)
fmt.Println(slice, len(slice), cap(slice))
fn(slice)
fmt.Println(slice, len(slice), cap(slice))
s1 := slice[0:9]//数组截取
fmt.Println(s1, len(s1), cap(s1))
}
func fn(in []int) {
in = append(in, 5)
}
|
我们来看输出结果:
1
2
3
|
[1] 1 10
[1] 1 10
[1 5 0 0 0 0 0 0 0] 9 10
|
显然,虽然在append后,slice中并未展示出5,也无法通过slice[1]
取到(会数组越界),但是实际上底层数组已经有了5这个元素,但是由于slice的len未发生改变,所以我们在上层是无法获取到5这个元素的。那么,再问一个问题,我们是不是可以手动强制改变slice的len长度,让我们可以获取到5这个元素呢?是可以的,我们来看一段程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
slice := make([]int, 0, 10)
slice = append(slice, 1)
fmt.Println(slice, len(slice), cap(slice))
fn(slice)
fmt.Println(slice, len(slice), cap(slice))
(*reflect.SliceHeader)(unsafe.Pointer(&slice)).Len = 2 //强制修改slice长度
fmt.Println(slice, len(slice), cap(slice))
}
func fn(in []int) {
in = append(in, 5)
}
|
我们来看输出结果:
1
2
3
|
[1] 1 10
[1] 1 10
[1 5] 2 10
|
可以看出,通过强制修改slice的len,我们可以获取到了5这个元素。
字符串
修改字符串
字符串为不可变类型,内部使用指针指向UTF-8字节数组
不过要修改字符串可以先将其转换成[]byte
或者[]rune
。如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package main
func main() {
s := "abcd"
bs := []byte(s)
bs[1] = 'B'
println(string(bs))
u := "电脑"
us := []rune(u)
us[1] = '话'
println(string(us))
}
|
遍历
对于字符串,看一下如何遍历吧,也许你会觉得遍历轻而易举,然而刚接触golang的时候,如果这样遍历字符串,那么将是非常糟糕的
1
2
3
4
5
|
str := "hello 世界"
for i := 0;i < len(str);i++ {
fmt.Println(string(str[i]))
}
|
输出
1
2
3
4
5
6
7
8
9
|
h
e
l
l
o
ä
¸
ç
|
如何解决这个问题呢?
第一个解决方法是用range循环
1
2
3
4
5
|
str := "hello 世界"
for _,v := range str {
fmt.Println(string(v))
}
|
输出
1
2
3
4
5
6
7
8
|
h
e
l
l
o
世
界
|
原因是range会隐式的unicode解码
第二个方法是将str 转换为rune类型的切片,这个方法上面已经说过了,这里就不再赘述了
当然还有很多方法,其本质都是将byte向rune上靠.
参考
深度解密Go语言之Slice
【golang】slice源码分析