< 个人复习使用 >
go语言基础
1.go语言的注意点
- 变量名大写,意味着变量是包外可见的,可以跨包使用。
- go语言的四个主要声明:变量var、常量const、类型type、函数func。
2.变量
-
声明
var name type = expression 类型和表达式(初始化)可以省略,但不能全部都省略,如果变量未初始化,默认以零值初始化。
name := expreesion 短变量声明,可以用来声明和初始化局部变量。 -
基本变量:uintxx(xx表示数据的大小16、32…)、浮点数floatxx、复数、布尔值、字符串(字符串是不可变序列,对字符串操作重要的包bytes、strings、strconv、unicode)、常量(go特有无类型常量)
-
复合数据类型:数组、slice、map、结构体
-
函数:捕获迭代变量问题、变长参数声明使用…、延迟函数defer在return语句之后执行,并且可以更新函数的结果变量。
-
宕机恢复:如果recover函数在延迟函数中调用,且包含这个延迟函数的函数发生了宕机,recover会终止宕机并返回宕机值,发生宕机函数正常返回;如果没有发生宕机,recover会返回nil。
-
方法
- 声明: func (t type)fname(xx)(xx){}
- 方法可以绑定到任何类型上,指针和接口类型除外。
- t type 称为接受者,可以为指针类型,如果为指针类型可以修改接受者的值;在使用方法时,也必须用指针变量来调用。
- 封装:封装一个对象,必须使用结构体,封装的原理是命名首字母小写,外包不可见;go语言的封装单元是包 -
接口
一个接口类型定义了一套方法,如果有个具体类型要实现该接口,那么必须实现接口类型中定义的所有方法。
一个接口类型的值可以分为两部分:具体的动态类型和这个动态类型的值。
空的接口值(类型和值都为nil)与仅仅动态类型的值为nil的接口值是不一样的。
interface{} 空接口,可以接受一切类型的值
- 类型断言 x.(T) x是一个接口类型的表达式,T是一个类型 -
goroutine和通道(重点)
- 通道chan:
发送语句 ch <- x
接受语句 x= <-ch
通过make语句可以创建无缓冲通道(参数2为0或者没有参数2)和有缓冲通道
通过 v, ok := <- ch 可判断通道是否关闭,或者range关键字也可以做到
ch <- int 只能发送,不允许接受;<-ch int 只能接受,不能发送
select 多路复用
sync.WaitGroup 用于回收goroutine,具体使用方法baidu
- 无缓冲通道
往无缓冲通道中发送数据将会阻塞,直到另一个routine接受收据;相反如果先接收数据,也会阻塞直到有数据传过来。
- 有缓冲通道
缓冲通道满了发送方阻塞,缓冲通道空了接收方阻塞。 -
并发控制(重点)
- sync.Mutex 互斥锁
-
go包工具(go mod 及 go path)
go help -
测试文件(需要实践)
go test 工具扫描*_test.go文件,并生成一个临时的main包来调用他们,最终运行生成结果。
在*_test.go文件中,三种函数需要特殊对待:1.以Test前缀命名的,功能测试函数;2.以Benchmark开头的,操作性能测试函数;3.以Example开头的,示例函数。 -
反射
在fmt.Printf中使用%T可以打印接口的类型-
reflect.Type
reflect.TypeOf函数接受任意interface{}参数,并把接口中的动态类型以reflect.Type形式返回
在fmt.Printf中使用%T可以打印值的类型 -
reflect.Value
reflect.ValueOf函数接受任意interface{}参数,并把接口的动态值以reflect.Value形式返回
使用Value的kind的方法可以来区分不同的类型,类型的分类只有如下少数几种:基础bool、string及各种数字类型;聚合类型array、struct;应用类型chan、map、slice、ptr、func;接口类型interface;空值invalid。
-
< 工作中学习goland的收获 >
goland中获取网络包导入 : go get
单个线程在无缓冲通道上发送信息会被阻塞,而在有缓冲通道上(通道不满)不会阻塞。
关于go语言的反射机制:[反射值对象valueOf()和反射类型typeOf()]
a = reflect.typeOf(value) 获取value的反射类型
a.Name() 获取反射类型的基本类型(已存在的基本类型和type声明的类型)
a.Kind() 获取反射类型的分类类型(存在的分类类型可以查询)
a.Elem() 获取指针所指元素的反射类型
a = reflect.ValueOf(value) 获取a的反射值对象
a.interface().(int)使用断言来获取值
接口拥有动态类型(赋予接口值的类型–相当于子类)和静态类型(编译时的类型 --即接口类型)
typeOf主要是获取接口的动态类型
valueOf主要是获取原始值,如果值是可寻址的将拥有改变原始值的能力
接口值可以分为动态类型和动态值
不确定:使用类型断言来判断切片和map是否能够承受
类型断言的前提是必须已知类型
a.(T) 当T类型满足a的动态类型是成功断言
我的理解:对于基本类型,完全可以使用类型断言来获取接口的原始值,但是对于未知类型的组合类型就必须的使用valueOf来获取原始值。
核心:对于基本类型、slice(不确定怎么获取)、map(可用断言,不太确定)、struct(未知结构体类型用反射,已知用断言)赋予空接口后想要获取原始值,对于已经赋值的空接口,需要获取他的动态类型使用反射。
在go语言程序设计中有所有类型通过valueOf获取值的方法
断言、接口和反射三大模块来解决void*问题(动态数据类型)
//工作代码,理解反射
func (t *FrameTask) OnWriteData(data interface{}) (isFull bool, err error) {
if data == nil {
return
}
dataType := reflect.TypeOf(data)
kind := dataType.Kind().String()
var modelName string
if kind == "slice" {
kind = dataType.Elem().Kind().String()
if kind == "ptr" {
modelName = dataType.Elem().Elem().Name()
} else {
modelName = dataType.Elem().Name()
}
commit, exist := dataCommits[modelName]
if !exist {
AddNormalCommit(modelName, strings.ToLower(modelName))
commit, exist = dataCommits[modelName]
}
if exist {
length := reflect.ValueOf(data).Len()
for i := 0; i < length; i++ {
isFull, err = commit.AddCommitData(reflect.ValueOf(data).Index(i).Interface())
}
results[commit.TaskApi.TypeName] += int64(length)
} else {
fmt.Printf("不存在%s类型commit!", modelName)
}
} else {
if kind == "ptr" {
modelName = dataType.Elem().Name()
} else {
modelName = dataType.Name()
}
commit, exist := dataCommits[modelName]
if !exist {
AddNormalCommit(modelName, strings.ToLower(modelName))
commit, exist = dataCommits[modelName]
}
if exist {
isFull, err = commit.AddCommitData(data)
results[commit.TaskApi.TypeName]++
} else {
fmt.Printf("不存在%s类型commit!", modelName)
}
}
return
}
go语言的嵌套结构体赋值:
type param_s struct {
Type int `json:"type"`
Params []struct {
Start string `json:"start"`
End string `json:"end"`
Length int `json:"length"`
Offset int `json:"offset"`
Ext []string `json:"ext"`
} `json:"params"`
Path []string `json:"path"`
}
ps = param_s{
Type: 1,
Params: []struct{
Start string "json:\"start\""; End string "json:\"end\""; Length int "json:\"length\""; Offset int "json:\"offset\""; Ext []string "json:\"ext\""
} {
{Start:"",End:"",Length:0,Offset:0,Ext:[]string{"jpg"}},
},
Path:[]string{"ceshitezhenghuifu.001/jiaqiong"},
}
注意在对切片赋值时候的方式。
注:go语言在赋值的时候会把复合类型放在{}前。
总结go path和go mod
当使用gopath时,依赖的文件都必须放在设置gopath/src的目录下,并且go get的命令下载依赖也是下载在gopath/src下,不可以指定依赖版本
当使用gomod时,依赖文件的下载路径在pkg/mod下,并且可以指定依赖版本,重要的一点是,拥有go.mod的目录为项目的根目录,在有本地依赖时必须在go.mod中request and replace
两者的主要区别在于对于gopath的依赖(即对于import导入依赖寻址,gomod使用go.mod而gopath使用gopath变量)
go channel的理解:
make(type, size)
当size为0时,为无缓冲通道,向里面写数据会阻塞,直到数据被读走。
当size大于0是,为缓冲通道,通道满时向里面写数据会阻塞。
_,ok := <-a 非阻塞读取
for a := range ch{
} for循环阻塞读取通道,直到通道被关闭
select{
} 如果没有符合的条件,将会阻塞,如果有default分支在不会阻塞
关于gin中间件的理解:
func Logger() gin.HandlerFunc {
//两种注册函数方式不同点在于这种方式可以在此处初始化一些
return func(c *gin.Context) {
// 可以通过上下文对象,设置一些依附在上下文对象里面的键/值数据
c.Set("example", "12345")
// 在这里处理请求到达控制器函数之前的逻辑
// 调用下一个中间件,或者控制器处理函数(路由处理函数),具体得看注册了多少个中间件。
l("BBBBB")
time.Sleep(time.Second)
c.Next()
time.Sleep(time.Second)
l("EEEEE")
// 在这里可以处理请求返回给用户之前的逻辑
// 例如,查询请求状态吗
/* status := c.Writer.Status()
log.Println(status)*/
}
}
//顺序是:next之前先Use的先使用,next之后先Use的后使用