1、语法
1.1 结构体
type Session struct{
}
结构体的字段属性的首字母大小写,影响了该属性的可见性,以及json的序列化和反序列化
- 1、大写的属性,可以被其他包使用,小写仅包内可见
- 2、使用json来序列/反序列化时,只有大写属性才会被编解码。
- 3、结构体还可以打tag,
json:"-"表示这个属性忽略
type Desk struct {
deskId int `json:"desk_id"` // 这个不会被解码赋值
OwnerId int `json:"owner_id"`
}
type DeskList struct {
Id int32 `json:"id"`
List []Desk `json:"list"`
}
list := make([]DeskList, 2 )
json.Unmarshal( []byte(str),&list)
==嵌套结构体==
一个结构体里面,嵌套了另外一个==匿名结构体==,如果2个都分别定义了一个同名函数,则用外层对象调用该函数时,实际调用的是外层的函数
package main
import "log"
type InnerData struct {
}
func (InnerData) SayHello() {
log.Println("在内层的 SayHello")
}
type OutData struct {
InnerData
}
func (OutData) SayHello() {
log.Println("在外层的 SayHello")
}
func main() {
d := OutData{}
d.SayHello()
}
输出
2021/11/09 16:15:05 在外层的 SayHello
将外层结构体的方法注释后,会调用内存结构体的方法 ,输出
2021/11/09 16:19:58 在内层的 SayHello
1.2 操作符的优先级
二元操作符,有5个优先级,从高往低依次如下。同级别的是从左到右结合。 5 * / % << >> & &^ 4 + - | ^ 3 == != < <= > >= 2 && 1 ||
1.3、类型
- 1、值类型
- int,byte,bool ,string , array等
- 2、引用类型
- slice,map,chan
对引用类型,作为函数参数时,实际传递的是引用,所以函数里面对他的修改,会影响到原来的对象。
- 3、类型转换
接口类型转换为其他类型
var data []interface{} = make([]interface{}, 5)
for i := 0; i < 5; i++ {
data[i] = int32(i)
fmt.Println(data[i].(int32))
}
需要用 xx.(int32) 这样,不能用 int32(xx) 这种
2、主要关键词
2.1 if
2.2 switch
2.3 for 循环
for i range xx {
}
2.4 range
- 1、用于切片时,返回的 下标 和 值
- 2、用于map时,可以只返回key,也可以返回key 和 value
- 3、用于string时,返回下标和unicode 码点
- 4、用于通道时,只返回一个值
panic,recover
func TestNewTickerLtZeroDuration(t *testing.T) {
defer func() {
if err := recover(); err == nil {
t.Errorf("NewTicker(-1) should have panicked")
}
}()
NewTicker(-1) // 会导致 panic
}
3. select
4、接口
5、管道 channel
方向,关闭,读取
5.1 for-range应用于通道
for-range循环控制流程也适用于通道。 此循环将不断地尝试从一个通道接收数据,直到此通道关闭并且它的缓冲队列为空为止。 和应用于数组/切片/映射的for-range语法不同,应用于通道的for-range语法中最多只能出现一个循环变量,此循环变量用来存储接收到的值。
for v := range aChannel {
// 使用v
}
5.2 select-case分支流程控制代码块
Go中有一个专门为通道设计的select-case分支流程控制语法。 此语法和switch-case分支流程控制语法很相似。 比如,select-case流程控制代码块中也可以有若干case分支和最多一个default分支。 但是,这两种流程控制也有很多不同点。在一个select-case流程控制中
- select关键字和{之间不允许存在任何表达式和语句。
- fallthrough语句不能被使用.
- 每个case关键字后必须跟随一个通道接收数据操作或者一个通道发送数据操作。 通道接收数据操作可以做为源值出现在一条简单赋值语句中。 以后,一个case关键字后跟随的通道操作将被称为一个case操作。
- 所有的非阻塞case操作中将有一个被随机选择执行(而不是按照从上到下的顺序),然后执行此操作对应的case分支代码块。
- 在所有的case操作均为阻塞的情况下,如果default分支存在,则default分支代码块将得到执行; 否则,当前协程将被推入所有阻塞操作中相关的通道的发送数据协程队列或者接收数据协程队列中,并进入阻塞状态。
6、函数
- 1、go中不支持重载,即不允许同同名函数,哪怕参数个数、类型不同
- 2、函数不允许嵌套
- 3、支出多返回值,命令返回值
- 4、不支持默认参数
- 5、无需前置声明
- 6、支持匿名函数和闭包,任意的表达式内都可以使用函数字面值(就像是函数声明但是没有名字func关键词后),函数字面值是一个表达式,它的值叫匿名函数。允许在使用的地方定义函数,匿名函数可以访问它的外面的函数的变量。函数值不仅仅是代码,还可以有状态。
- 7、支持不定长参数
- 8、函数的大花括号不能在下一行
- 9、函数属于第一类对象,具有相同签名(参数以及返回值列表)的函数属于同一类型,可以在运行期创建,作为函数参数或者返回值,可以存入变量的实体。
- 10、没有赋值的函数默认是nil,函数只能判断是否为nil,不能做其他比较操作,不能作为map的key。
- 11、推荐用type来命令函数类型,否则太长了不好阅读
- 12、Anonymous functions can be declared in Go, as in this example. Function literals are closures: they inherit the scope of the function in which they are declared.
- 13、命名函数只能在包级别定义
- 14、当一个匿名函数需要递归时,我们必须要先声明一个变量,然后把匿名函数赋值给那个变量
- 15、defer关键词,用于延迟执行,一般做清理工作,函数结束前会调用,多个defer会按照后注册先执行的顺序执行,函数的参数是语句执行时计算,但defer的函数实际调用是一直到所在函数结束前。典型用途 open/close connect/disconnect lock/unlock
mu.Lock()
defer mu.Unlock()
var d func()
d = func () {
d()
}
type FormatFunc func(string, ...interface{}) (string , error)
7、包
注意几个环境变量
- GOROOT
- GOPATH 工作空间的路径
导入包,用import,注意这个其实导入的是路径,不是包名,虽然一般路径和包名相同,但不是强制
2、数据结构
2.1 字符串
字符串不可变 len函数可以获取字符串长度,cap不接受字符串参数 字符串转byte切片 和 byte切片转字符串
s := "hello world"
bs := []byte(s)
s2 := string(bs)
2.2 数组
2.3 切片
2.4 字典
字典不能并发读写,需要用锁,哪怕一个是写,另外一个读
sync.RWMutex 来同步
ro run -race test.go // 启用数据竞争检查并发问题
不能直接修改字典的value的属性,要么取出整个元素,修改完对应的属性后,再重新设置这个key对应的value,或者 map里面存指针。
m := make(map[int]string, 20)
m[1] = "fish"
m[2] = "pig" // 插入 或者 更新都是这个
if v, ok := m[2]; ok { // 查询元素
fmt.Println(v)
}
delete(m, 2) // 删除元素
for i, v := range m {
fmt.Println(i,v)
}
3、测试
测试文件后缀是 _test.go ,测试函数名字以 Test打头
表驱动测试
func TestAdd( t *testing.T ) {
// 测试用例
tests := []struct{
a int32
b int32
expect int32
}{
{ 1,2,3},
{2,-1,1},
{1,0,1},
}
for _, v := range tests {
if v.expect != Add(v.a, v.b) {
t.Fail()
}
}
}
性能测试文件后缀也是 _test.go , 测试函数名字以 Benchmark打头 , 需要加上 --bench 参数
func BenchmarkAdd(b *testing.B) {
for i := 0 ; i < b.N; i++ {
Add(2,3)
}
}
编译时会忽略这些测试文件
go test ./... -v
- 1、-v 可以输出详细信息
- 2、./... 可以测试当前包以及他的所有子包
4、文件依赖
同一个包内的所有文件,各个类型都互相可见,不同的包,只有首字母大写的函数才是包外可见。 如果一个文件,用到了定义在同一个包内的另外一个文件的某个定义,比如一个struct结构,是不需要做额外的依赖说明的。但是编译的时候,会提示 未定义,解决办法有下面几种
- 如果是用的Goland ide,可以在ide的右上角,点击 edit configuration ,在弹出的窗口里面,Go build 下面,选中当前的构建,然后在右侧面板的 Run kind 里面,可以选择 package 或者 directory,默认是文件
- 在 ide 的 terminal 里面,cd 到工程的src 下的对应目录,然后输入 go build 目录名 或者 *.go
5、测试
go test 工具 和 testing 是组合在一起使用的。
5.1 单元测试
- 测试代码文件必须放在当前包,文件以_test.go 结尾
- 测试函数以Test开头,如 TestXxx 函数名以大写开头
- go test 命令会忽略以 _ 或者 . 开头的测试文件
- 正常编译 go build/install 会忽略测试文件
1、testing 标准库
- Fail ,失败后继续执行当前测试函数
- FailNow, 失败后,立即终止执行当前测试函数
- SkipNow, 跳过,停止执行当前测试函数
- Log, 输出错误信息,仅失败或者 -v时输出
- Parallel , 与其他设置有并行的测试函数一起并行,==缩短时间==
- Fatal,等效 FailNow + Log
2、go test 参数
- -args 命令行参数
- -v 输出详细信息
- -parallel 并行执行,默认值是 GOMAXPROCS
- -run 指定测试函数,正则表达式
- -timeout 全部测试累计时间超时将引发panic,默认是10分钟,可指定 1m20s
- -count 重复测试次数,默认值是1
- -bench 性能测试,需要指定测试哪个包,. 表示测试全部, regex 根据正则来
- -benchmem 输出内存分配信息
- -benchtime 测试时长
3、表驱动测试
测试一批输入条件,直接看例子
func TestAdd( t *testing.T ){
var tests = []struct {
x int
y int
expect int
} {
{1,1,2},
{2,2,4},
{3,3,6},
}
for _, tt := range tests {
actual := add(tt.x, tt.y)
if actual != tt.expect {
t.Errorf("add(%v,%v) : expect %v , actual %v", tt.x, tt.y, tt.expect, actual)
}
}
}
例子
main_test.go
package main
import (
testing
)
func TestAdd(t *test.T ){
if add(1,2) != 3 {
t.FailNow()
}
}
运行单元测试, go test ./.. 会测试当前包以及所有子包
有2种模式
-
1、本地文件夹模式 直接输入 go test 即可,会搜寻查找当前目录(==不包括子目录==)所有的测试文件
-
2、指定包列表的方式 如 go test math , go test ./..
5.2 性能测试
5.3 代码覆盖率
运行覆盖率测试
go test -coverprofile cover.out
查看覆盖情况
go tool cover -html=cover.out