Go周刊題解:切片的另類初始化 — 該題正確率出奇的低
閱讀本文大概需要 5 分鐘。
大家好,我是站長 polarisxu。
我在 Go 語言愛好者周刊第 87 和 88 期刊首出了兩道題,這兩道題有點類似,都是和切片初始化有關(guān)。但這兩道的題正確率比較低,特別是 88 期的題。
第 87 期題目如下:
package main
import (
"fmt"
)
func main() {
a := []int{2: 1}
fmt.Println(a)
}
正確答案是:C,正確率 52%。這道題相對簡單,但依然有近一半的人答錯了。

第 88 期題目和 87 期類似,但難度高一些,題目如下:
package main
func main() {
var x = []int{4: 44, 55, 66, 1: 77, 88}
println(len(x), x[2])
}
正確答案是:C,正確率很低,只有 25%。

為了更全面,我們講解下 array/slice 的一些相關(guān)知識。
01 數(shù)組和切片
關(guān)于兩者,Go 語言規(guī)范中都有明確定義。
數(shù)組[1]是這么說明的:
數(shù)組是單一類型元素的有序序列,該單一類型稱為元素類型。元素的個數(shù)被稱為數(shù)組長度,并且不能為負值。長度是數(shù)組類型的一部分;它必須為一個可以被 int 類型的值所代表的非負常量。
這里一個關(guān)鍵點就是,長度是數(shù)組的一部分,因此 [3]int 和 [4]int 是不同類型。
再看看切片[2]:
切片是針對一個底層數(shù)組的連續(xù)段的描述符,它提供了對該數(shù)組內(nèi)有序序列元素的訪問。切片類型表示其元素類型的數(shù)組的所有切片的集合。元素的數(shù)量被稱為切片長度,且不能為負。未初始化的切片的值為
nil。
從 EBNF 的表示可以看出區(qū)別:
ArrayType = "[", ArrayLength, "]", ElementType .
SliceType = "[", "]", ElementType .
也就是說,長度不是切片類型的一部分,切片長度可變。
02 常見字面量初始化
我不打算講解數(shù)組/切片初始化的各種情況,主要介紹常見的字面量初始化,以及和上面題目相關(guān)的部分。
通常我們會這么初始化一個數(shù)組:
var intSet = [6]int{2, 4, 6}
注意 [] 中的 6,它表示數(shù)組的長度。因為初始化時,我們只給定了 3 個數(shù),因此后 3 個元素是 0:
[2 4 6 0 0 0]
注意和這種寫法的區(qū)別:
var intSet = [...]int{2, 4, 6}
對于切片來說,一般這樣初始化:
var intSlice = []int{2, 4, 6}
// 或基于 intSet 進行初始化
var intSlice = intSet[:]
當然,針對 Slice,更多時候是通過 make 創(chuàng)建,然后其他方式初始化,這里不展開了。
03 特殊的初始化
在 Go語言規(guī)范「Composite literals[3]」部分對數(shù)組和切片的字面值初始化進行了規(guī)定,因為數(shù)組和切片類似,我們這里只說切片的情況。
先看組合字面值的 EBNF 表示:
CompositeLit = LiteralType, LiteralValue .
LiteralType = StructType | ArrayType | "[", "...", "]", ElementType |
SliceType | MapType | TypeName .
LiteralValue = "{", [ ElementList, [ "," ] ], "}" .
ElementList = KeyedElement, { ",", KeyedElement } .
KeyedElement = [ Key, ":" ], Element .
Key = FieldName | Expression | LiteralValue .
FieldName = identifier .
Element = Expression | LiteralValue .
從上到下看,簡單解釋一下:
第 1 行,表示組合字面值由 LiteralType 和 LiteralValue 構(gòu)成,其中 LiteralType 表示組合字面值的類型,LiteralValue 表示值; 第 2 行,解釋 LiteralType,它可以是 =后面的類型。允許的類型有:結(jié)構(gòu)體、數(shù)組、切片、map 等,其中還可以是類似[…]int的形式;第 4 行,解釋 LiteralValue,它由一對 {}包裹,其中包含可選的 ElementList;第 5 行,解釋 ElementList,它由若干 KeyedElement 組成; 第 6 行,解釋 KeyedElement,這是該篇題目的重點之處。在 EBNF 中, []表示這部分是可選的,因此表示具體元素時,一般 Key 可以省略(map 不能省略),這就是通常數(shù)組和切片的初始化語法;
在這個之后,規(guī)范上給出了針對數(shù)組和切片字面值的應用規(guī)則:
數(shù)組中的每個元素有一個關(guān)聯(lián)的標記其位置的整數(shù)索引。 帶鍵的元素使用該鍵作為其索引。這個鍵必須是可被類型 int 所表示的一個非負常量;而且如果其被賦予了類型的話則必須是整數(shù)類型。 不帶鍵的元素使用之前元素的索引加一。如果第一個元素沒有鍵,則其索引為零。
根據(jù)以上 3 點,我們很容易知道,在 a := []int{2: 1} 中,我們指定了第 3 個元素(注意索引是從 0 開始的)的值為 1,根據(jù)數(shù)組/切片的特性,自然存在第 1、2 個元素,沒有指定值時,Go 會為其設(shè)置默認值。因此這個寫法和下面的寫法等價:
a := []int{0, 0, 1}
對于第 88 期的題目:
var x = []int{4: 44, 55, 66, 1: 77, 88}
指定了第 5 個元素(對應索引是 4),值是 44。根據(jù)上面規(guī)則的第三點,55、66 都沒有指定索引,因此它們的索引是前一個元素的索引加一,即:
5: 55, 6: 66
下一個元素是 1: 77,為其指定了索引 1,因此它的下一元素 88 的索引就是 2 了,因此這個定義相當于如下的定義:
var x = []int{4: 44, 5: 55, 6: 66, 1: 77, 2: 88}
同樣,因為數(shù)組/切片的特性,缺少的元素(索引 0 和 3)值是 0,而整個切片的長度是最大索引加一,即 7。
04 總結(jié)
別覺得這道題目惡心,實際中這么寫代碼可能也確實會被打(當然,第 87 題的寫法還是很有可能的)。這里主要是希望大家多掌握一些規(guī)范、細節(jié),我想不少人不清楚,原來數(shù)組(切片)也可以指定索引進行初始化。語言語法畢竟必須嚴謹,而這些都在 Go 語言規(guī)范里。
延伸思考:第 88 期的題目,如果改為這樣結(jié)果又如何?
var x = []int{4: 44, 55, 66, 3: 77, 88}
歡迎大膽的留言說出你的答案!
參考資料
數(shù)組: https://hao.studygolang.com/golang_spec.html#ruby-rb-rb-rp-rp-rt-array-types-rt-rp-rp-ruby
[2]切片: https://hao.studygolang.com/golang_spec.html#ruby-rb-rb-rp-rp-rt-slice-types-rt-rp-rp-ruby
[3]Composite literals: https://hao.studygolang.com/golang_spec.html#ruby-rb-rb-rp-rp-rt-composite-literals-rt-rp-rp-ruby
歡迎關(guān)注我
都看到這里了,隨手點個贊支持下唄!
