跳到主要内容

函数

函数在Go语言中非常重要,在.go文件中,函数定义往往在第一层级。

函数的定义

函数包括func关键字、函数名、参数列表、返回值列表和函数体。

func 函数名(参数)(返回值) {
函数体
}

//
func getStudent(id int, classId int)(name string, age int) {
// 函数体
if id == 1 && classId == 1{
name = "bing"
age = 19
}
// 返回值
return name, age // 支持多个返回值
}

其中:

  • 函数名: 由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名不能重名
  • 参数: 由参数变量和其变量类型组成, 多个参数间用逗号分隔
  • 返回值: 由返回值和其变量类型组成, 也可只写返回值的类型, 多个返回值必须用()包括, 并用逗号分隔
  • 函数体: 显示功能的代码

函数的参数和返回值都是可选的。

小写字母开头的函数只在本包内可见,大写字母开头的函数可被其他包使用。

函数的参数

类型简写

函数的参数中如果相邻变量的类型相同,则可以省略类型。

func changeElement(a, b int) int {  // a b 都为int, 可以省略a的类型
return a + b
}

可变参数

可变参数是值函数的参数数量不固定。Go语言中的可变参数通过在参数后加...来标识。

可变参数要作为作为函数的最后一个参数。

package main

import "fmt"

func changeElement(a ...int) {
fmt.Printf("%T %v\n", a, a) // a是一个切片
}

func main() {
changeElement() // []int []
changeElement(1, 2) // []int [1 2]
changeElement(1, 2, 3) // []int [1 2 3]
}

本质上,函数的可变参数是通过切片来实现的。

参数传值类型

Go语言中,函数的参数在传入时使用的是完全复制的策略。Go语言中的数据类型分为值类型和指针类型,对应的函数中的参数也有值类型和指针类型之分。

package main

import "fmt"

func changeElement(arr [5]int) {
arr[0] = 100
}

func changeElementByPointer(arr *[5]int) {
arr[0] = 100
}

func main() {
arr := [5]int{1, 2, 3, 4, 5}
// 以值类型为参数, 尝试修改元素值
changeElement(arr)
fmt.Println(arr) // [1 2 3 4 5]

// 以指针类型为参数, 尝试修改元素值
changeElementByPointer(&arr)
fmt.Println(arr) // [100 2 3 4 5]
}

从输出结果可以看出,当参数是值类型时,无法修改原数组的内容。这是因为值类型是将原始的内容重新复制一份,在函数内部进行操作不会对原数组产生影响。与之相对的是,针对指针类型,复制到函数中的参数是数组的内存地址,参数和原始变量均指向同一内存地址,针对内存地址的操作均会反映到原数组中。

函数的返回值

Go语言函数的返回值有两个特点:一是其位置处于函数声明的最后; 二是允许出现多个返回值, 多个返回值必须使用()将所有返回值包括起来。

func sumElement(a, b int) (int, int) {
sum := a + b
sub := a - b
return sum, sub
}

函数定义时可以给返回值命名,并在函数体中直接使用这些变量。通过这种方式,可以省略return语句后的变量列表。

func sumElement(a, b int) (sum, sub int) {   // 返回值命名的变量在函数体中不需要在定义,直接赋值使用
sum = a + b
sub = a - b
return
}

函数的调用

定义函数后,可以通过函数名()的方式调用函数。

package main

import "fmt"

func sumElement(a, b int) (sum, sub int) {
sum = a + b
sub = a - b
return
}

func main() {
sumElement(5, 2) // 调用有返回值的函数可以不接收其返回值
m, b := sumElement(5, 2)
fmt.Println(m, b) // 7, 3
}

变量作用域

全局变量

全局变量是定义在函数外部的变量,其在整个程序运行期间都是有效的。

package main

import "fmt"

var x = "go"

func main() {
fmt.Println(x)
}

局部变量

局部变量有两种: 一是函数内定义的变量,只能在函数内使用。

package main

import "fmt"

var x = "go"

func main() {
var x = "python" // 当局部变量和全局变量同名时, 优先局部变量
fmt.Println(x) // python
}

二是语句块定义的变量,通常在流程控制语句上使用

package main

import "fmt"

func main() {
for i := 0; i < 10; i++ {
fmt.Println(i) // 变量 i 只能在for语句内使用
}
}

函数的转换

函数作为变量使用

Go语言中支持将函数赋值给变量,让后通过变量进行调用。

package main

import "fmt"

func add(a, b int) int {
return a + b
}

func main() {
c := add // 将函数add()赋值给变量c
fmt.Printf("%T\n", c) // func(int, int) int
// 利用变量c来调用函数add()
fmt.Println(c(1, 2)) // 3
}

函数类型的定义

使用 type 关键字来定义一个函数类型

type F1 func(int, int) int

上面语句定义了一个 F1 函数类型,这种函数类型接收2个int类型的参数,并有一个int类型的返回值。

此时,遵循函数类型F1的格式,写一个具体函数

func A1(a, d int) int {
return a + b
}

A1函数非常简单,同样接收2个int类型参数,返回一个int类型的两个参数相加值。

定义一个变量的类型是F1的,其初始化值为A1

func main() {
var a F1 // a声明为函数类型F1
a = A1 // 将函数A1()赋值给变量a
// 利用变量a来调用函数A1()
fmt.Println(a(2, 2)) // 4
}

函数作为参数

package main

import "fmt"

func add(a, b int) int {
return a + b
}

func calc(a, b int, op func(int, int) int) int {
return op(a, b) // 相当于执行add(1,2)
}

func main() {
c := calc(1, 2, add) // 将add()函数作为参数传入
fmt.Println(c) // 3
}

函数作为返回值

package main

import (
"errors"
"fmt"
)

func sum(a, b int) int {
return a + b
}

func sub(a, b int) int {
return a - b
}

func operator(s string) (func(int, int) int, error) {
switch s {
case "+":
return sum, nil
case "-":
return sub, nil
default:
err := errors.New("invalid operation")
return nil, err
}
}

func main() {
add, ok := operator("+") // 调用operator函数,返回sum函数赋值给add
if ok == nil {
fmt.Printf("%T\n", add) // func(int, int) int
fmt.Println(add(2, 2)) // 4
}
}

匿名函数

函数在调用时,最主要的标识就是其名称。而在Go语言中,还存在着没有名称的函数,这就是匿名函数。

匿名函数因为没有函数名,它的调用需要保存到某个变量或作为立即执行的函数。

package main

import "fmt"

func main() {
// 将匿名函数赋值给变量
add := func(a, b int) {
fmt.Println(a + b)
}
add(1, 2) // 通过变量调用匿名函数

// 自执行的函数 匿名函数定义完加()直接执行
func(a, b int) {
fmt.Println(a + b)
}(1, 2)
}

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

闭包

在 Go 语言中,闭包是指一个函数可以捕获并记住其外部环境中的变量,即使这个函数在外部环境之外执行,依然可以访问这些变量。闭包允许函数在其定义的上下文之外继续使用那些在定义时可见的变量。

闭包的特点:

  1. 捕获外部变量
    • 闭包可以访问和操作它在定义时所处环境中的变量,即使这些变量的作用域在闭包函数之外
  2. 状态保持
    • 闭包可以保持它捕获的变量的状态。每次调用闭包时,它访问的变量时之前调用的结果
  3. 匿名函数
    • 闭包通常是匿名函数。
package main

import "fmt"

func main() {
// 闭包函数,返回一个函数,并且捕获了外部变量 x
adder := func(x int) func(int) int {
sum := x
return func(y int) int {
sum += y
return sum
}
}

// 创建两个闭包实例
a := adder(10)
b := adder(100)

fmt.Println(a(1)) // 输出 11
fmt.Println(a(2)) // 输出 13
fmt.Println(b(1)) // 输出 101
fmt.Println(b(2)) // 输出 103
}

在上面示例中:

  • adder 函数返回一个匿名函数,这个匿名函数捕获了 adder 函数中的 sum 变量
  • 调用 adder(10) 创建了一个闭包,并且将 sum 初始化为 10。之后调用这个闭包(a(1)a(2)),它会累加传递给它的值并返回
  • 调用 adder(100) 创建了另一个独立的闭包,它的 sum 变量与第一个闭包中的 sum 变量是独立的

应用场景:

  • 延迟求值:闭包可以将计算推迟到函数调用时进行。

  • 状态保持:可以通过闭包实现函数内部的状态保持,例如在实现计数器、累加器时。

  • 回调函数:在异步操作或事件驱动的编程中,闭包常用于回调函数的定义。