在编程世界中,函数是最根本的组成单元,程序都是由一个个函数组成,它封装逻辑,让程序员写出更加清晰、模块化和易于维护的代码。
在 golang里,函数的使用也跟大部分编程语言一样,但是作为最年青的高富帅(它诞生于二十一世纪,出生于高贵的google家族,所以,风云我将golang定义为最年青的高富帅 ^_^),golang中的函数又提炼出很多新的功能,比如:闭包,可变参和指针接收者。
一、首先,来看一下golang中函数的构成,一个函数主要包含以下几部分:
1、关键字 func:用于声明一个函数。
2、函数名:标识函数的名称,通常采用驼峰命名法(例如 UserInfo)。
3、参数列表:括号内定义函数接受的参数,包括参数名和类型,可以有多个参数,也可以无参数,还可以是无固定数量的参数列表。
4、返回值类型:指定函数返回的值类型,可以是单一类型或多个类型(用括号括起来)。如果没有返回值,可以省略。
5、函数体:用花括号 {} 包含的代码块,定义函数的具体实现逻辑。

二、函数的定义与声明
函数的基本语法如下:
func FunctionName(parameters) returnType {
// function body
}
func 关键字用于定义函数。
FunctionName 是函数名称。
parameters 是函数参数,类型在后。
returnType 是返回值类型,可以省略(表示无返回值)。
三、函数参数
函数可以接受多个参数,参数的类型在参数名后指定。
func multiply(a int, b int) int {
return a * b
}Go 不支持默认参数。但是golang有一个非常好用的功能,可变参数:允许函数接受任意数量的参数。这在处理参数数量不确定的情况下非常有用。
下面是使用可变参数的详细演示。
package main
import (
"fmt"
)
// 定义一个可变参数函数,三个点 ... 即为可变形参
func sum(numbers ...int) int {
total := 0
// 遍历可变参数
for _, number := range numbers {
total += number
}
return total
}
func main() {
// 调用可变参数函数
fmt.Println(sum(1, 2, 3)) // 输出: 6
fmt.Println(sum(10, 20, 30, 40)) // 输出: 100
fmt.Println(sum()) // 输出: 0
}func sum(numbers ...int) 定义了一个名为 sum 的函数,它接受一个可变数量的 int 类型参数,所有参数被收集到切片 numbers 中。注意,这些不同数量的参数必是同一类型,要实现不同数量,不同类型的参数,需要借助空接口来实现,后面interface部分再给大家展开。
遍历参数:使用 for _, number := range numbers 遍历所有传入的参数,并计算总和。
使用可变参数可以让函数更加灵活,能够处理不确定数量的输入。这在许多场景中都很有用,如计算和、连接字符串等。通过适当使用可变参数,可以编写更简洁和可维护的代码。
三、返回值
函数可以返回一个或多个值。
示例
func swap(a int, b int) (int, int) {
return b, a
}调用返回多个值
x, y := swap(1, 2)
四、函数作为参数
Go 支持将函数作为参数传递。
func operate(a int, b int, operation func(int, int) int) int {
return operation(a, b)
}
func main() {
result := operate(5, 10, add) // 使用 add 函数
fmt.Println(result) // 输出 15
}五、匿名函数
Go 支持匿名函数,可以在函数内部定义和调用。
func main() {
add := func(a int, b int) int {
return a + b
}
fmt.Println(add(3, 4)) // 输出 7
}六、闭包
闭包指的是一个函数可以“闭合”并保留其外部作用域的变量。在闭包中,函数可以访问并修改定义在其外部的变量,即使外部函数已经返回。这使得闭包能够维持状态并提供封装的功能。这为实现状态保持、回调以及函数工厂等模式提供了便利。
首先看一个简单示例,看它是如何捕获外部作用域的变量
func main() {
counter := 0
increment := func() int {
counter++
return counter
}
fmt.Println(increment()) // 输出 1
fmt.Println(increment()) // 输出 2
fmt.Println(increment()) // 输出 3
}闭包的特性
保持状态:闭包能够记住它创建时的环境状态。
访问外部变量:闭包可以访问外部函数中的变量。
灵活性:通过闭包,可以创建工厂函数和回调函数。
以下是一个简单的闭包示例:
package main
import "fmt"
// 定义一个外部函数
func adder() func(int) int {
sum := 0 // 外部变量
// 返回一个闭包
return func(x int) int {
sum += x
return sum
}
}
func main() {
// 创建一个闭包
add := adder()
fmt.Println(add(1)) // 输出: 1
fmt.Println(add(2)) // 输出: 3
fmt.Println(add(3)) // 输出: 6
}外部函数 adder:定义了一个外部函数,内部有一个变量 sum。
返回闭包:adder 函数返回一个匿名函数,这个匿名函数可以访问并修改外部变量 sum。
使用闭包:在 main 函数中,调用 adder 创建了一个闭包 add。每次调用 add 函数时,sum 的值会被更新并返回。
应用场景
状态管理:使用闭包可以创建有状态的函数。例如,可以实现累加器、计数器等。
回调函数:在处理异步操作或事件时,闭包可以用作回调函数,携带必要的上下文。
函数工厂:通过闭包,可以创建配置化的函数,灵活生成具有不同参数的功能函数。
七、方法
Go 语言中的方法是与类型关联的函数,定义时需要指定接收者。
例如
package main
import "fmt"
type Rectangle struct {
width int
height int
}
func (r Rectangle) area() int {
return r.width * r.height
}
func main() {
rect := Rectangle{width: 10, height: 5}
fmt.Println(rect.area()) // 输出 50
}八、方法的指针接收者
可以使用指针接收者来改变接收者的值。
示例
package main
import "fmt"
func (r *Rectangle) resize(newWidth int, newHeight int) {
r.width = newWidth
r.height = newHeight
}
func main() {
rect := Rectangle{width: 10, height: 5}
rect.resize(20, 10)
fmt.Println(rect.area()) // 输出 200
}九、函数的延迟调用
使用 defer 关键字可以延迟函数的执行,直到封闭的函数返回。在 Go 语言中,defer 用于注册一个延迟执行的函数。当包含 defer 语句的函数执行完毕后,所有的 defer 函数会按后进先出(LIFO)的顺序执行。
仔细看:
package main
import "fmt"
func main() {
// 定义一个延迟执行的函数,它将在程序结束之前执行
defer fmt.Println("Goodbye!")
fmt.Println("Hello!")
}输出
Hello!
Goodbye!
当多个 defer 语句可以在函数内定义,它们将按声明的逆序执行,符合上面讲的(LIFO)原则,我们再来看一个例子:
defer fmt.Println("First")
defer fmt.Println("Second")
defer fmt.Println("Third")
// 输出:
// Third
// Second
// First
//后定义的先执行
为什么要有这种设计呢,常用于资源管理和清理工作,确保在函数结束时执行特定的操作。主要作用包括几点:
资源清理回收:defer 常用于文件关闭、网络连接关闭等操作,确保即使出现错误也能正常清理资源。虽然golang是一款有GC的语言,但是在特定的情况下,某些资源还是需要程序员手动释放,类似互斥锁的关闭,chan的关闭,套接字的关闭等,defer可以在函数退出的时候来释放这些资源。
错误处理:在执行一系列操作后,可以在 defer 中处理错误或执行回调,确保所有必要的清理工作都已完成。如果代码中发生panic,则发生panic之后的代码就不会再被执行,但defer中的动作会被执行,所以通常可以在defer中对代码中的panic进行recover,使程序不会因为panic而异常退出,recover只有在defer中才有意义,如果在defer之外,发生panic之后,recover就不会被执行到了。
代码可读性:通过使用 defer,可以使代码更清晰,因为所有清理代码都集中在一起,便于维护。
注意事项
参数评估:defer 的参数在 defer 语句执行时就被评估,而不是在延迟函数执行时。例如:
func example() {
x := 10
defer fmt.Println(x) // 这里 x 的值是 10
x = 20
}在上面的代码中,defer 会输出 10 而不是 20。
性能影响:虽然 defer 提供了便捷性,但由于其在调用栈中有额外的开销,过多使用可能影响性能,尤其是在循环中。
defer 是 Go 语言中一个强大且灵活的特性,适用于资源管理和清理工作。通过使用 defer,可以确保在函数退出时执行必要的操作,从而提高代码的可读性和可靠性。正确使用 defer 可以帮助你写出更安全和易维护的代码。
十、递归函数
函数可以调用自身,形成递归。
如下:
package main
import "fmt"
func factorial(n int) int {
if n == 0 {
return 1
}
return n * factorial(n-1)
}
func main() {
fmt.Println(factorial(5)) // 输出 120
}Go 语言中的函数具有以下特性:
函数可以有多个参数和返回值。
支持函数作为参数和返回值。
支持匿名函数和闭包。
方法与类型关联,支持指针接收者。
支持延迟调用和递归。
这些特性使得 Go 语言的函数非常灵活和强大,适用于多种编程场景。
















