MyGO!!!!! (速通Golang基础)
寒假打了Hgame,发现web基本给的都是go文件,就想着速通一下,至少能看懂(),本篇非零基础(即之前学过其他语言),部分不解释原理,有缺漏或者建议欢迎补充和提出
参考8小时转职Golang工程师(如果你想低成本学习Go语言)_哔哩哔哩_bilibili
原理图源来自上述视频(侵删)
1. Hello World
1 | package main |
1.什么是 package
?
- 在Go语言中,
package
是代码组织的基本单位,类似于其他语言中的“模块”或“模块化代码”。 - 文件是以包的形式分组的。每个源文件都属于一个包,并且每个包可以包含多个文件。
- 包声明的作用是声明当前文件所属的包。
2.package main
的作用
- 程序的入口点:当声明了一个
package main
时,这个Go程序会被编译成一个可执行文件。换句话说,只有在package main
中定义的代码才会成为独立运行的程序。 - 包含
main
函数:package main
是唯一一个可以定义main
函数的包。main
函数是程序的起点,程序从这里开始执行。 - 全局唯一性:在一个Go项目中,只能有一个
package main
,它决定了程序的入口。
2. 常见的变量声明
1.单变量
1 | 1.var a int = 0 //go语言中类型是在变量名后面的,赋值则默认值是0,但建议先赋值,不然vscode里可能给你弹 //提示,以及定义和赋值建议在同一行 |
2.多变量声明
1 | 1.var n1,name,n3 = 100,"name",888 |
3. const与const() 中的iota
1.const
定义一个变量不能被修改(常量)
1 | const a int = 10 |
2.iota
1 | //const 用来定义枚举类型 |
iota只能配合const()使用,不能在其他地方使用,但我个人觉得没啥用
4. 函数多返回值
1.单返回
1 | func re(a string , b int) int{ // 最后int是返回类型 |
2.多返回
匿名返回
1 | func res2(a string , b int) (int ,int){ |
有形参名字
1 | func foo3(a string, b int) (r1, r2 int){ // (r1 int, r2 int) |
5. 指针
与其他语言同理
1 | package main |
6. Defer
1.defer的顺序
defer
的基本作用
- 延迟执行:
defer
会将函数调用保存到一个栈中,直到当前函数执行完毕,并准备返回之前才会依次执行这些被延迟的函数调用。 - 执行顺序:多个
defer
语句会按照 后进先出(LIFO) 的顺序执行。 - 清理操作:常用于资源释放、文件关闭、锁解锁等场景,以确保某些操作在函数结束时被执行。
1 | package main |
这里可以看出defer是在最后执行的,而且先执行的defer排在第二个之后
总结来说defer是个先进后出的调用方式
2.defer 和 return 顺序
1 | package main |
这里说明defer 和 return 在同一个函数中的话,defer是比return后执行的,因为defer是在当前函数的生命周期结束之后才被执行(到函数最后一个大括号)
7. For循环
在Go语言中,for
循环是唯一支持的循环结构,以下是for
循环的常见用法:
1.基本形式
1 | for 初始化; 条件; 后操作 { |
示例:
1 | package main |
运行结果:
1 | 0 |
2.无限循环
1 | for { |
示例:
1 | package main |
3.类似 while
的用法
1 | for 条件 { |
示例:
1 | package main |
4.类似 do-while
的用法
Go语言没有 do-while
语句,但可以通过控制逻辑来实现:
1 | func main() { |
5.控制循环
- **
break
**:跳出当前循环。 - **
continue
**:跳过当前循环的剩余部分,继续下一次循环。
示例:
1 | package main |
6.使用 range
遍历
range
是 Go 中用于循环遍历切片、数组、字符串和字典的关键字
index
: 表示元素的索引位置(从 0 开始)。- 示例中,
index
的值分别为 0、1、2、3、4。
- 示例中,
- **
value
**:表示当前索引对应的值。- 示例中,
value
的值分别为 10、20、30、40、50。
- 示例中,
遍历数组/切片:
1 | package main |
运行结果:
1 | Index: 0, Value: 1 |
遍历字符串:
1 | package main |
遍历字典:
1 | package main |
7.总结
- **
for
**:通用循环结构。 - **
range
**:用于高效遍历集合。 - **
break
/continue
**:控制循环流程。
8. 数组和动态数组
1.定义
1 | var arr [5]int // 定义一个包含5个整数的数组,初始值为零值(0) |
初始化:
1 | arr := [3]int{10, 20, 30} // 初始化一个包含3个整数的数组 |
而也可以只定义前几个数字:
1 | package main |
注意:
- 数组的长度是其类型的一部分。例如,
[3]int
和[5]int
是不同的类型。 - 数组的长度是固定的,无法动态调整大小。
2.动态数组(slice)
切片是动态数组,它是一个引用类型,底层基于数组实现,可以动态调整大小。
定义语法:
1 | var slice []int // 定义一个空的切片,初始值为 `nil` |
1 | package main |
也可以动态调整数组大小:
追加元素:
1
arr = append(slice, 10, 20, 30) // 向切片追加元素
在此之前先介绍容量(cap):
1 | package main |
这样追加之后numbers = append(numbers , 1)
长度就会变成4,但是容量还是5
numbers = [0,0,0,1]
如果超出容量的话,就会再开辟一个cap容量(5),就是你设置的容量
切片操作:
1
sub := arr[1:4] // 创建一个从索引1到3(不包括4)的子切片
如果存在:s1 := s[0:2]
那么如果执行s1[0] = 100
后
s1和s都会被改变(因为底层数组相同)
简单示例:
1 | package main |
动态数组里还有两个重要的概念:
1. map
的含义
map
是 Go 语言中的一种动态数组,也称为“字典”或“哈希表”。- 它允许你存储键值对(Key-Value),并通过键快速查找对应的值。
map
的大小是动态调整的,可以根据需要增长或缩小。
语法定义:
1 | var m map[KeyType]ValueType |
创建 map
的方式:
1 | // 使用 `make` 创建 |
(单纯输出的话是乱序,但如果是遍历输出的话就是按顺序)
**操作 map
**:
1 | // 插入或更新键值对 |
注意:
如果一开始map不作初始化的时候相当于一个空指针,需要make创建空间,直接赋值就不需要
2. make
的含义
make
是一个内置函数,用于创建集合类型(如切片[]
、map
和通道chan
)。- 它为这些动态数据结构分配内存并返回一个引用。
语法:
1 | var variable_name = make(Type, arguments...) |
常见用法:
1 | // 创建一个切片 |
总结:
map
是一种动态数组,用于存储键值对,通过键快速查找值。make
是用于创建map
、切片和通道的函数,为它们分配内存。
9. Struct基本定义和使用
1.type关键字
定义一个类型别名:(类似C语言中的typedef,但这里的type是创建新的类型,并非只提供别名)
- 定义自定义类型:可以基于内置类型或结构体来定义新的类型,使其具有特定的特性。
- 定义结构体:用
type
来定义复杂的自定义数据结构。
1 | package main |
2.结构体定义
1 | package main |
也可以尝试另一种定义方式:
1 | var person Person |
如果定义一个函数要改变它的值:(传地址)
10. 面向对象类的表示和封装
Go 语言不像 Python 或 Java 那样有传统的类(Class)概念,Go 确实在语法层面没有直接支持类,但可以通过 结构体(struct) 和 方法(method) 的组合来实现类似面向对象编程(OOP)的行为,比如封装、继承和多态。
1.定义结构体
1 | type Game struct{ |
再创建实例:
1 | g := Game{ |
2.方法
在 Go 中,方法是绑定到某个类型(通常为结构体)上的函数。方法通过在函数声明前加上 receiver
(接收者)来指定所属的类型
1 | func (g *Game) SayHello(){ |
在这之中,方法接收者为*Game和Game
而至于为什么一个有*而一个没有呢
就是*的指针接收者可以修改原始Game的实例,下面的值接收者就不会改变实例,具体怎么用看实际需求
调用:
1 | g.SayHello() |
完整如下:
3.封装
Go 中通过控制字段和方法的可见性来实现封装。字段和方法的名称首字母大小写决定了它们的可见性:
- 小写字母开头:只能在当前包(package)内部访问。
- 大写字母开头:可以在其他包(package)中访问。
1 | type Person struct { |
11. 继承
定义一个新的结构体里面嵌套父级结构体:
1 | type Ten struct{ |
创建一个新的实例:
1 | s := Ten{Game{"starrail",2023,enterprise{"shanghai","mihoyo"}},5} |
如果觉得这样太麻烦了,可以var s Ten
然后再逐一赋值s.name = "???"
创建方法:
1 | func (s *Ten) Fly(){ |
调用方法:
1 | s.SayHello() |
12. 对象多态实现
1.Go语言中的多态
多态的定义
多态是面向对象编程(OOP)的三大特性之一,另外两个是封装和继承。在编程中,多态指的是同一个接口或方法可以有不同的实现方式。在Go语言中,多态主要通过 接口 和 类型断言 来实现。
通过接口实现多态
Go语言中的接口是一种 隐式实现 的方式,不需要显式的继承或实现声明。只要类型实现了某个接口的所有方法,即可视为实现了该接口。这种机制使得不同的类型可以遵循相同的接口规范,从而实现多态行为。
2.接口
接口是包含方法签名的集合。它定义了对象应该具有的行为,而不需要关心对象的具体实现细节。在 Go 中,接口是一种抽象类型,不能直接实例化。
简单定义:
接口通过 type
关键字和 interface
类型定义
1 | type Animal interface{ //本质是一个指针 |
简单实现:
在 Go 中,类型(如结构体)通过实现接口定义的所有方法来“隐式”满足接口。不需要显式声明“实现某个接口”,只要类型实现了接口的所有方法,它就被认为实现了该接口。
1 | type Dog struct{ |
在这个例子中,Dog实现了Animal的接口,这样的话Dog可以被赋值为Animal类型的变量
1 | func main(){ |
- 多态性:通过接口,可以在不同的实现中使用同一组方法,实现多态行为。
- 解耦:接口将实现与接口分开,使代码更加灵活和可维护。
- 抽象:接口提供了一种抽象的方式,隐藏了实现细节。
怎么体现他的多态性?我们可以再定义一个Cat类型:
1 | type Cat struct{ |
然后我们让animal等于具体的Cat:
1 | animal = &Cat{"Green","QQ"} //这里需要传址是因为interface本质上是一个指针 |
发现给他具体什么对象,就可以调用这个对象的方法,这样就触发了一种多态现象
另外还有一种触发方法:
1 | func show(animal Animal){ |
上面所展示的也是一样的效果
总结就是:
13. Interface空接口
1.空接口(interface{}
)
Go 中有一个特殊的空接口 interface{}
,它可以存储任何类型的值。任何类型都隐式满足空接口的要求,因为它没有任何方法。(通用万能类型)
2.类型断言
那么既然什么类型都能传进来,我们可以使用类型断言来判断接口变量实际类型,并获取值
1 | func MyFunc(arg interface{}){ |
也可以进行多个判断:
1 | if value,ok := arg.(int) ok{ |
基本语法
1 | value, ok := interfaceVar.(Type) |
参数解释:
interfaceVar
:一个接口类型的变量。Type
:期望的类型。value
:如果断言成功,返回Type
类型的值。ok
:布尔值,表示断言是否成功。
14. 变量内置pair结构
变量分为type和value,type和value就是一个pair
1 | package main |
就是无论给a如果赋值,interface就只找到string类型去给str赋值
这段代码告诉我们,不管值怎么变,类型和值都是一个pair,绑定的
另外,如果给定:
b := &Book{}
var r Reader = b
那么b和r的pair是相同的,这表示变量之间相互传递pair不变
另一种:
1 | package main |
可以发现pair都是不变的,实质上也算一种继承,因为Book同时实现了Type和Writer两个接口
15. Reflect 反射机制
在 Go 中,每个变量都有两个部分:类型(Type)和 值(Value)。反射允许你在运行时获取和操作这两个部分。
- 类型:表示变量的数据类型,例如
int
、string
、struct
等。 - 值:表示变量的实际数据内容,例如字符串
Hello
或整数42
。
Go 的反射机制主要依赖于以下两个核心方法:
reflect.TypeOf(v interface{})
:返回变量v
的类型。reflect.ValueOf(v interface{})
:返回变量v
的值。
包含头文件:**”reflect”**
1.用法
获取变量的类型和值:
1 | package main |
加上遍历:
1 | type User struct{ |
其中:(这些后缀都可以在对应头文件源码中找到,也可以通过https://studygolang.com/pkgdoc)
inputType.NumField()
NumField()
是reflect.Type
的一个方法,返回该结构体的字段数量。
inputType.Field(i)
- 获取结构体中索引为
i
的字段。 - 返回值是一个
reflect.StructField
类型,包含了字段的名称、类型、标签(struct tag
)等信息。
inputValue.Field(i).Interface()
Field(i)
获取结构体中索引为i
的字段的值。Interface()
方法将反射值(reflect.Value
)转换回接口类型(interface{}
),以便可以获取该值的实际类型和值。
inputType.NumMethod()
NumMethod()
是reflect.Type
的一个方法,返回该类型的方法数量。
inputType.Method(i)
- 获取结构体中索引为
i
的方法。 - 返回值是一个
reflect.Method
类型,包含了方法的名称、类型等信息。
2.动态设置变量的值:
1 | func main() { |
3.其他
1.动态函数调用
反射可用于调用未知类型的函数。例如,动态调用结构体的方法:
1 | package main |
2.通用的打印函数
反射可用于实现一个通用的打印函数,支持任何类型:
1 | package main |
3.自定义比较函数
反射可用于比较两个未知类型的变量:
1 | package main |
4.动态序列化
反射可用于动态序列化和反序列化数据,例如自定义的 JSON 序列化:
1 | package main |
16. 结构体中Tag标签
1.基本用法
其实相当于注释,但是是可以被获取的,用``表示
1 | package main |
2.在Json中的作用
包含头文件"encoding/json"
,看下面这个例子:
1 | package main |
其中:
json.Marshal
将game
转换为 JSON 字符串:1
{"title":"StarRailway","year":2023,"author":"miHoYo"}
反序列化
1 | my_Game := Game{} |
json.Unmarshal
将 JSON 字符串json_str
解析为my_Game
:my_Game
的字段值与game
一致
JSON 标签的必要性:
- 如果没有
json
标签,JSON 字符串中的字段名将与结构体字段名一致(因 Go 字段名是大写的)
错误处理:
- 如果
json.Marshal
或json.Unmarshal
出现错误(例如字段类型不匹配),程序会退出
大写字段:
- 结构体字段必须是大写(导出字段),否则无法序列化
指针传递:
json.Unmarshal
需要传入指针才能修改值(&my_Game
)
补充:
如果在上述代码中你打印json_str
用的是Println,会出现一长串数字,是因为:
json_str
是通过 json.Marshal
生成的,它的类型是 []byte
,fmt.Println
打印 []byte
类型时会输出字节的数值,而不是可读的字符串
所以要正确打印就需要
fmt.Println(string(json_str)) // 打印完整的 JSON 字符串
fmt.Printf("%s",json_str)
3.其他
排除字段
如果希望某个字段不参与序列化或处理,可以将标签值设置为 -
。例如:
1 | type User struct { |
标签的格式
标签的值可以包含多个键值对,用逗号分隔。例如:
1 | type User struct { |
omitempty
表示如果字段为空(零值),则在序列化时省略该字段。
17. Goroutine
- 定义 :goroutine 是一种轻量级的线程,由 Go 运行时环境管理和调度,而不是直接由操作系统内核线程管理。它可以在一个独立的函数或方法上调运行,与主线程或其他 goroutine 并发执行
- 启动 :通过在函数调用前加上关键字
go
,就可以启动一个新的 goroutine - **调度与执行 **:
- 调度机制 :Go 运行时通过一个称为 Goroutine Scheduler 的调度器来管理 goroutine 的执行。调度器会根据系统资源和程序运行情况,动态地将 goroutine 分配到少量的系统线程上运行,这些系统线程由 Go 运行时自动管理
- 执行模型 :当一个 goroutine 被启动后,它并不是立即执行,而是进入一个就绪队列,等待调度器分配给它运行的机会。调度器会根据一定的策略,如时间片轮转等,从就绪队列中选择 goroutine 进行执行
1.基本调用
1 | package main |
以上可以看出,goroutine和主进程是并发进行的
2.调用匿名函数
1 | package main |
想要结束一个goroutine可以使用runtime.Goexit()
使其在过程中退出,需要包含"runtime"
头文件
3.调用有形参函数
1 | package main |
但是如果你想得到返回值,比如:
1 | flag := go func(a int,b int) bool{ |
同样的,你换成:
1 | flag := go func(a int,b int) int{ |
也是一样的报错滴
那么怎么样能在goroutine中返回值呢?channel
√
18. Channel
Channel 是 Go 语言中的一个核心概念,用于在不同的 goroutine之间进行通信和数据交换
1.创建
使用make
函数创建 channel,语法为ch := make(chan Type)
,其中Type
是 channel 传递的数据类型
1 | make(chan Type) |
有缓冲 channel 允许在没有接收者的情况下发送一定数量的数据,只要缓冲区未满,发送操作就不会阻塞;同样,当缓冲区中有数据时,接收操作也不会阻塞
2.基本使用
1 | channel <- value //发送value到channel |
看个实例:
1 | package main |
3.有缓冲和无缓冲问题
无缓冲:如上面那个实例,如果执行到num := <- c
这一步时但此时 c <- 555
还未发生(就是比主程序慢了一点),就会发生阻塞,使num赋值的那一步等待c的赋值和传入
实例:(此时循环超出给定容量)
1 | package main |
此时就发生了阻塞现象
有缓冲:
实例:(将赋值循环限制在规定容量中)
1 | package main |
4.关闭channel
关闭 channel
- 关闭操作 :使用
close(ch)
可以关闭一个 channel,关闭后不能再向该 channel 发送数据,但仍然可以接收数据 - 接收已关闭 channel 的数据 :当从一个已关闭的 channel 接收数据时,如果 channel 中还有剩余数据,会继续接收这些数据;当数据接收完毕后,再次接收会立即返回该类型的零值
实例:
1 | package main |
这里我们看到关闭了通道后正常输出,如果没关闭呢:
在这里如果没有关闭channel的话,主程序中的for循环的ok一直会返回true,但此时c已经没有数据了,关闭之后,ok判断为false,则结束循环
注意:
- 但确实没有发送任何数据了,或者想显式的结束range循环,再关闭channel
- 关闭后无法向channel发送任何数据
- 关闭之后可以继续从channel中接收数据
- 对于nil channel,无论收发都会被阻塞
对应注意2,如果:
1 | go func(){ |
对应注意4:
1 | var ch chan int //这就是个nil channel,未被初始化 |
5.channel & range
实例:
1 | for data := range c{ |
range的作用是如果c中一旦有数据就传给data,没有的话就阻塞
可以使用range来迭代不断操作channel
6.channel & select
- 多路复用 :
select
可以同时监听多个 channel 上的事件,当任何一个 channel 上的事件发生时,执行相应的代码块。这使得一个 goroutine 可以同时处理多个 channel 上的通信 - 阻塞和非阻塞 :如果没有
default
分支,select
会阻塞,直到其中一个 channel 操作可以执行。如果有default
分支,select
不会阻塞,而是立即执行default
分支 - 随机选择 :如果有多个 channel 操作同时可以执行,
select
会随机选择其中一个执行,其他操作会被忽略。这种随机性可以用于实现公平性或避免死锁
select可以完成监控多个channel的状态:
1 | select{ |
**1.实例: **(超时处理)
1 | ch := make(chan int) |
2.实例:(监听多个)
1 | //原视频的斐波那契数列算法错了,以下应该是求二倍 |
3.实例:(关闭channel)
1 | ch := make(chan int) |
当一个 channel 被关闭后,select
仍然可以选择该 channel 的接收操作,但会立即返回该类型的零值
19. GoModuels
Go Modules 是 Go 语言官方推出的一种包管理机制,旨在解决 Go 语言项目中的依赖版本管理问题。自 Go 1.11 版本开始引入,Go 1.13 版本成为默认的依赖管理方式,并逐渐取代早期的 GOPATH
模式。
1.为什么使用 Go Modules
- 版本控制:可以为依赖库指定特定的版本,避免库的版本更新导致项目不可用。
- 离线工作:Go Modules 在本地缓存依赖项,允许在没有网络连接时继续开发。
- 模块隔离:每个项目都可以独立管理其依赖项,不再依赖全局的
GOPATH
。 - 可重现构建:每次构建都可以使用相同的依赖版本,保证项目的一致性。
2.go mod 命令
1.初始化模块
终端使用 go mod init
命令初始化 Go Modules 文件(go.mod
),它会在项目根目录生成一个 go.mod
文件,记录模块名和 Go 版本等信息
1 | go mod init 你的文件名 |
2.添加依赖
在终端使用(下面同理) go get
命令下载并安装依赖包,它会自动将依赖项的版本记录到 go.mod
中
1 | go get github.com/gin-gonic/gin |
如果需要安装特定版本的依赖包,可以在包名后面加上 @<version>
:
1 | go get github.com/gin-gonic/gin@v1.7.2 |
3.更新依赖
使用 go get -u
命令更新依赖项到最新的次版本或修订版本
1 | go get -u github.com/gin-gonic/gin |
4.清理依赖
使用 go mod tidy
命令清理不再使用的依赖项,并确保 go.mod
和 go.sum
文件是最新的。
1 | go mod tidy |
5.列出依赖项
使用 go list -m all
命令可以列出所有依赖项的模块及其版本。
1 | go list -m all |
6.部分命令
命令 | 介绍 |
---|---|
go mod init |
初始化项目依赖,生成 go.mod 文件 |
go mod download |
根据 go.mod 文件下载依赖 |
go mod tidy |
比对项目文件中引入的依赖与 go.mod 进行比对 |
go mod graph |
输出依赖关系图 |
go mod edit |
编辑 go.mod 文件 |
go mod vendor |
将项目的所有依赖导出至 vendor 目录(可用于无网络条件) |
go mod verify |
检验一个依赖包是否被篡改过 |
go mod why |
解释为什么需要某个依赖 |
3.go mod环境变量
1.常见的 Go Modules 环境变量
环境变量 | 描述 |
---|---|
GO111MODULE |
是否开启Go Modules模式 |
GOPROXY |
项目第三方依赖库的下载地址(建议设置国内的地址) |
GOSUMDB |
用来检验拉取的第三方库是否完整 |
GOMODCACHE |
设置 Go 模块缓存目录的路径 |
2.介绍
1. GO111MODULE
作用:控制 Go Modules 的启用状态
取值:
on
:启用 Go Modules,即使在GOPATH
模式下也会使用 Go Modulesoff
:禁用 Go Modules,使用GOPATH
模式auto
(默认值):在项目根目录有go.mod
文件时启用 Go Modules,否则使用GOPATH
模式
示例:
1
2
3
4
5
6
7
8# 启用 Go Modules
export GO111MODULE=on
# 禁用 Go Modules
export GO111MODULE=off
# 使用默认值
export GO111MODULE=auto
2. GOPROXY
作用:设置 Go 模块代理的地址
取值:一个或多个以逗号分隔的代理地址。支持的协议包括
https
、http
和file
示例:
1
2
3
4export GOPROXY=https://mirrors.aliyun.com/goproxy/ #阿里云
export GOPROXY=https://goproxy.cn,direct #七牛云
#direct用于指示GO回源到模块版本的源地址去抓取(GitHub....)
3. GOSUMDB
作用:设置校验和数据库的地址
取值:一个或多个以逗号分隔的校验和数据库地址。支持的协议包括
https
、http
和file
示例:
1
export GOSUMDB=sum.golang.org
4. GOMODCACHE
作用:设置 Go 模块缓存目录的路径
取值:一个有效的文件系统路径
示例:
1
export GOMODCACHE=/path/to/cache
3.常用命令总结
命令 | 介绍 |
---|---|
go env |
显示当前 Go 环境变量的值 |
go env -w |
设置环境变量的值 |
go env -u |
删除环境变量的值 |