学过编程的 xdm 对于函数自然不会陌生,那么函数是什么呢?
函数是一段可以重用的代码块,可以被多次调用,我们可以通过使用函数,提高咱们代码代码的模块化,提高程序的可读性和可维护性
对 GO 语言中的函数,入参可以有多个,返回值也可以有多个,此处建议,入参不大于 5 个,如果超过 5 个最好使用传入结构的方式来进行处理
要了解 GO 语言中程序的初始化顺序,就要先明白整个程序初始化流程中,都会涉及到哪一些内容
GO 语言中以包 package 作为 程序逻辑封装的基本单元,每一个包我们可以理解为他是独立的,封装良好的,并且对外暴露了接口的基本单元
然而,咱们的 GO 程序就是由这些包组成的,那么这个包里面一般又包含着 基本的常量,变量,函数,类型,方法,和接口等等
那么对于上述这些元素,我们就要弄明白在包里面他们是如何有序的进行初始化的
本章主要分享函数,GO 语言中除了 main 函数,还有一个特殊的函数就是 init 函数
fun init() {
// 具体的实现
// 具体的初始化
}
这里可以看到init 函数,是没有入参,也没有返回值的函数
init 函数用于在本包中进行初始化和做一些程序初始状态的检查工作,例如我们会把一些单例,数据库句柄,各种连接句柄放到 init 函数中进行初始化,init 函数在整个程序生命周期,只会被调用一次
且这里我们需要注意,一个包里面会有多个 GO 语言源文件,这些源文件中都可以定义 init 函数,但是在程序执行 init 函数时,是一个一个的去执行的,而不是并发的去执行的,那么此处的执行顺序我们就不要去过度依赖,按照我们使用惯例来看,过度依赖 init 函数的执行顺序可能会出现意想不到的问题
所以此处我们就需要注意,如果不同的 init 函数实现中,相互有依赖,那么可能就会导致程序出现我们不期望的结果
那么整个程序的初始化流程和顺序我们要知道是这样子的:
对于一个包而言,初始化顺序如下:
对于一个程序里面,从 main 函数开始,必然是包含了其他的子包,那么初始化的时候是怎么样的呢?
通过上图是不是对于 GO 程序初始化顺序更加清晰了呢,如果有表述不当的地方,还请多多评论留言,多多指教
我们可以写一个 demo 来看看效果:
demo 的目录结构如下
main.go 文件的内容如下:
package main
import "fmt"
import _ "ttt/p1"
import _ "ttt/p2"
var (
_ = checkConst()
c = varInit(4)
d = varInit(5)
)
const (
a = 2
b = 3
)
func init() {
fmt.Println("main : init")
}
func checkConst() int {
if a == 2 {
fmt.Println("main : const a")
}
if b == 3 {
fmt.Println("main : const b")
}
return 0
}
func varInit(x int) int {
fmt.Println("main : var ", x)
return x
}
func main() {
fmt.Println("main : main ")
}
运行程序之后,我们可以看到打印的结果如下, 通过这个 demo 我们就可以看到程序的初始化顺序正如上所述
p1 : const a
p1 : const b
p1 : var 4
p1 : var 5
p1 : init
p2 : const a
p2 : const b
p2 : var 4
p2 : var 5
p2 : init
main : const a
main : const b
main : var 4
main : var 5
main : init
main : main
具体的 demo 仓库可以查看如下地址:
GO 语言程序初始化顺序 demo
在 GO 中,我们可以看到没有那些高级语言面向对象的语法,例如 Class 类,继承,对象等等内容, GO 语言中可以通过函数或者方法的方式进行各种组合完成我们想做的一切事项
此处就体现了函数在 GO 语言中是极其的重要,函数在 GO 语言中是 一等公民 的存在。
如何体现 一等公民呢?函数在 GO 中可以像普通类型的值一样被创建和被使用,使用起来非常灵活和自由
例如,创建的函数可以存储在变量中,也可以做为其他函数的返回值(在函数内部创建了函数变量之后,通过返回值返回),还可以作为其他函数的参数进行传递
那么我们就来写一些 demo 查看这个 一等公民 是有多么的自由吧:
func helloworld() string{
name := "阿兵云原生"
return name
}
func add(a, b int) int {
return a + b
}
func cal(a int, f func(int, int) int) int {
return a + f(a, a)
}
func add(a, b int) int {
return a + b
}
func getFunc() func(int, int) int{
return add
}
type TestFunc struct {
f func(int, int) int
name string
}
func add(a, b int) int {
return a + b
}
func main() {
tt := &TestFunc{f: add, name: "阿兵云原生"}
fmt.Println(tt.f(1,2))
}
type PFunc func(int) int
综上所述,对于使用 GO 语言中的函数还是相当方便的,用起来是相当的顺手
那么对于普通类型的数据,我们可以进行显示的类型转换,那么对于函数是不是也可以??
自然是可以的,我们可以来看一个 一般类型的 demo:
var x int = 10
var y int32 = 20
fmt.Println(x + y)
上述代码很明显 x 的类型和 y 的类型是不同的, GO 语言是不会编译通过的,我们需要将其中一个变量的类型显示的转换才可,例如 fmt.Println(int32(x) + t)
再来看看显示转换函数的 demo:
Do(int) int
接口func(int) int
func playfootball(x int) int
type Processer interface {
Do(int) int
}
type PFunc func(int) int
func (f PFunc) Do(x int) int {
return f(x)
}
func playfootball(x int) int {
return x
}
func main() {
var i Processer = playfootball
fmt.Println(i.Do(20))
}
运行上述代码,很明显是编译不过的,因为 playfootball 的类型和并没有实现 Do 接口,虽然入参和返回值的类型和个数都一样
我们只需要将上述代码,将 playfootball 显示转换,写成 PFunc(playfootball)
即可顺利通过编译,正常看到打印
因为上述 PFunc 类型实现了 Processer 接口,因此对于 i 需要接收的是 PFunc 的实例,这个时候对 playfootball 进行显示转换后,实际上最终调用的函数 playfootball 函数
使用过 GO 语言的 xdm 对于 defer 不会陌生,对于那些我们需要在函数退出前释放或者需要关闭的资源,我们就可以使用到 defer 这里用起来就相当的省心,哪怕函数中出现了 panic,defer 也能给你守护的明明白白的
func test() {
defer func() {
if e := recover(); e != nil {
fmt.Println(e)
fmt.Println("recover ... ")
}
}()
panic("panic ...")
}
func main() {
test()
}
例如上述 demo ,会正常输出,不会 panic ,因为已经被捕获和处理了
使用 defer 能大大的减少我们的开发人员的心智负担,例如我们以前在使用锁的时候,加锁之后,我们可能会忘记写解锁的语句,可使用了 defer ,你完全可以是在加锁的时候,使用 defer 让函数关闭的时候解锁即可,当然具体逻辑还是要看具体的实现
关于 defer 的原理,以及使用 defer 的注意事项就不在过多赘述,可以查看如下文章获得答案
本次分享了函数相关的基本知识,以及 GO 程序的初始化顺序,对于 defer 的使用有想法的欢迎点击上述连接查看具体 defer 的分享细节
朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力
好了,本次就到这里
技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。
我是阿兵云原生,欢迎点赞关注收藏,下次见~
可以进入地址进行体验和学习:https://xxetb.xet.tech/s/3lucCI