Golang语言之函数(func)进阶篇
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.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://www.cnblogs.com/yinzhengjie/p/18284904
2.日期和时间相关函数
推荐阅读:
https://www.cnblogs.com/yinzhengjie/p/18310657
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://www.cnblogs.com/yinzhengjie/p/18286812
3.3 错误处理机制
推荐阅读:
https://www.cnblogs.com/yinzhengjie/p/18314033
当你的才华还撑不起你的野心的时候,你就应该静下心来学习。当你的能力还驾驭不了你的目标的时候,你就应该沉下心来历练。问问自己,想要怎样的人生。
欢迎交流学习技术交流,个人微信: "JasonYin2020"(添加时请备注来源及意图备注)
作者: 尹正杰, 博客: https://www.cnblogs.com/yinzhengjie/p/18308556