golang中数组和slice的区别及使用
- 人工智能
- 2025-08-26 22:30:01

来自于《go语言中文文档》的学习及自我分析
数组和切片的区别golang中有两个很相似的数据结构:数组(Array)和slice。数组和slice实际有各自的优缺点和区别,这里列出最主要的区别
功能点数组slice概念是同一种数据类型的固定长度的序列切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递数组长度数组长度必须是常量,且是类型的组成部分。一旦定义,长度不能变切片的长度可以改变,因此,切片是一个可变的数组访问数组可以通过下标进行访问切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制访问越界如果下标在数组合法范围之外,则触发访问越界,会panic访问越界也会panic值类型or引用赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值切片可以看做对数组的一个引用len返回什么数组长度切片长度cap返回什么数组长度cap可以求出slice最大扩张容量,不能超出数组限制其他注意事项多维数组只有最外层允许不指定长度使用…来判断 数组:实际上,对于数组的定义or初始化,已经有很多示例了,例如:
var arr0 [5]int = [5]int{1, 2, 3} // 正常定义,不足的部分默认为0 var arr1 = [5]int{1, 2, 3, 4, 5} var arr2 = [...]int{1, 2, 3, 4, 5, 6} // 根据值的数量得到数组长度 var str = [5]string{3: "hello world", 4: "tom"} // 定义某些key上的值,剩余的补默认值,string补""数组的定义就是这么简单,注意到上文提过数组是值传递,意思是将数组赋值或传参,修改了赋值或传参后的数据,原数组本身不会被改变。给出分别对应的示例:
赋值给另一个变量,修改另一个变量的值: func main() { a := [2]int{} // 定义一个长度为2的数组,值为[0, 0] b := a b[1] = 3000 fmt.Printf("res A: %v\n", a) // 打印a,虽然a赋值给b,且b的值有变化,但a不变 fmt.Printf("res B: %v\n", b) // 打印b, b的值已经重新赋值了 fmt.Printf("addr A: %p\n", &a) // a的地址 fmt.Printf("addr B: %p\n", &b) // b的地址,会发现与a并不相同 }运行结果为:
res A: [0 0] res B: [0 3000] addr A: 0xc000096080 addr B: 0xc000096090 传参给另一个function,修改值: func test(x [2]int) { fmt.Printf("x: %p\n", &x) x[1] = 1000 } func main() { a := [2]int{} // 定义一个长度为2的数组,值为[0, 0] test(a) fmt.Printf("res A: %v\n", a) // 打印a,虽然传参给test,但实际上不修改a的值 }以上代码结果为:
x: 0xc00000a0d0 res A: [0 0] 想要原数组被改变,使用数组指针 func test(x *[2]int) { fmt.Printf("x: %p\n", &x) x[1] = 1000 } func main() { a := [2]int{} // 定义一个长度为2的数组,值为[0, 0] test(&a) fmt.Printf("res A: %v\n", a) // 打印a,因为test实际传的是a的引用,在test中修改a也会对a本身产生影响 }以上代码运行结果为:
x: 0xc00005c028 res A: [0 1000]练习题:
题目描述:第一行输入一个int类型的n,表示将要计算的数组和,接下来的每行输入数组的元素,以空格分隔,当什么都不输入直接回车表示结束。计算每一行的数组中,有哪两个key的和为第一行给出的n,例如数组[1,3,5,8,7],找出两个元 素之和等于8的下标分别是(0,4)和(1,2)
以下是我的代码:
package main import ( "bufio" "fmt" "os" "strconv" "strings" ) func getAddSumNum(sliceArr []int, sum int) (res [][]int) { for i := 0; i < len(sliceArr); i++ { for j := i + 1; j < len(sliceArr); j++ { if sliceArr[i]+sliceArr[j] == sum { res = append(res, []int{i, j}) } } } return res } /* * 找出数组中和为给定值的两个元素的下标 第一行输入要找到和为n的值 每行输入数组的元素,以空格分隔 输入0代表结束 计算和为n的下标有哪些并输出 */ func main() { scanner := bufio.NewScanner(os.Stdin) if scanner.Scan() { sum := scanner.Text() for { if scanner.Scan() { input := scanner.Text() inputArr := strings.Split(input, " ") if len(inputArr) == 0 && inputArr[0] == "" { break } var inputIntSlice []int for _, v := range inputArr { vInt, _ := strconv.Atoi(v) inputIntSlice = append(inputIntSlice, vInt) } sumInt, _ := strconv.Atoi(sum) res := getAddSumNum(inputIntSlice, sumInt) fmt.Printf("origin: %#v, res %#v\n", inputIntSlice, res) } } } } sliceslice可以看做数组的引用,但slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案 创建切片的方式:
var s1 []int // 第一种方式,直接定义slice s2 := []int{1, 2} // 第二种方式,定义slice的值 var s3 []int = make([]int, 0) // 通过make var s4 []int = make([]int, 0, 0) // 初始化赋值 arr := [5]int{1, 2, 3, 4, 5} s5 := arr[2:4] //直接从数组切片,注意是左开右闭 fmt.Printf("res:%v\n", s1) fmt.Printf("res:%v\n", s2) fmt.Printf("res:%v\n", s3) fmt.Printf("res:%v\n", s4) fmt.Printf("res:%v\n", s5)slice[m:n]:切片,从slice[m]一直到slice[n-1]都在这个切片中 slice[:]表示完整的slice slice[m:n:q],表示从m-n的切片,依然是左开右闭,但是cap为q-m 通过make来创建slice: make([]int, 0, 0):
var slice []type = make([]type, len) slice := make([]type, len) // 不传cap则cap=len slice := make([]type, len, cap)注意若使用make定义slice的时候传了cap参数,那么定义slice的值的个数不允许超过cap的长度,例如以下代码会报错:
var sliceTest []int = make([]int, 2, 3) sliceTest[0] = 3 fmt.Printf("%d %v\n", 0, sliceTest) sliceTest[1] = 4 fmt.Printf("%d %v\n", 1, sliceTest) sliceTest[2] = 5 fmt.Printf("%d %v\n", 2, sliceTest) sliceTest[3] = 6 fmt.Printf("%d %v\n", 3, sliceTest) sliceTest[4] = 7 fmt.Printf("%d %v\n", 4, sliceTest) sliceTest[5] = 8 fmt.Printf("%d %v\n", 5, sliceTest) //执行结果为: 0 [3 0] 1 [3 4] panic: runtime error: index out of range [2] with length 2 // 但是使用append是不会报错的 var sliceTest []int = make([]int, 2, 3) sliceTest[0] = 3 fmt.Printf("%d %v\n", 0, sliceTest) sliceTest = append(sliceTest, 4) fmt.Printf("%d %v\n", 1, sliceTest) sliceTest = append(sliceTest, 5) fmt.Printf("%d %v\n", 2, sliceTest) sliceTest = append(sliceTest, 6) fmt.Printf("%d %v\n", 3, sliceTest) sliceTest = append(sliceTest, 7) fmt.Printf("%d %v\n", 4, sliceTest) sliceTest = append(sliceTest, 8) fmt.Printf("%d %v\n", 5, sliceTest)注意对切片的操作实际上是操作的底层数组,示例:
arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9} slice := arr[2:4] slice[0] += 100 slice[1] += 100 fmt.Printf("slice is: %#v", slice) fmt.Printf("arr is: %#v", arr) // 结果为: // slice is: []int{103, 104}arr is: [9]int{1, 2, 103, 104, 5, 6, 7, 8, 9}即对数组做一个切片,对这个切片的修改会反应到数组上。
超出cap限制时,cap自动扩容:超出原 slice.cap 限制,就会重新分配底层数组,即便原 数组并未填满。依旧以上文的append为示例:
var sliceTest []int = make([]int, 2, 3) sliceTest[0] = 3 fmt.Printf("%d %v, len: %d, cap:%d, \n", 0, sliceTest, len(sliceTest), cap(sliceTest)) sliceTest = append(sliceTest, 4) fmt.Printf("%d %v len: %d, cap:%d,\n", 1, sliceTest, len(sliceTest), cap(sliceTest)) sliceTest = append(sliceTest, 5) fmt.Printf("%d %v len: %d, cap:%d,\n", 2, sliceTest, len(sliceTest), cap(sliceTest)) sliceTest = append(sliceTest, 6) fmt.Printf("%d %v len: %d, cap:%d,\n", 3, sliceTest, len(sliceTest), cap(sliceTest)) sliceTest = append(sliceTest, 7) fmt.Printf("%d %v len: %d, cap:%d,\n", 4, sliceTest, len(sliceTest), cap(sliceTest)) sliceTest = append(sliceTest, 8) fmt.Printf("%d %v len: %d, cap:%d,\n", 5, sliceTest, len(sliceTest), cap(sliceTest)) // cap会自动扩容,输出结果如下: 0 [3 0], len: 2, cap:3, 1 [3 0 4] len: 3, cap:3, 2 [3 0 4 5] len: 4, cap:6, 3 [3 0 4 5 6] len: 5, cap:6, 4 [3 0 4 5 6 7] len: 6, cap:6, 5 [3 0 4 5 6 7 8] len: 7, cap:12,cap每次扩容都是上次cap的2倍
切片的拷贝:copy func copy(dst, src []T) int // dst:目标切片,数据将被拷贝到这里 // src:源切片(数据来源) // 返回值:实际复制的元素个数(取 dst 和 src 长度的较小值)copy的几个功能/注意事项 (1)copy只会copy dst 和 src 长度的较小值,也就是说如果源切片比目标切片长,那么目标切片不会自动扩容;反之目标切片只有部分值被覆盖,例如:
// 目标切片比源切片短时: src := []int{1, 2, 3, 4} dst := make([]int, 2) n := copy(dst, src) // n=2,dst=[1,2] // 目标切片比源切片长时: dst := []int{1, 2, 3, 4, 5} src := []int{10, 11} copy(dst, src) fmt.Printf("dst res is: %v\n", dst)// dst res is: [10 11 3 4 5] fmt.Printf("src res is: %v\n", src)(2)copy时源切片和目标切片的类型必须匹配,否则会编译错误 (3)如果目标切片长度不够,需要显式扩容后再复制,例如:
src := []int{1, 2, 3} dst := make([]int, 2) // 扩展 dst 长度 dst = append(dst, make([]int, len(src)-len(dst))...) copy(dst, src) // dst=[1,2,3](4)copy是深拷贝,修改拷贝后的切片不影响原切片
slice遍历:使用for range即可
切片resize(调整大小) var a = []int{1, 3, 4, 5} fmt.Printf("slice a : %v , len(a): %v, cap(a) %v: \n", a, len(a), cap(a)) b := a[1:2] // b的cap=原始数组长度-起始索引 fmt.Printf("slice b : %v , len(b): %v, cap(b) %v:\n", b, len(b), cap(b)) c := b[0:3] // b的容量为3,虽然b没有用到后几位,但是可以拿到 fmt.Printf("slice c : %v , len(c): %v, cap(c) %v:\n", c, len(c), cap(c)) // 以上代码输出结果为: /** slice a : [1 3 4 5] , len(a): 4, cap(a) 4: slice b : [3] , len(b): 1, cap(b) 3: slice c : [3 4 5] , len(c): 3, cap(c) 3: */这里有个注意事项,因为slice b的长度只有1,因此直接读取b[2]会报错:panic: runtime error: index out of range [2] with length 1,但是获取切片不会报错:c := b[0:3]
数组和切片的内存布局可以看出,数组实际上是存储在连续的内存地址中的,而切片实际上只是指向开始位置的指针+len+cap构成的结构
字符串和切片string底层就是一个byte的数组,因此,也可以进行切片操作,例如;
str := "hello world" fmt.Printf("str[0:5] is: %v\n", str[0:5]) // str[0:5] is: hello fmt.Printf("str[6:] is: %v\n", str[6:]) // str[6:] is: world不能直接修改字符串中的某个字符:
s := "hello" // 下面这行代码会报错,因为不能直接修改字符串的某个字符 // s[0] = 'H' // 如果要改变,可以重新赋值 s = "Hello" fmt.Println(s) // 要改变某个位置的值,可以现改为[]byte()或[]rune()(中文用这个),然后修改 sByte := []byte(s) sByte[6] = 'G' sByte = sByte[:8] sByte = append(sByte, '!') s = string(sByte) fmt.Printf("res of sByte's s: %s\n", s) // res of sByte's s: Hello Go! 切片中两个冒号的理解切片中可能出现1个冒号,两个冒号的情况,分别列举:
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} d0 := slice[:] // 实际就是和原数组大小相等,cap相等的 fmt.Printf("d0: %v, len(d0): %d, cap(d0):%d\n", d0, len(d0), cap(d0)) // [0 1 2 3 4 5 6 7 8 9] 10 10 d1 := slice[6:] //从第6个开始,截取到末尾 fmt.Printf("d1: %v, len(d1): %d, cap(d1):%d\n", d1, len(d1), cap(d1)) // [6 7 8 9] 4 4 d2 := slice[:3] // 从第0个开始,截取到3-1个 fmt.Printf("d2: %v, len(d2): %d, cap(d2):%d\n", d2, len(d2), cap(d2)) // [0 1 2] 3 10 d3 := slice[6:8] // 从第6个截取到第8-1个 fmt.Printf("d3: %v, len(d3): %d, cap(d3):%d\n", d3, len(d3), cap(d3)) // [6 7] 2 4 d4 := slice[:6:8] // 从第0个开始,截取到第6-1个,cap为8 fmt.Printf("d4: %v, len(d4): %d, cap(d4):%d\n", d4, len(d4), cap(d4)) // [0 1 2 3 4 5] 6 8常规slice : data[6:8],从第6位到第8位(返回6, 7),长度len为2, 最大可扩充长度cap为4(6-9) 另一种写法: data[:6:8] 每个数字前都有个冒号, slice内容为data从0到第6位,长度len为6,最大扩充项cap设置为8 a[x:y:z] 切片内容 [x:y] 切片长度: y-x 切片容量:z-x;
在书中提到了一个快速将slice转化为逗号分隔的字符串的方法:strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1),可以分解为:
(1)fmt.Sprint得到字符串,对于数组就是形如[1 2 3]的结果 (2)然后使用strings.Trim去除收尾的字符"[“以及”]" (3)使用strings.Replace替换空格为逗号,注意最后一个参数-1表示所有都要替换
golang中数组和slice的区别及使用由讯客互联人工智能栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“golang中数组和slice的区别及使用”