菜单

语法与类型

相关源文件

本文档全面概述了 Go 的语法和类型系统,涵盖了语言的词法结构、语法元素以及类型系统的基本原理。本材料着重介绍 Go 的语法定义方式以及语言规范中类型系统的结构。有关类型检查系统的实现信息,请参阅 语言规范和文档

Go 语法简介

Go 是一种静态类型语言,其语法在很大程度上源自 C,但进行了显著的简化。该语言的设计旨在易于解析和理解,从而实现更快的编译速度和更高的开发人员生产力。

Go 的语法使用扩展巴科斯范式(EBNF)的一个变体来定义,这为描述语言语法提供了正式的表示法。语法在设计上是紧凑且规则化的,使得该语言易于通过自动化工具进行分析。

来源: doc/go_spec.html27-69

词法元素

源代码表示

Go 源代码是使用 UTF-8 编码的 Unicode 文本。与一些要求规范形式的语言不同,Go 将每个 Unicode 码点视为不同的。语言规范使用“字符”一词来指代源代码文本中的 Unicode 码点。

来源: doc/go_spec.html79-103

字符和词法标记

Go 定义了几类字符

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

字母包括 Unicode 类别 Lu、Ll、Lt、Lm 或 Lo 中的任何 Unicode 字符。此外,下划线字符(_)在 Go 中被视为小写字母。

Go 的标记构成了语言的词汇,分为标识符、关键字、运算符和标点符号以及字面量。空格(空格、制表符、回车符和换行符)被忽略,除非它们用于分隔标记。

来源: doc/go_spec.html104-135 doc/go_spec.html163-177

注释

Go 支持两种形式的注释

  1. // 开头并延续到行尾的行注释
  2. /* 开头并以 */ 结尾的通用注释

注释不能嵌套,也不能在字符串或 rune 字面量内部开始。

来源: doc/go_spec.html139-161

分号

Go 的语法在许多产生式中使用分号作为终止符,但该语言允许在源代码中省略其中大部分分号。Go 编译器会根据特定规则自动插入分号

  1. 如果在行的最后一个标记后,并且该标记是以下之一,则插入分号:

    • 标识符
    • 字面量(整数、浮点数、虚数、rune 或字符串)
    • 特定关键字(breakcontinuefallthroughreturn
    • 特定运算符或标点符号(++--)]}
  2. 在闭合的 )} 之前可以省略分号

这种自动分号插入使得 Go 拥有简洁的、基于花括号的语法,同时保持了正式的语法定义。

来源: doc/go_spec.html179-231

标识符

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

identifier = letter { letter | unicode_digit } .

有效标识符示例

a
_x9
ThisVariableIsExported
αβ

在 Go 中,以大写字母开头的标识符是导出的(在包外可见),而小写标识符是包私有的。

来源: doc/go_spec.html233-253

关键字和运算符

Go 语言有 25 个保留关键字,不能用作标识符

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

该语言还定义了特定字符序列作为运算符和标点符号,包括:

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

来源: doc/go_spec.html255-282

字面量(Literals)

Go 支持几种类型的字面量

  1. 整数字面量:代表整数常量的数字序列,可选择性地带有非十进制基数的前缀(0b/0B 表示二进制,0o/0O 表示八进制,0x/0X 表示十六进制)。下划线可用于提高可读性。

  2. 浮点数字面量:浮点数常量的十进制或十六进制表示。十进制形式由整数部分、小数点、小数部分和指数部分组成。十六进制形式使用 0x 前缀和 p 指数。

  3. 虚数字面量:表示复数常量的虚部,由整数或浮点数字面量后跟 i 组成。

  4. Rune 字面量:表示一个 rune 常量(Unicode 码点),表示为用单引号括起来的一个或多个字符。

  5. 字符串字面量:两种形式

    • 用反引号(`...`)括起来的原始字符串字面量
    • 用双引号("...")括起来的解释型字符串字面量

来源: doc/go_spec.html284-630

Go 中的类型

类型概述

Go 中的类型决定了一组值以及与这些值相关的特定操作和方法。类型可以是命名的,也可以是未命名的(字面量类型)。语言预先声明了某些类型名称,其他类型则通过类型声明或类型参数列表引入。

Go 类型分为:

  • 预声明类型(内置于语言中)
  • 已定义类型(通过类型定义创建)
  • 类型参数(用于泛型编程)
  • 复合类型(数组、结构体、指针、函数、接口、切片、映射和通道)

来源: doc/go_spec.html799-830

图示:Go 类型系统层级结构

来源: doc/go_spec.html799-830 src/go/types/predicates.go16-31 src/cmd/compile/internal/types2/predicates.go16-26

基本类型

Go 有几种预声明的基本类型

  1. 布尔类型bool - 布尔真值集(truefalse

  2. 数值类型:

    • 整数类型int8int16int32(别名:rune)、int64int(平台相关的大小);uint8(别名:byte)、uint16uint32uint64uintuintptr
    • 浮点类型float32float64
    • 复数类型complex64complex128
  3. 字符串类型string - 不可变的字节序列

这些类型是已定义类型,彼此之间互不相同,但存在别名,如 byteuint8 的别名)和 runeint32 的别名)。

来源: doc/go_spec.html832-920

复合类型

Go 提供了几种从其他类型构建类型的方式

  1. 数组类型[n]T - 由 T 类型组成的 n 个元素的编号序列
  2. 切片类型[]T - 描述底层数组段的描述符
  3. 结构体类型:一系列命名字段,每个字段都有一个类型
  4. 指针类型*T - 指向 T 类型值的指针
  5. 函数类型:一组参数类型和结果类型
  6. 接口类型:一组方法签名和/或类型约束
  7. Map 类型map[K]V - 由类型为 K 的键索引的、类型为 V 的元素的无序集合
  8. Channel 类型chan T - 用于类型为 T 的值的通信通道

来源: doc/go_spec.html922-1016

图示:Go 类型检查过程

来源: src/go/types/expr.go17-57 src/cmd/compile/internal/types2/expr.go17-57 src/go/types/decl.go49-87 src/cmd/compile/internal/types2/decl.go49-87 src/go/types/stmt.go17-55 src/cmd/compile/internal/types2/stmt.go17-54 src/go/types/typeset.go17-23 src/cmd/compile/internal/types2/typeset.go17-23

类型系统实现

Go 编译器使用复杂的类型检查机制来实现类型系统。该实现包含几个关键组件,它们协同工作以强制执行类型安全。

类型检查架构

Go 中的类型检查遵循以下主要步骤:

  1. 将源代码解析为抽象语法树(AST)
  2. 处理声明以建立已声明实体的类型
  3. 检查表达式以确保类型正确性
  4. 验证语句以确保类型使用得当

类型检查器以递归方式处理 Go 代码,从顶级声明开始,向下移动到表达式和语句。

来源: src/go/types/expr.go17-57 src/cmd/compile/internal/types2/expr.go17-57 src/go/types/decl.go49-87 src/cmd/compile/internal/types2/decl.go49-87 src/go/types/stmt.go17-55 src/cmd/compile/internal/types2/stmt.go17-54

类型表示

在 Go 编译器中,类型由实现 Type 接口的结构表示。不同的具体类型实现处理 Go 类型类别的每一个:

类型类别实现结构
基本类型*Basic
数组类型*Array
切片类型*Slice
结构体类型*Struct
指针类型*Pointer
函数类型*Signature
接口类型*Interface
Map 类型*Map
Channel 类型*Chan

每个类型结构都包含类型检查所需的信息,例如数组和切片的元素类型、结构体的字段类型以及函数的参数/结果类型。

来源: src/go/types/predicates.go16-31 src/cmd/compile/internal/types2/predicates.go16-26

类型检查表达式

表达式类型检查是递归执行的,在表达式树上自底向上进行。类型检查器使用 operand 结构来评估表达式,该结构跟踪:

  • 表达式模式(常量、变量、值等)
  • 表达式本身
  • 表达式的类型
  • 对于常量,常量值

表达式检查的一般算法是:

  1. 递归检查子表达式
  2. 根据子表达式确定当前表达式的类型
  3. 如有必要,应用类型转换规则
  4. 验证操作对于给定类型是有效的

来源: src/go/types/expr.go58-142 src/cmd/compile/internal/types2/expr.go58-115

赋值兼容性

Go 类型系统定义了值何时可以赋值给变量或作为参数传递的规则:

  1. 相同类型的赋值彼此兼容
  2. 常量可以赋值给兼容类型的变量,前提是能在这些类型中表示
  3. 具有相同底层类型的命名类型通常不能直接赋值,需要显式转换
  4. 如果接口值实现了该接口,则可以将其赋值给接口变量
  5. 非接口值如果实现了该接口,则可以将其赋值给接口变量

这些规则在赋值、函数调用和返回语句的类型检查期间得到强制执行。

来源: src/go/types/assignments.go21-74 src/cmd/compile/internal/types2/assignments.go21-66

类型谓词

类型检查器使用谓词函数来分类类型并确定有效的操作。关键谓词包括:

  • isBasic:测试类型是否为基本类型
  • isIntegerisUnsignedisFloatisComplexisNumeric:检查数值类型类别
  • isString:测试类型是否为字符串类型
  • isBoolean:测试类型是否为布尔类型
  • isValid:测试类型是否为有效类型(非无效类型)

这些谓词在类型检查过程中用于验证操作和转换。

来源: src/go/types/predicates.go16-31 src/cmd/compile/internal/types2/predicates.go16-26

类型检查过程

内置函数

Go 提供了几个内置函数,如 makenewlencap 等,这些函数具有特殊的类型检查规则。类型检查器包含处理这些内置函数的特定代码,验证它们的参数并确定它们的返回类型。

内置函数的类型检查确保:

  1. 提供了正确数量的参数
  2. 参数是适当的类型
  3. 返回类型被正确确定

例如,make 只能与切片、Map 或 Channel 类型一起调用,而 len 可用于数组、切片、字符串、Map 和 Channel。

来源: src/go/types/builtins.go19-84 src/cmd/compile/internal/types2/builtins.go19-81

函数调用

函数调用类型检查包括:

  1. 确定函数的签名
  2. 检查是否提供了正确数量的参数
  3. 验证参数类型是否可以赋值给参数类型
  4. 对于可变参数函数,对可变参数进行特殊处理
  5. 确定调用的结果类型

对于泛型函数,类型检查器还处理类型参数的推断和实例化。

来源: src/go/types/call.go170-339 src/cmd/compile/internal/types2/call.go171-336

接口与类型约束

Go 中的接口类型定义了方法集和/或类型约束。类型检查器会验证:

  1. 类型的 메서드 집합与接口要求
  2. 类型与类型约束的兼容性
  3. 接口值的正确使用

随着 Go 1.18 中引入泛型,接口也充当类型约束,限制了满足类型参数的类型集合。

来源: src/go/types/typeset.go17-23 src/cmd/compile/internal/types2/typeset.go17-23

常量与无类型值

Go 拥有一个复杂的常量系统,其中包含无类型常量,它们会推迟类型承诺,直到需要时为止。类型检查器处理:

  1. 常量表达式的求值
  2. 无类型常量的默认类型规则
  3. 常量在目标类型中的可表示性
  4. 常量溢出检测

无类型常量具有默认类型,这些类型在需要显式类型时分配:无类型 bool → bool,无类型 int → int,无类型 float → float64 等。

来源: doc/go_spec.html632-738

类型检查中的错误处理

类型检查器会为类型错误生成有意义的错误消息,并按错误代码分类。一些常见的类型错误包括:

  • 赋值或操作中类型不匹配
  • 运算符的操作数类型无效
  • 未定义的 메서드 或字段
  • 歧义选择器
  • 无效的类型转换
  • 常量溢出

错误报告系统提供详细的错误消息,帮助开发人员理解和修复与类型相关的问

来源: src/internal/types/errors/codes.go9-12

结论

Go 的语法和类型系统旨在实现清晰、简洁和安全。EBNF 中定义的正式语法为语言语法奠定了坚实的基础,而静态类型系统则在不冗余的情况下确保了类型安全。Go 编译器的类型检查器实现了这些设计原则,在强制执行类型规则的同时,也提供了有用的错误消息来处理违规情况。

类型系统在灵活性和安全性之间取得了平衡,能够实现强大的抽象,同时在编译时就能防止许多常见的编程错误。随着 Go 1.18 中泛型的加入,类型系统在保持其基本原则的同时,也获得了更强的表达能力。