Go基础语法 | 青训营笔记

101 阅读9分钟

包、变量和函数

package main
import "fmt"
func main() {
    fmt.Println("hello world")
}

每个Go程序都是由包构成。

程序从main包开始运行。

导入包

分组形式导入包(推荐)

import (
    "fmt"
    "math"
)

编写多个导入语句

import "fmt"
import "math"

导出名

名字以大写字母开头是导出名,如Println。未导出的名字在该包外无法访问。

函数

类型在变量名之后。

func add(x int, y int) int 

当连续两个或多个函数的已命名形参类型相同时,可以只写最后一个的类型。

func add(x, y int) int 

效果与上面的例子一样。

多值返回

func swap(x, y string) (string, string) {
    return y, x
}

命名返回值

返回值可以被命名,它们被视作定义在函数顶部的变量。

返回值名称应当具有一定意义,它可以作为文档使用。

没有参数的return语句返回已命名的返回值,也就是直接返回。

直接返回语句应当仅用在下面的短函数中,长函数会影响代码可读性。

变量

var语句用于声明一个变量列表。var语句可以出现在包或者函数级别。

初始化

变量声明可以包含初始值,每个变量对应一个。

如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。

短变量声明

在函数中,简介赋值语句:=可代替var

在函数外必须以关键字(varfunc等等)开始,不能用:=

基本类型

基本类型有

boolstringint  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptrbyte // uint8 的别名rune // int32 的别名
    // 表示一个 Unicode 码点float32 float64complex64 complex128

变量声明可以和导入语句一样“分组”成一个语法块。

零值

没有明确初始值的变量声明被赋予零值。

数值类型为0,布尔类型为false,字符串为""(空字符串)。

类型转换

表达式 T(v) 将值 v 转换为类型 T。Go 在不同类型的项之间赋值时需要必须显式转换。

一些关于数值的转换:

var i int = 42

或者,更加简单的形式:

i := 42

类型推导

声明变量而不指定其类型时(即不带类型的:=var =),变量类型由右值推导得出。

当右值声明了类型时,新变量的类型与其相同:

var i int
j := i //j也是一个int

当右值没有明确类型时,新变量的类型取决于右值常量的精度:

i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

常量

使用const关键字。

常量不能用:=语法声明。

const Pi = 3.14

字符串操作

标准库strings里面有很多常用的字符串工具函数。比如contains判断一个字符串里面是否有包含另一个字符串,count,index查找某个字符串位置,join连接多个字符串。

字符串格式化

可以用标准fmt库里面的相关方法。比如用Printf时可以用%v来打印任何类型的变量,用%+v打印详细结果。

流程控制语句:for、if、else、switch和defer

for

无序小括号

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}
fmt.Println(sum) //45

for是Go中的“while”

去掉分号的for

无限循环

省略循环条件

for {
}

if

无需小括号

if简短语句

for一样,if可以在条件表达式前执行一个简单的语句,该语句声明的变量作用域仅在if之内。

if和else

switch

Go 只运行选定的 case,而非之后所有的 case。Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束。

Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。

无条件的switch

switch true

defer

defer 语句会将函数推迟到外层函数返回之后执行。

推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。

func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
}
hello
world

defer栈

推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。

更多类型:struct、slice和映射

指针

类型*T是指向T类型值的指针。其零值为nil

var p *int

&操作符会生成一个指向其操作数的指针。

i := 42 
p = &i 

*操作符表示指针指向的底层值。

fmt.Println(*p) // 通过指针p读取i
*p = 21 //通过指针p设置i

结构体

一个结构体(struct)就是一组字段(field)。

结构体字段

结构体字段使用.来访问。

v.X = 4

结构体指针

通过(*p).X来访问其字段,也可以隐式间接引用p.X

v := Vertex{1, 2}
p := &v
p.X = 1e9

结构体用法

结构体通过直接列出字段的值来新分配一个结构体。

使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)

特殊的前缀 & 返回一个指向结构体的指针。

type Vertex struct {
    X, Y int
}
var (
    v1 = Vertex{1, 2}  // 创建一个 Vertex 类型的结构体
    v2 = Vertex{X: 1}  // Y:0 被隐式地赋予
    v3 = Vertex{}      // X:0 Y:0
    p  = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)

JSON处理

对于struct类型,可以用encoding/json包里面的json.Marshaler来序列化或者反序列化。

数组

表达式

var a [10]int

切片

切片为数组提供动态大小的、灵活的视角。在实践中,切片比数组更常用。

形式:[]T

切片通过上届和下界来界定:a[low : high],左闭右开

primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)

切片就像数组的引用

切片不储存任何数据,它只是描述了底层数组中的一段。

更改切片的元素会修改其底层数组中对应的元素。

与它共享底层数组的切片都会观测到这些修改。

切片用法

类似于没有长度的数组。

[]bool{true, true, false}

切片下界的默认值为 0,上界则是该切片的长度。

var a [10]int 
a[0:10] //等价于a[:10]a[0:]a[:]

切片的长度与容量

假设有切片s

长度:它所包含的元素个数。通过len(s)获取。

容量:从它的第一个元素开始数,到其底层数组元素末尾的个数。通过cap(s)获取。

nil切片

切片的零值是nil,nil切片的长度和容量为0且没有底层数组。

var s []int

用make创建切片

切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:

a := make([]int, 5)  // len(a)=5

要指定它的容量,需向 make 传入第三个参数:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5

切片的切片

向切片添加元素

append函数。

原型:

func append(s []T, vs ...T) []T

append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。

s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。

    var s []int //len=0 cap=0 []
    // 添加一个空切片
    s = append(s, 0) //len=1 cap=1 [0]
    // 这个切片会按需增长
    s = append(s, 1) //len=2 cap=2 [0 1]
    // 可以一次性添加多个元素
    s = append(s, 2, 3, 4) //len=5 cap=6 [0 1 2 3 4]

range

for 循环的 range 形式可遍历切片或映射。

当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。

var pow = []int{1, 2, 4, 8}
for i, v := range pow {
    fmt.Printf("下标:%d,对应元素:%d\n", i, v) //0,1 1,2 2,4 3,8
}

可以将下标或值赋予 _ 来忽略它。

for i, _ := range pow
for _, value := range pow

若只需要索引,忽略第二个变量即可。

for i := range pow

map映射

映射将键映射到值。映射的零值为 nilnil 映射既没有键,也不能添加键。make 函数会返回给定类型的映射,并将其初始化备用。

若顶级类型只是一个类型名,你可以在文法的元素中省略它。

var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

修改映射

在映射 m 中插入或修改元素:

m[key] = elem

获取元素:

elem = m[key]

删除元素:

delete(m, key)

通过双赋值检测某个键是否存在:

elem, ok = m[key]

keym 中,oktrue ;否则,okfalse。若 key 不在映射中或者不存在,那么 elem 是该映射元素类型的零值。

:若 elemok 还未声明,可以使用短变量声明:

elem, ok := m[key]

函数值

函数也是值。它们可以像其它值一样传递。函数值可以用作函数的参数或返回值。

方法和接口

方法

Go 没有类。不过你可以为结构体类型定义方法。方法就是一类带特殊的 接收者 参数的函数。方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
...
    fmt.Println(v.Abs()) //调用方法

在此例中,Abs 方法拥有一个名为 v,类型为 Vertex 的接收者。

方法只是个带接收者参数的函数。

func Abs(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
...
    fmt.Println(Abs(v)) //调用函数

在为非结构体类型声明方法时,接收者的类型定义和方法声明必须在同一包内;不能为基本类型声明方法。

type MyFloat float64
func (f MyFloat) Abs() float64 { //不能直接func (f float64) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

指针接收者

可以为指针接收者声明方法。同样,接受者指针指向的类型不能是基本类型。指针用的更多。

func (v *Vertex) Scale(f float64) {

以指针为接收者的方法被调用时,接收者既能为值又能为指针:

func (v *Vertex) Scale(f float64) {
...
}
...
var v Vertex
v.Scale(5)  // OK
p := &v
p.Scale(10) // OK

Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)

同理,而以值为接收者的方法被调用时,接收者既能为值又能为指针。

错误

用一个函数来处理错误。错误处理是可以自定义的,