Golang 基础之函数使用 (三)

大家好,今天将梳理出的 Go语言函数用法内容,分享给大家。 请多多指教,谢谢。

本次《Go语言函数使用》内容共分为三个章节,本文为第三章节。

本章节内容

  • init 函数
  • 方法

init 函数

介绍

例如某些场景下,我们需要提前初始化一些变量或逻辑代码。在这种情况下,我们可以用一个特殊的init初始化函数来简化初始化工作,每个文件都可以包含一个或多个init初始化函数。

func init() {} // init()函数语法

init初始化函数除了不能被调用或引用外,其他行为和普通函数类似。在每个文件中的init初始化函数,在程序开始执行时按照它们声明的顺序被自动调用。

init函数先于main函数执行

注意:每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。因此,如果一个p包导入了q包,那么在p包初始化的时候可以认为q包必然已经初始化过了。

init函数的特征

  • init函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等
  • init函数没有输入参数、返回值
  • 每个包可以拥有多个init函数
  • 包的每个源文件也可以拥有多个init函数
  • 同一个包中多个init函数的执行顺序go语言没有明确的定义(说明)
  • 不同包的init函数按照包导入的依赖关系决定该初始化函数的执行顺序
  • init函数不能被其他函数调用,而是在main函数执行之前,自动被调用

初始化的过程

  1. 初始化导入的包(顺序并不是按导入顺序(从上到下)执行的,runtime需要解析包依赖关系,没有依赖的包最先初始化);
  2. 初始化包作用域的变量(并非按照“从上到下、从左到右”的顺序,runtime解析变量依赖关系,没有依赖的变量最先初始化);
  3. 执行包的init函数;
runtime是go语言运行所需要的基础设施,也是go的核心特性。 该内容将放到后续《go基础之特性》章节为大家分享。

使用

案例:init初始化顺序

package main

import "fmt"

var Num int = Call() // 全局变量声明

func init() { // 初始化函数
    fmt.Println("init()")
}

func Call() int {
    fmt.Println("Call()")
    return 1
}

func main() {
    fmt.Println("main()")
}

输出

Call()
init()
main()

结论,初始化的过程:Num变量初始化 -> init() -> main()

案例:同一个包不同源码的init初始化顺序

首先创建3个文件, main.go代码中包含全局变量、init初始化函数定义,和main函数入口; a.go 和 b.go代码文件中,只包含全局变量、init初始化函数的定义。

main.go文件

package main

import (
    "fmt"
)

var _ int = m()

func init() {
   fmt.Println("init in main.go")
}

func m() int {
   fmt.Println("call m() in main.go")
   return 1
}

func main() {
   fmt.Println("main()")
}

a.go 文件

package main

import "fmt"

var _ int = a()

func init() {
   fmt.Println("init in a.go")
}

func a() int {
   fmt.Println("call a() in a.go")
   return 1
}

b.go 文件

package main

import "fmt"

var _ int = b()

func init() {
   fmt.Println("init in b.go")
}

func b() int {
   fmt.Println("call b() in b.go")
   return 1
}
因为a.go 和 b.go 都归属于main包,但没有两文件中没有main函数入口。 在执行的时候,需要使用 go run main.go a.go b.go 这样形式执行,runtime会将所有文件进行加载初始化。

输出

call m() in main.go
call a() in a.go
call b() in b.go
init in main.go
init in a.go
init in b.go
main()

结论,同一个包不同源文件的init函数执行顺序,golang 没做官方说明。这块加载过程是按照 go run 文件排序。

案例:多个init函数初始化顺序

package main

import "fmt"

func init() {
   fmt.Println("init 1")
}

func init() {
   fmt.Println("init 2")
}

func main() {
   fmt.Println("main")
}

输出

init 1
init 2
main

结论:init函数比较特殊,可以在包里被多次定义。

方法

介绍

Golang中方法,实现是以绑定对象实例, 并隐式将实例作为第一实参 (receiver)。

定义说明

  • 只能为当前包内命名类型定义方法;
  • 参数 receiver 可任意命名,如方法中未曾使用,可省略参数名;
  • 参数 receiver 类型可以是 T 或 *T, 基类型 T 不能是接口或指针;
  • 不支持方法重载, receiver 只是参数签名的组成部分;
  • 可用实例 valuepointer 调用全部方法, 编译器自动转换

一个方法就是一个包含了接受者的函数, 接受者可以是命名类型或者结构体类型的一个值或者是一个指针。

方法定义

func (recevier type) methodName(参数列表) (返回值列表) {} // 参数和返回值可以省略

使用

定义一个结构类型和该类型的一个方法

package main

import "fmt"

// 结构体
type Info struct {
    Name  string
    Desc string
}

// 方法
func (u Info) Output() {
    fmt.Printf("%v: %v \n", u.Name, u.Desc)
}

func main() {
    // 值类型调用方法
    u1 := Info{"帽儿山的枪手", "分享技术文章"}
    u1.Output()
    // 指针类型调用方法
    u2 := Info{"帽儿山的枪手", "分享技术文章"}
    u3 := &u2
    u3.Output()
}

输出

帽儿山的枪手: 分享技术文章 
帽儿山的枪手: 分享技术文章

匿名方法

如类型S包含匿名字段 *T ,则 S 和 *S 方法集包含 T + *T 方法。

这条规则说的是当我们嵌入一个类型的指针, 嵌入类型的接受者为值类型或指针类型的方法将被提升, 可以被外部类型的值或者指针调用。
package main

import "fmt"

type S struct {
    T
}

type T struct {
    int
}

func (t T) testT() {
    fmt.Println("如类型 S 包含匿名类型 *T, 则 S 和 *S 方法集包含 T 方法")
}

func (t *T) testP() {
    fmt.Println("如类型 S 包含匿名字段 *T, 则 S 和 *S 方法集合包含 *T 方法")
}

func main() {
    s1 := S{T{1}}
    s2 := &s1
    fmt.Printf("s1 is : %v\n", s1)
    s1.testT()
    s1.testP() // 提升指针类型调用

    fmt.Printf("s2 is : %v\n", s2)
    s2.testT() // 提升值类型调用
    s2.testP()
}

输出

s1 is : {{1}}
如类型 S 包含匿名类型 *T, 则 S 和 *S 方法集包含 T 方法
如类型 S 包含匿名字段 *T, 则 S 和 *S 方法集合包含 *T 方法
s2 is : &{{1}}
如类型 S 包含匿名类型 *T, 则 S 和 *S 方法集包含 T 方法
如类型 S 包含匿名字段 *T, 则 S 和 *S 方法集合包含 *T 方法

表达式

根据调用者不同,方法分为两种表现形式

instance.method(args...) ---> .func(instance, args...)
前者称为 method value, 后者 method expression则须显式传参。
package main

import "fmt"

type User struct {
    id   int
    name string
}

func (self *User) Test() {
    fmt.Printf("%p, %v\n", self, self)
}

func main() {
    u := User{1, "帽儿山的枪手"}
    u.Test()

    mValue := u.Test
    mValue() // 隐式传递 receiver

    mExpression := (*User).Test
    mExpression(&u) // 显式传递 receiver
}

输出

0xc00000c018, &{1 帽儿山的枪手}
0xc00000c018, &{1 帽儿山的枪手}
0xc00000c018, &{1 帽儿山的枪手}

结论,方法是指针类型,method value 会复制 receiver。

技术文章持续更新,请大家多多关注呀~~

搜索微信公众号【 帽儿山的枪手 】,关注我

你可能感兴趣的:(go编程语言后端)