函数

什么是函数

开发中,如果多个程序都有同一段代码,我们可以将这部分代码剥离出来定义在程序的外面,使用的时候只需要调用这段代码。

那么,这一段代码的集合我们就称之为函数。

每一个函数都有一个名字,见名知意,我们调用函数就是使用函数名进行调用。

函数使用func关键字进行定义,所有的go程序都必须有一个main函数。

函数的特点

  • 首先,函数是一个类型,可以把一个函数赋给一个变量
  • 函数使用函数名()进行调用
  • 函数可以设定参数,调用时进行传递,完成业务逻辑,参数可以定义多个
  • go语言中函数支持多个返回值
  • 同一个包下函数名不能重复

函数的定义

格式:func 函数名(参数 参数类型) (返回值类型){}

使用:

package main

import "fmt"

func main() {
	
	test1(1)
	test2(1,"hello")
	test3(1,2)
	a := test4()
	fmt.Println(a)
	b,c:=test5()
	fmt.Println(b,c)
}
// 一个参数
func test1(a int)  {
	fmt.Println("一个参数",a)
}
// 两个不同类型的参数
func test2(a int,b string)  {
	fmt.Println("两个不同类型的参数",a,b)
}
// 两个相同类型的参数
func test3(a,b int)  {
	fmt.Println("两个相同类型的参数",a,b)
}
// 一个返回值
func test4() int {
	return 1
}
// 两个返回值
func test5() (int,int) {
	return 1,2
}

结果:

一个参数 1
两个不同类型的参数 1 hello
两个相同类型的参数 1 2
1
1 2

可变参数

当参数个数不固定时,可以使用变量名 ...变量类型

  • 可变参数个数不固定,一个函数只能有一个可变参数,且必须放在参数的最后。
  • 可变参数必须指明类型,如果类型不固定,使用go语言中any 或 interface{}类型指代所有类型
package main

import "fmt"

func main() {
	test1(1, 2, 3, 4, 5)
	test2(1, "2", 3, "4", true)
}
// 可变参数均为int类型
func test1(nums ...int) {
	fmt.Println(len(nums))
	for i := 0; i < len(nums); i++ {
		fmt.Println(nums[i])
	}
}
可变参数类型不固定
func test2(nums ...any) {
	fmt.Println(len(nums))
	for i := 0; i < len(nums); i++ {
		fmt.Println(nums[i])
	}
}

函数的作用域

  • 函数内部定义的变量称为局部变量,只能在函数体里使用。
  • 外部定义的变量称为全局变量,每个函数都可以调用。
  • 一个函数不能调用另一个函数的变量。
  • 如果局部变量和全局变量重名,优先使用局部变量,也就是函数体自己定义的变量
package main

import "fmt"

var num int = 1

func main() {
    test1()
    test2()
}

func test1() {
	fmt.Println(num)
}
func test2() {
    num := 100
    fmt.Println(num)
}

递归函数

函数自己调用自己的函数称之为递归函数

  • 递归函数需要有一个出口结束函数,否则会死循环

案例:计算n的阶乘,n* (n-1) * … * 1

package main

import "fmt"

func main() {
	sum := test(5)
	fmt.Println(sum)
}
func test(n int) int {
	if n == 1 {
		return 1
	} else {
		return n * test(n-1)
	}
}

结果为120

匿名函数

匿名函数就是没有名字的函数。

通常在{}后使用()直接进行调用或者赋给其它变量进行调用。

package main

import "fmt"

func main() {
	// 无参匿名函数,直接调用
	func(){
		fmt.Println("这是一个没有参数的匿名函数")
	}()
	// 有参匿名函数,直接调用
	func(a,b int){
		fmt.Println("有参数---",a,b)
	}(3,4)
	// 匿名函数赋值给变量f
	f := func(){
		fmt.Println("匿名参数赋给变量")
	}
	// 变量f的调用
	f()
}

回调函数

高阶函数:可以将一个函数作为另外一个函数的参数。

fun1()

fun2(fun1)

fun1 函数作为 fun2 函数的参数

fun2函数,叫做高阶函数,接收了另外一个函数作为参数的函数

fun1函数,叫做回调函数,作为另外一个函数的参数

函数的闭包

如果外层函数有内层函数,内层函数中,会操作外层函数的局部变量
并且该外层函数的返回值就是这个内层函数。
这个内层函数和外层函数的局部变量,统称为闭包结构。

局部变量的生命周期就会发生改变,正常的局部变量会随着函数的调用而创建,随着函数的结束而销毁
但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还在继续使用

package main

import "fmt"

var i int = 10

func main() {
	r1 := increment()
	fmt.Println(r1) // 返回的是一个 increment() 内存函数,还没有执行
	// -- 执行这个内层函数
	//
	v1 := r1()
	fmt.Println(v1)
	v2 := r1()
	fmt.Println(v2)
	fmt.Println(r1())
	fmt.Println(r1())
	fmt.Println(r1())
	// 你写的代码是对的,但是结果不对,你的变量被污染了
	fmt.Println("--------------------------")

	// r2和r1指向同一个地址
	r2 := increment() // 再次调用的时候 ,i = 0
	v3 := r2()
	fmt.Println(v3) // 1

	//因为我们内层还是用i,还存在引用,系统不会销货这个i,保护,单独作用r1
	fmt.Println(r1()) // 6 	// 这里的i 并没有随着 第二次创建就被销毁归0,而是在内层函数继续调用着。
	fmt.Println(r2()) // 2
	// r1 名字 ----> 内存地址 &r1
	fmt.Printf("%p\n", &r1)
	fmt.Printf("%p\n", &r2)

}

// 自增函数
// increment() 函数返回值为  func() int 类型
func increment() func() int { // 外层函数,项目(很多的全局变量)
	// 定义一个局部变量
	i := 0
	// 在外层函数内部定义一个匿名函数,给变量自增并返回。
	fun := func() int {
		i++
		return i
	}
	return fun
}

如果我们想使用闭包结构来解决全局变量污染的问题,那我们就可以写一个闭包结构来创建执行的函数。

通过这个闭包结构创建的函数内部的变量,都在这个函数中作用,不会和其他函数冲突。

**闭包结果的返回值是一个函数。**这个函数可以调用闭包结构中的变量。

defer延迟函数

  • 使用defer关键字修饰的函数,会在函数执行完成后进行调用。
  • 如果有多个defer,按照在程序中的顺序逆序执行,也就是先进后出。
  • defer修饰的函数不能使用函数声明位置之后定义的变量

defer的用途:

  • 关闭文件
  • 锁资源的释放
  • 数据库资源的释放