Go 中的复合字面量

在源代码中字面量可以描述像数字,字符串,布尔等类型的固定值。Go 和 JavaScript、Python 语言一样,即便是复合类型(数组,字典,切片,结构体)也允许使用字面量。Golang 的复合字面量表达也很方便、简洁,使用单一语法即可实现。在 JavaScript 中它是这样的:

var numbers = [1, 2, 3, 4]
var thing = {name: "Raspberry Pi", generation: 2, model: "B"}

在 Python 中有类似的语法:

elements = [1, 2, 3, 4]
thing = {"name": "Raspberry Pi", "generation": 2, "model": "B"}

在 Go 中也类似(这里有些地方可以变通):

elements := []int{1, 2, 3, 4}
type Thing struct {
    name       string
    generation int
    model      string
}
thing := Thing{"Raspberry Pi", 2, "B"}
// 或者直接使用结构体的项名称
thing = Thing{name: "Raspberry Pi", generation: 2, model: "B"}

除了字典类型以外的其他类型,键是可选的,便于理解没有歧义:

  • 对结构体而言,键就是项名称
  • 对于数组或切片而言,键就是索引

键不是字面量常量,就必须是常量表达式;因此这种写法是错误的:

f := func() int { return 1 }
elements := []string{0: "zero", f(): "one"}

这将导致编译异常 -- "index must be non-negative integer constant"。而常量表达式或者字面量是合法的:

elements := []string{0: "zero", 1: "one", 4 / 2: "two"}

编译一切顺利。

重复的键是不允许的:

elements := []string{
    0:     "zero",
    1:     "one",
    4 / 2: "two",
    2:     "also two"
}

在编译时会报出 "duplicate index in array literal: 2" 的异常信息。这也同样适用于结构体:

type S struct {
    name string
}
s := S{name: "Michał", name: "Michael"}

编译结果是 "duplicate field name in struct literal: name" 的错误。

相应的字面量必须被赋值给相应的键,元素或结构体的项。更多关于可赋值性的内容可查看 "Go 语言中的可赋值性"一文。

结构体

对于结构体类型定义的项,这里有两三个创建实例时的规定。

像下面的代码片段,结构体的定义必须指定内部项的名称,并且如果使用了那些定义以外的名称,那么在编译时会发生错误:"unknown S field ‘name’ in struct literal":

type S struct {
    age int8
}
s := S{name: "Michał"}

如果最先的字面量有对应的键,那么之后的字面量也必须有对应的键,下面这样写是不合理的 :

type S struct {
    name string
    age int8
}
s := S{name: "Michał", 29}

像这样,编译器会抛出异常"mixture of field:value and value initializers",可以通过省略结构体中所有元素对应的键来更正它。

s := S{"Michał", 29}

但这样又有一个附加限制:字面量的依次顺序必须与结构体定义时各项的顺序保持一致。
必须使用键:值或值的形式初始化结构体,并不代表我们必须赋值结构体中的每一项。被忽略的项将会默认赋值为该项类型对应的零值:

type S struct {
    name string
    age int8
}
s := S{name: "Michał"}
fmt.Printf("%#v\n", s)

输出:

main.S{name:"Michał", age:0}

只有使用键:值的形式初始化结构体时,才会有默认赋零值的操作:

s := S{"Michał"}

这种写法是不能编译通过的,会抛出异常"too few values in struct initializer"。当有新的项被添加到结构体的一个被字面量赋值的项之前时,这种报错的做法对程序员更加安全--如果这个结构体中,一个名为"title"的字符串类型的项被加到了"name"项之前,那么值"Michał"将被认为是一个"title",而这个问题是很难被排查出来的。
如果结构体字面量是空值,那么结构体内每一项都会被赋于零值:

type Employee struct {
    department string
    position   string
}
type S struct {
    name string
    age  int8
    Employee
}
main.S{name:"", age:0, Employee:main.Employee{department:"", position:""}}

最后一个规定,结构体的赋值与导出标示有关(简而言之,字面量不允许给非导出项赋值)

数组和切片

数组或者切片的元素都是被索引的,所以在字面上键必须是整数常量表达式。对于没有键的元素,键将会被赋值为前一个元素索引加一。在字面上,第一个元素的键(索引)如果没有被赋值,默认设为零。

numbers := []string{"a", "b", 2 << 1: "c", "d"}
fmt.Printf("%#v\n", numbers)
[]string{"a", "b", "", "", "c", "d"}

赋值元素的数量可以小于数组的长度(被忽略的元素将会被赋值为零):

fmt.Printf("%#v\n", [3]string{"foo", "bar"})
[3]string{"foo", "bar", ""}

不允许对超出范围的索引赋值,所以下面几行代码是无效的:

[1]string{"foo", "bar"}
[2]string{1: "foo", "bar"}

可以通过使用三个点(...)的快捷符号来省去程序员声明数组长度的工作,编译器会通过索引最大值加一的方式获得它:

elements := […]string{2: "foo", 4: "bar"}
fmt.Printf("%#v, length=%d\n", elements, len(elements))

输出:

[5]string{"", "", "foo", "", "bar"}, length=5

切片与之前数组内容基本一致:

els := []string{2: "foo", 4: "bar"}
fmt.Printf("%#v, length=%d, capacity=%d\n", els, len(els), cap(els))

得出结果:

[]string{"", "", "foo", "", "bar"}, length=5, capacity=5

字典

除了将数组长度替换成了键类型以外,字典字面量的语法和数组非常类似。

constants := map[string]float64{"euler": 2.71828, "pi": .1415926535}

快捷方式

如果作为字典键或者数组、切片、字典元素的字面量的类型与键或元素的类型一致,那么为了简洁,这个类型可以省略不写:

coords := map[[2]byte]string{{1, 1}: "one one", {2, 1}: "two one"}
type Engineer struct {
    name string
    age  byte
}
engineers := [...]Engineer{{"Michał", 29}, {"John", 25}}

同样,如果键或元素是指针类型的话,&T也可以被省略:

engineers := […]*Engineer{{"Michał", 29}, {"John", 25}}
fmt.Printf("%#v\n", engineers)

输出:

[2]*main.Engineer{(*main.Engineer)(0x8201cc1e0), (*main.Engineer)(0x8201cc200)}
posted @ 2018-08-29 10:58  叨叨的蜗牛  阅读(1220)  评论(0编辑  收藏  举报