表达式
46 分钟阅读
Expressions 表达式
表达式通过将运算符和函数应用于操作数来规定值的计算。
Operands 操作数
操作数表示表达式中的基本值。操作数可以是一个字面量,一个表示常量、变量或函数的(可以是限定的)非空白标识符,或者一对圆括号内的表达式。
Operand = Literal | OperandName [ TypeArgs ] | "(" Expression ")" .
Literal = BasicLit | CompositeLit | FunctionLit .
BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent .
表示泛型函数的操作数名称后面可以跟一个类型实参列表;产生的操作数是一个实例化过的函数。
实现限制:若操作数的类型是具有空类型集的类型形参,则编译器不必报告错误。具有这种类型形参的函数不能被实例化;任何尝试都会导致实例化处的错误。
Qualified identifiers 限定标识符
限定标识符是以包名作为前缀限定的标识符。包名和标识符都不能是空白标识符(即_
)。
QualifiedIdent = PackageName "." identifier .
限定标识符可以在不同包中访问一个标识符,但该标识符所在的包必须已经被导入。该标识符必须可被导出并在该包的package block中声明。
|
|
Composite literals 复合字面量
复合字面量每次被求值时都会构造新的复合值。它们由字面量的类型和一个由花括号组成的元素列表组成
。每个元素可以选择在前面加上一个相应的键。
CompositeLit = LiteralType LiteralValue .
LiteralType = StructType | ArrayType | "[" "..." "]" ElementType |
SliceType | MapType | TypeName [ TypeArgs ] .
LiteralValue = "{" [ ElementList [ "," ] ] "}" .
ElementList = KeyedElement { "," KeyedElement } .
KeyedElement = [ Key ":" ] Element .
Key = FieldName | Expression | LiteralValue .
FieldName = identifier .
Element = Expression | LiteralValue .
LiteralType 的核心类型T
必须是一个结构体、数组、切片或映射类型(除了当类型是作为TypeName给出时,语法会强制执行这个约束)。元素和键的类型必须可分配给T
类型的对应字段、元素和键类型;不需要进行额外的转换。
这里的键被解释为结构体字面量的字段名、数组字面量或切片字面量的索引、映射字面量的键。
对于映射字面量,所有的元素必须有一个键。用相同的字段名或常量键值指定多个元素是错误的。对于非常量的映射键,请参见关于求值顺序的章节。
对于结构体字面量来说,以下规则适用:
- 键必须是结构体类型中声明的字段名。
- 不包含任何键的元素列表必须按照字段的声明顺序为每个结构体字段列出一个元素。
- 如果任何元素有一个键,那么每个元素都必须有一个键。
- 包含键的元素列表不需要每个结构体字段都有一个元素。省略的字段将获得该字段类型的零值。
字面量可以省略元素列表;这样的字面量相当对其类型的求值为零值
。- 为属于不在同一个包中的结构体(即该结构体在其他包中定义)的非可导出字段指定元素是错误的。
给定的声明:
|
|
我们可以这样写:
|
|
对于数组字面量和切片字面量,以下规则适用:
- 每个元素都有一个相关的整数索引,标记其在数组中的位置。
- 带键的元素使用该键作为其索引。键必须是一个可由
int
类型的值表示的非负常数;如果它是有类型的,则它必须是整数类型。 - 不带键的元素使用前一个元素的索引加1。如果第一个元素没有键,它的索引是0。
对一个复合字面量取址会产生一个指向唯一变量的指针,该变量用字面量的值初始化。
|
|
请注意,切片或映射类型的零值与同一类型的初始化过但为空的值不同。因此,获取一个空切片或空映射复合字面量的地址与用new分配一个新的切片或映射值的效果不同。
|
|
数组字面量的长度是字面量类型中指定的长度。如果在字面量上提供的元素少于长度,缺少的元素将被设置为数组元素类型的零值。若提供的元素的索引值超出了数组的索引范围,将导致错误。标记法...
指定的数组长度等于最大元素索引加1。
|
|
切片字面量描述了整个底层数组字面量。因此,切片字面量的长度和容量是最大元素索引加1。切片字面量的形式是:
|
|
是对数组进行切片操作的简写:
|
|
在一个数组、切片或映射类型T
的复合字面量中,如果本身是复合字面量的元素或映射键与T
的元素或键类型相同,则可以省略(元素或映射键的)相应的字面量类型。同样,当元素或键类型为*T
时,作为复合字面量地址的元素或键可以省略&T
。
|
|
当使用LiteralType的TypeName形式的复合字面量作为操作数
出现在关键字和 “if
"、"for
“或 “switch
“等语句块的左花括号
之间,并且复合字面量没有被括在圆括号、方括号或花括号中时,会出现解析歧义。在这种罕见的情况下,字面量的左花括号被错误地解析为引入语句块的左花括号。为了解决这个问题,复合字面量必须出现在圆括号
内。
|
|
有效的数组、切片和映射字面量的例子:
|
|
Function literals 函数字面量
函数字面量表示一个匿名函数。函数字面量不能声明类型参数
。
|
|
|
|
函数字面量可以被分配给一个变量或直接调用。
|
|
函数字面量可以是闭包
:它们可以引用外层函数中定义的变量。然后,这些变量在外层的函数和函数字面量之间共享,并且只要它们可以被访问,它们就可以一直存在。
Primary expressions 主表达式
主表达式是一元、二元表达式的操作数。
PrimaryExpr =
Operand |
Conversion |
MethodExpr |
PrimaryExpr Selector |
PrimaryExpr Index |
PrimaryExpr Slice |
PrimaryExpr TypeAssertion |
PrimaryExpr Arguments .
Selector = "." identifier .
Index = "[" Expression "]" .
Slice = "[" [ Expression ] ":" [ Expression ] "]" |
"[" [ Expression ] ":" Expression ":" Expression "]" .
TypeAssertion = "." "(" Type ")" .
Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
|
|
Selectors 选择器
|
|
表示值x
(有时是*x
;见下文)的字段或方法f
。标识符f
被称为(字段或方法)选择器
;它不能是空白标识符。选择器表达式的类型是f
的类型。若x
是包名,请参见关于限定标识符一节。
选择器f
可以表示类型T
的f
字段或f
方法,也可以指代T
的嵌入字段或嵌入方法f
。在T
的一个嵌入字段A
中声明的字段或方法f
的深度是A
中f
的深度加1。
以下规则适用于选择器:
- 对于类型为
T
或*T
(T
不是指针或接口类型)的值x
,x.f
表示T
中存在这样一个最浅深度的字段或方法f
。如果不是恰好有仅有一个f
在最浅深度的话,那么选择器表达式是非法的。 - 对于接口类型
I
的值x
,x.f
表示动态值x
的名为f
的实际方法。如果在I
的方法集中没有名为f
的方法,那么选择器表达式是非法的。 - 作为例外,如果
x
的类型是一个定义的指针类型,并且(*x).f
是一个有效的表示一个字段(不是一个方法)的选择器表达式,那么x.f
是(*x).f
的简写。 - 在所有其它情况下,
x.f
是非法的。 - 如果
x
是值为nil
的指针类型,并且x.f
表示一个结构体字段,那么赋值或计算x.f
会引起运行时恐慌。 - 如果
x
是值为nil
的接口类型,那么调用或计值x.f
方法会引起运行时恐慌。
例如,给定声明:
|
|
则我们可以这样写:
|
|
但下面的内容是无效的:
|
|
Method expressions 方法表达式
如果M
在类型T
的方法集中,那么T.M
是一个可以作为普通函数来调用的函数,其实参与M
相同,不过其前缀有一个额外的(作为该方法的接收器的)实参。
MethodExpr = ReceiverType "." MethodName .
ReceiverType = Type .
考虑有两个方法的结构体类型T
,方法一的接收器是T
类型的Mv
,方法二的接收器是*T
类型的Mp
。
|
|
表达式
|
|
产生一个与Mv
等价的函数,但是它的第一个形参是一个明确的接收器;它的签名是:
|
|
该函数可以在带有一个明确接收器的情况下正常调用,因此如下这五种调用是等同的:
|
|
类似地,表达式
|
|
产生一个代表Mp
的函数值,它的签名是:
|
|
对于一个带值接收器
的方法,可以推导出
一个带有明确指针接收器的函数,所以
|
|
产生了一个代表Mv
的函数值,它的签名是:
|
|
Such a function indirects through the receiver to create a value to pass as the receiver to the underlying method; the method does not overwrite the value whose address is passed in the function call.
这样的函数通过接收器间接地
创建了一个值,作为接收器传递给底层方法;该方法不会覆盖(其地址在函数调用中被传递的)那个值。=>仍有疑问??
The final case, a value-receiver function for a pointer-receiver method, is illegal because pointer-receiver methods are not in the method set of the value type.
最后一种情况,将一个带指针接收器的方法当做
一个带值接收器的函数,是非法的,因为指针接收器的方法不在值类型的方法集中。=>仍有疑问??
从方法中推导出来的函数值是用函数调用语法
来调用的;接收器被作为调用的第一个实参来提供。也就是说,f := T.Mv
中的f
是作为f(t, 7)
被调用,而不是t.f(7)
。构造一个绑定接收器的函数,可以使用函数字面量或方法值。
从接口类型的方法中推导出来函数值是合法的。这样的函数需要一个该接口类型的显式接收器。
Method values 方法值
如果表达式x
有静态类型T
,并且M
在类型T
的方法集中,那么x.M
被称为一个方法值
。方法值x.M
是一个可调用的函数值,其实参与x.M
的方法调用相同。表达式x
在方法值的求值过程中被求值和保存;然后保存的副本被用作任何调用中的接收器上,这些调用可能在以后执行。
|
|
类型T
既可以是接口类型,也可以是非接口类型。
如同上面对方法表达式的讨论,考虑一个有两个方法的结构体类型T
,方法一的接收器是T
类型的Mv
,方法二的接收器是*T
类型的Mp
。
|
|
表达式
|
|
产生一个类型如下的函数值:
|
|
这两种调用是等价的:
|
|
同样地,表达式
|
|
产生一个类型如下的函数值:
|
|
和选择器一样,若对以值作为接收器的非接口方法,使用指针来引用,则(Go语言)将自动解除对该指针的引用:pt.Mv
等同于(*pt).Mv
。
和方法调用一样,若对以指针作为接收器的非接口方法,使用可寻址的值来引用,则(Go语言)将自动获取该值的地址:t.Mp
等同于(&t).Mp
。
|
|
尽管上面的例子使用了非接口类型,但从接口类型的值中创建一个方法值也是合法的。
|
|
Index expressions 索引表达式
若主表达式的形式是:
|
|
则表示可用x
来检索的数组a
、数组指针a
、切片a
、字符串a
或映射a
的元素,x
分别被称为索引
或映射键
。以下规则适用:
如果a
既不是映射也不是类型参数:
- 索引
x
必须是一个无类型的常量,或者其核心类型必须是整数类型 - 常量索引必须是非负且可以用
int
类型的值来表示 - 无类型常量索引会被赋予
int
类型。 - 如果
0 <= x < len(a)
,则索引x
在范围内,否则就超出了范围。
对于数组类型A
的a
:
对于数组类型指针的a
:
a[x]
是(*a)[x]
的简写
对于切片类型S
的a
:
- 如果
x
在运行时超出了范围,就会发生运行时恐慌 a[x]
是索引x
处的切片元素,a[x]
的类型是S
的元素类型。
For a
of string type:
对于字符串类型的a
:
对于映射类型为M
的a
:
对于参数类型为P
的a
:
- 索引表达式
a[x]
必须对P
的类型集中的所有类型的值有效。 P
的类型集中所有类型的元素类型必须是相同的。在此上下文中,字符串类型的元素类型是byte
。- 如果在
P
的类型集中有一个映射类型,那么该类型集中的所有类型必须是映射类型,且对应的键类型必须都是一致的。 a[x]
是索引为x
的数组、切片或字符串元素,或者P
实例化的类型实参中键为x
的映射元素,a[x]
的类型是(一致的)元素类型的类型。- 如果
P
的类型集包括字符串类型,则a[x]
不能再被赋值。
否则a[x]
是非法的。
若将类型为map[K]V
的映射a
上的索引表达式使用在赋值语句或特殊格式的初始化中:
|
|
将产生一个额外的无类型
布尔值。如果键x
存在于映射中,ok
的值为true
,否则为false
。
若给nil
映射的元素赋值,将导致运行时恐慌。
Slice expressions 切片表达式
切片表达式从一个字符串、数组、数组指针或切片中构造一个子串或切片。有两种变体:一种是指定低位和高位边界的简单形式,另一种是同时也指定容量的完整形式。
Simple slice expressions 简单切片表达式
主表达式
|
|
构造了一个子字符串或切片。a
的核心类型必须是字符串、数组、数组指针、切片或者bytestring。low
和high
所在的索引选择了哪些元素显示在操作数a
的结果中。若结果的索引从0开始,则长度等于high
减去 low
。在对数组a
进行切片后
|
|
切片s
有类型[]int
,长度3,容量4,以及元素
|
|
为方便起见,任何索引都可以被省略。缺少的low
索引默认为0;缺少的high
索引默认为被切片的操作数的长度:
|
|
如果a
是一个数组指针,则a[low:high]
是(*a)[low:high]
的简写。
对于数组或字符串,如果0 <= low <= high <= len(a)
,则索引在范围内,否则就超出了范围。对于切片,索引的上限是切片的容量cap(a)
,而不是长度。常量索引必须是非负的,并且可以用int
类型的值来表示;对于数组或字符串常量,常量索引也必须在范围内。如果两个索引都是常量,它们必须满足low <= high
。如果索引在运行时超出范围,就会发生运行时恐慌。
除了无类型字符串外:
如果被切片的操作数是字符串或切片,则切片的操作结果是一个与操作数相同类型的非常量值。
如果被切片的操作数是无类型的字符串,则切片的操作结果是一个
string
类型的非常量值。如果被切片的操作数是(必须可被寻址的)数组,则切片的操作结果是一个与数组的元素类型一致的切片。
如果有效切片表达式的切片操作数是nil
切片,那么切片的操作结果就是一个nil
切片。否则,如果切片的操作结果是一个切片,则它与操作数共享底层数组。
|
|
Full slice expressions 完整的切片表达式
主表达式
|
|
构造了一个与简单切片表达式a[low : high]
相同类型的切片,并且具有相同的长度和元素。此外,它通过将结果切片设置为max 减去 low
的容量。a
的核心类型必须是数组,数组指针,或者切片(但不是字符串)。在对数组a
进行切分后
|
|
切片t
有类型[]int
,长度2,容量4,以及元素
|
|
与简单切片表达式一样,如果a
是一个数组指针,则a[low:high:max]
是(*a)[low:high:max]
的简写。如果切片的操作数是一个数组,它必须是可被寻址的。
如果0 <= low <= high <= max <= cap(a)
,则索引就在范围内,否则就超出了范围。常量索引必须是非负数,并且可以用int
类型的值来表示;对于数组,常量索引也必须在范围内。如果多个索引是常量,那么出现的常量必须在相对于彼此的范围内。如果索引在运行时超出了范围,就会发生运行时恐慌。
Type assertions 类型断言
|
|
断言了x
不是nil
,并且x
中存储的值是T
类型。标记法x.(T)
被称为类型断言
。
更确切地说,如果T
不是接口类型,则x.(T)
断言x
的动态类型与T
的类型一致。在这种情况下,T
必须实现x
的(接口)类型;否则类型断言是无效的,因为对于x
来说存储T
类型的值是不可能的。如果T
是一个接口类型,则x.(T)
断言x
的动态类型实现了接口T
。
If the type assertion holds, the value of the expression is the value stored in x
and its type is T
. If the type assertion is false, a run-time panic occurs. In other words, even though the dynamic type of x
is known only at run time, the type of x.(T)
is known to be T
in a correct program.
如果类型断言成立,表达式的值就是存储在x
中的值,其类型是T
。 如果类型断言不成立,就会发生运行时恐慌。换句话说,尽管x
的动态类型只有在运行时才知道,但在一个正确的程序中,x.(T)
的类型是已知的,是T
。=> 仍有疑问??
|
|
在赋值语句或特殊格式的初始化中使用的类型断言
|
|
将产生一个额外的无类型布尔值。如果断言成立,ok
的值为true
。否则为false
,并且v
的值是T
类型的零值。在这种情况下不会
发生运行时恐慌。
Calls 调用
|
|
带实参a1, a2, ... an
调用了f
。除了一种特殊情况以外,实参必须是可分配给F
的参数类型的单值表达式,并且在函数被调用之前被求值。该表达式的类型是F
的结果类型。方法调用是类似的,但是方法本身被指定为一个(在该方法的接收器类型的值上的)选择器。
|
|
如果f
表示一个泛型函数,在它被调用或作为函数值使用之前,必须将其实例化。
在函数调用中,函数值和实参以通常的顺序被求值。在它们被求值之后,调用的参数被按值传递给函数,然后被调用的函数开始执行。当函数返回时,函数的返回参数按值传递给调用者。
调用一个nil
的函数值会引起运行时恐慌。
作为一个特例,如果一个函数或方法的返回值g
在数量上相等,并且可以单独分配给另一个函数或方法f
的参数,那么调用f(g(parameters_of_g))
将在把g
的返回值依次绑定到f
的参数后再调用f
。对f
的调用必须不包含对g
的调用以外的参数,并且g
必须至少有一个返回值。如果f
有一个在最后的...
参数,它将被分配给g
的(在分配完普通参数后所剩余的)返回值。
|
|
如果x
的方法集(x
的类型)包含m
,并且实参列表可以分配给m
的参数列表,那么方法调用x.m()
是有效的。如果x
是可寻址的,并且&x
的方法集包含m
,则x.m()
就是(&x).m()
的简写:
|
|
没有明确的方法类型,也没有方法字面量
。
Passing arguments to ...
parameters 向…参数传递实参
如果f
是带有一个...T
类型的位置在最后的参数p
的可变函数,那么在f
内部,p
的类型等同于[]T
类型。如果f
被调用时没有给p
的实参,传递给p
的值是nil
。否则,传递的值是一个新的[]T
类型的切片,这个切片带有一个新的底层数组,这个底层数组的连续元素作为实参,并且这些实参都必须可分配给T
。因此,切片的长度和容量等于绑定到p
的实参的数量,并且对每次调用(实参数量)都可能有所不同。
给出函数和调用
|
|
在Greeting
函数第一次被调用时,who
的值为nil
,在第二次被调用时,who
的值为[]string{"Joe", "Anna", "Eileen"}
。
如果最后一个实参可以分配给切片类型[]T
,并且后面是...
,那么它就会在不改变值的情况下传递一个...T
参数。在这种情况下,不会创建新的切片。
给出切片s
并调用
|
|
在Greeting
函数中,who
将拥有与s
相同的底层数组的值。
Instantiations 实例化
泛型函数
或泛型
是通过用类型实参
替换类型参数
而被实例化的。实例化分两步进行:
- 在泛型声明中,每个类型参数都被替换为其对应的类型实参。这种替换发生在整个函数或类型声明中,包括类型参数列表本身和该列表中的每个类型。
- 替换之后,每个类型实参必须实现相应类型参数的约束(若有需要则实例化它)。否则实例化就会失败。
实例化一个类型会产生一个新的非泛型的命名类型;实例化一个函数会产生一个新的非泛型的函数。
|
|
对于泛型函数,可以明确地提供类型实参,也可以靠部分或完整地推断出它们。非调用的泛型函数需要一个类型实参列表用于实例化;如果该列表是部分的,那么所有剩余的类型实参必须是可推断的。被调用的泛型函数可以提供一份(可能是部分的)类型实参列表,(如果省略的类型实参可以从普通(非类型)函数参数中推断出来)也可以完全省略。
|
|
部分类型实参列表不能是空的;至少第一个(类型)实参必须存在。该列表是完整的类型实参列表的前缀,剩下的参数需要推断。宽泛地说,类型实参可以从 “从右到左"省略。
|
|
对于泛型,所有的类型实参必须都被明确提供
。
Type inference 类型推断
缺失的函数类型实参可以通过一系列的步骤来推断,如下所述。每个步骤都试图使用已知的信息来推断额外的类型实参。一旦所有的类型实参都是已知的,类型推断就会停止。在类型推断完成后,仍然有必要将所有类型实参替换为类型参数,并验证每个类型实参是否实现了相关的约束;推断的类型实参有可能无法实现约束,在这种情况下,实例化就会失败。
类型推断是基于:
- 类型参数列表
- 使用已知类型实参(如果有的话)初始化过的用于替换的映射
M
- (可能是空的)普通函数实参列表(仅在函数调用的情况下)。
然后进行以下步骤:
如果没有普通函数实参或无类型函数实参数,则跳过相应的步骤。如果前一步没有推断出任何新的类型实参,则跳过约束类型推断,但如果有缺失的类型实参,则(这个步骤:即约束类型推断)至少要运行一次。
替换映射M
贯穿所有的步骤,每个步骤都可以向M
添加条目。一旦M
为每个类型参数提供了一个类型实参或者推断步骤失败,这个过程就会停止。如果推断步骤失败,或者在最后一步之后M
仍然缺少类型实参,则类型推断失败。
Type unification 类型联合
类型推断是基于类型联合的。单一的联合步骤适用于一个替换映射和两种类型,其中一个或两个可能是或包含类型参数。替换映射跟踪已知的(显式提供的或已经推断出的)类型实参:该映射包含每个类型参数P
和相应的已知类型实参A
的一个条目P
→A
。在联合过程中,已知的类型实参在比较类型时取代了它们对应的类型参数。联合过程是寻找使两个类型等同的替换映射条目的过程。
对于联合来说,如果两个类型不包含当前类型参数列表中的任何类型参数,或者它们是忽略了通道方向的通道类型,或者它们的底层类型是等同的,那么它们(这两个类型)就是一致的。
Unification works by comparing the structure of pairs of types: their structure disregarding type parameters must be identical, and types other than type parameters must be equivalent. A type parameter in one type may match any complete subtype in the other type; each successful match causes an entry to be added to the substitution map. If the structure differs, or types other than type parameters are not equivalent, unification fails.
联合是通过比较类型对的结构来实现的:它们的结构在不考虑类型参数时必须是一致的,而类型参数以外的类型必须是等同的。一个类型中的类型参数可以匹配另一个类型中的任何完整的子类型;每个成功的匹配都会将一个条目添加到替换映射中。如果结构不同,或者除类型参数外的其他类型不等同,那么联合就会失败。=> 仍有疑问??
For example, if T1
and T2
are type parameters, []map[int]bool
can be unified with any of the following:
例如,如果T1
和T2
是类型参数,[]map[int]bool
可以与以下任何一种类型联合:=> 仍有疑问??
|
|
On the other hand, []map[int]bool
cannot be unified with any of
另一方面,[]map[int]bool
不能与以下任何一个联合起来 => 仍有疑问??
|
|
作为这个通用规则的一个例外,由于定义类型D
和类型字面量L
从来都是不等同
的,联合将D
的底层类型与L
进行比较。例如,给定定义类型
|
|
和类型字面量[]E
,联合过程会将[]float64
与[]E
进行比较,并在替换映射中添加一个条目E
→float64
。
Function argument type inference 函数实参类型推断
Function argument type inference infers type arguments from function arguments: if a function parameter is declared with a type T
that uses type parameters, unifying the type of the corresponding function argument with T
may infer type arguments for the type parameters used by T
.
函数实参类型推断从函数实参中推断出类型实参:如果函数参数声明时带有使用了类型参数的类型T
,那么将对应函数实参的类型与T
进行联合,可能推断出被T
所使用的类型参数的类型实参。=>仍有疑问??
例如,给定泛型函数
|
|
和调用
|
|
Number
的类型实参,可以通过联合vector
的类型与对应的参数类型中推断出:[]float64
和[]Number
在结构上匹配,且float64
与Number
匹配。这就把Number
→float64
这个条目添加到替换映射中。在第一轮函数实参类型推断
中,无类型实参,比如这里的第二个函数实参42
,会被忽略,只有在还有未解决的类型参数时才会考虑。
推断发生在两个独立的阶段;每个阶段在一个特定的(参数,实参)对的列表上操作:
- 列表
Lt
包含所有使用了类型参数的参数类型和有类型的
函数实参所组成的(参数,实参)对。 - 列表
Lu
包含所有剩下的参数类型为单一类型参数的对。在这个列表中,各自的函数实参是无类型的
。
任何其他的(参数,实参)对都被忽略。
根据结构,列表Lu
中对的实参是无类型的常量(或无类型的布尔比较结果)。而且由于无类型值的默认类型总是预先声明的非复合类型,它们永远不能与复合类型相匹配,所以仅考虑作为单一类型参数的参数类型就足够了。
每个列表都在一个独立的阶段中被处理:
- 在第一阶段,
Lt
中的每各配对的参数和实参类型被联合。如果一个配对的联合成功了,它可能会产生新的条目,并被添加到替换映射M
中。 - 第二阶段考虑列表
Lu
中的条目。此阶段,将忽略已为其确定类型实参的类型参数。对于剩下的每一对,参数类型(这是一个单一的类型参数)和相应的无类型实参的默认类型进行联合。如果联合失败,则类型推断失败。
当联合过程成功时,即使所有的类型实参在最后一个列表元素被处理之前就被推断出来了,对每个列表的处理还是会继续进行,直到所有的列表元素都被考虑。
示例:
|
|
在例子min(1.0, 2)
中,处理函数实参1.0
会产生替换映射条目T
→ float64
。因为处理过程一直持续到所有无类型的实参被考虑,所以报告了一个错误。这确保了类型推断不依赖于无类型实参的顺序。
Constraint type inference 约束类型推断
Constraint type inference infers type arguments by considering type constraints. If a type parameter P
has a constraint with a core type C
, unifying P
with C
may infer additional type arguments, either the type argument for P
, or if that is already known, possibly the type arguments for type parameters used in C
.
约束类型推断通过考虑类型约束来推断类型实参。如果类型参数P
有一个核心类型C
的约束,将P
与C
联合起来可能会推断出额外的类型实参,要么是P
的类型实参,(如果这个是已知,则)要么可能是C
中使用的类型参数的类型实参。=>仍有疑问??已知指的是什么,是 类型参数P有一个核心类型C的约束?
例如,考虑具有类型参数List
和Elem
的类型参数列表:
|
|
约束类型推断可以从List
的类型实参推断出Elem
的类型,因为Elem
是List
的核心类型[]Elem
中的一个类型参数。如果类型实参是Bytes
类型:
|
|
将Bytes
的底层类型与核心类型联合起来,就意味着将[]byte
与[]Elem
联合起来。这一联合成功了,并产生了Elem
→byte
的替换映射条目。因此,在这个例子中,约束类型推断可以从第一个类型实参推断出第二个类型实参。
Using the core type of a constraint may lose some information: In the (unlikely) case that the constraint’s type set contains a single defined type N
, the corresponding core type is N
’s underlying type rather than N
itself. In this case, constraint type inference may succeed but instantiation will fail because the inferred type is not in the type set of the constraint. Thus, constraint type inference uses the adjusted core type of a constraint: if the type set contains a single type, use that type; otherwise use the constraint’s core type.
使用约束的核心类型可能会丢失一些信息。在(不太可能的)情况下,约束的类型集包含单一的定义类型N
,相应的核心类型是N
的底层类型而不是N
本身。在这种情况下,约束的类型推断可能会成功,但实例化会失败,因为推断的类型不在约束的类型集中。因此,约束类型推断使用调整后的约束的核心类型:如果类型集包含一个单一的类型,则使用该类型;否则使用约束的核心类型。=> 仍有疑问??
通常,约束类型推断分两个阶段进行。从一个给定的替换映射M
开始
- 对于所有带调整过的核心类型的类型参数,将该类型参数与该类型联合起来。如果任何联合失败,约束类型推断就会失败。
- 此时,
M
中的一些条目可能将类型参数映射到其他类型参数或包含类型参数的类型。对于M
中每个条目P
→A
,其中A
是或包含类型参数Q
,而M
中存在条目Q
→B
,用A
中相应的B
替换这些Q
。在无法进一步替换时,则停止。
约束类型推断的结果是从类型参数P
到类型实参A
的最终替换映射M
,其中在任何A
中都没有出现类型参数P
。
例如,给定类型参数列表
|
|
以及为类型参数A
提供的单一类型实参int
,初始替换映射M
包含条目A
→int
。
在第一阶段
,类型参数B
和C
与它们各自约束的核心类型联合起来。这就在M
中加入了B
→[]C
和C
→*A
的条目。
此时,M
中存在两个条目,其右手边是或包含类型参数,而M
中存在其他条目:[]C
和*A
。在第二阶段
,这些类型参数被替换成它们各自的类型。这发生在哪个顺序上并不重要。从第一阶段后的M
的状态开始:
A` → `int`, `B` → `[]C`, `C` → `*A
用int
替换→右边的A
。
A` → `int`, `B` → `[]C`, `C` → `*int
用*int
替换→右边的C
。
A` → `int`, `B` → `[]*int`, `C` → `*int
此时,不可能再进一步替换,该替换映射已满。因此,M
代表类型参数到类型实参列表的最终映射。
Operators 操作符
操作符将操作数组合成表达式。
Expression = UnaryExpr | Expression binary_op Expression .
UnaryExpr = PrimaryExpr | unary_op UnaryExpr .
binary_op = "||" | "&&" | rel_op | add_op | mul_op .
rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" .
add_op = "+" | "-" | "|" | "^" .
mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" .
unary_op = "+" | "-" | "!" | "^" | "*" | "&" | "<-" .
比较操作符将在其他地方讨论。对于其他二元运算符,操作数类型必须是一致的,除非操作涉及移位或无类型的常量。对于只涉及常量的操作,请参见常量表达式部分。
除了移位操作之外,如果一个操作数是无类型
常量,而另一个操作数不是,那么该常量将被隐式地转换为另一个操作数的类型。
移位表达式中的右操作数
必须是整数类型,或者是可以用uint
类型的值表示的无类型
常量。如果一个非常量移位表达式的左操作数
是一个无类型
常量,那么它首先被隐式地转换为假设移位表达式被其左操作数单独替换时的类型。
|
|
=> 仍有疑问??
Operator precedence 优先级运算符
一元运算符的优先级最高。由于++
和--
运算符构成的是语句,而不是表达式,因此它们不属于运算符等级体系。因此,语句*p++
与(*p)++
相同。
二元运算符有五个优先级别。乘法运算符
绑定最强,其次是加法运算符
、比较运算符、&&
(逻辑AND),最后是||
(逻辑OR)。
|
|
优先级相同的二元运算符按从左到右的顺序结合。例如,x / y * z
等同于(x / y)* z
。
|
|
Arithmetic operators 算术运算符
算术运算符适用于数字值,产生的结果与第一个操作数的类型
相同。四个标准的算术运算符(+
、-
、*
、/
)适用于整型、浮点型和复数型;+
也适用于字符串。位逻辑运算符和移位运算符只适用于整型。
+ sum (和) integers, floats, complex values, strings
- difference (差) integers, floats, complex values
* product (积) integers, floats, complex values
/ quotient (商) integers, floats, complex values
% remainder (余) integers
& bitwise AND integers
| bitwise OR integers
^ bitwise XOR integers
&^ bit clear (AND NOT) integers
<< left shift integer << integer >= 0
>> right shift integer >> integer >= 0
如果操作数类型是类型参数,那么操作数必须适用于该类型集中的每个类型。操作数被表示为类型参数被实例化的类型实参的值,并且操作以该类型实参的精度进行计算。例如,给定一个函数:
|
|
x * y
的乘积 和 s += x * y
的加法分别以float32
或float64
精度计算,这取决于F
的类型实参。
Integer operators 整数运算符
对于两个整数值x
和y
,它们的整数商q = x / y
和余数r = x % y
满足以下关系:
x = q*y + r and |r| < |y|
with x / y
被截断到零("截断除法")。
x y x / y x % y
5 3 1 2
-5 3 -1 -2
5 -3 -1 2
-5 -3 1 -2
这条规则有一个例外:如果)被除数
(dividendx
是x
的int
类型的最负值,那么商q = x / -1
就等于x
(而r = 0
),这是由于二元补码的整数溢出:
x, q
int8 -128
int16 -32768
int32 -2147483648
int64 -9223372036854775808
如果除数
(divisor)是一个常量,那么它一定不能为零。如果除数在运行时为零,就会发生运行时恐慌。如果除数是非负数,并且除数是2的常数幂,除法可以用右移
来代替,计算余数可以用按位与
操作来代替。
x x / 4 x % 4 x >> 2 x & 3
11 2 3 2 3
-11 -2 -3 -3 1
移位运算符通过右操作数指定的移位计数
对左操作数进行移位,移位计数
必须为非负数。如果移位计数
在运行时为负数,就会发生运行时恐慌。如果左操作数
是有符号整数
,移位操作符实现算术移位
;如果是无符号整数
,则实现逻辑移位
。移位计数
没有上限。移位计数
为n
的移位行为就像左操作数被1
移了n
次。因此,x<<1
与x*2
相同,x>>1
与x/2
相同(但向右移位被截断到负无穷大)。
对于整数操作数,一元运算符+
、-
和^
的定义如下:
+x is 0 + x
-x negation is 0 - x
^x bitwise complement is m ^ x with m = "all bits set to 1" for unsigned x
and m = -1 for signed x
Integer overflow 整数溢出
对于无符号整型值,+
、-
、*
和<<
运算是以2n
为模来计算的,其中n
是无符号整型的位宽。广义上讲,这些无符号整型操作在溢出时丢弃高位
,程序可以依靠 “wrap around
"。
对于有符号整型值
,+
、-
、*
、/
和<<
运算可以合法地溢出
,其产生的值是存在的,并且可以被有符号整型表示法、其操作和操作数明确地定义。溢出不会引起运行时恐慌。在假设不发生溢出的情况下,编译器可能不会优化代码。例如,它不会假设x<x+1
总是真的。
Floating-point operators 浮点运算符
对于浮点数和复数,+x
与x
相同,而-x
是负的x
。浮点数或复数除以0的结果,在IEEE-754标准中没有规定;是否会发生运行时恐慌是由具体实现决定的。
An implementation may combine multiple floating-point operations into a single fused operation, possibly across statements, and produce a result that differs from the value obtained by executing and rounding the instructions individually. An explicit floating-point type conversion rounds to the precision of the target type, preventing fusion that would discard that rounding.
某些实现可能会将多个浮点运算合并为一个单一的融合运算,可能会跨越语句,产生的结果与单独执行和舍入指令得到的值不同。明确的浮点类型转换是按照目标类型的精度进行舍入的,这样就可以避免融合时放弃舍入的做法。=> 仍有疑问??
例如,一些体系架构提供了一个 “fused multiply and add
"(FMA
)指令,其在计算x*y+z
时,不对中间结果x*y
进行舍入。这些例子显示了Go的实现何时可以使用该指令:
|
|
String concatenation 字符串连接
字符串可以使用+
运算符或+=
赋值运算符进行连接:
s := "hi" + string(c)
s += " and good bye"
字符串加法通过连接操作数创建一个新的字符串。
Comparison operators 比较运算符
比较运算符比较两个操作数,并产生一个无类型布尔值。
== equal
!= not equal
< less
<= less or equal
> greater
>= greater or equal
在任何比较中,第一个操作数必须是可分配给第二个操作数的类型,反之亦然。
相等运算符==
和!=
适用于可比较的操作数。排序运算符<
, <=
, >
, 和>=
适用于被排序的操作数。这些术语和比较结果的定义如下:
- 布尔值是可比较的。如果两个布尔值都是
true
或者都是false
,那么它们是相等的。 - 按照通常的方式,整数值是可比较的并且是可排序的。
- 按照IEEE-754标准的定义,浮点值是可比较的并且是可排序的。
- 复数值是可比较的。如果
real(u) == real(v)
和imag(u) == imag(v)
,则这两个复数值u
和v
是相等的。 - 字符串值是可(按字节顺序)比较的并且是可(按字节顺序)排序的。
- 指针值是可比较的。如果两个指针值指向同一个变量,或者两个指针值都是
nil
,则它们的值是相等的。指向不同的零尺寸变量的指针值可能相等,也可能不相等。 - 通道值是可比较的。如果两个通道是由同一个调用make创建的,或者它们的值都为
nil
,则它们的值是相等的。 - 接口值是可比较的。如果两个接口值有一致的动态类型和相同的动态值,或者两者的值都是
nil
,则它们的值是相等的。 - 非接口类型
X
的值x
和接口类型T
的值t
,在X
类型的值是可比较的并且X
实现T
时是可比较的。如果t
的动态类型等于X
,且t
的动态值等于x
,则它们是相等的。 - 如果结构体值的所有字段都是可比较的,那么结构体值就是可比较的。如果两个结构体值对应的非空白字段相等,那么它们就是相等的。
- 如果数组元素类型的值是可比较的,那么数组值是可比较的。如果两个数组的对应元素是相等的,那么这两个数组值就是相等的。
对两个动态类型相同的接口值进行比较,如果它们的类型值不具有可比性,则会引起运行时恐慌。这种行为不仅适用于直接的接口值比较,也适用于比较接口值的数组或带有接口值字段的结构体。
切片值、映射值和函数值是不可比较的
。然而,作为一种特殊情况,切片值、映射值或函数值可以与预先声明的标识符nil
比较。指针值、通道值和接口值与nil
的比较也是允许的,并遵循上述的通用规则。
|
|
Logical operators 逻辑运算符
逻辑运算符适用于布尔值,并产生一个与操作数相同类型的结果。右操作数是按条件进行求值的。
&& conditional AND p && q is "if p then q else false"
|| conditional OR p || q is "if p then true else q"
! NOT !p is "not p"
Address operators 地址运算符
对于类型为T
的操作数x
,寻址操作&x
产生一个类型为*T
的指针指向x
。该操作数x
必须是可寻址的,也就是说,它要么是一个变量、指针间接(pointer indirection)或对切片的索引操作(slice indexing operation,是一个名词)
;要么是一个可寻址结构体操作数的字段选择器;要么是一个可寻址数组的数组索引操作。作为可寻址要求的一个例外,x
也可以是一个(可能是括号内的)复合字面量。如果对x
的求值会引起运行时恐慌,那么对&x
的求值也会引起运行时恐慌。
对于指针类型*T
的操作数x
,指针间接*x
表示x
所指向的T
类型的变量,如果x
是nil
,试图求值*x
将导致运行时恐慌。
|
|
Receive operator 接收操作符
对于核心类型为通道的操作数ch
,接收操作<-ch
的值是从通道ch
中接收的值,通道方向必须允许接收操作,接收操作的类型是通道的元素类型。这个表达式会阻塞,直到有一个可用的值。从一个 nil
的通道接收时,将永远阻塞。在一个已经关闭的通道上的接收操作总是可以立即进行,并在任何先前发送的值被接收后,产生一个该元素类型的零值。
|
|
在赋值语句或特殊形式的初始化中使用的一个接收表达式
|
|
将产生一个额外的无类型布尔值结果,报告通信是否成功。如果收到的值是由一个成功的发送操作传递给通道的,那么ok
的值为true
,如果是一个因为通道关闭且空而产生的零值,则为false
。
Conversions 转换
转换将表达式的类型改变为转换所指定的类型。转换可以出现在源文件中的字面量上,也可以隐含在由表达式所在的上下文。
显式转换是T(x)
形式的表达式,其中T
是一个类型,x
是可以被转换为T
类型的表达式。
Conversion = Type "(" Expression [ "," ] ")" .
如果类型以运算符*
或<-
开头,或者如果类型以关键字func
开头,并且没有结果列表,那么在必要时必须用圆括号
括起来,以避免产生歧义:
|
|
一个常量值x
可以被转换为T
类型,如果x
可以用T
的一个值来表示的话。作为一种特殊情况,可以使用 与 非常量x
相同的规则显式地将整数常量x
转换为字符串类型。
将常量转换为非类型参数的类型,会得到一个有类型的常量。
|
|
将常量转换为一个类型参数会产生一个该类型的非常量值,该值表示为类型参数实例化时所带的类型实参的值。例如,给定一个函数:
|
|
转换P(1.1)
的结果是一个P
类型的非常量值,而值1.1
被表示为float32
或float64
,这取决于f
的类型参数。因此,如果f
被实例化为float32
类型,那么表达式P(1.1)+1.2
的数值会用与非常量float32
加法相同的精度进行计算。
非常量值x
可以在以下的任何情况下被转换为T
类型:
x
可以被分配给T
。- 忽略结构体标签(见下文),
x
的类型和T
不是类型参数,但有一致的底层类型。 - 忽略结构体标签(见下文),
x
的类型和T
是指针类型,不是命名类型,它们的指针基类型不是类型参数,但有一致的底层类型。 x
的类型和T
都是整型或浮点型。x
的类型和T
都是复数类型。x
是一个整型、字节型、符文型的切片,T
是一个字符串类型。x
是一个字符串类型,T
是一个字节型、符文型的切片。x
是一个切片,T
是一个指向数组的指针,而且切片和数组的类型有一致的元素类型。
此外,如果T
或x
的类型V
是类型参数,如果满足以下条件之一,x
也可以被转换为T
类型:
V
和T
都是类型参数,并且V
的类型集中的每个类型的值都可以转换为T
的类型集中的每个类型。- 只有
V
是一个类型参数,并且V
的类型集中的每个类型的值都可以转换为T
。 - 只有
T
是一个类型参数,并且x
可以转换为T
的类型集中的每个类型。
为了转换的目的,在比较结构体类型的是否一致时,结构体标签被忽略:
|
|
数值类型之间或与字符串类型之间的(非常量)转换有特殊的规则。这些转换可能会改变x
的表示,并产生运行时间成本。所有其他的转换只改变x
的类型而不改变其表示。
没有语言机制可以在指针和整型之间进行转换
。unsafe包在受限制的情况下实现了这个功能。
Conversions between numeric types 数值型之间的转换
对于非常量数值的转换,适用以下规则:
- 当在整型之间转换时,如果数值是有符号的整型,那么它被符号位扩展到隐式的无限精度;否则它被零扩展。然后,它被截断以适应结果类型的大小。例如,如果
v := uint16(0x10F0)
,那么uint32(int8(v)) == 0xFFFFFFF0
。该转换总是产生一个有效的值;没有溢出的迹象。 - 当把浮点型数值转换为整型时,小数会被丢弃(向零截断)。
- 当将一个整型或浮点型数值转换为浮点型,或将一个复数型数值转换为另一个复数类型时,结果值被舍入到目标类型所指定的精度。例如,
float32
类型的变量x
的值可能会使用超出IEEE-754 32位数的额外精度来存储,但是float32(x)
表示将x
的值舍入到32
位精度的结果。同样地,x + 0.1
可能使用超过32
位的精度,但是float32(x + 0.1)
则不会。
在所有涉及浮点值或复数值的非常量转换中,如果结果类型不能表示该值,转换仍会成功,但结果值取决于实现。
Conversions to and from a string type 与字符串类型的转换
将有符号或无符号的整型值转换为字符串类型,会产生一个包含该整型值的UTF-8表示的字符串。在有效的Unicode码点范围之外的值会被转换为
"\uFFFD"
。1 2 3 4 5 6
string('a') // "a" string(-1) // "\ufffd" == "\xef\xbf\xbd" string(0xf8) // "\u00f8" == "ø" == "\xc3\xb8" type myString string myString(0x65e5) // "\u65e5" == "日" == "\xe6\x97\xa5"
将字节切片转换为字符串类型,可以得到一个字符串,其连续的字节是该切片的元素。
1 2 3 4 5 6 7 8 9 10
string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø" string([]byte{}) // "" string([]byte(nil)) // "" type bytes []byte string(bytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'}) // "hellø" type myByte byte string([]myByte{'w', 'o', 'r', 'l', 'd', '!'}) // "world!" myString([]myByte{'\xf0', '\x9f', '\x8c', '\x8d'}) // "🌍"
将符文切片转换为字符串类型,可以得到一个字符串,即转换为字符串的各个符文值的连接。
1 2 3 4 5 6 7 8 9 10
string([]rune{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" string([]rune{}) // "" string([]rune(nil)) // "" type runes []rune string(runes{0x767d, 0x9d6c, 0x7fd4}) // "\u767d\u9d6c\u7fd4" == "白鵬翔" type myRune rune string([]myRune{0x266b, 0x266c}) // "\u266b\u266c" == "♫♬" myString([]myRune{0x1f30e}) // "\U0001f30e" == "🌎"
将字符串类型的值转换为字节类型的切片,得到一个切片,其连续的元素是字符串的字节。
1 2 3 4 5 6 7
[]byte("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []byte("") // []byte{} bytes("hellø") // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'} []myByte("world!") // []myByte{'w', 'o', 'r', 'l', 'd', '!'} []myByte(myString("🌏")) // []myByte{'\xf0', '\x9f', '\x8c', '\x8f'}
将字符串类型的值转换为符文类型的切片,会得到一个包含该字符串的各个Unicode码点的切片。
1 2 3 4 5 6 7
[]rune(myString("白鵬翔")) // []rune{0x767d, 0x9d6c, 0x7fd4} []rune("") // []rune{} runes("白鵬翔") // []rune{0x767d, 0x9d6c, 0x7fd4} []myRune("♫♬") // []myRune{0x266b, 0x266c} []myRune(myString("🌐")) // []myRune{0x1f310}
Conversions from slice to array pointer 从切片到数组指针的转换
将切片转换为数组指针,会得到一个指向切片底层数组的指针。如果切片的长度小于数组的长度,就会发生运行时恐慌。
|
|
Constant expressions 常量表达式
常量表达式可以只包含常量操作数,并在编译时进行求值。
无类型的布尔、数值和字符串常量可以作为操作数使用,只要合法地分别使用布尔、数值或字符串类型的操作数。
常量比较总是产生一个无类型的布尔常量。如果常量移位表达式的左操作数是一个无类型的常量,那么结果就是一个整型常量;否则就是一个与左操作数相同类型的常量(左操作数必须是整型)。
任何其他对无类型常量的操作都会产生一个相同类型的无类型常量,也就是布尔、整数、浮点、复数或字符串常量。如果一个二元运算(除移位外)的无类型操作数是不同种类的,那么结果就是出现在如下列表的操作数类型:整数,符文,浮点,复数。例如,一个无类型的整数常量除以一个无类型的复数常量,得到一个无类型的复数常量。
|
|
将内置函数 complex
应用于无类型的整数、符文或浮点常量,可以得到一个无类型的复数常量。
|
|
常量表达式总是被精确地求值
;中间值和常量本身可能需要比语言中任何预先声明的类型所支持的精度大得多
。以下是合法的声明:
|
|
常量除法或取余操作的除数一定不能为零
。
|
|
类型常量的值必须总是可以准确地由常量类型的值来表示。下面的常量表达式是非法的:
|
|
一元按位补运算符^
使用的掩码符合非常量的规则:对于无符号常量来说是所有(掩码)位都是1
,对于有符号和无类型的常量来说是-1
。=> 仍有疑问??
|
|
实现限制:编译器在计算无类型浮点或复数常量表达式时可能会使用舍入,请参见常量部分的实现限制。这种舍入可能会导致浮点常量表达式在整数上下文中无效,即使它在使用无限精度计算时是整数,反之亦然。
Order of evaluation 求值顺序
在包级别上,初始化依赖关系决定了变量声明中各个初始化表达式的求值顺序。除此之外,在求值表达式、赋值或返回语句的操作数时,所有的函数调用、方法调用和通信操作都是按词法从左到右的顺序求值的。
例如,在(函数局部)赋值中
|
|
函数调用和通信发生的顺序是f()
, h()
, i(),
j()
, <-c
, g()
, 和k()
。然而,与x
的求值和索引以及y
的求值相比,这些事件的顺序没有被指定。
|
|
在包级别上,对于独立的初始化表达式来说,初始化依赖关系会覆盖其从左到右的求值规则,但不覆盖每个表达式中的操作数:
|
|
函数调用按照u()
、sqr()
、v()
、f()
、v()
、g()
的顺序发生。
单个表达式中的浮点运算是按照运算符的结合性来求值的。显式的括号会通过覆盖默认的结合性来影响求值。在表达式x + (y + z)
中,加法y + z
会在加x
之前进行。