来源: mp.weixin.qq.com/s/gUkYwCodY…面试
欢迎关注公众号《Go后端干货》算法
各类Go,后端技术,面试题分享编程
下面代码中,会输出什么?后端
func Assign1(s []int) {
s = []int{6, 6, 6}
}
func Reverse0(s [5]int) {
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
}
func Reverse1(s []int) {
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
}
func Reverse2(s []int) {
s = append(s, 999)
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
}
func Reverse3(s []int) {
s = append(s, 999, 1000, 1001)
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
}
func main() {
s := []int{1, 2, 3, 4, 5, 6}
Assign1(s)
fmt.Println(s) // (1)
array := [5]int{1, 2, 3, 4, 5}
Reverse0(array)
fmt.Println(array) // (2)
s = []int{1, 2, 3}
Reverse2(s)
fmt.Println(s) // (3)
var a []int
for i := 1; i <= 3; i++ {
a = append(a, i)
}
Reverse2(a)
fmt.Println(a) // (4)
var b []int
for i := 1; i <= 3; i++ {
b = append(b, i)
}
Reverse3(b)
fmt.Println(b) // (5)
c := [3]int{1, 2, 3}
d := c
c[0] = 999
fmt.Println(d) // (6)
}
复制代码
上面的这几道题,也是Go编程中比较容易让人感到迷惑的地方,但若是懂slice的底层原理,你就能避开这些坑且能轻松的答对上面几道题。数组
Go的数组array底层和C的数组同样,是一段连续的内存空间,经过下标访问数组中的元素。array只有长度len
属性并且是固定长度的。app
array的赋值是值拷贝的,看如下代码:函数
func main() {
c := [3]int{1, 2, 3}
d := c
c[0] = 999
fmt.Println(d) // 输出[1, 2, 3]
}
复制代码
由于是值拷贝的缘由,c
的修改并无影响到d
。ui
掌握Go的slice,底层结构必需要了解。spa
type slice struct {
array unsafe.Pointer
len int
cap int
}
复制代码
关于知识点1,看如下代码:3d
func main() {
s := []int{1, 2, 3} // len=3, cap=3
a := s
s[0] = 888
s = append(s, 4)
fmt.Println(a, len(a), cap(a)) // 输出:[888 2 3] 3 3
fmt.Println(s, len(s), cap(s)) // 输出:[888 2 3 4] 4 6
}
复制代码
由于slice的底层是数组指针,因此slice a
和s
指向的是同一个底层数组,因此当修改s[0]
时,a
也会被修改。
当s
进行append
时,由于长度len
和容量cap
是int值类型,因此不会影响到a
。
关于知识点2,看如下代码:
func main() {
s := make([]int, 0, 4)
s = append(s, 1, 2, 3)
fmt.Println(s, len(s), cap(s)) // 输出:[1, 2, 3] 3 4
s = append(s, 4)
fmt.Println(s, len(s), cap(s)) // 输出:[1, 2, 3, 4] 4 4
}
复制代码
当s
进行append
后,长度没有超过容量,因此底层数组的指向并无发生变化,只是将值添加到数组中。
关于知识点3,看如下代码:
func main() {
s := []int{1, 2, 3}
fmt.Println(s, len(s), cap(s)) // 输出:[1, 2, 3] 3 3
a := s
s = append(s, 4) // 超过了原来数组的容量
s[0] = 999
fmt.Println(s, len(s), cap(s)) // 输出:[999, 2, 3, 4] 4 6
fmt.Println(a, len(s), cap(s)) // 输出:[1, 2, 3] 3 3
}
复制代码
上面代码中,当对s
进行append
后,它的长度和容量都发生了变化,最重要的是它的底层数组指针指向了一个新的数组,而后将旧数组的值复制到了新的数组当中。
a
没有被影响是由于进行s[0] = 999
赋值,是由于s
的底层数组指针已经指向了一个新的数组。
咱们经过观察容量cap
的变化,能够知道slice的底层数组是否发生了变化。cap
的增加算法并非每次都将容量扩大一倍的,感兴趣的读者能够看下slice的扩容算法。
一个很重要的知识点是:Go的函数传参,都是以值的形式传参。并且Go是没有引用的,能够看下这篇文章。
若是要给函数传递一个有100w个元素的array时,直接使用array传递的效率是很是低的,由于array是值拷贝,100w个元素都复制一遍是很是可怕的;这时就应该使用slice做为参数,就至关于传递了一个指针。
若是元素数量比较少,使用array仍是slice做为参数,效率差异并不大。
package main
import "fmt"
func Assign1(s []int) {
s = []int{6, 6, 6}
}
func Reverse0(s [5]int) {
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
}
func Reverse1(s []int) {
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
}
func Reverse2(s []int) {
s = append(s, 999)
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
}
func Reverse3(s []int) {
s = append(s, 999, 1000, 1001)
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
}
func main() {
s := []int{1, 2, 3, 4, 5, 6}
Assign1(s)
fmt.Println(s)
// (1) 输出[1, 2, 3, 4, 5, 6]
// 由于是值拷贝传递,Assign1里的s和main里的s是不一样的两个指针
array := [5]int{1, 2, 3, 4, 5}
Reverse0(array)
fmt.Println(array)
// (2) 输出[1, 2, 3, 4, 5]
// 传递时对array进行了一次值拷贝,不会影响原来的array
s = []int{1, 2, 3}
Reverse2(s)
fmt.Println(s)
// (3) 输出[1, 2, 3]
// 在没有对s进行append时,len(s)=3,cap(s)=3
// append以后超过了容量,返回了一个新的slice
// 至关于只改变了新的slice,旧的slice没影响
var a []int
for i := 1; i <= 3; i++ {
a = append(a, i)
}
Reverse2(a)
fmt.Println(a)
// (4) 输出[999, 3, 2]
// 在没有对a进行append时,len(a)=3,cap(a)=4
// append后没有超过容量,因此元素直接加在了数组上
// 虽然函数Reverse2里将a的len加1了,但它只是一个值拷贝
// 不会影响main里的a,因此main里的len(a)=3
var b []int
for i := 1; i <= 3; i++ {
b = append(b, i)
}
Reverse3(b)
fmt.Println(b)
// (5) 输出[1, 2, 3]
// 原理同(3)
c := [3]int{1, 2, 3}
d := c
c[0] = 999
fmt.Println(d)
// (6) 输出[1, 2, 3]
// 数组赋值是值拷贝,因此不会影响原来的数组
}
复制代码
len
和cap
是值类型。cap
观察append
后是否分配了新的数组。