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
posted @ 2024-07-18 00:06  尹正杰  阅读(29)  评论(0编辑  收藏  举报