有用的资料汇总
基本数据类型
字符串
1 | type stringStruct struct { |
有几个特点:
- 可以通过数组索引的方式访问字节单元,但是不能进行赋值和Java的String有类似之处
- 基于字符串创建的切片和原字符串都指向相同的底层字符数组,同样不可修改
- 末尾不像C/C++一样,包含结束字符
- 字符串转切片(slice)时,会执行拷贝操作,注意内存的占用
- 通过
len()
函数可以读取字符串的长度
复合数据类型
指针
Go语言支持* pointerType
声明指针,有如下特点
- 和C/C++一样,GO支持多级指针
**
,通过&
来获取变量的地址 - 和C/C++不同,Go获取指针中的内容同样使用
.
,并没有->
的操作符 - 不支持指针运算(为了方便Go进行垃圾回收)
- 允许在函数中返回局部变量的地址,Go语言的“栈逃逸”机制会将局部变量分配到heap上
数组
数组的类型名是[n] elementType
,n代表数组的长度,elementType表示数组元素的类型,创建方式如下
1 | a := [3]int{1, 2, 3} |
有以下特点
- 定长,不可以继续追加元素
- 数组是值类型,数组赋值或者作为函数参数都是值拷贝,和C/C++原理不同
- 数组的长度是数组类型的组成部分,
[10]int
和[20]int
表示不同的类型 - 可以转切片
切片
切片(slice)是为了弥补数组定长和值拷贝不便的问题的代替方案,结构如下
1 | ll type slice struct { |
切片的创建有两种方式 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 | len() // 返回切片的len |
map
map表示map[K]T
,属于引用类型。有如下两个注意事项:
go语言内置的map不是安全并发的,需要支持并发版可以使用
sync.map
不能通过map值修改T中的某个值。例如
1
2
3
4
5
6
7
8
9
10
11
12
13type 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 | // 通过字面量进行创建 |
map支持的操作
1 | mp := make(map[int]string) |
struct
Interface
chan
控制结构
if
1 | // 支持添加一条初始化语句使用;和后面的条件语句隔开,作用范围是这个if的所在的范围内(包括后面的else if以及else) |
switch
1 | // 若没有expression,case需要是布尔表达式, |
for
1 | for init; condition; post { |
label & goto
比较排斥,不想使用
函数
函数定义
go函数的定义如下几部分组成
1 | func [(self)] funcName([param-list])[(result-list)] {} |
有以下的特点:
函数可以没有输入参数,也可以没有返回参数(默认是0)
多个相邻的相同类型参数可以使用简写
a, b int
支持有名的返回值,参数名就相当于函数内最外层的一个初始化为0的局部变量,并且returne的时候,可以不加返回的参数名,例如
1
2
3
4
5func add(a, b int)(sum int) {
sum = a + b
return // return sum 的简写
// sum := a + b // 相当于新声明一个sum的变量名,覆盖自动生成的sum
}不支持默认参数
不支持函数重载
不支持函数内嵌套有名函数,但是可以嵌套无名函数
参数传递
go函数中的参数传递全部都是值拷贝,但我们可以通过传指针的方式,实现函数内修改实参的内容。例如
1 | func chpointer(a *int) { |
go支持不定参数的传入,有如下的特点
- 不定参数的类型必须相同
- 不定参数必须是函数的最后一个参数
- 不定参数在函数体内相当于切片
- 形参是不定参数的和形参是切片的在函数类型上是不一样的
1 | func sum(arr ...int) (sum int) { |
函数签名
函数类型即为函数签名,只包含函数的输入类型与输出类型,可以使用%T
格式化输出
匿名函数
可以被作为字面量、参数,进行传递
defer
使用defer可以注册延迟调用,和Java语言中的finally类似。有如下的特点
- defer后面跟的必须是函数或方法的调用,而不能是语句
- defer的参数是在注册时通过值拷贝进行传递的
- defer语句必须先注册之后才能执行
- 主动调用os.Exit(int),退出进程时,defer将不再执行,即使已经注册
1 | // 使用defer确保资源得到释放 |
闭包
闭包=函数+引用环境。闭包对闭包外的环境引入是直接引用,会将闭包引用的外部变量分配到堆上。使用例子
1 | package main |
在闭包中引用全局变量都是同一份地址空间上的,这容易造成意向不到的结果,不是一种好的编程方式。
同一个函数返回的多个闭包共享该函数的局部变量。
panic & recover
panic 用于主动抛出错误,recover用于捕获panic抛出的错误,其函数签名如下:
1 | panic(i interface{}) |
遇到panic的时候,开始逐层向上执行函数的defer语句,然后递归向上层退出,直到被recover捕获。需要注意的是,recover只有在defer后面的函数体内被直接调用才能捕获panic终止异常,否则返回nil,继续向外传播。具体如下
1 | // case 1 无效 |
panic还具有如下特点:
- 连续抛出多个panic的时候(defer延时调用),只有最后一个panic会被recover捕捉到。
- 函数并不能捕获内部新启动的goroutine所抛出的panic
error
Go语言内置错误接口类型error,Go语言的典型的错误处理方式是将error作为函数最后一个返回值,在调用函数时通过检测最后一个error参数的方式,来进行错误处理。
错误和异常
错误和异常之间的概念如下:
- 错误
- 未捕获错误:异常
- 可捕获错误
- 编译错误
- 运行时错误
- 逻辑错误
由于go语言是类型安全的语言,其运行时不会出现这种编译器和运行时都无法捕获的错误信息,也就是不会出现异常。逻辑错误会传到运行期,可以产生panic或者return error两类情况,运行时错误,会产生panic的情况。panic产生的错误会在recover被拦截,或者导致程序最终stop掉。
底层实现
多值返回分析
- 调用函数时的栈环境准备,包括参数和返回值开辟栈空间,都是由调用者来完成的,因此可以通过开辟多个地址分别存放返回值,实现多值返回
- 寄存器的保存和恢复也由调用方来负责
- 函数调用后回收栈空间,恢复BP也由主调函数负责
闭包底层实现
。。。