语言简介
go是强类型静态语言,也就是说在编译阶段就确定变量类型,并且转换类型需要显示转换,它有如下几个特点:
- 跟脚本语言相似,容易上手、容易流行
- 底层是c,性能好
- 对goroutine调度进行了封装,即在语言层面支持协程,对于开发者,用起来方便
编译过程
- go以包的形式管理源程序,每个包至少包含一个go文件。编译的时候会把main包生成可执行文件放到bin目录下,其他包生成xx.a包文件放到pkg目录下。
可执行程序初始化过程
- 先执行main包的go文件,对依赖的包递归执行import、常量和变量初始化、init函数流程之后,初始化main自己的常量和变量、再依次执行init函数和main函数,示意图如下(竟然不能直接放图片,????):
https://github.com/JJAWX/imag...
go文件基本构成
package xx
表明这个go文件是属于哪个包,这条语句是必须写的,因为go是以package的形式管理源文件的
import xx
引入当前文件依赖的包,包的路径只可以是相对路径,eg:"xxx/decorator",go查找包的过程是先在$GOROOT/src($GOROOT是go的安装路径,默认在/usr/local/go下)文件夹下找,如果找不到就从$GOPATH/src($GOPATH是go工作空间路径,当前工作代码所在的go文件夹路径)文件下找。
var/const声明初始化
整型
var test int = 123
布尔
var test bool = true
字符串
var test string = "abc"
数组(数组必须指定长度,否则属于切片,不赋值,指定长度默认[0,0])
var test [2]int
test[0] = 6
test[1] = 8
切片(没指定长度的数组或者用make初始化的数组)
// 添加元素方式一
var test = make([]string, 0)
test = append(test, 2)
// 添加元素方式二
var test = make([]string, 2)
test[0] = "123"
test[1] = "456"
结构体
type TestA struct {
c int
}
type Test struct {
a int
b string
c int
TestA
}
var test = Test{6, "aa", 8, TestA{3}} // 或者不初始化:var test Test
channel
test := make(chan int)
go func(){ test <- 1 }()
fmt.Printf("aaa: %+v", <-test)
interface{}(一个接口可以拥有任何实现了它所有方法的类型的属性,空接口代表所有对象都实现了它,也就是说用它可以接收任何类型)
type Test interface{
Say()
}
type TestA struct{}
func (a TestA) Say(){
fmt.Printf("hello")
}
var testa TestA
var test Test
test = testa // 接口类型,
指针
var testPtr *int // 定义了没有分配到任何变量时,为空指针
map(默认是nil,不初始化,不能存任何东西)
// 方式一
var test = make(map[string]string)
test["a"] = "123"
// 方式二
var test = map[string]string{}
test["a"] = "123"
// 方式三
test := make(map[string]string)
test["a"] = "123"
init函数
初始化一些全局变量,供所属包或者所有包调用
main函数
属于main包,是程序的入口,和main包是一一对应的关系。
其他函数
业务子逻辑,处理客户端的GET、POST、PUT、DELETE等请求,get请求使用url库对请求参数进行解析,跟它相关的几个方法是:DefaultQuery、Query、QueryArray,;GET、POST、PUT、DELETE请求的话使用的是gin框架的model binding模型,相关的几个方法是:BindWith、BindJSON、Bind、它们底层都使用了MustBindWith这个方法, 这个方法接收两个参数,一个是要解析的struct(ps:解析的对象必须是struct或者是指向struct的指针),另一个是binding,由跟Bind相关的几个函数指定,指定的方式区别如下:
- BindWith: 可接收两个参数,一个是要解析的对象,一个是binding
- BindJSON: 只接收一个参数,是要解析的对象,使用的binding是bing.JSON,它对要解析的数据是怎么处理的呢,从req.body中拿出要解析的数据,使用reflect反射对要解析的对象类型进行判断,然后从req.body中取要的数据
- Bind: 接收一个参数,会根据请求的method和content-type判断使用什么binding来解决。
逻辑处理完成,需要使用context.JSON(关键:json.Marshal)或context.HTML(ps: 分别接收三个参数:状态码、模版路径、模版数据)将结果返回给客户端。
go异常捕捉处理
和异常处理相关的几个关键字:defer(return的时候执行)、recover(捕捉异常)、panic(err)(抛出异常),需要注意的地方是如果子协程不捕捉处理异常,就会报到主线程,由主线程处理,但是子协程会异常退出。
func example () {
defer func() {
if err := recover(); err != nil {
fmt.Printf("error: %+v", err)
} else {
fmt.Printf("抛出了一个nil")
}
}()
// panic(nil) // 结果:抛出了一个nil
panic("fail") // 结果: error: failed
}
go并发
go关键字 + channel
// 无缓冲通道
func example () {
test := make(chan int)
go func(){
test <- 1
}()
fmt.Printf("channel value: %+v", <-test)
}
// 执行结果
channel value: 1
// 缓冲通道
func example () {
test := make(chan int, 2)
go func(){
for i := 0; i < 2; i++ {
test <- i
}
close(test) // 很重要,需要手动关闭,要不然会造成死锁
}()
for ch := range test {
fmt.Printf("channel value: %+v", ch)
fmt.Println("")
}
}
// 执行结果
channel value: 0
channel value: 1
sync.WaitGroup(例子 + 结果)
func example () {
var wg sync.WaitGroup
for i := 0; i < 2; i++ {
m := i // 考虑到主线程释放控制权之后协程才执行,缓存i
wg.Add(1)
go func(){
defer func(){
fmt.Println("ending...")
wg.Done()
}()
fmt.Println(fmt.Sprintf("当前循环到哪里了: %+v", m))
}()
}
fmt.Println("example...")
wg.Wait()
}
// 执行结果
example...
当前循环到哪里了: 0
ending...
当前循环到哪里了: 1
ending...
有疑问加站长微信联系(非本文作者)