Go面試必考題目之slice篇
來源: https://mp.weixin.qq.com/s/gUkYwCodYI0iAD5LMYXdMA
歡迎關注公眾號《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的底層原理,你就能避開這些坑且能輕鬆的答對上面幾道題。
array底層
Go的陣列array底層和C的陣列一樣,是一段連續的記憶體空間,通過下標訪問陣列中的元素。array只有長度len
屬性而且是固定長度的。
array的賦值是值拷貝的,看以下程式碼:
func main() {
c := [3]int{1, 2, 3}
d := c
c[0] = 999
fmt.Println(d) // 輸出[1, 2, 3]
}
因為是值拷貝的原因,c
的修改並沒有影響到d
。
slice底層
掌握Go的slice,底層結構必須要了解。
type slice struct {
array unsafe.Pointer
len int
cap int
}
slice的底層結構由一個指向陣列的指標ptr和長度len,容量cap構成,也就是說slice的資料存在陣列當中。
slice的重要知識點
- slice的底層是陣列指標。
- 當append後,slice長度不超過容量cap,新增的元素將直接加在陣列中。
- 當append後,slice長度超過容量cap,將會返回一個新的slice。
關於知識點1,看以下程式碼:
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的擴容演算法。
使用array還是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]
// 陣列賦值是值拷貝,所以不會影響原來的陣列
}
總結
- 謹記slice的底層結構是指標陣列,並且
len
和cap
是值型別。 - 使用
cap
觀察append
後是否分配了新的陣列。 - Go的函式傳參都是值拷貝傳遞。
感謝閱讀,歡迎大家指正,留言交流~
相關文章
- Go面試必考題目之method篇Go面試
- 分享一道Go面試必考的題Go面試
- Go Quiz: 從Go面試題搞懂slice range遍歷的坑GoUI面試題
- 面試必考:網路問題面試
- 100家IT 名企面試必考面試題java系列面試題Java
- Go語言之切片(slice)快速入門篇Go
- 前端面試必考題:React Hooks 原理剖析前端面試ReactHook
- Python面試中8個必考問題Python面試
- Python 面試中 8 個必考問題Python面試
- go(golang)之slice的小想法1Golang
- Java面試題:之ZooKeeper篇Java面試題
- 面試題整理之Vue篇面試題Vue
- go slice使用Go
- 大型網際網路公司必考java面試題與面試技巧Java面試題
- js面試必考三問JS面試
- 面試題聯盟之 VUE 篇面試題Vue
- Android面試題之Fragment篇Android面試題Fragment
- 面試題目面試題
- 2018最新《BAT Java必考面試題集》BATJava面試題
- go map 和 sliceGo
- 前端面試題目蒐集——理論知識篇前端面試題
- Go 面試題Go面試題
- 限時看!阿里、華為資料結構面試必考題!阿里資料結構面試
- 「高頻必考」Docker&K8S面試題和答案DockerK8S面試題
- TX 面試題目面試題
- JavaScript面試題目,JavaScript面試題
- 面試題目收集面試題
- DBA 面試題目面試題
- 面試題目(zt)面試題
- ABAP面試題目面試題
- Go中的切片SliceGo
- SQL面試必考——計算留存率SQL面試
- Go 切片 slice - Go 學習記錄Go
- Java最全面試題之Spring篇Java面試題Spring
- 持續輸出面試題之RocketMQ篇面試題MQ
- Android面試題之Gradle配置篇Android面試題Gradle
- iPhone 常用面試題目iPhone面試題
- 前端面試題目前端面試題