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的执行顺序?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值