13 go语言(golang) - 函数
函数
在 Go 语言中,函数是代码的基本组织单元。它们用于执行特定任务,并且可以接收输入参数和返回输出结果。
函数是一种一等公民(first-class citizen),这意味着函数可以像其他任何值一样被使用和操作。函数可以被赋值给变量,作为参数传递给其他函数,也可以作为其他函数的返回值。
1、函数定义
一个简单的函数定义包括关键字 func
、函数名、参数列表、返回值列表(可选)以及函数体。
func 函数名(参数列表) 返回值类型 {
函数体
}
- func:用于声明一个新函数。
- 函数名:这是函数的名称。
- 参数列表:表示该函数接的参数。
- 返回值类型:表示该函数返回的结果类型,没有返回时可以省略。
- 函数体:包含了实际执行操作的代码块。
2、参数与返回值
2.1 多参数与多返回值
Go 支持多个参数和多个返回值:
func add(a int, b int, c int, d int) (int, int) {
x := a + b
y := c + d
return x, y
}
func Test1(t *testing.T) {
println(add(1, 2, 3, 4))
}
2.2 命名返回值
可以为返回值命名,这样在使用时可以直接对其进行赋值,不需要显式地使用 return value1, value2...
func add2(a int, b int, c int, d int) (x int, y int) {
x = a + b
y = c + d
return // 隐式地以命名变量作为结果进行return
}
func Test2(t *testing.T) {
println(add2(1, 2, 3, 4))
}
2.3 可变参数
使用省略号语法来处理不定数量的输入参数:
// 省略变量名,不会报错,但是也用不到变量了,没有意义
//
// func add3(...int) int {
// return 1
// }
func add3(numbers ...int) int {
var result int
for _, number := range numbers {
result += number
}
return result
}
func Test3(t *testing.T) {
println(add3(1, 2, 3, 4))
}
3、函数作为一等公民
Go 中,函数是一等公民意味着函数可以像任何其他值一样被传递和操作,可以赋给变量,也可以作为其他函数的参数或从其他函数中返回。
3.1 将函数作为变量存储
func sayHi(name string) {
fmt.Println("你好:" + name)
}
func Test4(t *testing.T) {
// 将函数作为变量存储
f1 := sayHi
f1("小明")
// 匿名函数
f2 := func(name string) {
fmt.Println("你好:", name)
}
f2("小明") // 调用匿名函数
}
2. 将函数作为参数传递
// 定义一个函数,接受一个函数作为参数
func proxy(name string, f func(string)) {
f(name)
}
func Test5(t *testing.T) {
// 定义一个函数
sayHi := func(name string) {
fmt.Println("Hello,", name)
}
// 将函数作为参数传递
proxy("小明", sayHi)
}
3. 从其他函数返回函数
// 定义一个函数,返回一个函数
func createSayHi() func(string) {
return func(name string) {
fmt.Println("Hello,", name)
}
}
func Test6(t *testing.T) {
// 从函数中返回函数,并将其存储在变量中
greet := createSayHi()
greet("小明") // 调用返回的函数
}
4. 简单应用
简单利用execSql类似代理模式,只需要传入一个参数和一个函数,他就帮你执行并返回结果
func selectTableA(condition string) string {
return "select * from table_a where " + condition
}
func selectTableB(condition string) string {
return "select * from table_b where " + condition
}
func execSql(sql string, f func(string) string) string {
return f(sql)
}
func Test7(t *testing.T) {
println(execSql("1=1", selectTableA))
println(execSql("id = 1", selectTableB))
}
输出
select * from table_a where 1=1
select * from table_b where id = 1
解耦逻辑:通过将具体操作(如 SQL 查询构建)封装在独立函数中,可以使 execSql
本身保持简单和通用,而不需要关心具体执行细节。
灵活性:可以很容易地替换或添加新的行为,只需定义新的处理函数并传递给 execSql
即可。这种方式允许在运行时动态决定使用哪种处理逻辑。
复用性:由于 execSql
是通用的,它可以与任何符合签名要求的函数一起使用,从而提高代码复用率。
4、闭包
闭包是一个函数,它可以捕获并“记住”其所在作用域中的变量,即使这个函数在其原始作用域之外被调用。通俗地说,闭包让你能够创建一个“带着环境”的函数,这个环境包括了该函数定义时的所有局部变量。
func adder() func(int) int {
sum := 0
// 使用了匿名函数外部的变量sum
return func(x int) int {
sum += x
return sum
}
}
func Test8(t *testing.T) {
/*
想象一下,你有一个工厂(外部函数:adder),它生产玩具(内部函数)。
每个玩具都有一些特定的零件(外部变量:sum)。即使这个玩具被带到其他地方去,它仍然携带着这些零件,并且可以使用它们。
这就是闭包的概念:一个内部函数加上它所需要的外部变量。
*/
funcTest := adder()
fmt.Println(funcTest(1)) // 输出: 1,因为sum从0开始,加上1后变成1。
fmt.Println(funcTest(2)) // 输出: 3,因为sum现在是1,再加上2后变成3。
fmt.Println(funcTest(3)) // 输出: 6,因为sum现在是3,再加上3后变成6。
println(adder()(2)) // 输出: 2,因为sum从0开始,加上2后变成2。
}
adder
是一个工厂,它返回一个匿名内部函数。- 内部匿名函数使用了
adder
函数中的局部变量sum
,这就是所谓的“捕获”或“记住”。 - 即使在
Test8
函数中调用返回的匿名内部函数,这些捕获到的变量依然存在并保持状态。
5、defer
defer
语句用于延迟执行一个函数或方法,直到包含该 defer
的函数返回时才会被调用。它通常用于确保资源的释放、文件的关闭、解锁互斥锁等清理操作,即使在函数执行过程中发生错误,也能保证这些操作被执行。
- 当一个函数遇到
defer
语句时,它会将后面的函数调用推迟到当前函数结束时再执行。 - 多个
defer
语句按照先进后出的顺序(LIFO)进行执行。 defer
后面必须跟一个函数调用,不能是表达式。- 参数评估:
defer
调用的函数的参数在defer
语句执行时就被评估了,而不是在函数实际执行时。
func Test9(t *testing.T) {
a := 1
//defer b := 2 // Expression in defer must be a function call,defer后面必须是函数调用
defer fmt.Println("执行defer1") // defer是先进后出,所以这个是最后执行
defer fmt.Println("执行defer2")
defer fmt.Println(a) // 在此时,捕获了当前值的变量 a,即捕获值为 1。
a = 2
defer fmt.Println(a) // 此时捕获的是修改后的值,即捕获值为 2。
fmt.Println("正常执行")
}
输出:
正常执行
2
1
执行defer2
执行defer1
使用场景
- 文件处理:打开文件后立即使用
defer
确保文件关闭。 - 互斥锁:加锁后使用
defer
确保解锁。 - 数据库连接:打开连接后使用
defer
确保关闭。
6、自执行函数
自执行函数(Immediately Invoked Function Expression,IIFE)是一种设计模式,指在定义后立即执行的函数。它通常用于创建一个新的作用域,以避免变量污染全局命名空间。
- 立即执行:一旦定义,就会立即被调用,而不需要显式地调用。
- 独立作用域:自执行函数创建了自己的作用域,因此内部变量不会影响外部环境。
- 常用于封装:可以用来封装代码块,避免与其他代码发生冲突,特别适合需要模块化和隔离逻辑片段时使用。
func Test10(t *testing.T) {
// 定义并立即调用一个匿名函数
result := func(a int) int {
b := 100
return a + b
}(123)
fmt.Println(result) // 223
// 后续如果尝试访问b会导致错误,因为b仅在匿名函数内有效
}