Go中的哪些语义规则决定何时进行一值分配与何时进行二值分配?

问题描述

在研究A Tour of Go: Mutating Maps中的map时,令我感到惊讶的是,我们可以使用一值赋值或二值赋值来访问映射中键的值。示例代码

package main

import (
    "fmt"
)

func main() {
    m := map[int]int{2: 4,3: 9,4: 16}
    
    // Example 1
    fmt.Println(m[2])
    
    // Example 2
    v := m[2]
    fmt.Println(v)
    
    // Example 3
    v,ok := m[2]
    fmt.Println(v,ok)
}

输出

4
4
4 true

使用相同的语法支持一值和二值赋值涉及哪些语义规则? Go中是否还有其他特殊形式支持相同值的一值和二值分配,具体取决于赋值运算符的左侧?

我还可以自己编写一个函数foo(),该函数可以根据赋值运算符的左侧返回一个值或两个值吗?

解决方法

地图索引操作的一两个值赋值是一种特殊形式,为方便起见,不幸的是,它不能在“常规”赋值中完成。

常规分配表达式:

该规范对元组assignments的描述如下:

元组分配分配多值的各个元素 操作到变量列表。有两种形式。首先, 右手操作数是单个多值表达式,例如 函数调用,通道或映射操作或类型声明。的 左侧的操作数数目必须与 价值观。例如,如果f是一个返回两个值的函数,

x,y = f()

将第一个值分配给x,将第二个值分配给y。在第二种形式中 左边的操作数必须等于 右边的表达式,每个表达式必须为单值,并且 右边的第n个表达式被分配给 左:

one,two,three = '一','二','三'

这对赋值中的值数量没有歧义。

一个或两个值的表达式:

在4种情况下,表达式的左侧都允许一个和两个值。其中三个是赋值表达式的特殊形式,最后一个是range子句。

索引表达式

Index expressions被定义为a[x]形式,但地图明显例外:

在分配中使用的类型为map [K] V的map a上的索引表达式 或特殊形式

的初始化
v,ok = a[x]
v,ok := a[x]
var v,ok = a[x]

产生另一个无类型的布尔值。

接收运营商

Receive Operator也会发生同样的情况,通常其格式为x <-ch

一个接收表达式,用于赋值或初始化 特殊形式

x,ok = <-ch
x,ok := <-ch
var x,ok = <-ch
var x,ok T = <-ch

产量 附加的无类型布尔结果,报告是否 交流成功。

类型断言

type assertions中通常会以x.(T)的形式提到特殊形式

在类型的赋值或初始化中使用的类型断言 特殊形式

v,ok = x.(T)
v,ok := x.(T)
var v,ok = x.(T)
var v,ok T1 = x.(T)

产生另一个无类型的布尔值。

范围子句

for statement with range clause的语言较宽松,因为它不是对常规赋值表达式的修改:

左侧的函数调用每次迭代评估一次。对于每个 迭代,如果各自 存在迭代变量:

Range expression                          1st value          2nd value

array or slice  a  [n]E,*[n]E,or []E    index    i  int    a[i]       E
string          s  string type            index    i  int    see below  rune
map             m  map[K]V                key      k  K      m[k]       V
channel         c  chan E,<-chan E       element  e  E

用于非分配:

如上所述,所有三种特殊形式仅用于分配。尝试在其他表达式(函数调用,返回等)中使用多值返回将失败,因为这些不是赋值,也无法从特殊形式中受益。