go语言基础进阶
前言
一个好的学习者,一定要建立自己的节奏,看别人的教程,别人的视频,是别人的知识体系,不是自己的,最好的学习方式是:提出问题,自己主动寻找解答。
在提出问题之前,可能连如何提问都不知道,这时可以先看别人的视频,但看懂不是最重要的,速通一遍,知道视频的体系,哪个视频讲了什么知识点,很重要。这样才能在主动寻找解答的时候迅速定位答案。
一、import与init
main包的main()函数是整个程序的入口。
每个包中都会有一个init()函数,用于:
初始化全局的变量、常量,初始化缓存变量,加载静态资源、配置文件。
init()函数先于main()函数执行。
执行过程如图所示:
思考:init()定义的全局变量的生命周期?
二、defer关键字
类似于Java中的finally
作用是在函数结束的时候执行,执行顺序是在return之后。
如果有多个defer,则依次压栈,先入后出。
问题:如果函数中有defer和return,函数调用的同时给变量赋值,执行顺序?
package main//问题:如果函数中有defer和return,函数调用的同时给变量赋值,执行顺序?
import "fmt"
var i int = 10
func main(){
a := returnNum()
fmt.Println("a = ",a)
fmt.Println("i = ",i)
}
func returnNum() int {
defer deferChangeNum()
i = 11
fmt.Println("returnNum called, i = ",i)
return i
}
func deferChangeNum() int {
i = 9
fmt.Println("deferChangeNum called... i = ",i)
return i
}
打印结果:
returnNum called, i = 11
deferChangeNum called… i = 9
a = 11
i = 9
三、匿名函数与闭包
1、匿名函数的定义及调用
func main(){
func(){
fmt.Println("this is a NoNameFunc1")
}() //小括号代表直接调用
a :=func(){
fmt.Println("this is a NoNameFunc2")
} //函数可以作为一种数据类型进行传递
a() //a是函数,后加括号调用
}
2、闭包简单示例
func main(){
f := introduce("冯宝宝")
f(198)//My name is 冯宝宝 .I am 198 years old.
f(168)//My name is 冯宝宝 .I am 168 years old.
f = introduce("张楚岚")
f(16)//My name is 张楚岚 .I am 16 years old.
}
func introduce(name string) func(int){
return func(age int){//此匿名函数与外部变量name string构成闭包
fmt.Println("My name is ",name,".I am ",age," years old.")
}
}
四、数组转切片
直接在数组变量的后面加[:]即可。
五、空接口interface{}的妙用
空接口 interface{}是万能类型,万类鼻祖,类似Java的Object类。
Java中,Object是引用类型。golang中,int、float等基本类型也可以用interface{}代表。
示例:
var o interface{} = 11
fmt.Printf("type of o : %T",o)//打印出来的类型:int
函数的形参不确定传什么类型,则可用空接口;
println的参数就是空接口;
定义1个map: key是string, value是空接口,则可存任意数据类型;
定义1个空接口切片,其中存储任意类型的数据。
六、反射
反射的原理:
一个变量包含pair:
能够类型断言就是因为变量结构中有pair:
var a string
//pair<statictype:string, value:"aceld">
a = "aceld"
//pair<type:string, value:"aceld">
var obj interface{}
obj = a
str, b := obj.(string) //类型断言,判断obj是否string类型,返回字符串str,bool值b
if b {
fmt.Println("true", str)
}
lotus项目中的反射示例
lotus\cmd\lotus-seal-worker\main.go:397
rpcServer.Register("Filecoin", apistruct.PermissionedWorkerAPI(metrics.MetricedWorkerAPI(workerApi)))
进入metrics.MetricedWorkerAPI这个方法后:
func MetricedWorkerAPI(a api.WorkerAPI) api.WorkerAPI {
var out apistruct.WorkerStruct
proxy(a, &out.Internal)
return &out
}
进入proxy方法:
func proxy(in interface{}, out interface{}) {
rint := reflect.ValueOf(out).Elem()
ra := reflect.ValueOf(in)
for f := 0; f < rint.NumField(); f++ {
field := rint.Type().Field(f)
fn := ra.MethodByName(field.Name)
rint.Field(f).Set(reflect.MakeFunc(field.Type, func(args []reflect.Value) (results []reflect.Value) {
ctx := args[0].Interface().(context.Context)
// upsert function name into context
ctx, _ = tag.New(ctx, tag.Upsert(Endpoint, field.Name))
stop := Timer(ctx, APIRequestDuration)
defer stop()
// pass tagged ctx back into function call
args[0] = reflect.ValueOf(ctx)
return fn.Call(args)
}))
}
}
这里就用到了反射的技术,变量a相当于内容,out.Internal是空的框架,proxy方法里面通过反射reflect.ValueOf可以把内容填充到框架中。
七、三个点的用法
三个点,四个用处:
1、变长的函数参数
最后一个函数参数的类型可以是…T,那么在调用此函数时,可在参数列表的最后传入若干个类型为T的参数。这些传入的参数在函数内部会组成切片[]T
func Sum(nums ...int) int {
res := 0
for _, n := range nums {//传入的nums组成切片,遍历num
res += n
}
return res
}
Sum(1,2,3)//调用Sum函数,传入若干个类型为int的参数,组成切片
2、调用上述函数时
在调用上述函数时,如果我们希望直接传入切片ts []T,而不是一个个传入t int,则可在ts后面直接跟上…,而不必把切片拆开一个个传入。
primes := []int{1,2,3,4}
fmt.Println(Sum(primes...))
3、标识数组若干个元素
package main
import "fmt"
func main() {//...的意思是:我也不确定我要写几个,反正就是若干个
strs2 := [...]string{"a", "b"}//两个
strs3 := [...]string{"a", "b", "c"}//三个
strs4 := [...]string{"a", "b", "c", "d"}//四个
fmt.Println(strs2)
fmt.Println(strs3)
fmt.Println(strs4)
}
4、Go命令行中的通配符
go test ./...
八、new和make的区别
new用于包装一个基本类型并实例化。(不懂包装的可以看下java中的包装类)
i := new(int)
//上面的代码等价于下面两行:
var v int
i := &v
项目中常用new初始化一个big.Float、big.Int等,用于数据类型转换。
func FloatToBigInt(val float64) *big.Int {
bigval := new(big.Float)
bigval.SetFloat64(val)
// Set precision if required.
// bigval.SetPrec(64)
coin := new(big.Float)
coin.SetInt(big.NewInt(1000000000000000000))
bigval.Mul(bigval, coin)
result := new(big.Int)
bigval.Int(result) // store converted number in result
return result
}
make仅用于初始化切片、map、channel。
slice := make([]int, 0, 100)
m := make(map[int]bool, 10)
c := make(chan int, 5)
gorm的Updates方法,传入对象不能给字段赋值为0,可以传入map,此时需要使用make关键字来初始化一个map:
uid := 13
data := make(map[string]interface{})
data["balance"] = 0 //零值字段
data["freeze"] = 985
Db.Model(&model.StoReward{}).Where("uid = ? ", uid).Updates(data)
九、全局变量
golang没有Java中静态变量的概念,但是有全局变量。可以把全局变量理解为静态变量。
在声明全局变量时同时赋值,编译通不过,分析是因为那会儿可能gorm还没有连接数据库。
然后在声明的时候不赋值,在方法中加了个判断,如果需要用,而数据为空,就去数据库查然后赋值,这样一次赋值,以后就都有值了,也就不需要再重复查数据库了。
后记、面试题
1、切片的扩容机制是怎样的?
2、go中的面向对象,封装继承多态是如何实现的?
3、匿名函数和闭包是什么意思?
4、变量是存在堆还是栈?
5、空接口的作用是什么?
6、协程和线程有什么区别?
7、make和new的区别是什么?
8、context包的常用API?
9、map是线程安全的吗?
10、map的扩容机制?
11、unsafe包是如何使用的?
12、channel为什么会阻塞,底层原理是什么?
13、如何处理错误?
14、空切片占用内存吗?
15、defer的执行顺序?