Golang语言之函数(func)进阶篇

简介: 这篇文章是关于Golang语言中函数高级用法的教程,涵盖了初始化函数、匿名函数、闭包函数、高阶函数、defer关键字以及系统函数的使用和案例。

作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.init初始化函数

1 初始化函数作用

- 1.init初始化函数可以用来进行初始化操作
    每个"*.go"源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用。

- 2.全局变量定义,init函数,main函数的执行流程?
    顺序依次是: 全局变量定义,init函数,main函数。

- 3.多个源文件都有init函数,如何执行?

2 初始化函数定义案例

2.1 初始化项目

yinzhengjie@localhost 03-init % go mod init yinzhengjie-utlis
go: creating new go.mod: module yinzhengjie-utlis
go: to add module requirements and sums:
        go mod tidy
yinzhengjie@localhost 03-init %

2.2 utils.go源代码

package utils

import "fmt"

var (
    Name   string
    Age    int
    Gender string
)

func init() {

    fmt.Println("in utils package ... init ")
    Age = 18
    Name = "Jason Yin"
    Gender = "boy"
}

2.3 main.go源代码

package main

import (
    "fmt"
    // 第1步: 先导入第三方包
    "yinzhengjie-utlis/utils"
)

// 第2步: 全局变量定义
var number int = demo()

func demo() int {
    fmt.Println("in demo ...")
    return 100
}

// 第3步: 调用init函数
func init() {
    fmt.Println("init函数被调用...")
}

// 第4步: 调用main函数
func main() {
    fmt.Println("main函数被调用...")
    fmt.Printf("姓名:%s 年龄:%d 性别: %s\n", utils.Name, utils.Age, utils.Gender)
}

二.匿名函数

1 匿名函数概述

- Go支持匿名函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数。

- 匿名函数使用方式:
        - 1.在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次;
        - 2.将匿名函数赋给一个变量(该变量就是函数变量了),再通过该变量来调用匿名函数;

- 如何让一个匿名函数,可以在整个程序中有效呢?将匿名函数给一个全局变量就可以了。

2 匿名函数案例

package main

import "fmt"

// 3.如果匿名函数是全局变量则可以被全局调用哟~
var mul = func(a, b int) int {
    return a * b
}

func main() {

    var (
        x int = 7
        y     = 5
    )

    // 1.定义匿名函数,定义的同时调用
    sum := func(a int, b int) int {
        return a + b
    }(x, y)

    // 2.将匿名函数赋值给一个变量,这个变量实际就是函数类型的变量
    sub := func(a, b int) int {
        return a - b
    }

    result01 := sub(x, y)

    result02 := mul(x, y)

    fmt.Printf("sum = %d\n", sum)
    fmt.Printf("sub = %d\n", result01)
    fmt.Printf("mul = %d\n", result02)
}

三.闭包函数

1 闭包函数概述

- 什么是闭包函数:
    闭包就是一个函数和其他相关的引用环境组合的一个整体。

- 闭包的本质:
    闭包本质依旧是一个匿名函数,只是这个函数引用外界的变量/参数,因此我们说: "匿名函数 + 引用外界的变量/参数 = 闭包"。

- 闭包函数特点:
    - 1.返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数,因此这个匿名函数就和变量/参数形成一个整体,构成闭包;
    - 2.闭包中使用的变量/参数会一直保存在内存中,所以会一直使用,意味着闭包不可滥用;

- 闭包的应用场景:
    闭包可以保留上次引用的某个值,我们传入一次就可以反复使用了。

2 闭包函数案例之返回上级函数内部变量

package main

import (
    "fmt"
)

// 闭包指的是一个函数和与其相关的引用环境组合而成的实体。
func getSum() func(int) int {

    // 此变量属于getSum函数
    var sum int = 0

    // 此处的闭包: "返回的是一个匿名函数+函数以外的变量sum"
    return func(x int) int {
        sum += x
        return sum
    }
}

func getSum2(sum, x int) int {
    sum += x
    return sum
}

func main() {

    // 不使用闭包的时候,想要保留sum值,但不可以反复使用,因此每次调用都需要重新传入sum的值
    fmt.Println(getSum2(0, 10))
    fmt.Println(getSum2(10, 20))
    fmt.Println(getSum2(30, 30))

    fmt.Println("----- 分割线 -----")

    // 定义了一个函数变量,闭包返回的匿名函数引用的那个变量会一直保存在内存中,可以一直使用。
    var f1 = getSum()

    // 闭包可以保留上次引用的某个值,我们传入一次就可以反复使用了。
    fmt.Println(f1(10))
    fmt.Println(f1(20))
    fmt.Println(f1(30))

    fmt.Println("----- 分割线 -----")

    // 此处重新定义了一个函数变量哟,注意,f2和上面的f1函数是两个独立的匿名函数哟~
    f2 := getSum()
    fmt.Println(f2(40))
    fmt.Println(f2(50))

}

3 闭包函数案例之返回上级函数形参变量

package main

import (
    "fmt"
)

// 闭包函数不仅仅可以返回函数内部变量,也可以直接返回形参变量
func getSum(sum int) func(int) int {

    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {

    // 定义了一个函数变量,闭包返回的匿名函数引用的那个变量会一直保存在内存中,可以一直使用。
    var f1 = getSum(0)

    // 闭包可以保留上次引用的某个值,我们传入一次就可以反复使用了。
    fmt.Println(f1(10))
    fmt.Println(f1(20))
    fmt.Println(f1(30))

}

4 闭包函数返回多个匿名函数案例

package main

import (
    "fmt"
)

func calc(base int) (func(int) int, func(int) int) {
    add := func(i int) int {
        base += i
        return base
    }

    sub := func(i int) int {
        base -= i
        return base
    }
    return add, sub
}

func main() {
    // 闭包其实并不复杂,只要牢记闭包=函数+引用环境。
    f1, f2 := calc(10)
    fmt.Println(f1(1), f2(2))
    fmt.Println(f1(3), f2(4))
    fmt.Println(f1(5), f2(6))
}

5 闭包函数实现给文件加后缀

package main

import (
    "fmt"
    "strings"
)

// 我们可以利用闭包函数,给文件加后缀
func makeSuffixFunc(suffix string) func(string) string {
    return func(name string) string {
        if !strings.HasSuffix(name, suffix) {
            return name + suffix
        }
        return name
    }
}

func main() {
    jpgFunc := makeSuffixFunc(".jpg")
    txtFunc := makeSuffixFunc(".txt")
    fmt.Println(jpgFunc("yinzhengjie"))
    fmt.Println(txtFunc("yinzhengjie"))
}

四.高阶函数

1 高阶函数概述

- 什么是高阶函数指的是: 
    一个函数的参数是函数,或者返回值是函数,满足其中一个就是高阶函数。
    闭包函数由于返回值是"匿名函数",因此我们说闭包也算得上是高阶函数的一个分支,但"高阶函数"不一定是匿名函数哟~

- 高阶函数的应用场景:
     - 1.函数的参数或返回值可以使用函数,用于实现回调函数等功能;
     - 2.高阶函数可以用于实现递归函数,比如斐波拉契数列;
     - 3.高阶函数常常用于函数式编程,如列表的map,filter,reduce等函数; 
     - 3.可用于实现柯力化函数,比如: 技术实现函数的复用和参数的延迟传递;
     - 4.部分应用(partial application),比如:将一个函数的参数固定下来,得到一个新的函数,继续调用;
     - 5.函数可以接受一个函数作为参数,然后根据这个函数的不同实现进行不同的处理;
     - 6.可以用函数组合技术将多个函数组合成一个新的函数;

2 高阶函数案例之函数作为参数案例

package main

import (
    "fmt"
)

func add(x, y int) int {
    return x + y
}

func sub(x, y int) int {
    return x - y
}

// 定义的形参中,要求传递的op变量是一个函数哟~
func calc(x, y int, op func(int, int) int) int {
    return op(x, y)
}

func main() {
    var (
        a int = 100
        b int = 20
    )

    // 函数可以作为参数
    sum := calc(a, b, add)
    sub := calc(a, b, sub)

    fmt.Printf("%d + %d = %d\n", a, b, sum)
    fmt.Printf("%d - %d = %d\n", a, b, sub)
}

3 高阶函数案例之函数作为返回值

package main

import (
    "errors"
    "fmt"
)

func add(x, y int) int {
    return x + y
}

func sub(x, y int) int {
    return x - y
}

// 函数也可以作为返回值
func do(s string) (func(int, int) int, error) {
    switch s {
    case "+":
        return add, nil
    case "-":
        return sub, nil
    default:
        err := errors.New("无法识别的操作符")
        return nil, err
    }
}

func main() {
    var (
        a int = 100
        b int = 20
    )

    // 注意,此处返回的sum和sub都是函数哟~
    sum, _ := do("+")
    sub, _ := do("-")

    fmt.Printf("%d + %d = %d\n", a, b, sum(a, b)) // 调用sum函数
    fmt.Printf("%d - %d = %d\n", a, b, sub(a, b)) // 调用sub函数
}

五.defer关键字

1 defer执行时机

- 什么是defer:
    - 1.Go语言中的"defer"语句会将其后面跟随的语句进行延迟处理;
    - 2.在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,先被defer的语句最后被执行,最后被defer的语句,最先被执行。
    - 3.在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。

- defer的作用:
    在函数中,程序员经常需要创建资源,为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer关键字。

- defer的执行机制:
    defer语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如上图所示。

- defer应用场景:
    比如你想关闭某个使用的资源,因为defer有延迟执行机制(函数执行完毕再执行defer压入栈的语句),所以你用完随手写了关闭,比较省心,省事。

2 defer案例

package main

import "fmt"

func getSum(a, b int) (sum int) {
    /*
        1.在Golang中,程序遇到defer关键字,不会立即执行defer后的语句,而是将defer后的语句压入一个栈中,然后继续执行函数后面的代码;

        2.将defer语句压入栈中的同时,也会将相关的值同时拷贝到栈中,不会随着函数后面的变化而变化;

        3.栈的特点: 先进后出;

        4.在函数执行完毕以后,从栈中取出语句开始执行,按照先进后出的的规则执行语句;
    */
    defer fmt.Printf("a = %d\n", a)
    defer fmt.Printf("b = %d\n", b)

    // 此处我们将a和b的值进行修改,但是并不会影响到defer语句中里的a和b变量对应的值哟~
    a += 100
    b += 200

    sum = a + b
    fmt.Printf("a = %d, b = %d, sum = %d\n", a, b, sum)

    return sum

}

func main() {
    fmt.Println(getSum(10, 20))
}

3 defer面试题

3.1 观察代码手写运算结果1

package main

import (
    "fmt"
)

func f1() int {
    x := 100
    defer func() {
        x++
        fmt.Println("in f1 x = ", x)
    }()
    return x
}

func f2() (x int) {
    defer func() {
        x++
        fmt.Println("in f2 x = ", x)
    }()
    return 200
}

func f3() (y int) {
    x := 300
    defer func() {
        x++
        fmt.Println("in f3 x = ", x)
    }()
    return x
}

func f4() (x int) {
    defer func(x int) {
        x++
        fmt.Println("in f4 x = ", x)
    }(x)
    return 400
}

func main() {
    fmt.Println(f1())
    fmt.Println(f2())
    fmt.Println(f3())
    fmt.Println(f4())
}

3.2 观察代码手写运算结果2

package main

import (
    "fmt"
)

func calc(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}

func main() {
    x := 1
    y := 2
    defer calc("AA", x, calc("A", x, y))
    x = 10
    defer calc("BB", x, calc("B", x, y))
    y = 20
}

六.系统函数

1.字符串相关函数

推荐阅读:
    https://developer.aliyun.com/article/1604773

2.日期和时间相关函数

推荐阅读:
    https://developer.aliyun.com/article/1604899

3.内置函数

3.1.常见的内置函数介绍

内置函数 介绍
close 主要用来关闭channel
len 用来求长度,比如string、array、slice、map、channel
new 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
make 用来分配内存,主要用来分配引用类型,比如chan、map、slice
append 用来追加元素到数组、slice中
panic和recover 用来做错误处理

3.2 make和new函数案例

推荐阅读:
    https://developer.aliyun.com/article/1604781

3.3 错误处理机制

推荐阅读:
   https://developer.aliyun.com/article/1604901
目录
相关文章
|
2月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
111 4
Golang语言之管道channel快速入门篇
|
2月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
66 4
Golang语言文件操作快速入门篇
|
2月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
101 3
Golang语言之gRPC程序设计示例
|
2月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
86 4
|
2月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
50 4
Golang语言goroutine协程篇
|
2月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
51 3
Golang语言之Prometheus的日志模块使用案例
|
1月前
|
前端开发 中间件 Go
实践Golang语言N层应用架构
【10月更文挑战第2天】本文介绍了如何在Go语言中使用Gin框架实现N层体系结构,借鉴了J2EE平台的多层分布式应用程序模型。文章首先概述了N层体系结构的基本概念,接着详细列出了Go语言中对应的构件名称,包括前端框架(如Vue.js、React)、Gin的处理函数和中间件、依赖注入和配置管理、会话管理和ORM库(如gorm或ent)。最后,提供了具体的代码示例,展示了如何实现HTTP请求处理、会话管理和数据库操作。
30 0
|
2月前
|
Go
Golang语言错误处理机制
这篇文章是关于Golang语言错误处理机制的教程,介绍了使用defer结合recover捕获错误、基于errors.New自定义错误以及使用panic抛出自定义错误的方法。
46 3
|
2月前
|
Go
Golang语言之函数(func)基础篇
这篇文章深入讲解了Golang语言中函数的定义和使用,包括函数的引入原因、使用细节、定义语法,并通过多个案例展示了如何定义不返回任何参数、返回一个或多个参数、返回值命名、可变参数的函数,同时探讨了函数默认值传递、指针传递、函数作为变量和参数、自定义数据类型以及返回值为切片类型的函数。
59 2
Golang语言之函数(func)基础篇
|
2月前
|
Go
Golang语言之映射(map)快速入门篇
这篇文章是关于Go语言中映射(map)的快速入门教程,涵盖了map的定义、创建方式、基本操作如增删改查、遍历、嵌套map的使用以及相关练习题。
36 5