WRY

Where Are You?
You are on the brave land,
To experience, to remember...

0%

编程语言-Go

有用的资料汇总

继承组合

批量消息处理

基本数据类型

字符串

1
2
3
4
type stringStruct struct {
str unsafe.Pointer // 指向底层字节数组的指针
len int // 字节数组的长度
}

有几个特点:

  • 可以通过数组索引的方式访问字节单元,但是不能进行赋值和Java的String有类似之处
  • 基于字符串创建的切片和原字符串都指向相同的底层字符数组,同样不可修改
  • 末尾不像C/C++一样,包含结束字符
  • 字符串转切片(slice)时,会执行拷贝操作,注意内存的占用
  • 通过len()函数可以读取字符串的长度

复合数据类型

指针

Go语言支持* pointerType声明指针,有如下特点

  • 和C/C++一样,GO支持多级指针**,通过&来获取变量的地址
  • 和C/C++不同,Go获取指针中的内容同样使用.,并没有->的操作符
  • 不支持指针运算(为了方便Go进行垃圾回收)
  • 允许在函数中返回局部变量的地址,Go语言的“栈逃逸”机制会将局部变量分配到heap上

数组

数组的类型名是[n] elementType,n代表数组的长度,elementType表示数组元素的类型,创建方式如下

1
2
a := [3]int{1, 2, 3}
b := [...]int{1, 2, 3}

有以下特点

  • 定长,不可以继续追加元素
  • 数组是值类型,数组赋值或者作为函数参数都是值拷贝,和C/C++原理不同
  • 数组的长度是数组类型的组成部分,[10]int[20]int表示不同的类型
  • 可以转切片

切片

切片(slice)是为了弥补数组定长和值拷贝不便的问题的代替方案,结构如下

1
2
3
4
5
ll type slice struct {
array unsafe.Pointer // 指向内存空间的指针
len int
cap int
}

切片的创建有两种方式

1
2
3
4
5
6
7
// 通过数组创建
var array = [...]int{0, 1, 2, 3, 4, 5}
s1 := array[2:5] // 实际内存空间和array一样

// 通过make函数创建
a := make([]int, 10) // 创建len=10, cap=10的切片
a := make([]int, 10, 20) // 创建len=10, cap=20的切片

切片支持的操作

1
2
3
4
5
6
7
8
9
10
11
12
len()  // 返回切片的len
cap() // 返回切片的cap
append() // 对切片追加元素
copy() // 复制一个切片

a := [...]int{0, 1, 2, 3, 4}
b := make([]int, 2, 4)
c := a[0:3]

b = append(b, c...) // 可以实现切片的cap自动扩展
d := make([]int, 2, 2)
copy(d, c) // 复制d和c长度较小的长度,方向:c->d

map

map表示map[K]T,属于引用类型。有如下两个注意事项:

  • go语言内置的map不是安全并发的,需要支持并发版可以使用sync.map

  • 不能通过map值修改T中的某个值。例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    type User struct{
    name string
    age int
    }
    mp := make([int]User)
    andes := User{
    name: "andes"
    age: 18,
    }
    mp[1] = andes
    // mp[1].age = 19 //ERROR
    andes.age = 19
    mp[1] = andes // 只可进行完整的替代

map的创建

1
2
3
4
5
// 通过字面量进行创建
ma := map[string]int{"a": 1, "b": 2}
// 使用内置的make函数进行创建
mp1 := make(map[K]T) //容量使用默认值
mp2 := make(map[K]T, len) // 使用给定的容量

map支持的操作

1
2
3
4
5
6
7
mp := make(map[int]string)
mp[1] = "tom" // 添加或者修改元素
name := mp[1] // 取值
for k,v in range mp { // 遍历,但是不保证每次遍历的顺序(猜测使用了hash的方法)

}
len(mp) // 获取长度

struct

Interface

chan

控制结构

if

1
2
3
4
5
6
7
8
// 支持添加一条初始化语句使用;和后面的条件语句隔开,作用范围是这个if的所在的范围内(包括后面的else if以及else)
if [init ;] condition {

} else if condition {

} else {

}

switch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 若没有expression,case需要是布尔表达式,
// 若有expression,case中的内容是和expression做相等运算
switch [init ;] [expression] {

}
// 举例
switch i := "y"; i {
case "y", "Y" :
// do something
fallthrough // 关键字,强制执行下一个case语句
case "n", "N" :
// do something
}

// switch 也支持default,在没有匹配项的时候使用

for

1
2
3
4
5
6
7
8
9
10
11
12
for init; condition; post {

}
for condition { // while 的功能

}
for { // 死循环

}
for xx := range xxx { // 可以实现对数组、切片、字符串、map和chan的访问

}

label & goto

比较排斥,不想使用

函数

函数定义

go函数的定义如下几部分组成

1
func [(self)] funcName([param-list])[(result-list)] {}

有以下的特点:

  • 函数可以没有输入参数,也可以没有返回参数(默认是0)

  • 多个相邻的相同类型参数可以使用简写a, b int

  • 支持有名的返回值,参数名就相当于函数内最外层的一个初始化为0的局部变量,并且returne的时候,可以不加返回的参数名,例如

    1
    2
    3
    4
    5
    func add(a, b int)(sum int) {
    sum = a + b
    return // return sum 的简写
    // sum := a + b // 相当于新声明一个sum的变量名,覆盖自动生成的sum
    }
  • 不支持默认参数

  • 不支持函数重载

  • 不支持函数内嵌套有名函数,但是可以嵌套无名函数

参数传递

go函数中的参数传递全部都是值拷贝,但我们可以通过传指针的方式,实现函数内修改实参的内容。例如

1
2
3
4
func chpointer(a *int) {
*a = *a + 1
return
}

go支持不定参数的传入,有如下的特点

  • 不定参数的类型必须相同
  • 不定参数必须是函数的最后一个参数
  • 不定参数在函数体内相当于切片
  • 形参是不定参数的和形参是切片的在函数类型上是不一样的
1
2
3
4
5
6
7
8
9
10
11
func sum(arr ...int) (sum int) {
for _, v := range arr {
sum += v
}
return sum
}

func main() {
slice := []int{1, 2, 3, 4}
fmt.Printf("%v\n", sum(slice...)) // 切片作为参数进行传递
}

函数签名

函数类型即为函数签名,只包含函数的输入类型与输出类型,可以使用%T格式化输出

匿名函数

可以被作为字面量、参数,进行传递

defer

使用defer可以注册延迟调用,和Java语言中的finally类似。有如下的特点

  • defer后面跟的必须是函数或方法的调用,而不能是语句
  • defer的参数是在注册时通过值拷贝进行传递的
  • defer语句必须先注册之后才能执行
  • 主动调用os.Exit(int),退出进程时,defer将不再执行,即使已经注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 使用defer确保资源得到释放
func CopyFile(dst, src string)(w int64, err error) {
src, err := os.open(src)
if err != nil {
return
}
defer src.close()
dst, err := os.Create(dst)
if err != nil {
return
}
defer dst.close()

w, err = io.Copy(dst, src)
return
}

闭包

闭包=函数+引用环境。闭包对闭包外的环境引入是直接引用,会将闭包引用的外部变量分配到堆上。使用例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
func fa(a int) func(i int) int {
return func(i int) { // 闭包就是由函数(匿名函数)+环境变量a组成的
println(&a, a)
a = a + i
return a
}
}
func main() {
f := fa(1)
g := fa(2) // 多次调用,是创建副本的形式,即函数中的a的地址空间是不相同的
println(f(1))
println(f(1)) // 重复调用,a的地址空间是相同的
println(g(1))
}
// out
// 0xc000014068 1
// 2
// 0xc000014068 2
// 3
// 0xc000014070 2
// 3
  • 在闭包中引用全局变量都是同一份地址空间上的,这容易造成意向不到的结果,不是一种好的编程方式。

  • 同一个函数返回的多个闭包共享该函数的局部变量。

panic & recover

panic 用于主动抛出错误,recover用于捕获panic抛出的错误,其函数签名如下:

1
2
panic(i interface{})
recover() interface{}

遇到panic的时候,开始逐层向上执行函数的defer语句,然后递归向上层退出,直到被recover捕获。需要注意的是,recover只有在defer后面的函数体内被直接调用才能捕获panic终止异常,否则返回nil,继续向外传播。具体如下

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
// case 1 无效
defer recover()
// case 2 无效
defer fmt.Println(recover())
// case 3 无效
defer func() {
func() {
println("defer inner 1")
recover()
}()
}()
// case 4 有效
defer func() {
println("defer inner 2")
recover()
}()
// case 5 有效
func except() {
println("defer inner 3")
recover()
}
func test() {
defer except()
panic("test panic")
}

panic还具有如下特点:

  • 连续抛出多个panic的时候(defer延时调用),只有最后一个panic会被recover捕捉到。
  • 函数并不能捕获内部新启动的goroutine所抛出的panic

error

Go语言内置错误接口类型error,Go语言的典型的错误处理方式是将error作为函数最后一个返回值,在调用函数时通过检测最后一个error参数的方式,来进行错误处理。

错误和异常

错误和异常之间的概念如下:

  • 错误
    • 未捕获错误:异常
    • 可捕获错误
      • 编译错误
      • 运行时错误
      • 逻辑错误

由于go语言是类型安全的语言,其运行时不会出现这种编译器和运行时都无法捕获的错误信息,也就是不会出现异常。逻辑错误会传到运行期,可以产生panic或者return error两类情况,运行时错误,会产生panic的情况。panic产生的错误会在recover被拦截,或者导致程序最终stop掉。

底层实现

多值返回分析

  • 调用函数时的栈环境准备,包括参数和返回值开辟栈空间,都是由调用者来完成的,因此可以通过开辟多个地址分别存放返回值,实现多值返回
  • 寄存器的保存和恢复也由调用方来负责
  • 函数调用后回收栈空间,恢复BP也由主调函数负责

闭包底层实现

。。。

类型系统

类型简介

命名类型和未命名类型