Go 基础知识点自我总结

tips: 总结知识点一定要自己动手,这样知识点才能牢记!

​比如开源社区有雨痕大佬的 Go 语言笔记,但是光看几个用法,而不进行代码的编写,代码能力提升不明显,能看懂,但是做不到,形成一种眼高手低的习惯。更何况,以后的工作本来就是写需求的代码,所以,各种基础知识点,快速过完!

1、保留字与预定义字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//保留字有 25 个
break continue func interface select
case defer go map struct
chan else goto package fallthrough
const switch if range type
default for import return var

//以下为相关的预定义字
true false iota nil

//内建字类型
int int16 int32 int64
uint uint16 uint32 uint64
float32 float64 complex64 complex128
bool byte rune string error

//内建函数
make len cap new append
copy close delete complex real
imag panic recover

2、Go 中的一些常用函数

1
2
3
4
5
6
7
8
9
10
11
12
append          -- 用来追加元素到数组、slice中,返回修改后的数组、slice
close -- 主要用来关闭 channel
delete -- 从map中删除 key 对应的 value
panic -- 停止常规的 goroutine (panicrecover:用来做错误处理)
recover -- 允许程序定义goroutine的panic动作
real -- 返回complex的实部 (complexreal imag用于创建和操作复数)
imag -- 返回complex的虚部
make -- 用来分配内存,返回 Type 本身(只能应用于 slice, map, channel )
new -- 用来分配内存,主要用来分配值类型,如 intstruct 返回指向 Type 的指针
cap -- capacity是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map
copy -- 用于复制和连接 slice,返回复制的数目
len -- 来求长度,比如 string、array、slice、map、channel,返回长度

3、Go 中的 Array 数组

答:Go 语言中的数组与其他语言的数组不一样
1、Go 语言中的数组是值类型,赋值和传参会复制整个数组,而不是指针

​ 2、Go 语言中的数组长度必须是常量,而且是类型的组成成分,[2]int,[3]int 是不同类型

​ 3、Go 语言中的数组能支持 “==” “!=” 操作,因为内存被初初始化过。

​ 4、指针数组 [n]T //表示由 n 个T 指针构成

​ 5、数组指针 *[n]T //表示由一个指针指向这个数组,可以视为一维数组来看

​ 6、array 数组不建议拷贝,如需要拷贝使用 slice 更好,或者数组指针

4、Go 中的 slice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
slice 为结构体,通过内部指针和相关的结构属性引用数组片段
*/
struct Slice {
byte* array //引用类型,但是是结构体,所以采用值拷贝传递
uintgo len //slice 数量,读写不超过该限制
uintgo cap //slice 容量,不能超出数组限制
//slice == nil, len = cap = 0
}
//Go 中还有一个关于 slice 的拼接问题 如下
package main

import "fmt"

func main() {
s1 := []int{1, 2, 3}
s2 := []int{4, 5}
s1 = append(s1, s2)
fmt.Println(s1)
}
//上述代码编译失败 8:13: cannot use s2 (type []int) as type int in append; 两个slice 在 append 的时候,记住需要进行第二个 slice 时候,将slice打散再拼接!
s1 = append(s1, s2...)

5、Go 中的面向对象

​ 面向对象的三大特征:多态、继承、封装
Go 语言只支持封装行为,没有继承与多态,也没有 class 关键字但是能做到 struct 中嵌入 struct类型

6、Go 方法

方法:方法是绑定对象实例的,隐式将实例作为第一形参

  • 1、只能为当前包内命名类型定义方法
  • 2、参数 receiver 随意命名,如果未使用此名称,可省略
  • 3、参数 receiver 类型可以为 T、*T,但是基类T不能是指针与接口
  • 4、不支持方法重载,receiver 为参数签名的组成成分
  • 5、可用实例 value 或 pointer 调用全部方法,编译器自动转换
  • 6、通常使用简单工厂模式返回对象实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Queue struct{
elements []interface{}
}

func NewQueue() {
return &Queue{
make([]interface{}, 10)
}
}

func (*Queue) push(e interface{}) error {
panic("not implemented")
}

//func (Queue) push(e int) error {
// panic("not implemented")
//}

func (this *Queue) length() int {
return len(this.elements)
}

6、Go 接口

​ 接口是方法的集合,由于不支持重载,所以每个方法命名唯一

  • 1、接口命名以 er 结尾,结构体
  • 2、接口不实现方法
  • 3、接口无数据字段
  • 4、接口内可以嵌入其他接口
  • 5、类型可实现多接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Stringer interface {
String() string
}

type Pointer interface {
Stringer
Print()
}

type User struct {
id int
name string
}

func (this *User) String() string {
return fmt.Sprintf("User %d, %s", this.id, this.name)
}

func (this *User) Print() {
return fmt.Println(this.String())
}

7、Goroutine

​ Goroutine 为 go 协程,仅仅需要在函数调用语句前添加 go 关键字即可调用goroutine,并且创建并发执行单元。goroutine 的设计具有轻量级!

1
2
3
4
5
6
go func() {
fmt.Println("Hello World") //此为 goroutine
}()
//main 函数的执行其实也是 goroutine
runtime.Goexit() //立即终止当前 goroutine 的执行
此处学一下使用即可,具体 CSP 的模式与 GMP 的模型下一篇博客继续学习

8、Channel

​ 引用类型 channel 为 CSP 的模式的具体实现,使用 channel 用于多个 goroutine 的通信,确保并发安全,尽量使用 channel 来代替锁进行同步。
​ channel 默认为同步的模式,需要发送和接受配对,否则一直处于被阻塞的状态,直到两边都处于活跃状态然后才被唤醒。
​ 以下为同步模式

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
28
29
30
31
package main

import "fmt"

func main() {
data := make(chan int) // 数据交换队列
exit := make(chan bool) // 退出通知

go func() {
for d := range data { // 从队列迭代接收数据,直到 close 。
fmt.Println(d)
}

fmt.Println("recv over.")

exit <- true // 发出退出通知。
}()

data <- 1 // 发送数据。

data <- 2

data <- 3

close(data) // 关闭队列。

fmt.Println("send over.")

<-exit // 等待退出通知。
}

​异步方式:通过判断缓冲区来决定是否阻塞。
如果缓冲区满,发送阻塞;缓冲区为空,接受阻塞。
通常情况下,异步的 channel 可以减少排队阻塞,具有更高的效率。
​但是应该考虑使用指针规避大对象的拷贝,将大对象分治为小对象,如多个元素打包、减小缓冲区大小等。

以下为异步模式

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
28
29
30
package main

import "fmt"

func main() {
data := make(chan int, 3) // 数据交换队列

exit := make(chan bool) // 退出通知

data <- 1 // 在缓冲区未满前,不会阻塞。
data <- 2
data <- 3

go func() {

for d := range data { // 在缓冲区未空前,不会阻塞。
fmt.Println(d)
}
exit <- true // 发出退出通知。
}()

data <- 4
data <- 5
close(data)

<-exit // 等待退出通知。
}

其中缓冲区是内部属性,非类型构成要素,内置函数 len 返回未被读取的缓冲元素数量, cap 返回缓冲区⼤⼩。
channel 是第一类对象,可传参 (内部实现为指针) 或者作为结构成员。

Go 语言还内建了 close() 函数来关闭一个 channel
但是存在以下几种情况:

1、读写 nil Channel 会永远阻塞,关闭 nil Channel 会导致 panic

2、关闭一个已关闭的 Channel 会导致 panic

3、向已经关闭的 Channel 发送数据会导致 panic

4、向已经关闭的 Channel 读取数据不会导致 panic,但读取的值为 Channel 传递的数据类型的零值,可以通过接受语句第二个返回值来检查 Channel 是否关闭且排空:

1
2
3
4
5
v, ok := <- ch
if !ok {
... // 如果是非缓冲 Channel ,说明已经关闭;
// 如果是带缓冲 Channel ,说明已经关闭,且其内部缓冲区已经排空
}

9、Go 中的 select 用法

​ select 是Go中的一个控制结构,类似于用于通信的 switch 语句,其用于处理异步的 I/O 操作。。其有以下的几个特征

1
2
3
4
5
6
7
8
9
1、每个case都必须是一个通信,所有 channel 表达式都会被求值。

3、所有被发送的表达式都会被求值

4、如果任意某个通信可以进行,它就执行;其他被忽略。

5、如果有多个case都可以运行,select 会随机 (看源码) 公平地选出一个执行。其他不会执行。

6 、如果有default子句,则执行该语句。如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。

select 会监听每个 case 中 channel 的读写操作,但是每次只能运行一个channel 进行读或写

10、初始化函数

初始化函数 init():go语言中init函数用于包(package)的初始化,该函数是go语言的一个重要特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
 1 init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等

2 每个包可以拥有多个init函数

3 包的每个源文件也可以拥有多个init函数

4 同一个包中多个init函数的执行顺序go语言没有明确的定义(说明)

5 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序

6 init函数不能被其他函数调用,而是在main函数执行之前,自动被调用

main 对比 init 函数,main函数只能用于main包中,且只能定义一个。

11、go mod 包管理

go mod 有以下几种命令

1
2
3
4
5
6
初始化模块 go mod init <项目模块名称>
依赖关系处理 ,根据go.mod文件 go mod tidy
将依赖包复制到项目下的 vendor目录。 go mod vendor 如果包被屏蔽(墙),可以使用这个命令,随后使用go build -mod=vendor编译
显示依赖关系 go list -m all
显示详细依赖关系 go list -m -json all
下载依赖 go mod download [path@version] [path@version]是非必写的

12、go debug

  • go run -race 可以查看有无相应的锁竞争一块
  • go tool complie -S [filename] 查看相应的汇编代码
  • Linux 下使用 top/htop 指令查看 CPU、Memory、VIRT 等信息,其中关于内存泄漏的相应的问题,主要查看的是 VIRT 是否出现不断增长的一种情况。

13、go 内存逃逸

内存逃逸的主要概念是:编译时 go 语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis),当发现变量的作用域没有跑出函数范围,就可以在栈上,反之则必须分配在堆

一般内存逃逸都是编译器进行分析的,go 语言的设计者不希望我们过多的研究这个方面的内容,所以一般不用管!


————END 如果本博客存在误解的问题,恳请大家指出!—————-


Go 基础知识点自我总结
https://chaggle.github.io/2022/02/13/go/basic/go-study/
作者
chaggle
发布于
2022年2月13日
许可协议