前言:该文档阅读需要有一定的编程语言基础,这里将默认读者已经学习过一门或多门编程语言
补充:该文档更倾向于个人笔记,如果想学习,可以读 Go 语言圣经:Go 语言圣经中文版
# Go 语言介绍
Go 语言的三个作者:Rob Pike (罗伯。派克),Ken Thompson (肯。汤普森) 和 Robert Griesemer (罗伯特。格利茨默)
Rob Pike:曾是贝尔实验室 (Bell Labs) 的 Unix 团队,和 Plan 9 操作系统计划的成员。他与 Thompson 共事多年,并共创出广泛使用的 UTF-8 字元编码。
Ken Thompson:主要是 B 语言、C 语言的作者、Unix 之父。 1983 年图奖 (Turing Award) 和 1998 年美国国家技术奖 (National Medal ofTechnology) 得主。他与 Dennis Ritchie 是 Unix 的原创者。Thompson 也发明了 后来衍生出 C 语言的 B 程序语言。
Robert Griesemer:在开发 Go 之前是 Google V8、Chubby 和 HotSpot JVM 的主要贡献者。
Go 语言出现的目的在于平衡开发速度与运行速度,相较于 C/C++,Go 语言能够实现更快速的开发,而相较于 Java,Go 语言能实现更快速的运行。当然,这只是通常而言,并不绝对。
# Go 语言环境安装
打开 Go 语言的官网下载
# Windows(win11)
找到 windows 系统对应的包进行下载,例如这里选择种类 (Kind) 为压缩包 (Archive),系统 (OS) 为 Windows,64 位 (x86-64) 进行下载
下载好后将压缩包进行解压
文件资源管理器 ==> 右键此电脑 ==> 属性 ==> 高级系统设置 ==> 环境变量
新建变量 GO_HOME,变量值设置为刚刚解压的 go 文件
编辑 Path 变量,在 Path 变量中新建:% GO_HOME%\bin
按 win+r 键,输入 cmd,打开命令窗口,使用 go version 检查环境是否配置完成,如果出现 go 语言的版本,则表明配置成功
# Linux(Unbuntu)
使用 uname -a (其他 Linux 系统指令自行查找) 指令查看系统指令集,我这里是 x86_64
找到 Linux 系统对应的包进行下载,例如这里选择种类 (Kind) 为压缩包 (Archive),系统 (OS) 为 Linux,指令集 x86_64
打开到下载路径,然后使用指令
1 sudo tar -zxvf go压缩包名(自行替换) -C /usr/local
进行解压,解压目标路径为 /usr/local
使用 vim 修改 /etc/profile 文件:
在文件最后添加内容:
1 2 export GOROOT=/usr/local/go export PATH=$PATH:$GOROOT/bin
保存退出。
调用指令:
使用 go version 命令检查是否配置成功,如果出现 go 语言的版本,则标明配置成功。
# 编译运行
使用 go build FileName.go 来编译 go 文件,编译后会出现相应的可执行文件,进行执行即可,例如编写文件 Hello.go
1 2 3 4 5 6 7 package mainimport "fmt" func main () { fmt.Println("Hello World!" ) }
然后对其进行编译运行:
1 2 3 4 # 编译 go build Hello.go # 运行,Windows系统下会生成Hello.exe,直接双击执行即可 ./Hello
也可以使用 go run 指令直接运行:
但对于代码较多的文件并不推荐使用 go run,最好还是先编译再执行。
# Go 基础语法
# 注意点
注释的方式和 C 语言一致。
运算符、流程控制等不再赘述,与其他语言基本保持一致。
需要注意的是使用 switch 时,不再需要使用 break 来避免穿透现象,Go 语言的 switch 没有穿透现象。
Go 语言中没有 while 和 do while 循环,仅有 for 循环。
Go 语言的变量声明出来就必须调用,不调用会报错!
Go 语言结尾不需要添加分号来标志语句结束。
Go 语言流程控制语句,大括号必须跟在关键词后面,不能另起一行,例如:只有 if condition {正确
Go 语言的 if 语句,for 循环,switch 语句不需要使用小括号,如 switch 直接写 switch key {}
# 变量声明与赋值
声明一个变量 s1,并指定其类型为 string 类型
直接赋值,让编译器自行推导其类型
短变量声明,这种声明方式仅能在函数的内部使用
匿名变量,用于接受一些不需要使用的值,例如接受数组索引
# for range
for 循环的另一种使用方式,和 python 类似,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" ) func main () { arr := [...]int {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 } for _, i := range arr { fmt.Println(i) } }
# 数组与切片
声明一个长度为 5 的 int 数组
声明并初始化一个 int 数组,其中… 表示让编译器自行判断数组长度
1 var arr2 = [...]int {1 ,2 ,3 ,4 ,5 ,6 }
短变量声明法声明一个数组
1 arr3 := [...]int {1 ,2 ,3 ,4 ,5 ,6 }
多维数组不再赘述
需要注意的是,Go 语言中的数组是值类型而非引用类型,换言之,传递时进行的是值传递而非指针传递
声明一个 int 切片,切片类似于 C 语言的数组,属于引用类型,传递时是指针传递而非值传递
使用 make 函数初始化一个切片,其变量类型 int 可变,len 为切片长度,cap 为切片容量,cap 可省略
1 var slice2 []int = make ([]int , len , cap )
短变量声明法,声明一个切片
1 slice3 := make ([]int , len )
从数组中直接切出一个切片,区间为 [startIndex, endIndex),左闭右开。
1 2 3 s := arr[startIndex:endIndex]
# 函数
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package mainimport "fmt" func sayHello () { fmt.Println("Hello World!" ) } func cout (str string ) (ret bool ) { fmt.Println(str) ret = true return } func intSum (num ...int ) int { ret := 0 for _, arg := range num { ret = ret + arg } return ret } func calc (a, b int ) (sum, sub int ){ sum = a + b sub = a - b return } func main () { sayHello() ok := cout("Hello World!" ) fmt.Println(ok) fmt.Println(intSum(1 , 2 )) fmt.Println(intSum(1 , 2 , 3 , 4 , 5 )) _, sub := calc(20 , 10 ) fmt.Println(sub) }
Go 语言的函数中并不存在默认参数。
# 函数指针与回调函数
与 C 语言的函数指针和回调函数类似,写个案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func add (x, y int ) int { return x + y } func calc (x, y int , op func (int , int ) int ) int { return op(x, y) } func main () { v := add v(1 , 2 ) fmt.Println(calc(100 , 200 , add)) }
# defer: 延迟执行
defer 语句会在函数将要结束时才执行,具有延迟调用的特性 (类似于析构)。
其采用栈结构,例如:
1 2 3 4 5 6 7 8 9 package mainimport "fmt" func main () { fmt.Println("Start..." ) defer fmt.Println(1 ) defer fmt.Println(2 ) defer fmt.Println(3 ) fmt.Println("End..." ) }
最后输出结果为:
defer 通常用于处理资源释放问题,例如资源清理,文件关闭,解锁及记录时间等。
# 匿名函数与闭包
匿名函数,懂的都懂。与函数唯一的区别在于不写名字,例如:
1 2 3 4 5 6 7 8 9 package mainimport "fmt" func main () { func () { fmt.Println("Hello World!" ) }() }
闭包 = 函数 + 外层变量调用,举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func a (name string ) func () { return func () { fmt.Println("Hello " , name) } } func main () { r := a("张三" ) r() }
可以看到,在这个示例中,函数 a 中的匿名函数调用到了它外层的函数 a 的变量,这样的使用方式叫做闭包。
再来看看示例 2:
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 package mainimport ( "fmt" "strings" ) func CheckSuffix (suffix string ) func (file string ) string { return func (file string ) string { if !strings.HasSuffix(file, suffix) { return file + suffix } return file } } func main () { r := CheckSuffix(".txt" ) ret := r("张三" ) fmt.Println(ret) }
同样的,对于函数 CheckSuffix 中的匿名函数,其也调用了外层的变量,那么这就是一个闭包的使用。
需要注意,内部的匿名函数是可以修改到外层变量的,如果我要这样使用:
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 32 package mainimport ( "fmt" "strings" ) func CheckSuffix (suffix string ) func (file, suf string ) string { return func (file, suf string ) string { if strings.HasPrefix(suf, "." ) { suffix = suf } if !strings.HasSuffix(file, suffix) { return file + suffix } return file } } func main () { r := CheckSuffix(".txt" ) ret := r("张三" , ".docx" ) ret2 := r("李四" , "" ) fmt.Println(ret, ret2) }
可以发现,输出结果为
张三.docx 李四.docx
也就是说,对于 suffix 的修改是持续生效的,并非只作用于匿名函数内部
如果用 lambda 表达式来说明,那么它应该相当于 [&](我个人推测)
# panic 与 recover
Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种异常,因为 Go 语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,甚至一个小小的错误都抛出一个异常。在 Go 语言中,使用多值返回来返回错误。不要用异常代替错误,更不要用来控制流程。
panic 与 recover 是 Go 的两个内置函数,这两个内置函数用于处理 Go 运行时的错误,panic 用于主动抛出错误,recover 用来捕获 panic 抛出的错误。
其中 recover 通常搭配 defer 使用,例如:
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 package mainimport "fmt" func a () { fmt.Println("Hello in a" ) } func b () { defer func () { err := recover () if err != nil { fmt.Println(err) } }() panic ("panic in b" ) } func c () { fmt.Println("Hello in c" ) } func main () { a() b() c() }
# 指针与 C 语言基本无异,不再赘述
# type
作用差不多相当于 typedef,使用如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" type MyInt int type Int = int func main () { var a Int a = 10 var b MyInt b = 20 fmt.Println(a, b) }
# 结构体
# 定义结构体
使用方法和 C 语言类似,使用 type 和 struct 关键字来定义一个结构体,如下:
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 32 33 34 35 36 37 38 39 40 41 package mainimport "fmt" type people struct { name string age int8 city string } func main () { var p people p.name = "张三" p.age = 18 p.city = "北京" fmt.Printf("p=%v\n" , p) var user struct { name string married bool } user.name = "李四" user.married = false var p2 = new (people) (*p2).name = "王五" p2.age = 18 p2.city = "上海" fmt.Printf("%v\n" , p2) p3 := &people{} fmt.Printf("%v\n" , p3) }
# 结构体初始化
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 package mainimport "fmt" type people struct { name, city string age int8 } func main () { p := people{ name: "张三" , age: 18 , } fmt.Printf("%#v\n" , p) p2 := &people{ "李四" , "北京" , 18 , } fmt.Printf("%#v\n" , p2) }
# 构造函数
Go 语言的结构体没有构造函数,但是可以自己实现。例如,实现 people 的构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" type people struct { name, city string age int8 } func newPeople (name, city string , age int8 ) *people { return &people{ name: name, city: city, age: age, } } func main () { p1 := newPeople("张三哥" , "北京" , int8 (18 )) fmt.Printf("type:%T value:%#v\n" , p1, p1) }
# 匿名结构体与结构体嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" type Person struct { string int8 } func main () { p1 := Person{ "小王子" , 18 , } fmt.Println(p1) fmt.Println(p1.string , p1.int8 ) }
使用匿名结构体,对结构体进行嵌套
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 32 33 34 35 package mainimport "fmt" type Address struct { province, city string } type Person struct { name, gender string age int8 Address } func main () { p1 := Person{ name: "张三" , gender: "男" , age: 18 , Address: Address{ province: "河北" , city: "石家庄" , }, } fmt.Println(p1) fmt.Println(p1.name, p1.province) }
# 方法和接收者
Go 语言中方法 (Method) 是一种作用域特定类型变量的函数
这种特定类型变量叫做接收者 (Receiver)
接收者的概念就类似于其他语言中的 this 或者 self
方法的定义格式如下:
1 2 3 func (接收者变量 接收者类型) 方法名(参数列表)(返回此参数){ 函数体 }
使用示例:
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 32 33 34 35 36 37 38 39 40 41 package mainimport "fmt" type People struct { name string age int8 } func NewPeople (name string , age int8 ) *People { return &People{ name: name, age: age, } } func (p People) Dream() { fmt.Printf("%s的梦想是学好Go语言!\n" , p.name) } func (p *People) SetAge(newAge int8 ) { p.age = newAge } func main () { p1 := NewPeople("张三" , int8 (18 )) p1.Dream() fmt.Println(p1.age) p1.SetAge(int8 (24 )) fmt.Println(p1.age) }
Go 语言中,接收者类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。
但是只能给自己的包中的类型定义方法。
可以通过 type 关键字,基于 int 定义一个新的 MyInt 类型,然后为其定义方法。
# 结构体继承
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 32 33 34 35 36 37 38 package mainimport "fmt" type Animal struct { name string } func (a *Animal) move() { fmt.Printf("%s会动\n" , a.name) } type Dog struct { Feet int8 *Animal } func (d *Dog) bark() { fmt.Printf("%s会汪汪汪\n" , d.name) } func main () { d1 := &Dog{ Feet: 4 , Animal: &Animal{ name: "旺财" , }, } d1.bark() d1.move() }
# 结构体字段的可见性
Go 语言的结构体字段,如果开头字母是大写的,那么就是公开的,可以被外部访问的 (类似于 public)
如果开头字母是小写的,那么就是私有的,只能被定义该结构体的包中使用 (类似于 protected)
# 结构体与 JSON 序列化
JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。
JSON 键值对是用来保存 JS 对象的一种方式:
键 / 值对组合中的键名写在前面,并用双引号包裹
使用冒号分隔
然后紧接着值
多个键值之间用逗号分隔
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package mainimport ( "encoding/json" "fmt" ) type student struct { Id int Name string } func newStudent (id int , name string ) student { return student{ Id: id, Name: name, } } type class struct { Title string Student []student } func main () { c1 := class{ Title: "火箭班" , Student: make ([]student, 0 , 20 ), } for i := 0 ; i < 10 ; i++ { tmpStu := newStudent(i, fmt.Sprintf("stu%02d" , i)) c1.Student = append (c1.Student, tmpStu) } data, err := json.Marshal(c1) if err != nil { fmt.Println("json marshal failed, err:" , err) return } fmt.Printf("%T\n" , data) jsonStr := `{"Title":"火箭班","Student":[{"Id":0,"Name":"stu00"},{"Id":1,"Name":"stu01"},{"Id":2,"Name":"stu02"}]}` var c2 class err = json.Unmarshal([]byte (jsonStr), &c2) if err != nil { fmt.Println("json unmarshal failed, err:" , err) return } fmt.Printf("%#v\n" , c2) }
需要注意的是,如果结构体中的字段首字母变为小写,那么其将对外不可见。
则使用 json 包中的方法时,json 的方法并不能够调用其字段,会导致错误。
# 结构体标签 (Tag)
Tag 用于解决其他语言字段首字母小写与 Go 语言字段首字母大写不兼容的问题
Tag 是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
Tag 在结构体字段的后方定义,由一对反引号 (ESC 下面那个键) 包裹起来,具体格式如下:
1 2 `key1:"value1" key2:"value2"`
注意:为结构体编写 Tag 时,必须严格遵守键值对的规则。结构体标签的解析代码容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正常取值。
例如:不要在 key 和 value 之间添加空格
具体使用如下:
1 2 3 4 5 type class struct { Title string `json:"title"` Student []student }
# 小练习:
实现学员信息管理系统,包含以下功能:
添加学员信息
编辑学员信息
展示所有学员信息
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package mainimport ( "fmt" "os" ) func showMenu () { fmt.Println("欢迎来到学员信息管理系统" ) fmt.Println("1.添加学员" ) fmt.Println("2.编辑学员信息" ) fmt.Println("3.展示所有学员信息" ) fmt.Println("4.退出系统" ) } func getNewStu () *student { var id, name, class string fmt.Println("请按要求输入学员信息" ) fmt.Print("请输入学员的学号:" ) fmt.Scanln(&id) fmt.Print("请输入学员的姓名:" ) fmt.Scanln(&name) fmt.Print("请输入学员的班级:" ) fmt.Scanln(&class) return newStudent(id, name, class) } func main () { sms := newStuManSys() for { showMenu() fmt.Println("请输入你要操作的序号" ) var choice int fmt.Scanf("%d\n" , &choice) switch choice { case 1 : sms.addStu(getNewStu()) case 2 : sms.editStu(getNewStu()) case 3 : sms.showStu() case 4 : os.Exit(0 ) } } }
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package mainimport "fmt" type student struct { id, class, name string } func newStudent (id, name, class string ) *student { return &student{ id: id, name: name, class: class, } } type stuManSys struct { allStudents []*student } func newStuManSys () *stuManSys { return &stuManSys{ allStudents: make ([]*student, 0 , 100 ), } } func (s *stuManSys) addStu(newStu *student) { s.allStudents = append (s.allStudents, newStu) } func (s *stuManSys) editStu(newStu *student) { for i, v := range s.allStudents { if newStu.id == v.id { s.allStudents[i] = newStu return } } fmt.Println("输入学号有误" ) } func (s *stuManSys) showStu() { for _, i := range s.allStudents { fmt.Printf("学号:%s\t姓名:%s\t班级:%s\n" , i.id, i.name, i.class) } }
# Go 语言 - 包 (package)
# 介绍
包 (package) 是多个 Go 源码的集合,是一种高级的代码复用方案,Go 语言提供了很多内置包,如 fmt、os、io 等
# 定义包
可以根据自己的需要创建自己的包。
一个包可以简单的理解为一个存放.go 文件的文件夹。
该文件夹下面的所有 go 文件都要在代码的第一行添加如下代码,声明该文件归属的包。
注意事项:
一个文件夹下面只能有一个包,同样一个包的文件不能在多个文件夹下
包名可以和文件夹名不同,包名不能包含 - 符号
包名为 main 的包为应用程序的入口包,编译不包含 main 包的源代码时不会得到可执行文件
# 可见性
如果想在一个包中引用另外一个包里的标识符 (如变量、常量、类型、函数等) 时,该标识符必须是对外可见的 (public)
在 Go 语言中只需要将标识符的首字母大写,就可以让标识符对外可见了
示例:
文件路径:/GoLearn/package_demo/calc/add.go
1 2 3 4 5 6 package calcfunc Add (x, y int ) int { return x + y }
文件路径:/GoLearn/package_demo/main/main.go
1 2 3 4 5 6 7 8 9 10 package mainimport ( "GoLearn/package_demo/calc" "fmt" ) func main () { fmt.Println(calc.Add(10 , 20 )) }
也可以给导入的包起别名:
1 2 3 4 5 6 7 8 9 10 11 package mainimport ( Another_name "GoLearn/package_demo/calc" "fmt" ) func main () { fmt.Println(Another_name.Add(10 , 20 )) }
# init 函数与匿名包
init 函数是一种特殊的函数,它没有参数也没有返回值
在包被导入的时候会自动调用
示例:
1 2 3 func init () { fmt.Println("Hello!" ) }
当仅需要执行包的 init 函数而不需要其内部的数据时,可以使用匿名包的形式,格式如下:
导入包与 init 函数的调用符合栈结构
即先导入者后 init,而 main 包是最先被导入的,所以它的 init 函数会被最后调用
# Go 语言 - 接口 (interface)
Go 语言中的接口是一种抽象的类型
一个类型可以实现多个接口,一个接口也可以对应多种类型,二者之间是多对多的关系
接口实现示例:
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 32 33 34 package mainimport "fmt" type usbFlashDisk struct {}func (u usbFlashDisk) usbLink() { fmt.Println("U盘已连接" ) } type phone struct {}func (p phone) usbLink() { fmt.Println("手机已连接" ) } type usb interface { usbLink() } func link (arg usb) { arg.usbLink() } func main () { u1 := usbFlashDisk{} link(u1) p1 := phone{} link(p1) }
当一个接口不要求实现任何方法时,该接口是一个空接口
任意结构都满足空接口
# 值接收者与指针接收者关于接口的差异
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 32 33 34 package mainimport "fmt" type mover interface { move() } type person struct { name string age int8 } func (p *person) move() { s := "在跑" fmt.Println(p.name, s) } func main () { var m mover p1 := &person{ name: "张三" , age: 18 , } m = p1 m.move() fmt.Println(m) }
# 接口内部存储
接口内部存储分为两部分:
一部分保存其动态类型,用于记录存储变量的类型。
另一部分保存其动态值,用于记录存储变量的值。
类型断言:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package maintype xxx interface {}func main () { var x xxx x = false ret, ok := x.(string ) if !ok { fmt.Println("不是string类型" ) }else { fmt.Println("是字符串类型" , ret) } }
使用 switch 进行类型断言:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" type xxx interface {}func main () { var x xxx x = false switch v := x.(type ) { case string : fmt.Println("是字符串类型,value:" , v) case bool : fmt.Println("是布尔类型,value:" , v) case int : fmt.Println("是int类型,value:" , v) default : fmt.Println("猜不到了,value:" , v) } }
# Go 语言标准库
# time 包
time 包提供的部分方法:
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 32 33 34 35 now := time.Now() now.Unix() now.UnixNano() time.Unix() func (t Time) Add(d Duration) Timefunc (t Time) Sub(u Time) Durationfunc (t Time) Equal(u Time) bool func (t Time) Before(u Time) bool func (t Time) After(u Time) bool tick := time.Tick(time.Second) ret1 := now.Format("2006-01-02 15:04:05" ) timeStr := "2023/08/12 09:29:00" loc, err := time.LoadLocation("Asia/Shanghai" ) timeObj, err := time.ParseInLocation("2006/01/02 15:04:05" , timeStr, loc) timeObj, err := time.Parse("2006/01/02 15:04:05" , timeStr)
具体使用可以看 Go 语言 - 时间对象
# os 包
os 包提供的部分方法:
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 func Open (path string ) (*File, error )func OpenFile (path string , flag int , perm FileMode) (*File, error )func (f *File) Read(b []byte )(n int , err error )reader := bufio.NewReader(f *File) line, err := reader.ReadString('\n' ) content, err := ioutil.ReadFile(path) func (f *File) Write(b []byte )(n int , err error )func (f *File) WriteString(s string )(n int , err error )func NewWriter (w io.writer) *Writerfunc (w *Writer) WriteString(s string )(n int , err error )func (w *Writer) Flush() error
打开模式 flag 包括:
模式
含义
os.O_WRONLY
只写
os.O_CREATE
创建文件
os.O_RDONLY
只读
os.O_PDWR
读写
os.O_TRUNC
重写
os.O_APPEND
追加
perm:按照 linux 权限规定:r=4,w=2,x=1
具体使用可以看 Go 语言 - 文件读写
# sync 包
1 2 3 4 5 sync.WaitGroup sync.Mutex sync.RWMutex sync.Once sync.Map
# net 包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func Listen ("protocol", "Port") (Listener, error )func (l Listener) Accept() (Conn, error )func (c Conn) Close()func (c Conn) Write(b []byte ) (n int , err error )func Dial ("protocol", "address") (Conn, error )func (c Conn) Read(b []byte ) (n int , err error )func ListenUDP ("protocol", *UDPAddr) (*UDPConn, error )func (c *UDPConn) ReadFromUDP(b []byte ) (n int , addr *UDPAddr, err error )func (c *UDPConn) WriteToUDP(b []byte , addr *UDPAddr) (int , error )func DialUDP (network string , laddr *UDPAddr, raddr *UDPAddr) (*UDPConn, error )func (u *UDPConn) Write(b []byte ) (n int , err error )func (u *UDPConn) Close()func (u *UDPConn) ReadFromUDP(b []byte ) (n int , err error )
# Go 语言 - 时间对象
使用示例:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package mainimport ( "fmt" "time" ) func main () { now := time.Now() fmt.Println(now) year := now.Year() month := now.Month() day := now.Day() hour := now.Hour() minute := now.Minute() second := now.Second() fmt.Println(year, month, day, hour, minute, second) timeStamp1 := now.Unix() timeStamp2 := now.UnixNano() fmt.Println(timeStamp1, timeStamp2) t := time.Unix(timeStamp1, 0 ) fmt.Println(t) sleepTime := 5 time.Sleep(time.Duration(sleepTime) * time.Second) go t2 := now.Add(time.Hour) fmt.Println(t2) fmt.Println(t2.Sub(now)) ticker := time.Tick(time.Second) for i := range ticker { fmt.Println(i) } }
# Go 语言 - 文件读写
# os 读取具体使用
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package mainimport ( "bufio" "fmt" "io" "os" ) func ReadAll (path string ) { file, err := os.Open(path) if err != nil { fmt.Println("open file failed, err:" , err) } defer file.Close() for { var temp = make ([]byte , 128 ) n, err := file.Read(temp) if err == io.EOF { fmt.Println(string (temp[:n])) return } if err != nil { fmt.Println("read from file failed, err:" , err) } fmt.Printf(string (temp[:n])) } } func ReadByBufio (path string ) { file, err := os.Open(path) if err != nil { fmt.Println("open file failed, err:" , err) } defer file.Close() reader := bufio.NewReader(file) for { line, err := reader.ReadString('\n' ) if err == io.EOF { fmt.Println(line) fmt.Println("file read over" ) break } if err != nil { fmt.Println("read file failed, err:" , err) return } fmt.Print(line) } } func main () { ReadAll("./example.txt" ) ReadByBufio("./example.txt" ) }
# os 写入具体使用
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package mainimport ( "bufio" "fmt" "os" ) func write (path string ) { file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0644 ) if err != nil { fmt.Println("open file failed, err:" , err) return } defer file.Close() str := "Hello World!" _, err = file.Write([]byte (str)) if err != nil { fmt.Println("Writer err:" , err) } _, err = file.WriteString(str) if err != nil { fmt.Println("WriteString err:" , err) } } func writeByBufio (path string ) { file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0644 ) if err != nil { fmt.Println("open file failed, err:" , err) return } defer file.Close() writer := bufio.NewWriter(file) _, err = writer.WriteString("Hello World!" ) if err != nil { fmt.Println("WriteString err:" , err) } _ = writer.Flush() } func main () { write("./example.txt" ) writeByBufio("./example.txt" ) }
# Go 语言 - 反射
# Go 语言 - 并发编程
# 基本概念
串行:像串一样,顺序执行。比如先读小学,小学结束读初中,初中结束读高中
并发:同一时间段内执行多个任务。比如上午我要学习和刷视频,可能是学一会儿习,然后刷一会儿视频
并行:同一时刻执行多个任务。比如我在看小说的同时听音乐
进程:程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位
线程:操作系统基于进程开发的轻量级进程,是操作系统调度执行的最小单位
协程:非操作系统提供而是由用户自行创建和控制的用户态‘线程’,比线程更轻量级
并发模型:
业界将如何实现并发编程总结归纳为各式各样的并发模型,常见的有以下几种:
线程 & 锁模型
Actor 模型
CSP 模型
Fork&Join 模型
Go 语言中的并发程序主要是通过基于 CSP (communicating sequential processes) 的 goroutine 和 channel 来实现,当然也支持使用传统的多线程共享内存的并发模式
# goroutine 使用
使用关键字 go 来开启一个 goroutine,例如:
具体使用:
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 package mainimport ( "fmt" "sync" ) var wg sync.WaitGroupfunc hello () { fmt.Println("Hello World!" ) wg.Done() } func main () { wg.Add(1 ) go hello() fmt.Println("Hello Main!" ) wg.Wait() }
# GOMAXPROCS
通过 runtime 包中的 GOMAXPROCS 可以指定占用的 CPU 数量,Go1.5 版本之后默认使用所有逻辑核心。示例:
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 32 33 34 package mainimport ( "fmt" "runtime" "sync" ) var wg sync.WaitGroupfunc a () { for i := 0 ; i < 10 ; i++ { fmt.Println("a:" , i) } wg.Done() } func b () { for i := 0 ; i < 10 ; i++ { fmt.Println("b:" , i) } wg.Done() } func main () { runtime.GOMAXPROCS(4 ) wg.Add(2 ) go a() go b() for i := 0 ; i < 10 ; i++ { fmt.Println("main:" , i) } wg.Wait() }
# channel
channel 是一种类型,一种引用类型。声明方式如下:
1 2 3 4 5 6 7 8 9 var 变量 chan 元素类型var ch1 chan int var ch2 chan bool var ch3 chan []int ch1 = make (chan int , [缓冲大小])
发送:
接受:
关闭:
需要注意的是,在 go 语言中,channel 的关闭并不一定是必须的。通道是可以被垃圾回收机制回收的。
只有在通知接收方 goroutine 所有的数据都发送完毕的时候才需要关闭通道。
关闭后的通道有以下特点:
对一个已经关闭的通道发送值会导致 panic
对一个已经关闭的通道进行接收会一直获取值,直到通道为空
对一个已经关闭的并且没有值的通道执行接收操作会得到对应类型的零值
关闭一个已经关闭的通道会导致 panic
# 缓冲
无缓冲通道又被称为阻塞的通道,创建如下通道:
1 2 3 4 5 func main () { ch := make (chan int ) ch <- 10 fmt.Println("send success" ) }
执行上面这段代码会导致程序死锁。
这是因为这段代码创建了一个无缓冲区的通道,则该通道无法缓存发送过来的值 10,所以它会一直等待有一个 goroutine 来取走这个值,从而阻塞程序。
而对于有缓冲区的通道:
1 2 3 4 5 func main () { ch := make (chan int , 1 ) ch <- 10 fmt.Println("send success" ) }
make 函数里的 1 表示有一个位置的缓冲区,有缓冲区的通道会将发送过来的值暂存值缓冲区,有 goroutine 来取值时,则从缓冲区发送给它。
当缓冲区被存满后,则会变成无缓冲区的阻塞情况。
# goroutine 与 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 32 33 34 35 36 37 38 39 40 41 package mainimport "fmt" func f1 (ch chan <- int ) { for i := 0 ; i < 100 ; i++ { ch <- i } close (ch) } func f2 (ch1 <-chan int , ch2 chan <- int ) { for { temp, ok := <-ch1 if !ok { break } ch2 <- temp * temp } close (ch2) } func main () { ch1 := make (chan int , 100 ) ch2 := make (chan int , 200 ) go f1(ch1) go f2(ch1, ch2) for ret := range ch2 { fmt.Println(ret) } }
# worker pool (giriytube 池)
工作中通常会使用 workerpool 模式,控制 goroutine 的数量,防止 goroutine 泄露和暴涨,简单示例:
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 32 33 34 35 36 37 package mainimport ( "fmt" "time" ) func worker (id int , jobs <-chan int , results chan <- int ) { for job := range jobs { fmt.Printf("worker:%d start job:%d\n" , id, job) results <- job * 2 time.Sleep(time.Millisecond * 500 ) fmt.Printf("worker:%d stop job:%d\n" , id, job) } } func main () { jobs := make (chan int , 100 ) results := make (chan int , 100 ) for j := 0 ; j < 3 ; j++ { go worker(j, jobs, results) } for i := 0 ; i < 5 ; i++ { jobs <- i } close (jobs) for i := 0 ; i < 5 ; i++ { ret := <-results fmt.Println(ret) } }
# select
select 语句的使用类似于 switch,当匹配到其可以执行的操作时,则进行该操作。
需要注意的是,当有多个 case 都满足时,select 并不会顺序执行,而是从中随机抽一个进行执行,使用示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" ) func main () { ch := make (chan int , 10 ) for i := 0 ; i < 10 ; i++ { select { case x := <-ch: fmt.Println(x) case ch <- i: default : fmt.Println("default" ) } } }
# 并发同步和锁
有时候在 Go 代码中可能会存在多个 goroutine 同时操作一个资源 (临界区),这种情况会发生竞态问题 (数据竞态)
# 互斥锁
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个 goroutine 可以访问共享资源
Go 语言中使用 sync 包的 Mutex 类型来实现互斥锁,使用方法如下:
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 mainimport ( "fmt" "sync" ) var ( x int64 wg sync.WaitGroup lock sync.Mutex ) func add () { for i := 0 ; i < 50000 ; i++ { lock.Lock() x += 1 lock.Unlock() } wg.Done() } func main () { wg.Add(2 ) go add() go add() wg.Wait() fmt.Println(x) }
# 读写互斥锁
互斥锁是完全互斥的,但有些时候有些资源仅被少量修改,大量读取时,可以尝试仅添加写锁,而不限制其访问。
使用 sync 包中的 RWMutex 类型实现读写锁,具体如下:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package mainimport ( "fmt" "sync" "time" ) var ( x int64 wg sync.WaitGroup rwLock sync.RWMutex ) func read () { rwLock.RLock() time.Sleep(time.Millisecond) rwLock.RUnlock() wg.Done() } func write () { rwLock.Lock() x += 1 time.Sleep(time.Millisecond * 10 ) rwLock.Unlock() wg.Done() } func main () { start := time.Now() for i := 0 ; i < 1000 ; i++ { wg.Add(1 ) go read() } for i := 0 ; i < 10 ; i++ { wg.Add(1 ) go write() } wg.Wait() fmt.Println(time.Now().Sub(start)) }
# Go 语言 - socket 编程
使用 Go 语言内置的 net 包来进行 tcp 或者 udp 通讯
该包所提供的方法,可以查看 Go 语言标准库 ->net 包
# TCP 样例
# server
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package mainimport ( "bufio" "fmt" "net" ) func process (conn net.Conn) { defer conn.Close() for { reader := bufio.NewReader(conn) var buf [128 ]byte n, err := reader.Read(buf[:]) if err != nil { fmt.Println("read from conn failed, err:" , err) break } recv := string (buf[:n]) fmt.Println("receive message:" , recv) conn.Write([]byte ("ok" )) } } func main () { address := "localhost:20000" listen, err := net.Listen("tcp" , address) if err != nil { fmt.Println("listen failed, err:" , err) return } else { fmt.Println("listen success." ) fmt.Println("Be listening " , address) } for { conn, err := listen.Accept() if err != nil { fmt.Println("accept failed, err:" , err) continue } else { fmt.Println("connect success" ) } go process(conn) } }
# client
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package mainimport ( "bufio" "fmt" "net" "os" "strings" ) func main () { conn, err := net.Dial("tcp" , "localhost:20000" ) if err != nil { fmt.Println("dial failed, err:" , err) return } else { fmt.Println("dial success" ) } input := bufio.NewReader(os.Stdin) for { s, _ := input.ReadString('\n' ) s = strings.TrimSpace(s) if strings.ToUpper(s) == "Q" { return } _, err := conn.Write([]byte (s)) if err != nil { fmt.Println("send failed, err:" , err) return } var buf [1024 ]byte n, err := conn.Read(buf[:]) if err != nil { fmt.Println("read failed, err:" , err) return } fmt.Println("receive server respond:" , string (buf[:n])) } }
# UDP 样例
# server
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 32 33 34 35 36 package mainimport ( "fmt" "net" ) func main () { listen, err := net.ListenUDP("udp" , &net.UDPAddr{ IP: net.IPv4(127 , 0 , 0 , 1 ), Port: 30000 , }) if err != nil { fmt.Println("listen failed, err:" , err) } else { fmt.Println("Be listening 127.0.0.1:30000" ) } defer listen.Close() for { var buf [1024 ]byte n, addr, err := listen.ReadFromUDP(buf[:]) if err != nil { fmt.Println("read from udp failed, err:" , err) return } fmt.Println("receive message:" , string (buf[:n])) _, err = listen.WriteToUDP(buf[:n], addr) if err != nil { fmt.Println("write to" , addr, "failed, err:" , err) return } else { fmt.Println("send success" ) } } }
# client
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 32 33 34 35 36 37 38 package mainimport ( "bufio" "fmt" "net" "os" ) func main () { conn, err := net.DialUDP("udp" , nil , &net.UDPAddr{ IP: net.IPv4(127 , 0 , 0 , 1 ), Port: 30000 , }) if err != nil { fmt.Println("dial failed, err:" , err) return } defer conn.Close() input := bufio.NewReader(os.Stdin) for { s, _ := input.ReadString('\n' ) _, err = conn.Write([]byte (s)) if err != nil { fmt.Println("send to server failed, err:" , err) return } var buf [1024 ]byte n, addr, err := conn.ReadFromUDP(buf[:]) if err != nil { fmt.Println("recv from udp failed, err:" , err) return } fmt.Println("read from" , addr, "message:" , string (buf[:n])) } }