Go愛好者周刊題解:關(guān)于 len 函數(shù)的問題
閱讀本文大概需要 5 分鐘。
大家好,我是 polarisxu。
在 Go語言愛好者周刊第 104 期有一道題目,以下代碼輸出什么:
package main
func main() {
var x *struct {
s [][32]byte
}
println(len(x.s[99]))
}
答題結(jié)果如下:

正確率只有 16%。
本文就講解下為什么結(jié)果是 32。
01 解析題目
先剖析下這段代碼,x 變量:
var x *struct {
s [][32]byte
}
注意這里不是定義一個結(jié)構(gòu)體類型,而是定義一個結(jié)構(gòu)體類型指針變量,即 x 是一個指針,指針類型是一個匿名結(jié)構(gòu)體。很顯然,x 的值是 nil,因為沒有初始化,可以打印證實這一點。
package main
import "fmt"
func main() {
var x *struct {
s [][32]byte
}
fmt.Printf("x.Type = %T; x.Value= %v\n", x, x)
}
輸出:
x.Type = *struct { s [][32]uint8 }; x.Value= <nil>
這也是為什么 48% 的人選擇 A (panic) 的原因,畢竟 x 是 nil,panic 很自然的。比如這樣就會 panic:
println(x.s)
// panic: runtime error: invalid memory address or nil pointer dereference
相應(yīng)的,fmt.Println(x.s[99]) 也會 panic。但為什么 len(x.s[99]) 就不 panic 了呢?所以得從 len 入手一探究竟。
02 len 詳解
len 函數(shù)是一個內(nèi)置類型,什么意思?就是由編譯器實現(xiàn)的。它的參數(shù)可以接收多種類型,有泛型的味道。
func len(v Type) int
關(guān)于它的說明,標準庫文檔有說明:
內(nèi)建函數(shù) len 返回 v 的長度,這取決于具體類型:
數(shù)組:v 中元素的數(shù)量 數(shù)組指針:*v 中元素的數(shù)量(v 為 nil 時 panic) 切片、map:v 中元素的數(shù)量;若 v 為nil,len(v) 即為零 字符串:v 中字節(jié)的數(shù)量 通道:通道緩存中隊列(未讀取)元素的數(shù)量;若 v 為 nil,len(v) 即為零
光這個解釋,還不夠全面,len 函數(shù)還有其他一些特殊的點。這要看 Go 語言規(guī)范。
在規(guī)范中,有一節(jié)是關(guān)于 len 和 cap 的[1]。有如下幾個要點:
返回結(jié)果總是 int; 返回結(jié)果有可能是常量; 有時對函數(shù)參數(shù)不求值,即編譯期確定返回值;
2、3 點解釋下。(規(guī)范中有說明)
如果 len 或 cap 的函數(shù)參數(shù) v 是字符串常量,則返回值是一個常量。
如果 v 的類型是數(shù)組或指向數(shù)組的指針,且表達式 v 沒有包含 channel 接收或(非常量)函數(shù)調(diào)用,則返回值也是一個常量。這種情況下,不會對 v 進行求值(即編譯期就能確定)。否則返回值不是常量,且會對 v 進行求值(即得運行時確定)。
這一點是這道題的關(guān)鍵。
首先,x.s[99] 的類型是 [32]byte,它是一個數(shù)組,且表達式 x.s[99] 沒有包含 channel 接收也不是函數(shù)調(diào)用,因此不會對 x.s[99] 進行求值,不求值自然不會 panic(想不明白?可以想成沒有解引用操作)。也就是說,編譯器能夠在編譯階段分析出 x.s[99] 的類型是 [32]byte,且不需要對 x.s[99] 求值,因此直接返回數(shù)組的長度,即 32。
03 其他類似情況
類似這樣不求值的情況還有沒有?還真有。比如下面的代碼:
var testdata *struct {
a *[7]int
}
for i, _ := range testdata.a {
fmt.Println(i)
}
同樣不會 panic,原理和上面的類似,在 Go 語言規(guī)范有說明[2]。
"range" 子句中右側(cè)的表達式被稱為 range 表達式 ,它可以是數(shù)組、數(shù)組的指針、切片、字符串、map或是允許接收操作 的 channel。range 表達式會在開始此循環(huán)前被求值一次,但有一個例外:當存在最多一個迭代變量且 len(x) 是常量時,range 表達式是不被求值的。
所以上面代碼中 testdata.a 不會被求值,因為 len(testdata.a) 是常量。
但如果改為這樣:
var testdata *struct {
a *[7]int
}
for i, j := range testdata.a {
fmt.Println(i, j)
}
就會 panic。
04 總結(jié)
通過這么一道「詭異」的面試題,希望你能夠?qū)?len 有更深的理解,也希望你能夠重視 Go 語言規(guī)范,多留意一些細節(jié),同時學會如何尋找問題的答案。
參考資料
關(guān)于 len 和 cap 的: https://hao.studygolang.com/golang_spec.html#id221
[2]在 Go 語言規(guī)范有說明: https://hao.studygolang.com/golang_spec.html#id355
