來源: 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
}
複製代碼
slice的底層結構由一個指向數組的指針ptr和長度len,容量cap構成,也就是說slice的數據存在數組當中。
關於知識點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
後是否分配了新的數組。