Golang 知识点梳理
new
和make
的区别
1 | package main |
数组&切片
长度是数组类型的一部分,不同长度的数组是不能比较的。
数组作为参数传递给函数时,是值传递(不是引用传递)。所以在函数内对数组做任何修改,不会影响原数组。
切片
扩容机制
当切片长度小于1024时,双倍扩容。当切片长度大于1024时,每次扩大1/4,直到满足位置。
当切片扩容后,会被分配新的数组存储空间,如果扩容之前某个指针指向了切片中的某个元素,那么扩容后,无论如何修改切片内容,之前的指针永远指向的是扩容之前的旧值。
1 | s1 := []int{0, 1, 2, 3} |
切片指针和指针切片
函数
参数传递是传值还是传引用
在 Go 语言中,所有的传参都是值传递
,都是传的一个副本、一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等)
,这种类型的传递在函数中是无法修改内容数据的。有的拷贝的则是引用类型(指针、map、slice、chan等)
,这种就可以在函数找那个修改内容数据。
传值
传值的意思是,函数传递的总是原来这个东西的一个副本(拷贝),比如传递一个int
类型的参数,传递的其实就是这个参数的一个拷贝;传递一个指针类型的参数,其实传递的是这个指针的一份拷贝,而不是这个指针指向的值。
1 | func main() { |
通过输出可以看到,这是一个指针的拷贝,因为存放的两个指针的内存地址不同,只是这两个指针的值相同(值都是同一个内存地址)。
这个例子中不管是0xc00000e028
还是0xc00000e038
,我们都可以称之为指针的指针,他们指向的是同一个指针0xc000010200
,当我们改变这个指针的值时也就意味着改变了指向这个指针的变量的值。
传引用(引用传值)
Golang 中不支持传引用。
Map
在使用make
函数创建map
的时候,make
函数返回的是一个hmap
类型的指针*hmap
,只是使用map
字面量将其包装为我们省去了指针的操作可以更容易使用map,所以map是引用类型,但不是传引用。
Chan
chan
也是一个引用类型,make
返回的是一个 *hchan
Slice
slice
也是引用类型
匿名函数和闭包
匿名函数
- 匿名函数即没有函数名的函数
- 因为没有函数名,所以不能像普通函数那样调用,所以匿名函数需要保存到某个变量中或者昨晚立即执行的函数。
- 匿名函数多用于实现回调函数或闭包
1
2
3
4
5
6
7
8func main() {
func(x, y int){
fmt.Println(x + y)
}(1, 3)
}闭包
- 闭包是一个函数和其相关的引用环境组合而成的实体,即
闭包=函数+引用环境
- 闭包捕获的变量和常量是引用传递,不是值传递
- 闭包是一个函数和其相关的引用环境组合而成的实体,即
defer
Go 语言中defer
语句会将其后面跟随的语句进行延迟处理,在defer
归属的函数即将返回时。将延迟的语句按defer
定义的顺序进行逆序
执行。
由于defer
语句延迟调用的特性,所以defer
语句能非常方便的处理资源释放的问题。比如:资源清理,文件关闭,解锁,记录时间等。
defer 的执行时机
在 Go 语言的函数中 return
语句在底层并不是原子操作,它分为给返回值赋值
和RET
指令两步。
defer
语句执行的时机就在返回赋值操作之后,RET
指令执行之前。defer
注册要延迟执行的函数时该函数所有的参数都需要确定其值
每次 defer 语句执行的时候,会把函数压栈
,函数参数会被拷贝下来;当外层函数(非代码块,如一个 for 循环)退出时,defer 函数按照定义的逆序执行
;如果 defer 执行的函数为 nil, 那么会在最终调用函数的产生 panic.
defer 语句并不会马上执行,而是会进入一个栈,函数 return 前,会按先进后出的顺序执行。也说是说最先被定义的 defer 语句最后执行。先进后出的原因是后面定义的函数可能会依赖前面的资源,自然要先执行;否则,如果前面先执行,那后面函数的依赖就没有了。
在 defer 函数定义时,对外部变量的引用是有两种方式的,分别是作为函数参数和作为闭包引用。作为函数参数,则在 defer 定义时就把值传递给 defer,并被 cache 起来;作为闭包引用的话,则会在 defer 函数真正调用时根据整个上下文确定当前的值。
defer 后面的语句在执行的时候,函数调用的参数会被保存起来,也就是复制了一份。真正执行的时候,实际上用到的是这个复制的变量,因此如果此变量是一个“值”,那么就和定义的时候是一致的。如果此变量是一个“引用”,那么就可能和定义的时候不一致。
结构体
结构体指针
当结构体开销比较大的时候尽量使用结构体指针,减少程序的内存开销
1
2
3
4
5
6
7
8
9
10
11
12
13type person struct {
name string
age int
}
user := &person{
name: "xiaoxia",
age: 18,
}
user.age = 20
fmt.Println(user)
(*user).age = 21
fmt.Println(user)结构体构造函数
构造函数一般使用
new
开头(约定俗成)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16type student struct {
name string
class string
sno int
}
func newStudent(name string, class string, sno int) *student {
return &student{
name: name,
class: class,
sno: sno,
}
}
student1 := newStudent("xiaoxia", "毕业班", 20200912029)
fmt.Println(student1)
方法和接受者
1 |
|
方法
是作用于特定类型的函数
只能给当前包里的类型加方法
接受者
表示的是调用该方法的具体类型,多用类型首字母小写表示
1 |
|
值接收者&指针接收者
1 |
|
指针接收者使用场景
- 当需要修改接收者中的值
- 当接收者作为拷贝值代价比较大的对象(占内存)
- 保证一致性,如果某个地方方法使用了指针接收者,那么其他地方也需要使用指针接收者
匿名字段
接口
在 Go 语言中,接口(interface)
是一种抽象的类型,当看到一个接口类型时,唯一能知道的是通过他能做什么。
interface
是一组method
的集合,他做的事就像是定义了一个协议。
接口定义
1 | type 接口类型名 interface{ |
接口名
- 一般会在单词后面添加
er
,接口名最好突出该接口的类型含义
- 一般会在单词后面添加
方法名
- 当方法名首字母是大写,且该接口类型名首字母也是大写时,这个方法可以被接口所在包之外的代码调用
1 |
|
接口是一个
需要实现的方法的列表
,只要实现了接口中的所有方法,就实现了这个接口。
空接口
空接口
是指没有定义任何方法的接口,因此,任何类型都实现了空接口
。
空接口
可以存储任意类型的变量。
空接口作为函数的参数,可以接收任意类型的函数参数
1
2
3func show(a interface{}) {
fmt.Printf("type: %T ", a)
}空接口作为 map 的值,可以保存任意值的字典
1
2
3
4
5student := make(map[string]interface{})
student["name"] = "xia"
student["age"] = 19
student["is_rich"] = false
文件读写
读
写
时间
协程
ing
goroutine
调度模型
GMP
GC
锁
map 并发安全
context
进程
进程的本质
进程间通讯方式有多少种
协程暴增有哪些原因
实现斐波那契数列
使用动态规划和递归两种方式