函数进阶

一 函数类型

go语言中函数是可以复制给一个变量的,变量又必须要有确定的类型,该类型就是函数类型

使用type定义函数类型,函数类型就是值该函数的传参与返回值的格式

type 类型名 func(参数类型列表) 返回值类型列表
  • func: 关键字
  • type: 关键字,用于重新命名类型(自定义类型)

函数名是保存的函数的入口地址,所以函数类型实际上是一个指针类型

package main

import "fmt"

func calc(x, y int) (sum, sub int) {
	sum = x + y
	sub = x - y
	return
}
type funcType func(int, int)(int, int)  // 定义一个函数类型

func main() {
	var ca funcType  // 定义一个函数类型变量
	ca = calc  // 给变量复制
	x, y := 30, -10
	x, y = ca(x, y)  // 调用函数
	fmt.Println(x, y) 
}
  • 函数类型是确定函数的传参和返回值的格式
  • 如果格式一致是指传参和返回值的类型都是一样的。

二 作用域

go语言中,变量存在于不同的作用域中,变量的查找先冲局部变量查找到全局变量

2.1 局部作用域

go语言中区别作用域的是{},因此ifforswitch和函数都会新建
局部作用域,局部作用域中变量只能在该作用域中使用,出了该区域变量立即被销毁。

  • 对于局部作用域,变量的适用范围是在其定义之后。

2.2 全局作用域

函数外部的空间被整个工程全局捕获。所以只要是在函数外的区域都是全局作用域。
在全局作用域中的变量,在任何位置都是可以访问和修改。

  • 在全局作用域中的变量,无论在那个函数前后定义,整个工程项目的所有文件都能访问
  • 全局变量是可以获取地址的
package main

import "fmt"

func test() {
	var c = 20  // c是局部变量,只能在函数test中使用
	fmt.Println(c)
}
func main() {
	fmt.Println(a)
	a = 20  // 函数内部可以修改全局变量
	fmt.Println(a)
	//fmt.Println(c) // 无法访问不变量c

	//fmt.Println(b)  // 无法访问变量b
	b := 30
	fmt.Println(b)  // 变量定义之后才能访问
}

var a int = 10  // 全局变量

三 函数作为参数和返回值

由于go语言中的函数作为一等公民,具有被变量接收的性质,因此函数可以作为
函数的参数,和函数的返回值

3.1 函数作为参数

函数当作参数传递到函数内部,实现函数回调。回调函数是指函数函数的调用取决于另一个
函数的逻辑。

示例,计算器程序

package main

import "fmt"

func plus(a...int) int {
	sum := 0
	for _, v := range a {
		sum += v
	}
	return sum
}


func minus(a ... int) int {
	return a[0] - a[1]
}

func calculator(option func(...int) int, a...int) int {
	return option(a...)
}

func main() {
	result := calculator(plus, 1,2,3,4)
	fmt.Println(result)
	result = calculator(minus, 5, 4)
	fmt.Println(result)
}

3.2 函数作为返回值

go语言中函数是可以作为返回值进行返回的

package main

import (
	"fmt"
)

func plus(a ...int) int {
	sum := 0
	for _, v := range a {
		sum += v
	}
	return sum
}

func minus(a ...int) int {
	return a[0] - a[1]
}

func calculator(option func(...int) int, a ...int) (int, func(...int) int) {
	return option(a...), option
}

func main() {
	result, option := calculator(plus, 1, 2, 3, 4)
	fmt.Println(result, option)
	result, option = calculator(minus, 5, 4)
	fmt.Println(result, option)
}

四 匿名函数

函数可以作为返回值,但是在Go语言中函数内部不能再像之前那样定义函数了,
只能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数的定义格式如下

func(参数)(返回值){
    函数体
}
  • func: 关键字

匿名函数因为没有函数名,所以没办法像普通函数那样调用,
所以匿名函数需要 保存到某个变量 或者 作为立即执行函数

package main
func function1(a int, b int) int {
	
	f := func(x int, y int) int {
		return x + y
	}  // 匿名函数
	
	return f(a, b)
}

func function2(a int, b int) int {

	f := func(x int, y int) int {
		return x + y
	}(a, b)  // 定义匿名函数,立即执行
	return f
}

匿名函数多用于实现回调函数和闭包

对于无参数无返回值且直接调用的匿名函数可以直接使用一对{}包裹即可

package main

import "fmt"

func main() {
	{
		fmt.Println("hello 匿名函数1")
	}

	func () {
		fmt.Println("hello 匿名函数2")
	}()

}

五 闭包函数

闭包指的是一个函数和与其相关的引用环境组合而成的实体。
简单来说,闭包=函数+引用环境

package main
// 该函数就是一个闭包函数
func adder() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}

闭包其实并不复杂,只要牢记闭包=函数+引用环境

闭包应用示例: 装饰器

package main

import "fmt"

func f1(f func()){
	fmt.Println("this is function f1")
	f()
}

func f2(x, y int)  {
	fmt.Println("this is function f2")
	fmt.Println(x + y)
}

// 将f2函数在f1函数中进行调用,需要对f2函数进行如下定制
func f3(f func(int, int), x, y int) func() {
	/**
	闭包函数。装饰器
	 */
  return func() {
    f(x, y)
  }
}
// 也可以对函数f2进行如下包装 
func f4(f func(int, int)) func(int, int) func(){
  ret := func(x, y int) func() {
    return func() {
      f(x, y)
    }
  }
  return ret
}

func main() {
  function := f3(f2, 10, 20)
  f1(function)
  f := f4(f2)

  ff := f(10, 20)
  f1(ff)
}

六 递归函数

递归函数是指一个函数的函数体直接或间接调用了该函数自身.
递归函数调用执行过程分为两个阶段:

  • 递推阶段: 从原问题出发, 按递归公式递推. 从未知到已知, 最终达到递归终止条件.
  • 回归阶段: 按递归终止条件求出结果, 逆向逐步带入递归公式, 回归到原问题求解.

求一个数的阶乘

package main
func factorial(n int64) (result int64) {
	if n < 1 {
		result = 1
	} else {
		result = n * factorial(n - 1)
	}
	return
}
  • 递归函数在执行是需要有一个出口,没有出口的递归函数为死递归。

求斐波拉且数列,并使用数组保存过程值

package main

var count [1000] int64

func fibolach(x int) int64 {
	if x == 1 || x == 2 {
		return 1
	} else if count[x - 1] != 0 && count[x - 2] != 0 {
		return count[x - 1] + count[x - 2]
	} else {
		count[x - 1] = fibolach(x - 1)
		count[x - 2] = fibolach(x - 2)
		return count[x - 1] + count[x - 2]
	}
}

七 defer延迟调用

Go语言中的defer语句会将其后面跟随的语句进行延迟处理。
defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,
也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行

go中go func括号带参数 go语言 func_lua

package main

import "fmt"

func main() {
	fmt.Println("start")
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println("end")
}

面试题

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
}
  • 由于calc(“A”,x,y)和calc(“B”,x,y)没有被defer修饰。所以按顺序最先调用。
  • defer延迟调用,当要执行defer时,被defer修饰的函数的所有参数值都确定。
  • 最后被defer修饰的函数最先调用。
  • 最后的调用输出结果为
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4

工程管理

为了更好的管理工程中的文件,go语言建立了如下几个文件夹

  1. src: 源码文件夹,用于保存go语言的源码
  2. pkg: 存放代码包
  3. bin: 存放可执行文件

分文件编程(多个源文件)必须放在src目录下

一 同级目录文件

同级别目录下的文件,package指定的包必须是相同的

同级目录文件,调用别的文件的函数,直接调用即可,无需包名引用

二 不同目录文件

一个工程可能不只一个包,不同包存放在不同的文件夹中。

当要在其他包中使用时,一定要进行导包操作。

对于需要在其他包总调用的函数,需要大写首字母

不同的目录下,不能有起相同的报名

随机数

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano())// 设置随机数种子, 如果随机数种子不变,程序每次产生的随机数将会是一致的

	// 产生随机数
	fmt.Println(rand.Int()) // 产生整型随机数
	fmt.Println(rand.Intn(10))  // 产生10以内的随机数

}