Go: 基本语法

Go基本语法

包管理

  • package关键字表示当前文件属于哪个包,主函数文件想要当作入口文件,必须声明package main,包名设计和路径不同,不像java通过.分隔,而是单纯的一个字段表示包名,但仍然是一个目录对应一个包,不会进行递归扫描

    不强制目录名和包名相同,导入后使用的是package后声明的字段而不是目录名,但一般目录名和包名相同

  • import是导入其它包的关键字,这里引入的是用/分隔的包含包所在网址或本地项目路径的绝对包名

    因此如果导入多个不同路径的同时,它们包含一个相同的包名,会发生冲突,需要重命名

  • import可以通过()对导入进行分组,也可以重命名:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main

    import (
    "fmt"
    strs "strings"
    )

    func main() {
    fmt.Print("Hello world!")
    strs.Split("a,b,c", ",")
    }
  • import起别名时可以用_表示匿名引入,只加载其中的init函数

    1
    import _ "mysql-driver-go"
  • 内部包:Go约定,在命名为internal包内的函数无法被外部包导入

    可以导入内部包的文件,只有该内部包直接父目录的所有子文件

数据类型

值类型

  • 整型:

    • uint8/16/32/64:无符号整k型
    • int8/16/32/64
    • uintint:至少32位的整型
    • byte:等价于uint8
    • uintptr:保证足够大以至于能存储任何指针
  • 浮点型:float32/64

  • 复数:complex64/128,本质为两个float32/64的封装

  • 字符型:

    • byte/uint8:可存储ASCII

    • rune:等价于int32,可存储utf8字符,用rune来在语义上区分整型和字符,也可表示一个Unicode

    • string:字符序列,可视为一个只读[]byte,它同样可以表示utf8(编码为byte序列后)字符串

      string是一个不可变类型

  • 布尔型:bool

  • 数组类型:[n]Type,其中n必须是编译时可确定的

    数组也是一种值类型

  • 结构体:struct {}自定义类类型,是值类型

  • 指针类型:*Type,指针类型的对象可通过&作用于对象来创建,同时可以不使用解引用符访问值的属性和方法

    因此解引用符*更多作用于右值,此时解出来的是副本

    通过使用指针,可以实现类似引用类型的效果

引用类型

  • 切片类型:[]Type,是可变长的序列

  • 映射类型:map[keyType]valueType

  • 方法:func(T...) R自定义方法

  • 通道类型:Go擅于多线程场景,通道是Go提供的线程间通信手段

    • chan Type:双向通道,chan表明它是一个通道,Type是通道传输的数据的类型
    • chan<- Type:只写通道
    • <-chan Type:只读通道
  • 接口:interface {}自定义接口类型

  • 任意类型(别名):any = interface{}

  • 可比较类型(别名):comparable = interface{ comparable }

    comparable应该作为函数参数的类型以限制形参,而不是用来定义一个变量

    所有基本数据类型、以及由可比较类型组成的数组、以及由可比较类型组成的struct都是comparable

  • Go的引用类型只有切片、映射、方法、通道、接口五种,它们像指针那样共享同一份堆中的数据,拷贝的是地址

  • 使用类型断言:obj.(type)获取obj的类型,obj.(Type)返回(value, ok)

字面量

  • truefalse

  • 整型可使用_分割加强可读性

  • iota:是预定义的字面量,只在const块下生效,表示自增的自然数序列,即0-idx

    const组的一个特性是:后续没有显式赋值的常量会延用最近使用的表达式,因此只需要第一项指定为iota,后续可默认生成

    1
    2
    3
    4
    5
    6
    7
    8
    const (
    a = iota // 0
    b // 延用 iota, 为1
    c = iota * 2 // 2 * 2
    d // 延用 iota * 2, 为3 * 2
    e = 1
    f = iota // 5
    )

    实际上,iota并不算很好的命名,目前只有CppAPL以及Go在使用

    一种场景是:

    1
    2
    3
    4
    5
    const (
    a = 1 << iota
    b // 1 << 1
    c // 1 << 2
    )
  • nil:空值,它不是类型,而是用于初始化指针类型、内置引用类型的零值

  • 数组/切片字面量:一维数组如下,高维数组类似

    1
    2
    3
    4
    a := [5]int32{1, 2, 3, 4, 5}
    // 可以通过 idx:value 局部赋值
    // 0-idx
    b := [5]int32{1: 2, 4: 5}
  • 结构体字面量:结构体字面量有若干种写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 一个类型的字面量是紧跟该类型后的大括号
    var a AStruct = AStruct{
    1, // 如果不带字段名, 则按顺序赋值, 且必须全部手动赋值
    2, // 所有属性值需要紧跟 ','
    }
    // 可以指定字段名, 此时顺序无影响
    // 此时会将所有未手动赋值的属性赋为零值
    var b BStruct = BStruct{
    a: 1,
    }
    // 匿名结构体习惯使用 := 赋值
    a := struct {
    a int32
    b int32
    }{
    1,
    2,
    }
    // 允许混合声明, 指定字段必须放在最后
    // 但这是不好的代码习惯
    b := struct {
    a int32
    b int32
    }{
    1,
    a: 2,
    }
  • new()make():用于值类型和引用类型的内存分配

    • new(Type) *Typenew只能用于创建值类型,且使用零值初始化,返回指针

      不常用,更常用的是先创建一个值类型的值然后取地址

    • make(Type, ...int) Typemake专门用于为映射、切片、通道这三个内置引用类型分配内存,并返回它们的引用

      这三种类型对应的make函数格式不同,根据第一个参数解析后续的参数

声明语句

变量声明

  • Go是静态强类型语言,虽然可以使用varconst,但是最终需要编译时确定类型,即指定类型或指定初始值必须含一

  • 根据字面量动态推断类型:

    1
    2
    3
    a := 1
    const b = 1 // const变量只能进行一次初始化
    c := 1 // 等价于 var c = 1

    短变量赋值语句只能在函数作用域内使用

  • 指定数据类型:Go是类型后置的,如果使用var声明变量不附带初始值,则必须指定类型

    1
    2
    3
    var a int

    const b int = 1

    类型后置能带来一系列优点,首先是可以抛弃void而不影响观感,最重要的是方法签名和函数类型易于阅读

  • 变量分组:通过()对变量进行分组能提高可读性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var (
    a int32
    b float32
    )

    const (
    a = 1
    b
    )
  • 多返回值:Go支持多返回值以及函数多返回值

    1
    2
    3
    4
    5
    a, b := 1, 2
    a, b := b, a // swap
    a, b = func() (int32, int32) {
    return 1, 2
    }()

类型声明

  • 类型声明(创建新类型)使用type TypeName OriginTypeName,基于其它类型创建新类型(两者类型的头信息不同)

    所有在之前数据类型小节讲述的类型以及自定义的类型都可以作为OriginTypeName

  • varconst类似,type关键字也可以使用type ()分组声明

    同一个type分组的类型声明,遵循先后顺序,且在前语句声明的类型可以在后语句立刻使用,需要注意循环引用

    Go的引用类型不像Java的引用类型,其更相似的是指针类型,使用指针类型可以实现循环引用

  • 示例-数组:type IntArr5 [5]int32

  • 示例-切片:type IntSlice []int32

  • 示例-结构体:结构体是一系列属性的集合

    1
    2
    3
    4
    type StructName struct {
    a int64
    b int32
    }
  • 示例-接口:接口是一系列方法的集合

    1
    2
    3
    4
    type InterfaceName interface {
    A() int
    B(a int32)
    }
  • 示例-通道:

    1
    2
    3
    4
    5
    type (
    DEIntChan chan int32
    RIntChan <-chan int32
    WIntChan chan<- int32
    )
  • 示例-函数:

    1
    type RetIntFunc func() int32

类型别名

  • 类型别名使用type TypeName = OriginTypeName,使用=后,这个类型是类型别名,两者等价

    别名,意味着OriginTypeName必须已经声明,而不能是匿名的(如匿名结构体、匿名接口),但函数类型是允许的

  • 类型别名可以和类型声明放进同一个type分组里

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type (
    RetIntFunc = func() int32 // ok
    StrcutA = struct { // error
    a int32
    }
    StructB struct {
    a int32
    }
    StructC = StructB
    )
  • 同一个type分组里,类型别名的位置没有任何影响,即使其引用的原类型在同一分组内的后面定义

    1
    2
    3
    4
    5
    type (
    A = B

    B int32
    )

运算与表达式

运算

  • 大多数运算符和其它语言类似
  • 不含前缀++或前缀--,且后缀++和后缀--都不具有返回值,即它们变成了原子操作
  • 不支持三元表达式
  • 条件表达式不需要小括号括住,且条件表达式必须返回布尔值,其它类型不会自动隐式地转换为布尔值

条件控制

  • if表达式与其它语言类似

  • switch表达式则有较大不同:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // switch 后可跟简单的计算语句
    // 特殊的是这些语句需要以 ; 结尾
    switch t := nbr; {
    // case 语句后面跟条件表达式
    case t == 1:
    stmt
    // 和其它语言不同, 默认情况下,
    // 每个条件块执行结束结束即退出(默认break)
    // 如果想继续执行下一分支则应使用 fallthrough
    case t == 2:
    stmt
    fallthrough
    default:
    default_stmt
    }
  • 虽然支持labelgoto语句,但是尽量不用goto,倒是可以结合breakcontinue使用标签

循环控制

  • 没有while表达式,全部使用for

    1
    2
    3
    4
    5
    6
    7
    8
    9
    for init_stmt; expr; post_stmt {
    for_stmt
    }
    for expr {
    while_stmt
    }
    for { // 不带表达式默认为 true
    while_true_stmt
    }
  • for-range

    1
    2
    3
    for idx, val := range iterable {
    stmt
    }
  • 不支持省略iffor语句的大括号

  • 不支持左大括号换行

  • 大部分风格可以由格式化工具如goimportgofumpt决定,不需要过多注意