文章目录
- 引子
- 知识图谱
- 包
- 代理设置
- 关键字
- 数据类型
- 变量
- struct 和 interface
- 控制语句
- 字符串
- 单引号、双引号、反引号
- 数组与切片
- 字典
- make和new
- json与yaml
- 基本语法
- 指针
- Channel
- defer
- init函数
- 类
- error, panic, recover
- channel与协程
- 调试
- 热加载
- Gin的热加载
- Iris的热加载
- 常用Golang框架
- 常用链接
- 开源项目
引子
玩腻了Python和Java的同学,可以试试Golang,换个口味。其实Golang已经流行好几年了,我们项目中2016年就在使用Golang了。但我一直没有时间去尝试,最近入手了一下,果然是香。
我最初看中它的是部署方便,代码保密性高于Java。目前看来,大部分后端程序都是可以用Golang开发的。
Golang适用场景:
- 网络编程:构建高性能的网络应用程序,如web服务器、API服务器
- 并发编程:如消息队列、爬虫
- 分布式系统:如etcd、Consul、ZooKeeper,支持RPC和序列化机制
- 云计算:构建以docker和k8s为基础设施的云原生应用
- 区块链:其实是一大应用,如Ethereum的Go语言客户端Geth
- 数据库编程和系统编程:不弱于C/C++,编写底层工具,得益于Go和C的无缝衔接
总而言之,后端网络和分布式编程是Golang的主战场,主打高性能、高并发和云原生。
知识图谱
包
Golang都是按包名调用,和文件名无关。通常包名和所在的目录名称相同。
import (
. "fmt" // 调用时可以去掉包名
_ "go-common/app/infra/notify/conf" // _操作只是为了简单的调用包里的init函数(),不能引用其它函数
xsql "go-common/library/database/sql" // 别名,防止冲突
)
包名为main,且其中包含main()函数的文件能编译成可执行文件,该文件未必一定要放在main目录下。
代理设置
goproxy
https://goproxy.io/zh/
七牛云
https://goproxy.cn
阿里云
https://mirrors.aliyun.com/goproxy/
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
$ go env -u GOPROXY //取消代理
关键字
Golang一共25个关键字:
break、case、chan、const、continue、default、defer、else、fallthrough、for
func、go、goto、if、import、interface、map、package、range、return
select、struct、switch、type、var
此外,rune 和 byte 是Go 语言中仅有的两个类型别名。
它的定义:
type rune = int32
type byte = uint8
rune相当于Java里的char类型。
数据类型
• 布尔类型:bool
• 整型:int8、byte、int16、int、uint、uintptr 等
• 浮点类型:float32、float64
• 复数类型:complex64、complex128
• 字符串:string
• 字符类型:rune,本质上是 uint32
• 错误类型:error
此外,Go 语言也支持以下这些复合类型:
• 指针(pointer)
• 数组(array)
• 切片(slice)
• 字典(map)
• 通道(chan)
• 结构体(struct)
• 接口(interface)
变量
new(T): 根据类型T分配内存;设置内存为0;返回内存的指针
make只用于初始化 slice,map 和 channel
makeSlice := make([]int, 5, 10)
struct 和 interface
空结构体struct{}一般用作占位符
空接口interface{}代表任意类型
控制语句
- if 语句的判断条件都没有了括号包起来,还可以前置写变量初始化语句,类似于 for 循环
- 左花括号{必须与 if 或者 else 处于同一行
字符串
//字符相关的转化一般用 strconv 包
v1 := “100”
v2, err := strconv.Atoi(v1) // 将字符串转化为整型,v2 = 100
v3 := 100
v4 := strconv.Itoa(v3) // 将整型转化为字符串, v4 = “100”
避免[]byte和string的反复来回转换
单引号、双引号、反引号
- 单引号在 Golang 表示一个字符,使用一个特殊类型 rune 表示字符型。rune 为 int32 的别名,它完全等价于 int32,习惯上用它来区别字符值和整数值。rune 表示字符的 Unicode 码值
- 双引号用来创建可解析的字符串字面量,支持转义,但不能用来引用多行。
- 反引号用来创建原生的字符串字面量 ,这些字符串可能由多行组成,不支持任何转义序列。原生的字符串字面量多用于书写多行消息、HTML 以及正则表达式。
数组与切片
//定义数组
var a [8]byte // 长度为 8 的数组,每个元素为一个字节 var b [3][3]int // 二维数组(9 宫格)
var c [3][3][3]float64 // 三维数组(立体的 9 宫格)
var d = [3]int{1, 2, 3} // 声明时初始化
var e = new([3]string) // 通过 new 初始化
var f = make([]string, 3) // 通过 make 初始化
//初始化
a := [5]int{1,2,3,4,5} b := […]int{1, 2, 3}
//切片
b := []int{} //数组切片 slice 就是一个可变长数组
c := a[1:3] // 有点类似于 subString,或者 js.slice
d := make([]int, 5) //make 相当于,new、alloc,用来分配内存
遍历删除数组:
var next *Element
for e := l.Front(); e != nil; e = next {
next = e.Next()
l.Remove(e)
}
字典
var testMap map[string]int testMap = map[string]int{
"one": 1, "two": 2, "three": 3,
}
//还可以这样初始化:
var testMap = make(map[string]int) //map[string]int{} testMap["one"] = 1
testMap["two"] = 2
testMap["three"] = 3
如果map的value较大,通常应该使用指针来存储,以避免性能问题,类似的还有channel,slice等。
make和new
- new()返回的永远是类型的指针,指针指向分配类型的内存地址;
- make()返回值
- new 可以分配任意类型的数据
- slice、chan、map 只能用 make,本身就是指针。其他 make、new 都行
func new(Type) *Type
// 切片
make([]T, length, capacity)
json与yaml
基本语法
- 根据其首字母大小写来决定,如果属性名或方法名首字母大写
指针
指针在 Go 语言中有两个使用场景: 类型指针和数组切片。
Go 语言中的类型指针不能进行偏移和运算。
不同于C/C++,Go中可以返回局部变量的指针,因为Go编译器会对每个局部变量进行逃逸分析,会将这样的变量分配在堆上。
Channel
channel类似于消息队列,用于生产者和消费者间的通信,并适配两者的速度。
done := make(chan Result, 1)
go func() {
done <- Result{}
}()
result := <-done fmt.Println(result)
并行编程中,多个 goroutine 间采用生产者/消费者模型通信时,就用到channel。
defer
defer 流程有点像 Java 里面的 finally,保证了一定能执行,我感觉底层也是 goto 的 实现吧。在后面跟一个函数的调用,就能实现将这个 xxx 函数的调用延迟到当前函 数执行完后再执行。
常用来做资源清理、关闭文件、解锁、记录执行时间。
禁止函数退出的几种方法:
func main() {
defer func() { for {} }()
}
func main() {
defer func() { select {} }()
}
func main() {
defer func() { <-make(chan bool) }()
}
init函数
主要作用:
- 初始化不能采用初始化表达式初始化的变量。
- 程序运行前的注册。
- 实现sync.Once功能。
- 其他
init函数的主要特点:
- init函数先于main函数自动执行,不能被其他函数调用;
- init函数没有输入参数、返回值;
- 每个包可以有多个init函数;
- 包的每个源文件也可以有多个init函数,这点比较特殊;
- 同一个包的init执行顺序,golang没有明确定义,编程时要注意程序不要依赖这个执行顺序。
- 不同包的init函数按照包导入的依赖关系决定执行顺序。
类
没有 class 这个关键字,Go 里面的类是用结构体来定义的:
type Student struct {
id uint
name string
male bool
score float64
}
//没有构造函数,但是可以用函数来创建实例对象,并且可以指定字段初始化,类似于 Java 里面的静态工厂方法
func NewStudent(id uint, name string, male bool, score float64) *Student {
return &Student{id, name, male, score} }
func NewStudent2(id uint, name string, male bool, score float64) Student {
return Student{id, name, male, score}
}
error, panic, recover
channel与协程
channel可以理解为消息队列,通信操作符 <- 的箭头指示数据流向。
- 向 channel 中添加数据(channel<-data);
- 从 channel 中读取数据(data<-channel);
- data<-channel, 从 channel 中接收数据并赋值给 data
- <-channel,从 channel 中接收数据并丢弃
- 关闭 channel(通过 close()函数实现)
channel 分为有缓冲 channel 和无缓冲 channel,无缓冲 channel 在读和写的过程中是都会阻塞。
var ch = make(chan int) // 无缓冲
var ch = make(chan int,10) // 有缓冲,实际上是一个阻塞队列,长度为0, 容量为10
func longTask(signal chan int) {
// 不带参数的 for
// 相当于 while 循环
for {
// 接收 signal 通道传值
v := <- signal
// 如果接收值为 1,停止循环
if v == 1 {
break
}
time.Sleep(1 * Second)
}
}
func main() {
// 声明通道
sig := make(chan int)
// 异步调用 longTask
go longTask(sig)
// 等待 1 秒钟
time.Sleep(1 * time.Second)
// 向通道 sig 传值
sig <- 1
// 然后 longTask 会接收 sig 传值,终止循环
}
调试
- 安装delve:
go install github.com/go-delve/delve/cmd/dlv
- 然后在vscode中配置launch.json:
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${file}"
}
]
}
- 打开要调试的文件,如main.go,然后F5即可启动调试
热加载
Gin的热加载
安装fresh: github.com/pilu/fresh
Iris的热加载
安装rizla:
go install github.com/kataras/rizla@latest
然后命令行执行:
rizla main.go
常用Golang框架
- Gin : web 框架
- Echo : High performance, minimalist Go web framework
- fresh : About Build and (re)start go web apps after saving/creating/deleting source files
常用链接
- Go的官方文档
- Go的中文api文档
- Go中文社区网站
- 地鼠文档
- uber Go编码规范
- Go高性能编程
开源项目
- 开源内容管理系统ponzu
- 开源内容管理:perkeep
- buttercms
- 开源社区系统bbs-go
- 一个基于 Beego 开发的可切换模板的 BBS 社交博客系统