GOLANG 1.9 语言规范

简介

本文是GO语言的使用手册。对于更多的信息,请前往 golang.org。

GO 语言是一门通用的系统编程语言。它是一种强类型语言,支持自动垃圾收集,并且对语言层面对并发编程进行了支持。GO 程序以包的形式进行组织,对程序间的依赖关系进行高效的管理。当前实现方式使用传统的编译/链接模型生成二进制可执行文件。

GO语言语法紧凑且规范,故而便于如集成开发环境这样的自动化工具对其进行分析。

标识符

语法使用扩展的巴科斯范式(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 "}" .

Production 是由项和以下操作符构成的表达式,操作符优先级递增:

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

小写字母名字用来识别词法符号。非终结元素使用驼峰命名法表示。词法符号用“”或者 “ 括起来。

a...b 表示包含从 a 到 b 的所有字符的字符集。...在本语言规范中还被用来表示枚举或者
省略的代码片段。字符 ... (不同于三个. 字符)不是GO语言的符号。

源码表示

GO 语言源码使用Unicode字符集,编码方式为 UTF-8。文本不是标准化的,所以带重音符的代码点与带重音符的单个字母不同,它被认为是两个代码点。为了简单,本文档会使用不带限制的项字符来表示源码中的一个Unicode代码点。

每个代码点都是独一无二的;例如,一个字符的大小写是不同的字符。

语言实现限制:为了与其他工具兼容,编译器可能不允许源码文件中出现 NUL 字符。

语言实现限制:为了与其他工具兼容,如果UTF-8字节序标识(U+FEFF)是源码文件的第一个Unicode代码点,编译器可能忽略它。字节序标识可能不允许出现在源码文件中。

字符

以下项用来表示具体的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" .
octal_digit = "0" ... "7" .
hex_digit = "0" ... "9" | "A" ... "F" | "a" ... "f" .

文法元素

注释

注释起到程序文档的作用。由两种形式:

1.行注释为 //
2. 通用注释为 /**/

注释不能嵌套在 Rune 或者字符串字面量里,也不能嵌套在其他注释里。不包含新行的通用注释就如同一个空格。其他任何注释就像一个换行符。

符号

符号组成GO语言的词汇。由四类符号:标识符,关键字,操作符以及字面量。空白字符,由空格、水平制表符、回车符、换行符将被忽略,除非它作为分隔符能形成一个新的符号。另外,换行符或者文件结束符会触发插入一个分号。把输入分隔为符号的原则是下一个符号是形成一个合法符号的最长字符序列。

分号

正式语法使用分号作为语句的终结符。GO 程序使用如下规则去掉大部分多余的分号:

  1. 当输入被分隔成多个符号时,分号被自动添加到字符流的每一行最后一个字符后,如果此字符满足
    • 一个标识符
    • 一个整数,浮点数,复数,Rune或者字符串字面量
    • 关键字:break,continue,fallthrough,return
    • 操作符及标点符号:++,–,),],}
      需要了解更符合语言习惯的用法,使用如上规则编码本文档中省略分号的代码例子。

标识符

标识符命名程序中的实体,例如变量和类型。标识符是字母和数字组成的字符串,首字符必须是字母。

identifier = letter { letter | unicode_digit } .

a
_x9
ThisVariableIsExported
a β

一些标识符是预定义的。

关键字

下列关键字是保留的,不能用作标识符。

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

操作符与标点符号

下列字符序列表示操作符(包括赋值操作符)以及标点符号:

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

整数字面量

整数字面量是用来表示整数常量的一个数字串。有一个可选的前缀设置非十进制的基数:八进制前缀是 0,十六进制前缀使用 0x 或者 0X。在十六进制中,字母 a~f 以及 A~F 表示 10 ~15。

int_lit = decimal_lit | octal_lit | hex_lit .
decimal_lit = ( "1" ... "9" ) { decimal_digit } .
octal_lit = "0" { octal_digit } .
hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } .
42
0600
0xBadFace
170141183460469231731687303715884105727

浮点数字面量

浮点数字面量是十进制表示的浮点数常量。一个浮点数常量包含整数部分、十进制小数点、小数部分以及指数部分。整数部分和小数部分组成十进制数值,指数部分是 e 或者 E 后面带一个可选的带符号的十进制数部分。整数部分或者小数部分之一可以省略;小数点或者指数部分之一也可以省略。

float_lit = decimals "." [ decimal ] [ exponent ] | decimals exponent | "." decimals [ exponent ] .
decimals = decimal_digit { decimal_digit } .
exponent = ( "e" | "E" ) [ "+" | "-" ] decimals .
0.
72.40
072.40  // == 72.40
2.71828
1.e+0
6.67428e-11
1E6
.25
.12345E+5

复数虚部字面量

虚部字面量是复数常量虚部的十进制表示,它有一个浮点数字面量或者一个十进制整数后面跟一个小写的字母 i 组成。

imaginary_lit = ( decimail | float_lit ) "i" .
0i
011i  // == 11i
0.i
2.71828i
1.e+0i
6.67428e-11i
1E6i
.25i
.12345E+5i

Rune字面量

Rune 字面量表示一个 Rune 常量,它是标识一个Unicode 代码点的整数值。Rune 字面量由一个或多个字符外加单引号包括组成,例如 ‘x’ 或者 ‘\n’。在单引号之间可以写除了换行符和非转移的单引号外的任意字符。一个由单引号括起来的字符代表该字符的 Unicode 值,而以反斜杠开头的多字符序列以不同的格式编码。

最简单的形式即是单引号括起来的单个字符;因为 GO 语言源代码文件是使用 UTF-8 编码的 Unicode 字符,多个 UTF-8 编码的字节可能表示一个整数值。例如,字面量 ‘a’ 使用一个字节编码表示 ‘a’, Unicode 值 U+0061,值 0x61,而 ‘ä’ 使用两个字节( 0xc3 0xa4 )表示字面量 a-dieresis,U+00E4, 值 0xe4。

多反斜杠允许任意值以 ASCII 文本的格式编码。由四种方式将一个整数值表示成一个数值常量:\x 后面加两个十六进制数字; \u 后面加四个十六进制数字; \U 后面加八个十六进制数字,以及一个反斜杠后面加三个八进制数字。对于每一种方式,字面量的值是相应进制数字表示的值。

虽然这四种表示方式都是表示一个整数,但是他们的有效范围不同。八进制转义必须表示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 字面量种,所有其他的以反斜杠开头的序列都是非法的。

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”。在双引号之间,可以包含除了换行符和非转义双引号外的任意字符。双引号之间的文本组成字面量的值,反斜杠转义的规则跟 Rune 字面量中一样(除了 \’ 是非法的,而 \” 是合法的)。三个八进制数字的转义 (\nnn) 和两个十六进制数字的转义 (\xnn) 表示结果字符串的独立字节表示;所有其他转义表示单独字符的UTF-8编码(可能是多字节 UTF-8 编码)。因此,在字符串字面量中 \377 和 \xFF 表示单个字节的值 0xFF=255,而 ÿ,\u00FF,\U000000FF 以及 \xc3\xbf 表示对字符U+00FF进行 UTF-8 编码的两个字节 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、整数、浮点数以及复数常量统称为数值常量。

常量值是由rune、整数、浮点数、虚数或者字符串字面量表示,一个标识符标识一个常量,可以是一个常量表达式、一个结果为常量的转换,或者一个内置函数(例如unsafe.Sizeof)的结果、cap 或者 len 函数作用于一些表达式、real 或者 imag 函数应用于一个复数常量以及 complex 函数应用于数值常量。布尔真值由预定义的常量 true 和 false 表示。预定义的标识符 iota 表示一个整数常量。

一般情况下,复数常量是一个常量表达式的形式,它将会在相应的章节讨论到。

数值常量表示任意精度的精确值,当然,不能出现溢出。

由以上描述可知,没有表示 IEEE-754 负零、无穷值、非数值值的常量。

常量可以是由类型的可以是无类型的。字面量常量,true、false、ioto以及一些只包含无类型常量操作数的特定常量表达式也是无类型的。

常量可以通过常量声明或者转换显示的声明类型,或者通过在变量声明、赋值、作为表达式操作数过程中隐示声明。如果一个常量值不能表示成相应类型的值将会报错。例如,3.0可以作为任意整数或者浮点数,而2147483648.0 (等同于 1<<31) 可以作为 float32、float64 或者 uint32,但是不能作为 int32 或 字符串。

实现限制:虽然数值常量在语言中有任意精度,但是编译器在实现的时候将使用有限精度的内部表示。也就是说,任意实现需要满足:

  • 表示至少 256 位整数常量。
  • 表示浮点数常量,包括负数常量的实部和虚部,尾数至少256位,带符号的二进制指数至少16位。
  • 如果不能精确表示一个整数常量,报错提示。
  • 如果由于内存溢出不能表示一个浮点数或者复数常量,报错提示。
  • 由于精度的限制不能表示一个浮点数或者复数常量时,四舍五入到最接近的能表示的常量。

这些需求适用于字面量常量以及常量表达式的计算结果。

变量

变量是保存值的一个存储地址。变量中允许保存的值由变量类型决定。

变量声明,或者对于函数参数及返回值声明,函数声明或者函数字面量保存所命名的变量。调用一个内置函数 new 或者取一个混合字面量的地址将为该变量分配程序运行时的内存空间。一个匿名变量将通过指针(可能是隐示的)间接寻址。

复合变量,例如数组、切片,以及结构体类型的元素及字段可以单独进行寻址。每个元素操作起来都像一个变量。

变量的静态类型(或者说类型)是在变量声明的时候给出的类型、在 new 调用或者复合字面量提供的类型或者复合类型变量中一个元素的类型。接口类型的变量也有一个独特的动态类型,它是在运行时赋值给该变量的值的具体类型(值为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 .

布尔类型、数值类型以及字符串类型的命名实例是预定义的。其他类型名称是通过类型声明引入的。复合类型–数组、结构体、指针、函数、接口、切片、映射以及通道类型–可以通过类型字面量构建。

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 类型的方法集)。对于包含嵌入字段的结构体的规则的进一步描述将在结构类型这一节进行描述。所有未作为接收者声明的类型都有一个空的方法集。在一个方法集中,每个方法都有唯一一个非空的方法名。

类型的方法集决定了它实现的接口以及能它作为接受者能调用的方法。

布尔类型

布尔类型表示布尔值的真值集,真值由预定义的常量 true 和 false 表示。预定义的布尔类型是 bool。

数值类型

数值类型表示整数或者浮点数值的集合。预定义的与系统架构相关的数值类型有:

uint8       the set of all unsigned 8-bit integers (0 to 255) uint16 the set of all unsigned 16-bit integers (0 to 65535) uint32 the set of all unsigned 32-bit integers (0 to 4294967295) uint64 the set of all unsigned 64-bit integers (0 to 18446744073709551615) int8 the set of all signed 8-bit integers (-128 to 127) int16 the set of all signed 16-bit integers (-32768 to 32767) int32 the set of all signed 32-bit integers (-2147483648 to 2147483647) int64 the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807) float32 the set of all IEEE-754 32-bit floating-point numbers float64 the set of all IEEE-754 64-bit floating-point numbers complex64 the set of all complex numbers with float32 real and imaginary parts complex128 the set of all complex numbers with float64 real and imaginary parts byte alias for uint8 rune alias for int32

n 比特位整数的值的宽度是 n 个比特,它由二进制补码表示。

一些数值类型与实现相关:

uint     either 32 or 64 bits
int      same size as uint
uintptr  an unsigned integer large enough to store the uninterpreted bits of a pointer value

为了可移植性,除了 byte 是 uint8 的别名、rune 是 int32 的别名, 所有其他数值类型都是不同的类型。当不同的数值类型出现在一个表达式或者赋值语句中时需要进行类型转换。例如,虽然 int32 与 int 在特定的系统架构下可能大小相同,但他们是不同的类型。

字符串类型

字符串类型表示字符串值的集合。一个字符串值(可以为空)是一个字节序列。字符串是不可变的:一旦创建出来,不能改变字符串的内容。预定义的字符串类型是 string。

字符串 s 的长度可以通过内建的 len 函数得到。如果字符串是常量,那么它的长度也是一个编译时常量。字符串的字节可以通过下标 0 到 len(s)-1 访问。对字符串中字节进行取址操作是非法的;如果 s[i] 是字符串 s 的第 i 个字节,那么 &s[i] 是非法的。

数组类型

数组是同一种类型的元素组成的序列,序列中的每个元素是有数字编号的。数组中元素的个数可以通过 len 函数获取,并且永远不可能为负。

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 访问到。切片中某个元素的索引值可能比该元素在切片底层数组中的索引值要小。

一个切片,一旦进行初始化,总是与一个包含其元素的底层数组相关联。因此,两个拥有相同底层数组的切片共享存储;相比之下,不同的数组总是拥有不同的存储空间。

切片的底层数组大小可以扩展到切片结尾后。容量是对切片存储限度的度量标准:它表示切片的长度加上底层数组除切片之外的长度;达到存储容量限制的切片可以通过从原切片进行切片操作得到一个新切片。切片 s 的容量可以通过内建的 cap 函数获取。

一个指定类型的新初始化的切片可以通过内建的 make 函数进行创建,这个函数有一个切片类型参数、长度参数以及一个可选的容量参数。使用 make 函数创建的切片总是分配一个新的底层数组与该切片相关联。也就是说,执行

make([]T,length,capacity)

的结果与申请一个新的数组并对其进行切片是一样的,所以下面两个表达式是等价的。

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

如数组一样,切片总是一维的,但是可以通过组合得到高维的对象。对于数组的数组,通过构建,其内部的数组总是相同长度的数组;但是,对于切片的切片(或者切片的数组),其内部的切片的长度是可以动态变化的。而且,其内部的切片必须单独初始化。

结构体

结构体是字段的序列,每个字段都有一个名称和相应的类型。字段名称可以显示声明(标识符列表)或者隐示声明(嵌入字段)。一个结构体中的非空字段名称必须是唯一的。

StructType = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName .
Tag = string_lit .
// An empty struct.
struct {}

// A struct with 6 fields.
struct {
    x,y int
    u float32
    _ float32  // padding
    A *[]int
    F func()
}

声明但是未给出显示名称的字段称为嵌入字段。嵌入字段必须声明为类型名称 T 或者一个非接口类型名称 *T 的指针,并且 T 自己不能是指针类型。未加任何修饰的类型名称直接作为字段名称。

// A struct with four embedded fields of types T1,*T2,P.T3 and *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 标识,如果 x.f 是一个表示该字段或者方法的合法选择符,那么 f 被称为是被晋升的。

除了在结构体的混合字面量中不能用作字段名称,被晋升的字段在结构体中就如普通字段一样。

给定一个结构体 S 和一个类型 T,被晋升的方法按照如下规则包含到 S 的方法集中:
* 如果 S 包含一个嵌入字段 T, S 和 *S 的方法集都包含以 T 作为接收者的被晋升字段。*S的方法集还包含以 *T 作为接收者的被晋升方法。
* 如果 S 包含一个嵌入字段 *T,S 和 *S 的方法集都包含以 T 或者 *T 作为接受者的被晋升方法。

字段声明后可以跟一个可选的字符串字面量标记 tag,它将作为相应被声明字段的一个属性。一个空的标记字符串等价于一个缺失的标记。标记可以通过反射接口进行访问并参与到结构体的类型身份识别中,其他情况下这个属性将被忽略。

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 .

在函数类型的参数和返回值列表中,参数名称或者都写或者都不写。如果写了,每个名称表示给定类型的一个项,函数签名中的非空名称必须都是唯一的。如果不写,每个类型表示该类型的一个项。参数或者返回值列表总是用小括号括起来,只有一个情况除外,即函数只有一个未命名的返回值时,可以省略小括号。函数签名中最后一个参数可以有一个…前缀。带这种参数的函数称为可变的,调用函数时,这个参数部分可以写0到多个实参。

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,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(b Buffer) bool
    Write(b Buffer) bool
    Close()
}

一个接口可以被多个类型实现。例如,如果类型 S1 和 S2 都有方法集:

func (p T) Read(b Buffer) bool { return … }
func (p T) Write(b Buffer) bool { return … }
func (p T) Close() { … }

(其中 T 代表 S1 或者 S2),那么 S1 和 S2 均实现了接口 File, 尽管 S1 和 S2 可能还独自实现或共同实现了其他的方法。一个类型实现了任意方法集是其子集的接口,因此一个类型可能实现多个不同的接口。例如,所有的类型都实现了空接口:

interface{}

同样,使用类型声明中所述的接口规范定义一个接口 Locker :

type Locker interface { Lock() Unlock() }

如果 S1 和 S2 都实现了如下方法:

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

那么,他们实现了 File 接口,同时还实现了 Locker 接口。

接口 T 可以在方法声明的地方使用一个接口类型名称 E(可以带修饰)。 此时, E 被称为是 T 中的嵌入接口;接口 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 }

接口类型不能循环嵌入,即不能嵌入自身类型或者嵌入自身类型的类型。

// illegal: Bad cannot embed itself
type Bad interface { Bad }

// illegal: Bad1 cannot embed itself using Bad2
type Bad1 interface { Bad2 }
type Bad2 interface { Bad1 }

映射类型

映射类型是一个给定类型元素的无序组,组中的元素被另一种类型的唯一键值索引。未初始化的映射的值是 nil。

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

键类型操作数的比较操作 == 与 != 必须都有定义;因此,键类型不能是函数、映射或者切片。如果键类型是一个接口类型,那么该接口类型的动态值类型的比较操作符必须有定义;失败将会导致一个运行时恐慌。

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

映射中元素的个数称为它的长度。对于一个映射 m,它的长度值可以通过内建函数 len 获取,并且长度可能随着程序的运行改变。可以通过赋值操作添加新的元素,通过索引表达式获取一个元素值;也可以通过内建的 delete 函数删除元素。

一个新的空映射值可以通过内建的 make 函数创建,该函数有一个映射类型参数和一个可选的映射容量参数。

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

初始映射容量并不是映射大小的最大值:除了 nil 映射,所有映射都会根据存储在其中的元素个数不断调整存储空间。nil 映射等价于一个空映射,但是不允许添加任何元素。

通道类型

通道为并发执行的函数之间进行通信提供了一种机制,可以通过它发送和接收指定类型的元素。未初始化的通道的值为 nil。

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

可选的 <- 操作符标明了通道的方向,发送或者是接收。如果没有声明方向,那么通道默认是双向的。一个通道可以通过赋值或者类型转换限制为只能发送或者接收。

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 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 函数创建,这个函数有一个通道类型参数和一个可选的通道容量参数。

make(chan int, 100)

通道容量设置通道中可以缓冲元素的个数。如果容量设置为 0 或者未设置,那么通道是无缓冲的,只有在通道的发送端和接收端都就绪的情况下才能进行通信。否则,通道是带缓冲的,此时只要通道缓冲部不满就能发送,只要通道非空就能接收,通信是非阻塞的。nil 通道不能进行通信。

通道可以使用内置的 close 函数进行关闭。接收操作符的多值赋值形式会提示接收到的值在通道关闭前是否已经发送。

一个通道可以在任意多个 Goroutine 中使用来进行通信,可以用来发送和接收元素、调用内建的 cap 和 len 函数而不需要其他同步手段。通道就如一个先进先出的队列。例如,一个 Goroutine 往一个通道中发送元素,另一个 Goroutine 从这个通道中接收元素,接收到的元素的顺序个发送的顺序一致。

类型和值的属性

类型标识

两个类型可以是等价的也可以不同的。

一个定义的类型总是和其他任何类型不同。否则,如果两个类型的底层类型字面量在结构上等价,那么这两个类型是相等的;也就是说,他们有相同的字面量结构,并且对应元素相应的类型也相等。详细来说:

- 如果两个数组的元素类型和长度都相同,那么这两个数组类型是相等的。
- 如果两个切片的元素类型是相同的,那么这两个切片类型是相同。
- 如果两个结构体类型有相同序列的字段,并且字段的类型、名称以及标记都相同,那么这两个结构体是相同的。不同包的未导出字段的名称总是不同的。 
- 如果两个指针类型的基类型是相同的,那么这两个指针类型是相等的。
- 如果两个函数类型的参数列表和返回值列表中对应位置参数的类型是相同的,那么不论函数参数是否是不定的这两个函数类型都是相等的。参数和返回值名称不要求相同。
- 如果两个接口类型有相同的方法集(即所有方法对应的名称和类型都相等),那么这两个接口类型是相同的。不同包的未导出方法总是不同的。方法声明的顺序与此无关。
- 如果两个映射类型有相同的键类型和值类型,那么这两个映射类型是相等的。
- 如果两个通道类型有相等的值类型和方向,那么这两个通道类型是相等的。

给定如下声明:

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,and []string
A2 and struct{ a,b int }
A3 and int
A4,func(int,float64) *[]string,and A5

B0,B0,and C0
[]int and []int
struct{ a,b *T5 } and struct{ a,b *T5 }
func(x int,y float64) *[]string,float64) (result *[]string),and A5

B0 与 B1 是不相等的,这是因为他们是由不同的类型定义创建的新类型。func(int,float64) *B0func(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 是一个指针类型、函数、切片、映射、通道或者接口类型。
- x 是一个可被类型 T 的值表示的无类型的常量。

块是成对的大括号包括的可空语句序列。

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

源码文件中除了显示声明的块,还有隐示声明的块。
* 整个程序有一个全局块包含所有的go 源文件。
* 每个包有一个包含包中所有源文件的块。
* 每个源文件有一个包含该文件中所有代码的块。
* 每个 if、for、switch 语句都在他们自己的隐示块中。
* switch 与 select 语句的每一个条件子句都是一个隐示块。

块可以嵌套,并对范围有影响。

声明和范围

一个声明绑定了一个非空的标识符到一个常量、类型、变量、函数、标签或者包。程序中的每个标识符都必须先声明。一个标识符在同一个块中不可以声明两次,并且一个标识符不能文件块中声明又在包块中声明。

空标识符(_)在声明过程中和其他标识符一样使用,但是它没有生成一个绑定,因此是未声明的。在包块中,标识符 init 可能只在 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

导出的标识符

一个标识符可以被导出使得在其他包中能访问。标识符必须同时满足以下两点才是导出的:

1. 标识符的首字母是Unicode大写字母(Unicode 类 "Lu")
2. 标识符在包块中定义或者它表示一个字段名或方法名。

所有其他标识符都不是导出的。

标识符的唯一性

给定一个标识符集合,如果一个标识符与该集合中任意标识符都不相同,那么称这个标识符是唯一的。如果两个标识符拼写不同,或者他们在不同的包并且未导出,那么这两个标识符是不同。否则,他们是相同的。

常量声明

一个常量声明将一系列标识符(常量的名称)绑定到一个系列常量表达式。标识符的数量必须等于常量表达式的数量,并且左边第 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 声明列表中,除第一个表达式列表外的其他表达式列表可以省略。这样的空列表等价于第一个表达式列表的文本替代,如果第一个表达式列表包含类型,文本替代中也包含类型。标识符的数量必须与前一个列表中表达式的数量相同。结合 iota 常量生成器,这个机制可以达到轻量级的数值序列生成的效果。

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

iota

在一个常量声明中, 预定义标识符 iota 表示连续的无类型整数常量。每当源码中出现 const 关键字,iota 的值重置为 0,并且在每个 ConstSpec 中它的值递增。它可以用来构建一系列相关的常量。

const ( // iota is reset to 0
    c0 = iota  // c0 == 0
    c1 = iota  // c1 == 1
    c2 = iota  // c2 == 2
)

const ( // iota is reset to 0
    a = 1 << iota  // a == 1
    b = 1 << iota  // b == 2
    c = 3          // c == 3 (iota is not used but still incremented)
    d = 1 << iota  // d == 8
)

const ( // iota is reset to 0
    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 (iota has been reset)
const y = iota  // y == 0 (iota has been reset)

在一个表达式列表中,iota 的值是相同的,这是由于它只在每个 ConstSpec 中递增。

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

最后一个例子主要为了说明该例子中最后一个非空表达式列表的隐示重复。

类型声明

类型声明将一个类型名称绑定到一个类型。类型声明由两种形式:别名声明和类型定义。

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

类型别名

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

AliasDecl = identifier "=" Type .

在这个标识符的作用域中,它作为这个类型的一个别名。

type ( nodeList = []*Node // nodeList and []*Node are identical types Polar = polar // Polar and polar denote identical types )

类型定义

类型定义创建一个新的不同的类型,这个新类型用于与给定类型相同的底层类型和操作,新的标识符将绑定到这个新类型。

TypeDef = identifier Type .

这个新类型被称为定义的类型。它与其他类型都不同,与创建基于的类型也不一样。

type ( Point struct{ x,y float64 } // Point and struct{ x,y float64 } are different types polar Point // polar and Point denote different types )

type TreeNode struct { left,right *TreeNode value *Comparable }

type Block interface { BlockSize() int Encrypt(src,dst []byte) Decrypt(src,dst []byte) }

定义的类型由相应的方法集。它不继承给定类型的任何方法,但是接口的方法集或者复合类型的元素保持不变:

// A Mutex is a data type with two methods,Lock and Unlock.
type Mutex struct { /* Mutex fields */ }
func (m *Mutex) Lock()    { /* Lock implementation */ }
func (m *Mutex) Unlock()  { /* Unlock implementation */ }

// NewMutex has the same composition as Mutex but its method set is empty.
type NewMutex Mutex

// The method set of the base type of PtrMutex remains unchanged,
// but the method set of PtrMutex is empty.
type PtrMutex *Mutex

// The method set of *PrintableMutex contains the methods
// Lock and Unlock bound to its embedded field Mutex.
type PrintableMutex struct { Mutex }

// MyBlock is an interface type that has the same method set as 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 lookup; only interested in "found"

如果给定了一个表达式列表,那么变量就按照赋值规则使用表达式进行初始化。否则,变量被初始化为它的零值。

如果类型存在,变量将被赋予此类型。否则变量将被赋予赋值语句中相应初始值的类型。如果初始值是无类型的常量,它将先转换为它的默认类型;如果它是一个无类型的布尔值,它将先转换为类型 bool。预定义的 nil 值在没有显示声明类型的情况下不能用来初始化一个变量。

var d = math.Sin(0.5)  // d is float64
var i = 42             // i is int
var t,ok = x.(T)      // t is T,ok is bool
var n = nil            // illegal

实现限制:如果在一个函数体中定义的变量未使用,编译器将认为其非法。

短变量声明

一个短变量声明使用如下语法:

ShortVarDecl = IdentifierList ":=" ExpressionList .

它是常规变量声明的简写形式,只带初始化值而不带类型。

"var" IdentifierList = ExpressionList .
i,j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r,w := os.Pipe(fd)  // os.Pipe() returns two values
_,y,_ := coord(p)  // coord() returns three values; only interested in y coordinate

与常规变量声明不同的是,简写的变量声明可以重复声明变量,假如变量在相同块(或者如果块是函数体时的参数列表)以相同类型定义过,只要至少含有一个非空的新变量就行。因此,重复定义只可能出现在多变量声明的简写形式中。
重定义并不引入新的变量;它只是给原来定义的变量赋于一个新的值。

field1,offset := nextField(str,0)
field2,offset)  // redeclares offset
a,a := 1,2                              // illegal: double declaration of a or no new variable if a was declared elsewhere

变量声明简写只能用在函数体中。在一个上下文环境中,例如 if、for 或 switch 的初始化语句中,它可以被用来声明局部临时变量。

函数声明

函数声明绑定一个标识符,即函数名,到一个函数。

FunctionDecl = "func" FunctionName ( Function | Signature ) .
FunctionName = identifier .
Function     = Signature FunctionBody .
FunctionBody = Block .

如果函数的签名中声明了返回值参数,那么函数体必须以中止于一个终结语句。

func IndexRune(s string,r rune) int {
    for i,c := range s {
        if c == r {
            return i
        }
    }
    // invalid: missing return statement
}

函数声明可以省略函数体。此时,函数体使用 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 ( Function | Signature ) .
Receiver   = Parameters .

接收者通过一个额外的参数段在方法名前声明。此参数段必须声明一个非不定参数,即接收者。类型必须以 T 或者 *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 | MethodExpr | "(" Expression ")" .
Literal     = BasicLit | CompositeLit | FunctionLit .
BasicLit    = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent.

带修饰的标识符

带修饰的标识符是标识符加一个包名前缀。包名和标识符不能为空。

QualifiedIdent = PackageName "." identifier .

一个带修饰的标识符用来访问另一个包中的标识符,该包必须事先被导入过。此标识符必须是导出类型的,并且声明在该包的包块中。

math.Sin    // denotes the Sin function in package math

混合字面量

混合字面量为结构体、数组、切片和映射构建值,并且每次构建的时候创建一个新的值。他们包含该类型字面量后面跟一个包含的元素列表。每个元素前面可以有一个对应的键。

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 .

该类型的底层类型字面量必须是一个结构体、数组、切片或者映射类型(语法强制规定这个限制,除非类型是以类型名的形式给出)。元素的类型和键必须能赋值给相应字面量类型的字段、元素和键;不能有额外的转换。键是作为结构体的字段名、数值的索引或者切片字面量、映射类型的键。对于映射类型,所有元素都必须有键。对相同字段或者键赋予多个元素是错误的。对非常量映射键,请查看计算顺序这一个节。

对于结构体字面量,有如下规定:

  • 键必须是结构体中定义的字段。
  • 不包含键的元素列表必须为结构体中每一个字段指定一个元素,元素指定的顺序必须与结构体中字段声明的顺序一致。
  • 如果有一个字段有键,那么所有字段都必须带键。
  • 带键的元素列表不一定要为结构体的所有字段指定元素。没有指定元素的字段自动得到零值。
  • 字面量可以省略元素列表;这样的字面量被赋予该类型的零值。
  • 为其他包中未导出字段指定元素是非法的。

给定如下声明:

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 类型的非负常量;如果它出现在声明中,必须是整数类型。没有带键的元素使用前一个元素的索引值加1。如果第一个元素没有带键,那么它的索引是0。

取一个混合字面量的地址将得到一个使用该字面量值初始化的唯一变量的指针。

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

数组字面量的长度是字面量中声明的长度。如果字面量中提供的元素个数比声明的长度小,缺失的元素被赋予该数组元素类型的零值。如果提供的元素的索引值在数值的下标范围之外也是非法的。符号 … 声明数组长度等于最大元素下标值加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,… xn} tmp[0 : n]

语句

内置函数

程序初始化及执行

错误

运行时崩溃

系统折中考虑

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


类型转换 1、int转string 2、string转int 3、string转float 4、用户结构类型转换
package main import s &quot;strings&quot; import &quot;fmt&quot; var p = fmt.Println func main() { p(&quot;Contains: &quot;, s.Contains(&quot;test&quo
类使用:实现一个people中有一个sayhi的方法调用功能,代码如下: 接口使用:实现上面功能,代码如下:
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件信息 4、删除文件,成功返回true,失败返回false
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http://www.golangtc.com/downloadBeego开发文档地址:http://beego.me/docs/intro/ 安装步骤: 一、GO环境安装 二、配置系统变量 三、Beego安装 一、GO环境安装 根
golang获取程序运行路径:
Golang的文档和社区资源:为什么它可以帮助开发人员快速上手?
Golang:AI 开发者的实用工具
Golang的标准库:为什么它可以大幅度提高开发效率?
Golang的部署和运维:如何将应用程序部署到生产环境中?
高性能AI开发:Golang的优势所在
本篇文章和大家了解一下go语言开发优雅得关闭协程的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1.简介本文将介绍首先为什么需要主...
这篇文章主要介绍了Go关闭goroutine协程的方法,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。1.简介本文将介绍首先为什么需要主动关闭gor...
本篇文章和大家了解一下go关闭GracefulShutdown服务的几种方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。目录Shutdown方法Regi...
这篇文章主要介绍了Go语言如何实现LRU算法的核心思想和实现过程,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。GO实现Redis的LRU例子常
今天小编给大家分享的是Go简单实现多租户数据库隔离的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会...
这篇“Linux系统中怎么安装NSQ的Go语言客户端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希
本文小编为大家详细介绍“怎么在Go语言中实现锁机制”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在Go语言中实现锁机制”文章能帮助大家解决疑惑,下面...
今天小编给大家分享一下Go语言中interface类型怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考