优秀的编程知识分享平台

网站首页 > 技术文章 正文

Golang(四)语言特性(上)(golang编程语言)

nanyue 2024-11-02 12:18:47 技术文章 4 ℃

Golang是一种强类型的通用系统编程语言,具有垃圾回收机制并明确支持并发编程。go程序由包构成,这允许对依赖关系进行高效的管理;它的语法规则且紧凑,允许通过诸如集成开发环境之类的自动化工具进行简单的分析.......都是关于Golang语言特性的一些总结,本文将结合官方文档对Golang的语言特性进行一一阐述

记法

使用扩展的Backus-Naur格式(EBNF)指定语法格式,形如

 Production = production_name "=" [ Expression ] "." .
 Expression = Alternative { "|" Alternative } .
 Alternative = Term { Term } .
 Term = production_name | token [ "…" token ] | Group | Option | Repetition .
 Group = "(" Expression ")" .
 Option = "[" Expression "]" .
 Repetition = "{" Expression "}" .

Productions是由terms和下列运算符构成的表达式,优先级逐渐递增

 | alternation
 () grouping
 [] option (0 or 1 times)
 {} repetition (0 to n times)

小写的production_name用于标识词汇标记,非终结符使用驼峰命名法,词汇标记用双引号""或反引号``括起来

"a...b"形式表示从a到b的可选字符集;省略号(...)也用于非正式地表示未进一步指定的各种枚举或代码段,省略号...(不同于. . .)不是go语言的符号

源代码表示

源代码是用UTF-8编码的Unicode文本。这个文本不是规范化的,所以单个重音代码点不同于由重音和字母组合构成的相同字符;那些被视为两个代码点。为了简单起见,本文将使用非限定术语字符来引用源文本中的Unicode代码点

每个代码点都是不同的;例如,大写字母和小写字母表示的是不同的字符

实现限制:为了与其他工具兼容,编译器kennel不会允许源代码中出现NUL(U+0000)字符; 如果源代码文本中的第一个Unicode代码点是UTF-8编码的字节顺序标记(U+FEFF),编译器可能会忽略它

字符 以下术语用于表示特定的Unicode字符类

 newline = /* the Unicode code point U+000A */ .
 unicode_char = /* an arbitrary Unicode code point except newline */ .
 unicode_letter = /* a Unicode code point classified as "Letter" */ .
 unicode_digit = /* a Unicode code point classified as "Number, decimal digit" */ .

在Unicode 8.0标准中的第4.5节"通用分类"定义了字符分类集,go将Lu, Ll, Lt, Lm, 或 Lo字母类中的所有字符作为Unicode字母对待,而将所有数字类Nd中的字符作为Unicode数字

字母和数字 下划线字符_(U+005F)被视为一个字母

 letter = unicode_letter | "_" .
 decimal_digit = "0" … "9" .
 binary_digit = "0" | "1" .
 octal_digit = "0" … "7" .
 hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .

词汇元素

注释 用作程序文档,有两种格式

  • 行注释以字符序列 // 开始,并在行的末尾结束
  • 块注释从字符序列 / 开始,并用后续第一个字符序列 / 停止

注释不能在宽字符、字符串文字内或注释内启动;块注释就像一个空间,可以包含任意数量换行符

词汇 有四类:标识符、关键字、运算符和标点符号以及文字。由空格(U+0020)、水平制表符(U+0009)、回车符(U+000D)和换行符(U+000A)组成的空白将被忽略,除非它作为词汇的分隔符否则将被合并为单个词汇。此外,换行符或文件结尾可以触发分号的插入。在将输入分解为词汇时,下一个词汇是形成有效词汇的字符的最长序列

分号 形式语法使用分号";"作为多个productions中的终止符,go程序可以使用以下两个规则省略大部分分号

1.当输入被分解为词汇时,分号会被自动插入到行的最后一个词汇标记之后,如果该词汇是

  • 一个标识符
  • 整数、浮点、虚数、宽字符(rune)或字符串文字
  • 关键字break、continue、fallthrough或return之一
  • 运算符和标点符号 ++、--、)、]或} 之一

2.为了允许复杂语句占用一行,可以在结束")"或"}"之前省略分号

标识符 标识符命名程序实体,如变量和类型。标识符是一个或多个字母和数字的序列。标识符中的第一个字符必须是字母

 identifier = letter { letter | unicode_digit } .

预先声明一些标识符

 a
 _x9 // invalid
 ThisVariableIsExported
 αβ

关键字 以下保留关键字,不能用作标识符

 break default func interface select
 case defer go map struct
 chan else goto package switch
 const fallthrough if range type
 continue for import return var

运算符和标点符号 下面的字符序列表示运算符(包括赋值运算符)和标点符号

 + & += &= && == != ( )
 - | -= |= || < <= [ ]
 * ^ *= ^= <- > >= { }
 / << /= <<= ++ = := , ;
 % >> %= >>= -- ! ... . :
 &^ &^=

整数字面值 整数文字是表示整数常量的一系列数字。非十进制可选前缀设置表示法: 二进制0b或0B、八进制0, 0o或0O、十六进制0x或0X; 单个0被认为是十进制的0; 在十六进制的文本中,a~f或A~F代表十进制值10~15

为了可读性,下划线字符(_)可能出现在基前缀或连续数字之间;这样的下划线不会改变文字的值

 int_lit = decimal_lit | binary_lit | octal_lit | hex_lit .
 decimal_lit = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] .
 binary_lit = "0" ( "b" | "B" ) [ "_" ] binary_digits .
 octal_lit = "0" [ "o" | "O" ] [ "_" ] octal_digits .
 hex_lit = "0" ( "x" | "X" ) [ "_" ] hex_digits .
 decimal_digits = decimal_digit { [ "_" ] decimal_digit } .
 binary_digits = binary_digit { [ "_" ] binary_digit } .
 octal_digits = octal_digit { [ "_" ] octal_digit } .
 hex_digits = hex_digit { [ "_" ] hex_digit } .
 42 -> 4_2
 0600 -> 0_600
 0o600
 0O600 // 第二个字符是大写字母'O'
 0xBadFace -> 0xBad_Face
 0x_67_7a_2f_cc_40_c6
 170141183460469231731687303715884105727 -> 170_141183_460469_231731_687303_715884_105727
 _42 // 表示一个标识符而不是数字
 42_ // 非法: _ 必须分隔连续的数字
 4__2 // 非法: _ 仅出现一次
 0_xBadFace // 非法: _ 必须分隔连续数字

浮点字面值 浮点文字是浮点常量的十进制或十六进制表示形式

十进制浮点文字由整数部分(十进制数字)、小数点、小数部分(十进制数字)和指数部分(后跟可选符号e或E和十进制数字)组成。整数部分或小数部分之一可以省略;小数点部分或指数部分之一可以省略。指数值exp将尾数(整数和小数部分)按10exp缩放

十六进制浮点文字由0x或0X前缀、整数部分(十六进制数字)、基数点、小数部分(十六进制数字)和指数部分(后面跟着一个可选的符号p或P和十进制数字)组成。整数部分或小数部分之一可以省略;基点也可以省略,但指数部分是必需的。(此语法与IEEE754-2008第5.12.3节中给出的语法相匹配)指数值exp将尾数(整数和小数部分)按2exp缩放

为了可读性,下划线字符(_)可能出现在基前缀或连续数字之间;这样的下划线不会改变文字值

 float_lit = decimal_float_lit | hex_float_lit .
 decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] |
 decimal_digits decimal_exponent |
 "." decimal_digits [ decimal_exponent ] .
 decimal_exponent = ( "e" | "E" ) [ "+" | "-" ] decimal_digits .
 hex_float_lit = "0" ( "x" | "X" ) hex_mantissa hex_exponent .
 hex_mantissa = [ "_" ] hex_digits "." [ hex_digits ] |
 [ "_" ] hex_digits |
 "." hex_digits .
 hex_exponent = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
 0.
 72.40
 072.40 // == 72.40
 2.71828
 1.e+0
 6.67428e-11
 1E6
 .25
 .12345E+5
 1_5. // == 15.0
 0.15e+0_2 // == 15.0
 0x1p-2 // == 0.25
 0x2.p10 // == 2048.0
 0x1.Fp+0 // == 1.9375
 0X.8p-0 // == 0.5
 0X_1FFFP-16 // == 0.1249847412109375
 0x15e-2 // == 0x15e - 2 (integer subtraction)
 0x.p1 // 非法: 尾数(整数和小数部分)没有数字
 1p-2 // 非法: 指数p需要十六进制尾数
 0x1.5e-2 // 非法: 十六进制的尾数需要符号p(P)作为指数
 1_.5 // 非法: _ 必须分隔连续的数字
 1._5 // 非法: _ 同上
 1.5_e1 // 非法: _ 同上
 1.5e_1 // 非法: _ 同上
 1.5e1_ // 非法: _ 同上

虚数字面值 表示复数常量的虚部,它由一个整数或浮点文字组成,后面是小写字母i。虚文字的值是相应的整数或浮点文字乘以虚数单位i的值

 imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .

考虑向后兼容性,一个虚文字的整数部分完全由十进制数字组成(可能包含下划线),即使它以0开头也被认为是一个十进制整数

 0i
 0123i // == 123i 向后兼容
 0o123i // == 0o123 * 1i == 83i
 0xabci // == 0xabc * 1i == 2748i
 0.i
 2.71828i
 1.e+0i
 6.67428e-11i
 1E6i
 .25i
 .12345E+5i
 0x1p-2i // == 0x1p-2 * 1i == 0.25i

宽字符字面值 宽字符表示一个rune常数,标识Unicode代码点的整数值。宽字符表示为单引号中包含的一个或多个字符,如'x'或'n',在引号中,除换行符和非转义单引号外,可以出现任何字符。单引号字符表示字符本身的Unicode值,而以反斜杠开头的多字符序列以各种格式编码值

go源代码文本是用UTF-8编码的Unicode字符,所以多个UTF-8编码的字节可以表示单个整数值。例如,'a'占一个字节表示字母a(U+0061),而'?'占两个字节(0xc3 0xa4),代表一个文字(U+00E4),值为0xe4

一些反斜杠转义允许任意值被编码为ASCII文本。将整数值表示为数字常量有四种方法: x 后跟两位十六进制数字; u 后跟四位十六进制数字; U 后跟八位十六进制数字; 一个简单反斜杠 后跟三位八进制数字。在每种情况下,文本的值都是由相应基中的数字表示的值

虽然这些都表示整数,但它们的有效值范围不同: 八进制转义表示[0,255]之间的值(包括0和255),十六进制转义通过构造满足此条件。u和U代表Unicode码点,如果转义大于0x10FFFF的值是非法的

特殊转义: 在反斜杠之后,某些单个字符转义表示特殊值

 \a U+0007 alert or bell
 \b U+0008 backspace
 \f U+000C form feed
 \n U+000A line feed or newline
 \r U+000D carriage return
 \t U+0009 horizontal tab
 \v U+000b vertical tab
 \\ U+005c backslash
 \' U+0027 single quote (valid escape only within rune literals)
 \" U+0022 double quote (valid escape only within string literals)
 rune_lit = "'" ( unicode_value | byte_value ) "'" .
 unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
 byte_value = octal_byte_value | hex_byte_value .
 octal_byte_value = `\` octal_digit octal_digit octal_digit .
 hex_byte_value = `\` "x" hex_digit hex_digit .
 little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
 big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
 hex_digit hex_digit hex_digit hex_digit .
 escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .

所有其他以反斜杠开头的序列在符文文本中都是非法的

 'a'
 '?'
 '本'
 '\t'
 '\000'
 '\007'
 '\377'
 '\x07'
 '\xff'
 '\u12e4'
 '\U00101234'
 '\'' // rune literal containing single quote character
 'aa' // illegal: too many characters
 '\xa' // illegal: too few hexadecimal digits
 '\0' // illegal: too few octal digits
 '\uDFFF' // illegal: surrogate half
 '\U00110000' // illegal: invalid Unicode code point

字符串文本 表示连接字符序列获得的字符串常量。有两种形式: 原始字符串文字和解释字符串文字

原始字符串文字是反引号之间的字符序列,如foo,引号中,除反引号外,可以出现任何字符。原始字符串文字的值是由引文之间的未解释(隐式UTF-8编码)字符组成的字符串;特别是,反斜杠没有特殊含义,字符串可以包含换行符。原始字符串中的回车字符('r')会从原始字符串值中舍弃

解释字符串文字是双引号之间的字符序列,如"bar",在引号中,除换行符和非转义双引号外,任何字符都可以出现。引号之间的文本构成文本的值,反斜杠转义解释为它们的宽字符(除了'是非法的,"是合法的),具有相同的限制。三位八进制(nnn)和两位十六进制(xnn)转义表示结果字符串的单个字节;所有其他转义表示单个字符(可能是多字节)的UTF-8编码。因此,字符串内文字377和xFF表示值0xFF=255的一个字节,而?、u00FF、U000000FF和xc3xbf表示UTF-8编码字符U+00FF的两个字节0xc3 0xbf

 string_lit = raw_string_lit | interpreted_string_lit .
 raw_string_lit = "`" { unicode_char | newline } "`" .
 interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
 `abc` // same as "abc"
 `\n
 \n` // same as "\\n\n\\n"
 "\n"
 "\"" // same as `"`
 "Hello, world!\n"
 "日本語"
 "\u65e5本\U00008a9e"
 "\xff\u00FF"
 "\uD800" // illegal: surrogate half
 "\U00110000" // illegal: invalid Unicode code point
 // 下述示例都表示相同字符串
 "日本語" // UTF-8 input text
 `日本語` // UTF-8 input text as a raw literal
 "\u65e5\u672c\u8a9e" // the explicit Unicode code points
 "\U000065e5\U0000672c\U00008a9e" // the explicit Unicode code points
 "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // the explicit UTF-8 bytes

如果源代码表示的一个字符当作两个码点,例如包含重音和字母的组合形式,则如果放置在宽字符文字(它不是单个代码点),结果将是一个错误,如果放置在字符串文本中,它将出现为两个代码点

常量

有布尔常量、rune常量、整型常量、浮点常量、复数常量和字符串常量。rune、整数、浮点和复数常量统称为数字常量

常数值由rune、整数、浮点、虚数或字符串文字表示,表示常量的标识符、常量表达式、具有常量结果的转换,或应用于某些表达式、应用于某些表达式的任意值、cap或len的一些内置函数的结果值,例如unsafe.Sizeof,实数和虚数应用于复数常数,复数应用于数值常数。布尔真值由预声明的常数true和false表示。预先声明的标识符iota表示整数常量

一般来说,复常量是常数表达式的一种形式;数值常量表示任意精度的精确值并且不会溢出,因此,不存在表示IEEE-754负零、无穷大和非数字值的常数

常量可以是类型化的,也可以是非类型化的。文本常量、true、false、iota和某些仅包含未定义类型的常量操作数的常数表达式是非类型化的。常数可以由常数声明或转换显式给出,也可以隐式地用于变量声明或赋值或表达式中的操作数。如果常量值不能表示为相应类型的值,则为错误

未定义类型的常量具有默认类型,默认类型是在需要类型化值的上下文中隐式地转换常数的类型,例如,在短变量声明 i := 0 中,则没有显式定义类型 。非类型化常量的默认类型分别为bool、rune、int、float64、complex128或string,具体取决于它是布尔、rune、整型、浮点、复数还是字符串常量

实现限制:虽然数值常量在语言中具有任意精度,但编译器可以使用有限精度的内部表示来实现它们。也就是说,每一个实现都必须满足

  • 表示整数常量,至少有256位精度
  • 表示浮点常数,包括复数常数的部分,尾数至少为256位,有符号的二进制指数为至少16位
  • 如果无法精确表示整数常量,则抛出一个错误
  • 如果由于溢出而无法表示浮点或复数常量,需要抛出一个错误
  • 如果由于精度限制而无法表示浮点或复数常数,则舍入到最接近的可表示常数

这些要求既适用于文字常量,也适用于常量表达式的计算结果

变量

变量是保存值的存储位置,变量的类型决定是否允许设置值。变量声明或函数参数和结果,函数声明或函数文本的签名保留对命名变量的存储。调用内置函数new或使用复合文字的在运行时为变量分配存储地址。这样的匿名变量通过一个(可能是隐式的)指针间接引用

array、slice和struct类型的结构化变量具有可以单独寻址的元素和字段。每个这样的元素都像一个变量

变量的静态类型(或只是类型)是其声明时给出的类型、新调用或复合文字中提供的类型或结构变量元素的类型。接口类型的变量会在运行时分配给该变量一个具体类型的值,因此可能具有不同的动态类型(除非值是预先声明的标识符nil,它没有类型)。动态类型可在执行期间变化,但存储在接口变量中的值总是可分配给静态类型的变量

 var x interface{} // x is nil and has static type interface{}
 var v *T // v has value nil, static type *T
 x = 42 // x has value 42 and dynamic type int
 x = v // x has value (*T)(nil) and dynamic type *T

通过引用表达式中的变量来检索变量的值;它是分配给变量的最新值。如果变量尚未赋值,则其值为其类型的零值

类型

类型可以用类型名或类型文字(通常是由现有类型组成)表示,它决定一组值以及特定于这些值的操作和方法

 Type = TypeName | TypeLit | "(" Type ")" .
 TypeName = identifier | QualifiedIdent .
 TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
 SliceType | MapType | ChannelType .

go语言预先声明了某些类型名称。复合类型:数组、结构、指针、函数、接口、切片、映射和通道类型可以使用类型文字构造

每个类型T都有一个基础类型:如果T是预先声明的布尔、数字或字符串类型或类型文字的一种,对应的基础类型是T本身。否则,T的基础类型是T在其类型声明中引用的类型的基础类型

 type (
 A1 = string
 A2 = A1
 )
 type (
 B1 string
 B2 B1
 B3 []B1
 B4 B3
 )

类型string、A1、A2、B1和B2的基础类型是string,类型B1、B3和B4的基本类型是B1

方法集

任何其他类型T的方法集都包括以类型T作为接收器声明的所有方法,对应指针类型T的方法集是用T或T作为接收器声明的所有方法的集合(即它也包含T的方法集)

进一步的规则适用于包含嵌入字段的结构类型,任何其他类型的方法集都为空。在方法集中,每个方法必须具有唯一的非空方法名

接口类型的方法集是它的接口,类型的方法集确定该类型实现的接口以及可以使用该类型的接收器调用的方法

布尔类型 预声明的布尔类型是bool,它是一种定义类型,表示由预先声明的常数true和false表示的布尔真值的集合

数字类型 表示一组整数或浮点值,预先声明的体系结构独立的数字类型是

 uint8 所有无符号8位整数的集合,取值范围为 [0,255]
 uint16 所有无符号16位整数的集合,取值范围为 [0,65535]
 uint32 所有无符号32位整数的集合,取值范围为 [0,4294967295]
 uint64 所有无符号64位整数的集合,取值范围为 [0,18446744073709551615]
 int8 所有有符号8位整数的集合,取值范围为 [-128,127]
 int16 所有有符号16位整数的集合,取值范围为 [-32768,32767]
 int32 所有有符号32位整数的集合,取值范围为 [-2147483648,2147483647]
 int64 所有有符号64位整数的集合,取值范围为 [-9223372036854775808,9223372036854775807]
 float32 所有 IEEE-754 32位浮点数的集合
 float64 所有 IEEE-754 64位浮点数的集合
 complex64 具有float32实部和虚部的所有复数的集合
 complex128 具有float64实部和虚部的所有复数的集合
 byte uint8的别名
 rune int32的别名

一个n位整数的值是n位宽的,用2的补码算法表示。还有一组size特定于实现的预声明数字类型

 uint 32位或64位
 int 与uint相同
 uintptr 一种无符号整数,其大小足以存储指针值的未解释位

为了避免可移植性问题,除了byte(uint8的别名)和rune(int32的别名)类型不同外,所有的数值类型都是定义类型。当表达式或赋值中混合不同的数值类型时,需要显式类型转换,例如: int32和int不是同一类型,即使它们在特定的体系结构上可能具有相同的大小

字符串类型 预声明字符串类型是string,它是定义类型,代表字符串值(字节序列,可能为空)的集合。字符串是不可变的: 一旦创建,就不可能更改字符串的内容

字符串的长度被称为字节数,并且从不为负;字符串的长度可以通过内置函数len来计算,如果字符串为常量,则其长度为编译时常数。字符串的字节可以通过索引[0, len(s)-1]来访问,通过字符串元素获取到的地址是非法的,例如,如果s[i]是字符串的第i个字节,&s[i]是无效的

数组类型 数组是某种类型的元素的编号序列,称为元素类型。元素的数量称为数组的长度(取值非负)

 ArrayType = "[" ArrayLength "]" ElementType .
 ArrayLength = Expression .
 ElementType = Type .

长度是数组类型的一部分;它的值必须为int类型所表示的非负常量。数组a的长度可以使用内置函数len来计算。这些元素可以通过整数索引 [0, len(a)-1] 来寻址。数组类型总是一维的,但可以组合成多维类型

 [32]byte
 [2*N] struct { x, y int32 }
 [1000]*float64
 [3][5]int
 [2][2][2]float64 // same as [2]([2]([2]float64))

切片类型 切片是底层数组的连续段的描述符,并提供对来自该数组的元素的编号序列的访问。切片类型表示其元素类型的数组的所有切片的集合。元素的数量称为切片的长度,取非负值。未初始化切片的值为nil

 SliceType = "[" "]" ElementType .

切片s的长度可以由内置函数len计算;与数组不同,它的长度在执行期间可能会更改。这些元素可以通过整数索引 [0, len(s) - 1] 来寻址,给定元素的切片索引可以小于底层数组中相同元素的索引

切片一旦初始化,总是与包含它的元素的基础数组相关。因此,一个切片与它的数组和引用该数组的其他切片共享存储;相比之下,不同的数组总是表示不同的存储

切片的底层数组可以延伸到切片的末尾,可以使用容量来度量扩展程度: 它是切片的长度和超出切片的数组长度的总和,可以通过从原始切片中切出一个新的切片来创建能容纳该长度的切片。切片a的容量可以使用内置函数cap(a)来计算

使用内置函数make为给定的元素类型T创建一个新的经初始化的切片值,该函数接受切片类型、切片长度及容量(可选)作为参数。使用make创建的切片总是分配一个新的隐藏数组,返回的切片值引用该数组。也就是说,执行

 make([]T, length, capacity)

直接make或申请分配数组并对其进行切片,可以产生相同切片,因此如下两个表达式是等效

 make([]int, 50, 100)
 new([100]int)[0:50]

与数组一样,切片总是一维的,但可以组合起来构造更高维的对象。对于数组的数组,构造的内部数组具有相同的长度;然而,切片的切片(或数组的切片),内部切片的长度可以动态变化。此外,内部切片必须逐个单独初始化

结构类型 结构是命名元素的序列,称为字段,每个字段都有名称和类型。字段名可以显式指定(IdentifierList)或隐式指定(EmbeddedField)。在结构中,非空字段名必须是唯一的

 StructType = "struct" "{" { FieldDecl ";" } "}" .
 FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] .
 EmbeddedField = [ "*" ] TypeName .
 Tag = string_lit .
 // 一个空结构.
 struct {}
 // 具有6个字段的结构.
 struct {
 x, y int
 u float32
 _ float32 // padding
 A *[]int
 F func()
 }

用类型声明但没有显式字段名声明的字段称为嵌入式字段,嵌入字段必须指定为类型名 T 或指向非接口类型名 *T 的指针,而 T 本身不能是指针类型。非限定类型名用作字段名

 // 具有四个嵌入字段的结构,包括 T1, *T2, P.T3 和 *P.T4 类型
 struct {
 T1 // field name is T1
 *T2 // field name is T2
 P.T3 // field name is T3
 *P.T4 // field name is T4
 x, y int // field names are x and y
 }

如下示例中声明是非法的,因为字段名称在结构类型中必须是唯一的

 struct {
 T // conflicts with embedded field *T and *P.T
 *T // conflicts with embedded field T and *P.T
 *P.T // conflicts with embedded field T and *T
 }

如果x.f是表示该字段或方法f的合法选择器,在结构类型x中嵌入一个字段或方法f将被调用。增强字段除了不能用作结构的复合文本中的字段名称,它们像结构的普通字段一样作用

给定一个结构类型S和一个定义的类型T,在结构的方法集中包含的方法,如下所示

  • 如果S包含一个嵌入字段T,则S和S的方法集都包括接受器T的方法,S的方法集还包括接收器T所带的方法
  • 如果S包含一个嵌入字段T,则S和S的方法集都包含接收器T或*T所带的方法

字段声明后面可以跟一个可选的字符串文本标记,它成为对应字段声明中所有字段的属性。空标记字符串相当于缺少标记。标记通过反射接口可见,并参与结构的类型标识,但在其他情况下被忽略

 struct {
 x, y float64 "" // an empty tag string is like an absent tag
 name string "any string is permitted as a tag"
 _ [4]byte "ceci n'est pas un champ de structure"
 }
 // A struct corresponding to a TimeStamp protocol buffer.
 // The tag strings define the protocol buffer field numbers;
 // they follow the convention outlined by the reflect package.
 struct {
 microsec uint64 `protobuf:"1"`
 serverIP6 uint64 `protobuf:"2"`
 }

指针类型 表示指向给定类型变量的所有指针集,称为指针的基类型。未初始化指针的值为nil

 PointerType = "*" BaseType .
 BaseType = Type .
 *Point
 *[4]int

函数类型 表示具有相同参数和返回结果类型的所有函数的集合。函数类型的未初始化变量的值为nil

 FunctionType = "func" Signature .
 Signature = Parameters [ Result ] .
 Result = Parameters | Type .
 Parameters = "(" [ ParameterList [ "," ] ] ")" .
 ParameterList = ParameterDecl { "," ParameterDecl } .
 ParameterDecl = [ IdentifierList ] [ "..." ] Type .

在参数或返回结果列表中,名称(IdentifierList)必须全部存在或全部不存在。如果存在,每个名称代表指定类型的一个项(参数或返回结果),签名中的所有非空白名称必须是唯一的;如果不存在,则每种类型代表该类型的一项。参数和返回结果列表总是带圆括号的,除非只有一个未命名的返回结果,可以将它写成类型名称

 func()
 func(x int) int
 func(a, _ int, z float32) bool
 func(a, b int, z float32) (bool)
 func(prefix string, values ...int)
 func(a, b int, z float64, opt ...interface{}) (success bool)
 func(int, int, float64) (float64, *[]int)
 func(n int) func(p *T)

接口类型 接口类型指定其接口的方法集。接口类型的变量可以存储任何方法集是该接口的任意超集的类型的值,这种类型被称为实现接口。接口类型的未初始化变量的值为nil

 InterfaceType = "interface" "{" { MethodSpec ";" } "}" .
 MethodSpec = MethodName Signature | InterfaceTypeName .
 MethodName = identifier .
 InterfaceTypeName = TypeName .

与所有方法集一样,在接口类型中,每个方法必须具有唯一的非空名称

 // A simple File interface.
 interface {
 Read([]byte) (int, error)
 Write([]byte) (int, error)
 Close() error
 }
 interface {
 String() string
 String() string // illegal: String not unique
 _(x int) // illegal: method must have non-blank name
 }

多个类型可以实现一个接口。例如,如果两种类型S1和S2具有方法集

 func (p T) Read(p []byte) (n int, err error) { return … }
 func (p T) Write(p []byte) (n int, err error) { return … }
 func (p T) Close() error { return … }

(T代表S1或S2)那么可以说File接口由S1和S2实现,而不管S1和S2可能拥有或共享其他什么方法

一种类型实现可由它方法的任何子集组成的任何接口,因此可以实现几个不同的接口。例如,所有类型都实现空接口

 interface{}

类似地,考虑这个接口规范,它出现在类型声明中,定义一个称为Locker的接口

 type Locker interface {
 Lock()
 Unlock()
 }

如果S1和S2也实现以下方法集,那么它们实现Locker接口,也实现File接口

 func (p T) Lock() { … }
 func (p T) Unlock() { … }

接口T可以使用(可能是限定的)接口类型名称E来代替特定方法,这称为在T中嵌入接口E;它将E的所有(可导出和不可导出的)方法添加到接口T中

 type ReadWriter interface {
 Read(b Buffer) bool
 Write(b Buffer) bool
 }
 type File interface {
 ReadWriter // same as adding the methods of ReadWriter
 Locker // same as adding the methods of Locker
 Close()
 }
 type LockedFile interface {
 Locker
 File // illegal: Lock, Unlock not unique
 Lock() // illegal: Lock not unique
 }

接口类型T不能递归地嵌入自身或任何嵌套了T类型的接口类型

 // 非法: 自身类型不可嵌套
 type Bad interface {
 Bad
 }
 // 非法: Bad1 不能使用 Bad2 嵌套自身
 type Bad1 interface {
 Bad2
 }
 type Bad2 interface {
 Bad1
 }

Map类型 map是一种类型的元素的无序分组,称为元素类型,由另一种类型的一组唯一键(称为键类型)索引。未初始化映射的值为nil

 MapType = "map" "[" KeyType "]" ElementType .
 KeyType = Type .

必须为key类型的操作数完全定义比较运算符==或!=,因此,key类型不能是函数类型、切片类型及Map类型。如果key的类型为接口类型,则必须为动态键的值(接口的实现)定义比较运算符(==、!=),否则会导致运行时panic

 map[string]int
 map[*T]struct{ x, y float64 }
 map[string]interface{}

map元素个数被称为它的长度,其值在执行期间可能会发生变化,可以使用内置函数len来获取。map在执行期间可以使用赋值为其添加元素,并使用索引表达式进行检索;也可以使用delete内置函数删除某些元素

使用内置函数make生成一个新的空map值,该函数将map类型和可选的容量作为参数

 make(map[string]int)
 make(map[string]int, 100)

初始容量不限制其大小: map会增长以容纳其中存储的项。nil map除外,它相当于空映射,不能添加任何元素

Channel类型 提供了一种用于并发执行功能的机制,通过发送和接收指定元素类型的值来进行通信。未初始化channel的值为nil

 ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .

<-运算符指定channel的方向,用于发送或接收。如果没有给出方向,则channel是双向的。channel可以被约束为仅通过赋值或显式转换以用来发送或接收

 chan T // can be used to send and receive values of type T
 chan<- float64 // can only be used to send float64s
 <-chan int // can only be used to receive ints

<-运算符与最左边的chan关联

 chan<- chan int // same as chan<- (chan int)
 chan<- <-chan int // same as chan<- (<-chan int)
 <-chan <-chan int // same as <-chan (<-chan int)
 chan (<-chan int)

可以使用内置函数make生成一个新的初始化通道值,该函数将channel类型和可选容量作为参数

 make(chan int, 100)

容量,以元素个数为单位,设置通道中缓冲区的大小。如果容量为零或未设置,则channel是无缓冲的,只有发送方和接收方都准备好时,才可以通信;否则,如果缓冲区未满(发送)或不为空(接收),则在通道不阻塞的情况下能成功通信。nil通道永远无法进行通信

可以使用内置函数close关闭通道,接收操作的多值赋值形式报告是否在信道关闭之前发送接收值

单个通道可用于发送语句、接收操作以及由任意数量的goroutine调用内置函数cap和len,而无需进一步同步。通道充当先进先出队列,例如,如果一个goroutine在通道上发送值,而第二个goroutine接收值,则按发送顺序接收值

类型和值的属性

类型标识 两个类型要么相同,要么不同。定义的类型总是不同于任何其他类型。如果两个类型的底层类型文本在结构上等价,则它们是相同的

  • 如果两个数组类型具有相同的元素类型和相同的数组长度,则它们是相同类型
  • 如果两个切片类型具有相同的元素类型,则它们是相同类型
  • 如果两个结构类型具有相同的字段序列,并且对应的字段具有相同的名称、相同的类型和相同的tags,则它们是相同类型。来自不同包的不可导出字段名的称总是不同的
  • 如果两个指针类型具有相同的基类型,则它们是相同类型
  • 如果两个函数类型具有相同数量的参数和返回结果,则它们是相同类型,相应的参数和返回类型是相同的,并且这两个函数要么是可变的,要么两者都不是。参数名和返回结果名不需要完全一致
  • 如果两个接口类型具有相同名称及相同函数类型的相同方法集(方法的顺序无关紧要),则它们是相同的。来自不同包的不可导出方法名总是不同类型
  • 如果两个map类型具有相同的key和value元素类型,则它们是相同类型
  • 如果两个channel类型具有相同的元素类型和相同的方向,则它们是相同类型

给定下述声明

 type (
 A0 = []string
 A1 = A0
 A2 = struct{ a, b int }
 A3 = int
 A4 = func(A3, float64) *A0
 A5 = func(x int, _ float64) *[]string
 )
 type (
 B0 A0
 B1 []string
 B2 struct{ a, b int }
 B3 struct{ a, c int }
 B4 func(int, float64) *B0
 B5 func(x int, y float64) *A1
 )
 type C0 = B0
 // 这些类型是等价的
 A0, A1, 和 []string
 A2 和 struct{ a, b int }
 A3 和 int
 A4, func(int, float64) *[]string, 及 A5
 B0 和 C0
 []int 和 []int
 struct{ a, b *T5 } 和 struct{ a, b *T5 }
 func(x int, y float64) *[]string, func(int, float64) (result *[]string), 及 A5
 // B0 和 B1不同,因为它们是由不同的类型定义创建的新类型
 // func(int, float64) *B0 和 fun(x int, y float64) *[]string不同,因为B0与[]string不是相同类型

可赋值性 如果满足下列条件之一,值x可赋值给T类型的变量(x可赋值给T)

  • x的类型与T相同
  • x的类型V和T具有相同的基础类型,并且V或T中至少有一个不是定义的类型
  • T是接口类型,x实现T
  • x是双向通道类型的值,T是通道类型,x的类型V和T具有相同的元素类型,并且V或T中至少有一个不是定义的类型
  • x是预声明的标识符nil,T是指针、函数、切片、map、通道或接口类型
  • x是一个非类型化常数,可由T类型的值表示

可表示性 如果满足下列条件之一,常数x可由T类型的值表示

  • x是由T确定的一组值
  • T是浮点类型,x可以舍入到T的精度范围内而不溢出。使用IEEE-754舍入到偶数规则,但IEEE负零进一步简化为无符号零。注意,常量值的结果永远不会出现IEEE负零、NAN或无穷大
  • T是一个复数类型,x的实部和虚部可由T的组成类型(float32或float64)的值表示
 x T 以下x可以表示为T类型的值,因为...
 'a' byte 97属于byte类型的值集合
 97 rune rune是int32的别名,97属于32位整数集
 "foo" string "foo" 属于字符串值集
 1024 int16 1024 属于16位整数集
 42.0 byte 42 属于8位无符号整数集
 1e10 uint64 10000000000 属于64位无符号整数
 2.718281828459045 float32 2.718281828459045 舍入到 2.7182817 属于32位浮点数集
 -1e-1000 float64 -1e-1000 按IEEE舍入到 -0.0,然后进一步简化为 0.0
 0i int 0 是一个整数值
 (42 + 0i) float32 42.0 (虚部为零) 属于32为浮点数集
 x T 以下x不可以表示为T类型的值,因为...
 0 bool 0 不属于布尔真值集(true,false)
 'a' string 'a' 是 rune,不属于字符串值集
 1024 byte 1024 不属于8位无符号整数值集
 -1 uint16 -1 不属于16位无符号整数值集
 1.1 int 1.1 不是一个整数值
 42i float32 (0 + 42i) 表示一个纯虚数,不是32位浮点数值
 1e1000 float64 1e1000 按IEEE舍入后溢出到无穷(+Inf)

大括号内的声明和语句序列(可能为空),块嵌套影响作用域

 Block = "{" StatementList "}" .
 StatementList = { Statement ";" } .

除了源代码中的显式块之外,还有隐式块

  • 全局块包含所有go源文本
  • 每个包都有一个包块,其中包含该包的所有go源文本
  • 每个文件都有一个包含该文件中所有go源文本的文件块
  • 每个if、for、switch语句都被视为在自己的隐式块中
  • switch、select语句中的每个子句都充当隐式块

声明和作用域

声明将非空标识符绑定到常量、类型、变量、函数、标签或包。程序中的每一个标识符都必须声明,相同标识符在同一语句块中不能声明两次。文件块和包块中都不能声明标识符

空白标识符可以像声明中的任何其他标识符一样使用,但它不引入绑定,因此不需要声明。在包块中,init标识符只能用于init函数声明,与空白标识符一样,它不会引入新的绑定

 Declaration = ConstDecl | TypeDecl | VarDecl .
 TopLevelDecl = Declaration | FunctionDecl | MethodDecl .

声明的标识符的作用域是源文本的可用范围,其中标识符表示特定的常量、类型、变量、函数、标签或包

go使用块在词汇上限定作用域

  • 预声明的标识符的作用域是全局块
  • 在顶层(任何函数外部)声明的常量、类型、变量或函数(但不是方法)的标识符的作用域是包块
  • 导入包的包名称的作用域是包含包导入声明的文件的文件块
  • 方法接收器、函数参数或结果变量的标识符的作用域是函数体
  • 在函数中声明的常量或变量标识符的作用域从ConstSpec或VarSpec(ShortVarDecl表示短变量声明)的末尾开始,到最内部包含块的末尾结束
  • 在函数中声明的类型标识符的作用域从TypeSpec中的标识符开始,到最里面的包含块结束

在块中声明的标识符可以在内部块中重新声明,当内部声明的标识符在内部作用域内时,它由内部声明的实体表示

包子句不是一个声明,包名称不出现在任何作用域中。其目的是标识属于同一个包的文件,并为导入声明指定默认包名

标签作用域 由带标签的语句声明,并在break、continue和goto语句中使用,定义但未使用的标签是非法的。与其他标识符不同的是,标签没有块作用域,并且与非标签的标识符不冲突。标签的作用域是其声明的函数体并且不包括任何嵌套函数体

空白标识符 由下划线字符(_)表示,它用作匿名占位符而不是常规(非空)标识符,在声明、操作数和赋值中具有特殊意义

预声明标识符 以下标识符是在全局块中隐式声明的

 Types:
 bool byte complex64 complex128 error float32 float64
 int int8 int16 int32 int64 rune string
 uint uint8 uint16 uint32 uint64 uintptr
 Constants:
 true false iota
 Zero value:
 nil
 Functions:
 append cap close complex copy delete imag len
 make new panic print println real recover

可导出的标识符 可导出标识符允许从另一个包访问它,可导出标识符需同时满足以下条件

  • 标识符名称的第一个字符是Unicode大写字母(Unicode Lu 类型);并且
  • 标识符在包块中声明或者它是字段名或方法名

否则,其他所有标识符都是不可导出的

标识符的唯一性 给定一组标识符集,如果标识符与集合中的其他标识符不同,则称其为唯一标识符。如果两个标识符的拼写不同或者它们出现在不同的包中且都不可导出,则它们是不同的。否则,它们是一样的

常量声明 常量声明将标识符列表(常量的名称)绑定到常量表达式列表的值,标识符的数目必须等于常量表达式的数目,左侧的第n个标识符绑定到右侧的第n个表达式的值

 ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
 ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .
 IdentifierList = identifier { "," identifier } .
 ExpressionList = Expression { "," Expression } .

如果指定了类型,则所有常量都采用指定的类型,并且表达式必须可赋值给该类型;如果省略了类型,则常量将采用相应表达式的类型;如果表达式值是非类型化常量,则声明的常量保持非类型化,常量标识符表示常量值。例如,如果表达式是浮点文字,即使文字的小数部分为零,常量标识符也表示浮点类型常数

 const Pi float64 = 3.14159265358979323846
 const zero = 0.0 // untyped floating-point constant
 const (
 size int64 = 1024
 eof = -1 // untyped integer constant
 )
 const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", untyped integer and string constants
 const u, v float32 = 0, 3 // u = 0.0, v = 3.0

在带圆括号的const声明列表中,表达式列表可以省略除第一个ConstSpec之外的任何一个ConstSpec。这样的空表达式列表相当于前面第一个非空表达式列表及其类型(如果有的话)的文本替换,因此,省略的表达式列表等同于重复前面的表达式。标识符的数目必须等于上前一个列表中的表达式数目

与iota常量生成器一起,该机制允许轻量级的顺序值声明

 const (
 Sunday = iota
 Monday
 Tuesday
 Wednesday
 Thursday
 Friday
 Partyday
 numberOfDays // this constant is not exported
 )

Iota 在常量声明中,预声明标识符iota表示连续的未定义类型的整数常量。它的值从零开始,每使用一次加一(相当于 x++ )。它可以用来构造一组相关常数

 const (
 c0 = iota // c0 == 0
 c1 = iota // c1 == 1
 c2 = iota // c2 == 2
 )
 const (
 a = 1 << iota // a == 1 (iota == 0)
 b = 1 << iota // b == 2 (iota == 1)
 c = 3 // c == 3 (iota == 2, unused)
 d = 1 << iota // d == 8 (iota == 3)
 )
 const (
 u = iota * 42 // u == 0 (untyped integer constant)
 v float64 = iota * 42 // v == 42.0 (float64 constant)
 w = iota * 42 // w == 84 (untyped integer constant)
 )
 const x = iota // x == 0
 const y = iota // y == 0

根据定义,同一ConstSpec中在多用途的iota具有相同值

 const (
 bit0, mask0 = 1 << iota, 1<<iota - 1 // bit0 == 1, mask0 == 0 (iota == 0)
 bit1, mask1 // bit1 == 2, mask1 == 1 (iota == 1)
 _, _ // (iota == 2, unused)
 bit3, mask3 // bit3 == 8, mask3 == 7 (iota == 3)
 )

最后一个例子利用了最后一个非空表达式列表的隐式重复

类型声明 类型声明将标识符(类型名)绑定到类型。类型声明有两种形式: 别名声明和类型定义

 TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
 TypeSpec = AliasDecl | TypeDef .

别名声明 将标识符绑定到给定的类型

 AliasDecl = identifier "=" Type .

在标识符的作用域内,它充当类型的别名

 type (
 nodeList = []*Node // nodeList 和 []*Node 是相同类型
 Polar = polar // Polar 和 polar 表示相同的类型
 )

类型定义 类型定义创建一个与给定类型具有相同基础类型和操作的新的非重复类型,并将标识符绑定到该类型

 TypeDef = identifier Type .

新类型称为已定义类型,它不同于任何其他类型,包括创建它的源类型

 type (
 Point struct{ x, y float64 } // Point 和 struct{ x, y float64 } 是不同d的类型
 polar Point // polar 和 Point 是不同的类型
 )
 type TreeNode struct {
 left, right *TreeNode
 value *Comparable
 }
 type Block interface {
 BlockSize() int
 Encrypt(src, dst []byte)
 Decrypt(src, dst []byte)
 }

已定义的类型可能具有与其相关联的方法。它不继承绑定到给定类型的任何方法,但如果绑定的是接口类型或内嵌复合类型的元素将保持其方法集不变

 // Mutex是一个数据类型,有Lock和Unlock两个方法
 type Mutex struct { /* Mutex 字段 */ }
 func (m *Mutex) Lock() { /* Lock 方法实现 */ }
 func (m *Mutex) Unlock() { /* Unlock 方法实现 */ }
 // NewMutex 与 Mutex 有相同的组成,但是其方法集为空.
 type NewMutex Mutex
 // PtrMutex 的基础类型 *Mutex 的方法集保持不变,但是 PtrMutex 的方法集是空.
 type PtrMutex *Mutex
 // *PrintableMutex 的方法集包含 Lock 和 Unlock bound 绑定到它的内嵌字段 Mutex.
 type PrintableMutex struct {
 Mutex
 }
 // MyBlock 是一个接口类型,它与 Block 具有相同的方法集
 type MyBlock Block

类型定义可用于定义不同的布尔、数字或字符串类型,并将方法与它们关联

 type TimeZone int
 const (
 EST TimeZone = -(5 + iota)
 CST
 MST
 PST
 )
 func (tz TimeZone) String() string {
 return fmt.Sprintf("GMT%+dh", tz)
 }

变量声明 创建一个或多个变量,将相应的标识符绑定到这些变量,并为每个变量提供一个类型和一个初始值

 VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
 VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
 var i int
 var U, V, W float64
 var k = 0
 var x, y float32 = -1, -2
 var (
 i int
 u, v, s = 2.0, 3.0, "bar"
 )
 var re, im = complexSqrt(-1)
 var _, found = entries[name] // 查找map; 只对 "found" 感兴趣

如果给定了表达式列表,则使用遵循赋值规则的表达式初始化变量。否则,将每个变量初始化将被为其零值。如果存在类型,则为每个变量指定该类型;否则,将在赋值中为每个变量指定相应初始化值的类型。如果该值是非类型化常量,则首先隐式转换为其默认类型;如果它是非类型化布尔值,则首先隐式转换为bool类型。预声明值nil不能用于初始化没有显式类型的变量

 var d = math.Sin(0.5) // d 的类型是 float64
 var i = 42 // i 的类型是 int
 var t, ok = x.(T) // t 类型是 T, ok 的类型是 bool
 var n = nil // 非法

实现限制:如果在函数体内声明未使用的变量,编译器会报错

短变量声明 使用声明语法为

 ShortVarDecl = IdentifierList ":=" ExpressionList .

它是带有初始值表达式但没有类型的常规变量声明的简写

 "var" IdentifierList = ExpressionList .
 i, j := 0, 10
 f := func() int { return 7 }
 ch := make(chan int)
 r, w, _ := os.Pipe() // os.Pipe() 返回一对连接的文件和一个错误(如果有的话)
 _, y, _ := coord(p) // coord() 返回三个值; 只对 y 坐标感兴趣
 

与常规变量声明不同,短变量声明可以重新声明变量,前提是它们最初是在具有相同类型的同一语句块(如果块是函数体,则参数列表)中声明的,并且至少有一个非空变量是新的。因此,重新声明只能出现在多变量短声明中。重新声明不会引入新变量;它只是将新值赋给原始变量

 field1, offset := nextField(str, 0)
 field2, offset := nextField(str, offset) // 重新声明 offset
 a, a := 1, 2 // 非法: 如果 a 已经在别处声明过,此处,a声明了两次并且没有引入新变量声明

短变量声明只能出现在函数内部。在某些上下文中,例如if、for或switch语句的初始值设定项,它们可用于声明局部临时变量

函数声明 函数声明将标识符(函数名)绑定到函数

 FunctionDecl = "func" FunctionName Signature [ FunctionBody ] .
 FunctionName = identifier .
 FunctionBody = Block .

如果函数的签名声明了返回结果参数,则函数体的语句列表必须以终止语句(return)结尾

 func IndexRune(s string, r rune) int {
 for i, c := range s {
 if c == r {
 return i
 }
 }
 // 非法: 缺少返回语句
 }

函数声明可以省略函数体,这样的声明为go外部实现的函数提供签名

 func min(x int, y int) int {
 if x < y {
 return x
 }
 return y
 }
 func flushICache(begin, end uintptr) // implemented externally

方法声明 方法是带有接收器的函数,方法声明将标识符(方法名)绑定到方法,并将该方法与接收方的基类型相关联

 MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] .
 Receiver = Parameters .

接收器是通过方法名前面的额外参数部分指定的,该参数部分必须声明一个非变量参数接收器。它的类型必须是定义的类型T或指向定义的类型T的指针,T称为接收器基类型。接收器基类型不能是指针或接口类型,它必须与方法在同一个包中定义。该方法被称为绑定到其接收器基类型,并且该方法名称仅在类型T或*T的选择器中可见

非空接收器标识符在方法签名中必须是唯一的。如果接收器的值未在方法主体内引用,则可以在声明中省略其标识符,这同样适用于函数和方法的参数

对于基类型,绑定到它的方法的非空名称必须是唯一的。如果基类型是结构类型,则非空方法和字段名必须是不同的

给定定义的类型Point,声明如下方法集

 func (p *Point) Length() float64 {
 return math.Sqrt(p.x * p.x + p.y * p.y)
 }
 func (p *Point) Scale(factor float64) {
 p.x *= factor
 p.y *= factor
 }

将方法Length和Scale与接收器类型*Point,绑定到基类型Point

方法的类型是以接收器作为第一个参数的函数的类型。例如,方法Scale具有

 func(p *Point, factor float64)

但这样声明的函数不是方法

表达式

表达式通过对操作数应用运算符和函数来指定值的计算

操作数 表示表达式中的基本值。操作数可以是文本、或由(可能是限定的)非空标识符表示的常量、变量或函数或带圆括号的表达式。空白标识符只能作为操作数出现在赋值操作符的左侧

 Operand = Literal | OperandName | "(" Expression ")" .
 Literal = BasicLit | CompositeLit | FunctionLit .
 BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
 OperandName = identifier | QualifiedIdent.

限定标识符 限定标识符是用包名称前缀限定的标识符。包名称和标识符都不能为空

 QualifiedIdent = PackageName "." identifier .

限定标识符访问另一个包中的标识符,该包必须被导入。标识符必须可导出并在该包的包块中声明

 math.Sin // 表示 math 包中的 Sin 函数

复合文本 复合文本为结构、数组、切片和映射构造值,并在每次求值时创建新值。文本的类型由用大括号绑定的元素列表组成。每个元素前面可以有一个对应的键

 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 .

LiteralType的基础类型必须是struct、array、slice或map类型(语法强制执行此约束,除非该类型被指定为TypeName)。元素和键的类型必须可赋值给文本类型的相应字段、元素和键类型,不存在额外的转换。键被解释为结构文本的字段名、数组和切片文本的索引以及映射文本的key

对于map,所有元素都必须有一个key。使用相同的字段名或常量key值指定多个元素是错误的

  • 对于结构文本,以下规则适用
  • 键必须是在结构类型中声明的字段名
  • 不包含任何键的元素列表必须按照声明字段的顺序为每个结构字段列出元素
  • 如果任何元素有键,则每个元素都必须有键
  • 包含键的元素列表不需要为每个结构字段都有元素。省略的字段获得该字段的零值
  • 文本可以省略元素列表;这样的文本的类型计算结果为零
  • 为属于不同包的结构的非导出字段指定元素是错误的
 type Point3D struct { x, y, z float64 }
 type Line struct { p, q Point3D }
 origin := Point3D{} // zero value for Point3D
 line := Line{origin, Point3D{y: -4, z: 12.3}} // zero value for line.q.x

对于数组和切片文本,应用以下规则

  • 在数组中,每个元素都有一个关联的整数索引,标记其位置
  • 具有键的元素使用键作为其索引。键必须是可由int类型的值表示的非负常量;如果它是类型化的,则必须是整型的
  • 没有键的元素使用前一个元素的索引加一。如果第一个元素没有键,则其索引为零

引用复合文本的地址将生成指向用该文本的值初始化的唯一变量的指针

 var pointer *Point3D = &Point3D{y: 1000}

注意,切片或映射类型的零值与初始化的值不同,但为同一类型的空值。因此,采用空切片或映射复合文本的地址与使用new分配新切片或映射值的效果不同

 p1 := &[]int{} // p1 指向一个初始化的空切片,值是 []int{} 长度为 0
 p2 := new([]int) // p2 指向一个为初始化的切片,值是nil,长度是0

数组文本的长度是在文本类型中指定的长度。如果在文本中提供的元素少于长度,则缺少的元素将被设置为数组元素类型的零值。在数组的索引范围之外为元素提供索引值是错误的。符号…指定数组长度等于最大元素索引号加1

 buffer := [10]string{} // len(buffer) == 10
 intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6
 days := [...]string{"Sat", "Sun"} // len(days) == 2

切片描述整个底层数组。因此,切片文本的长度和容量是最大元素索引号加上1。切片文本具有以下形式

 []T{x1, x2, … xn}

应用于数组的切片操作的缩写

 tmp := [n]T{x1, x2, … xn}
 tmp[0 : n]

在数组、切片或映射类型T的复合文本中,如果元素或映射键本身是复合文本,则如果它们与T的元素或键类型相同,则可以省略相应的文本类型。类似地,当元素或键类型为*T时,作为复合文本地址的元素或键可以省略&T

 [...]Point{{1.5, -3.5}, {0, 0}} // same as [...]Point{Point{1.5, -3.5}, Point{0, 0}}
 [][]int{{1, 2, 3}, {4, 5}} // same as [][]int{[]int{1, 2, 3}, []int{4, 5}}
 [][]Point{{{0, 1}, {1, 2}}} // same as [][]Point{[]Point{Point{0, 1}, Point{1, 2}}}
 map[string]Point{"orig": {0, 0}} // same as map[string]Point{"orig": Point{0, 0}}
 map[Point]string{{0, 0}: "orig"} // same as map[Point]string{Point{0, 0}: "orig"}
 type PPoint *Point
 [2]*Point{{1.5, -3.5}, {}} // same as [2]*Point{&Point{1.5, -3.5}, &Point{}}
 [2]PPoint{{1.5, -3.5}, {}} // same as [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}

有效数组、切片和映射文本的示例

 // list of prime numbers
 primes := []int{2, 3, 5, 7, 9, 2147483647}
 // vowels[ch] is true if ch is a vowel
 vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}
 // the array [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
 filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1}
 // frequencies in Hz for equal-tempered scale (A4 = 440Hz)
 noteFrequency := map[string]float32{
 "C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
 "G0": 24.50, "A0": 27.50, "B0": 30.87,
 }

函数文本 通常表示匿名函数

 FunctionLit = "func" Signature FunctionBody .
 func(a, b int, z float64) bool { return a*b < int(z) }

函数文本可以赋值给一个变量或直接调用

 f := func(x, y int) int { return x + y }
 func(ch chan int) { ch <- ACK }(replyChan)

函数文本是一个闭包:它们可以引用在闭包函数中定义的变量。然后,这些变量可以在闭包的函数和函数文本之间共享,只要它们是可访问的,它们就可以存活

基本表达式 是一元和二元表达式的操作数

 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 ] ) [ "..." ] [ "," ] ] ")" .
 x
 2
 (s + ".txt")
 f(3.1415, true)
 Point{1, 2}
 m["foo"]
 s[i : j + 1]
 obj.color
 f.p[i].x()

选择器 对不是包名称的基本表达式x,选择器表达式

 x.f

表示x(有时*x)的方法或字段f的值。标识符f称为(字段或方法)选择器;它不能是空标识符。选择器表达式的类型是f的类型

选择器f可以表示T类型的字段或方法f,也可以引用T嵌入字段的嵌套字段或方法f。遍历到f的嵌入字段数称为其在T中的深度。T中声明的字段或方法f的深度为零。在T的嵌套字段A中声明的字段或方法f的深度是A在T中的深度加1

下列规则适用于选择器

  • 对于一个类型为T或*T的值x,此处的T不是指针或接口类型,其中存在这样的f,x.f表示T中最浅深度处的字段或方法;如果不存在深度最浅的f,x.f则为非法表达式
  • 对于一个类型为I的值x,此处I是接口类型,x.f表示动态值x的名为f的实际方法;如果I的方法集中没有名为f的方法,则选择器表达式x.f是非法的
  • 作为例外,如果x的类型是已定义的指针类型,并且(x).f是表示字段(但不是方法)的有效选择器表达式,则x.f是(x).f的缩写
  • 在所有其他情况,x.f是非法的
  • 如果x是一个指针类型并且值为nil,x.f表示一个结构字段,赋值或计算x.f会造成一个运行时panic
  • 如果x是接口类型且值为nil,调用或计算方法x.f会造成一个运行时panic

例如

 type T0 struct {
 x int
 }
 func (*T0) M0()
 type T1 struct {
 y int
 }
 func (T1) M1()
 type T2 struct {
 z int
 T1
 *T0
 }
 func (*T2) M2()
 type Q *T2
 var t T2 // with t.T0 != nil
 var p *T2 // with p != nil and (*p).T0 != nil
 var q Q = p
 t.z // t.z
 t.y // t.T1.y
 t.x // (*t.T0).x
 p.z // (*p).z
 p.y // (*p).T1.y
 p.x // (*(*p).T0).x
 q.x // (*(*q).T0).x (*q).x is a valid field selector
 p.M0() // ((*p).T0).M0() M0 expects *T0 receiver
 p.M1() // ((*p).T1).M1() M1 expects T1 receiver
 p.M2() // p.M2() M2 expects *T2 receiver
 t.M2() // (&t).M2() M2 expects *T2 receiver, see section on Calls
 // 下面是非法的
 q.M0() // (*q).M0 是有效的,但不是一个字段选择器

方法表达式 如果M在类型T的方法集中,T.M是一个可作为正则函数调用的函数,其参数与M相同,其前缀参数是方法的接收器

 MethodExpr = ReceiverType "." MethodName .
 ReceiverType = Type .

考虑一个类型为T的结构,它有两个方法: Mv它的接收器是T类型; Mp它的接收器是*T类型

 type T struct {
 a int
 }
 func (tv T) Mv(a int) int { return 0 } // value receiver
 func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver
 var t T

表达式 T.Mv 产生一个与Mv等价的函数,但第一个参数是显式接收器,函数签名形如

 func(tv T, a int) int

该函数通常可以使用显式接收器调用,因此,以下这五个调用是等效的

 t.Mv(7)
 T.Mv(t, 7)
 (T).Mv(t, 7)
 f1 := T.Mv; f1(t, 7)
 f2 := (T).Mv; f2(t, 7)

相似的,表达式 (*T).Mp 产生一个用以下签名表示的Mp函数

 func(tp *T, f float32) float32

对于具有值接收器的方法,可以使用显式指针接收器派生函数,因此,(*T).Mv 产生一个具有下述签名的函数值

 func(tv *T, a int) int

这样的函数通过接收器间接创建一个值,传递给作为接收器底层的方法;该方法不会覆盖在函数调用中传递其地址的值

最后一种情况,指针接收器方法到值接收器方法是非法的,因为指针接收器的方法不在值类型接收器方法集中

从方法派生的函数值使用函数调用语法调用;接收器作为调用的第一个参数提供。也就是说,给定 f := T.Mv ,f被调用为f(t, 7)

从接口类型的方法派生函数值是合法的,结果函数接受该接口类型的显式接收器

方法值 如果表达式x具有静态类型T,并且M在类型T的方法集中,x.M被称为方法值。方法值x.M是一个函数值,可以使用与x.M的方法调用相同的参数来调用。表达式x在方法值求值期间被求值并保存;保存的副本随后被用作任何调用的接收器,这些调用可以在以后执行

类型T可以是接口或非接口类型。在上面方法表达式的讨论中,考虑一个T类型的结构,它有两个方法: Mv它的接收器类型为T;Mp它的接收器类型为*T

 type T struct {
 a int
 }
 func (tv T) Mv(a int) int { return 0 } // value receiver
 func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver
 var t T
 var pt *T
 func makeT() T

表达式 t.Mv 生成下述类型的函数值

 func(int) int

以下两个调用等价

 t.Mv(7)
 f := t.Mv; f(7)

对于表达式 pt.Mp 产生一个下述类型的函数值

 func(float32) float32

与选择器一样,对使用指针的值接收器的非接口方法的引用将自动取消对该指针的引用: pt.Mp 与 (*pt).Mp 等价

与方法调用一样,使用可寻址值的指针接收器对非接口方法的引用将自动获取该值的地址: t.Mp 等价于 (&t).Mp

 f := t.Mv; f(7) // like t.Mv(7)
 f := pt.Mp; f(7) // like pt.Mp(7)
 f := pt.Mv; f(7) // like (*pt).Mv(7)
 f := t.Mp; f(7) // like (&t).Mp(7)
 f := makeT().Mp // 非法: makeT() 的结果不可寻址

尽管上面的示例使用非接口类型,但是从接口类型的值创建方法值也是合法的

 var i interface { M(int) } = myVal
 f := i.M; f(7) // like i.M(7)

索引表达式 基本表达式形如

 a[x]

表示数组的元素、指向数组的指针、切片、字符串或可由x索引的map,值x分别被称为索引键或map键。以下规则适用

(文章上部分end)

最近发表
标签列表