GOLANG 1.9 语言规范

u013148156 · · 5751 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

简介


本文是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, 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(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, func(int, 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 的值表示的无类型的常量。


声明和范围


表达式


语句


内置函数



程序初始化及执行


错误


运行时崩溃


系统折中考虑



有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:u013148156

查看原文:GOLANG 1.9 语言规范

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

5751 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传