当前位置: 首页 > news >正文

go/函数

go/函数

函数定义

func 函数名(参数)(返回值){函数体
}
- 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
- 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用`,`分隔。
- 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用`()`包裹,并用`,`分隔。
- 函数体:实现指定功能的代码块。
函数可以接收参数,参数类型在参数名称后面指定,可以指定多个参数。func add(a int, b int) int {return a + b
}// 调用函数
result := add(5, 7)
fmt.Println(result) // 输出: 12

函数可以有返回值,可以有多个返回值。返回值类型在参数列表后面指定。

func swap(x, y int) (int, int) {return y, x
}a, b := swap(3, 4)
fmt.Println(a, b) // 输出: 4 3

命名返回值

可以为返回值命名,这样可以在函数体内直接使用该名称。

func divide(x, y float64) (result float64) {if y != 0 {result = x / y}return // 使用命名返回值
}

函数可以返回切片(slice)作为返回值,切片是一种动态大小的数组,可以方便地处理集合类型的数据。

package mainimport "fmt"// 定义一个返回切片的函数
func generateNumbers(n int) []int {numbers := make([]int, n) // 创建一个长度为n的切片for i := 0; i < n; i++ {numbers[i] = i + 1 // 填充切片}return numbers // 返回切片
}func main() {result := generateNumbers(5) // 调用函数并获取返回的切片fmt.Println("Generated Numbers:", result) // 输出: Generated Numbers: [1 2 3 4 5]
}

可变参数函数

可以使用…语法定义可变参数函数,以便接受任意数量的参数。

func sum(nums ...int) int {total := 0for _, num := range nums {total += num}return total
}totalSum := sum(1, 2, 3, 4) // 接受多个参数
fmt.Println(totalSum) // 输出: 10

函数的可变参数实际上是通过切片实现的。

使用…语法来定义可变参数时,Go会将传入的多个参数收集到一个切片中。

从技术上讲,这使得函数内部可以像处理切片一样处理这些参数

传递切片
另外,如果你已经有一个切片,并希望将其作为可变参数传递给函数,可以使用展开操作符…:

func main() {slice := []int{1, 2, 3, 4, 5}result := sum(slice...) // 使用 ... 来展开切片fmt.Println("Sum:", result) // 输出: Sum: 15
}

函数类型与变量

1. 定义函数类型

  1. 我们可以使用type关键字来定义一个函数类型,具体格式如下:
package mainimport ("fmt"
)// 定义一个函数类型
type IntOperation func(int, int) int// 定义两个具体的函数符合这个类型
func add(a int, b int) int {return a + b
}func multiply(a int, b int) int {return a * b
}
在这个例子中,IntOperation是一个函数类型,它接受两个int参数并返回一个int

2. 使用函数类型

定义完函数类型后,你可以使用这个类型来声明变量,并将具体的函数赋给这些变量。

func main() {// 声明一个变量,类型为 IntOperationvar operation IntOperation// 将具体的函数赋值给该变量operation = addfmt.Println("Addition:", operation(5, 3)) // 输出: Addition: 8// 重新赋值为另一个函数operation = multiplyfmt.Println("Multiplication:", operation(5, 3)) // 输出: Multiplication: 15
}

3. 函数作为参数

你还可以将你定义的函数类型作为参数传递给其他函数,增加了函数的灵活性。

func performOperation(a int, b int, op IntOperation) int {return op(a, b) // 调用传入的函数
}func main() {result := performOperation(5, 3, add)fmt.Println("Result of addition:", result) // 输出: Result of addition: 8result = performOperation(5, 3, multiply)fmt.Println("Result of multiplication:", result) // 输出: Result of multiplication: 15
}

4. 函数作为返回值

函数类型也可以用作返回值,这样你可以返回一个函数。

func createOperator(opType string) IntOperation {if opType == "add" {return add}return multiply
}func main() {operation := createOperator("add")fmt.Println("Result of addition:", operation(5, 3)) // 输出: Result of addition: 8operation = createOperator("multiply")fmt.Println("Result of multiplication:", operation(5, 3)) // 输出: Result of multiplication: 15
}

匿名函数和闭包

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

匿名函数是指没有名称的函数。它可以被赋值给变量、作为参数传递或作为返回值返回。匿名函数非常适合用于一次性执行的操作。

package mainimport "fmt"func main() {// 定义一个匿名函数并立即调用func() {fmt.Println("This is an anonymous function.")}() // 立即调用// 将匿名函数赋值给变量greet := func(name string) {fmt.Printf("Hello, %s!\n", name)}greet("Go") // 调用赋值的匿名函数
}

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

闭包是一个函数值,它引用了其定义范围内的变量。

当一个函数被嵌套在另一个函数中时,外部函数的作用域(包括其中变量)可以被内部的匿名函数所访问。

闭包可以记住并访问它创建时的环境。

package mainimport "fmt"// 生成闭包的函数
func makeCounter() func() int {count := 0// 返回一个匿名函数,形成闭包return func() int {count++ // 访问外部变量return count}
}func main() {counter := makeCounter() // 创建一个闭包fmt.Println(counter()) // 输出: 1fmt.Println(counter()) // 输出: 2fmt.Println(counter()) // 输出: 3// 每次调用 counter() 时,都能访问并修改 count 变量
}
func makeSuffixFunc(suffix string) func(string) string {return func(name string) string {if !strings.HasSuffix(name, suffix) {return name + suffix}return name}
}
makeSuffixFunc是一个高阶函数,接受一个字符串类型的参数suffix,并返回一个匿名函数。
返回的匿名函数也接受一个字符串类型的参数name,并检查这个名字是否已经带有指定的后缀。
使用strings.HasSuffix函数来检查name是否以suffix结尾。如果没有结尾,则在name后添加suffix。func main() {jpgFunc := makeSuffixFunc(".jpg")txtFunc := makeSuffixFunc(".txt")fmt.Println(jpgFunc("test")) //test.jpgfmt.Println(txtFunc("test")) //test.txt
}
jpgFunc和txtFunc是通过调用makeSuffixFunc函数创建的两个闭包,分别用于处理.jpg和.txt后缀。
当调用jpgFunc("test")时,它返回了"test.jpg",而调用txtFunc("test")返回了"test.txt"。
这说明这些闭包记住了它们创建时的特定后缀。

calc函数接受一个整数参数base,并返回两个函数:一个用于加法add,一个用于减法sub。
匿名函数add接收一个参数i,将其添加到base上,并返回新的base值。
匿名函数sub接收一个参数i,从base中减去它,并返回新的base值。
这两个函数都形成闭包,能够访问并修改base变量。func calc(base int) (func(int) int, func(int) int) {add := func(i int) int {base += ireturn base}sub := func(i int) int {base -= ireturn base}return add, sub
}在main函数中,调用calc(10),并将返回的两个函数赋值给f1和f2。
f1和f2分别用于加法和减法。
当调用f1(1)时,base从10变为11,并返回11。
当调用f2(2)时,base在减去2后变为9,并返回9。
重复的调用显示了base的状态是持续的,第一次调用后base就会更新到新的值,这正是闭包的作用。func main() {f1, f2 := calc(10)fmt.Println(f1(1), f2(2)) //11 9fmt.Println(f1(3), f2(4)) //12 8fmt.Println(f1(5), f2(6)) //13 7
}

defer语句 后进先出(LIFO)

Go语言中的defer语句会将其后面跟随的语句进行延迟处理。

在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,

先被defer的语句最后被执行,最后被defer的语句,最先被执行。

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

由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。

当你使用defer关键字时,它会将随后的函数调用推迟到包含它的函数执行完毕后再执行。

无论函数是正常返回还是因错误而提前返回,defer语句所指向的函数都会被调用。

defer与函数参数

defer注册要延迟执行的函数时该函数所有的参数都需要确定其值

func main() {x := 10defer fmt.Println("Value of x:", x) // 这里的x会在defer定义时被捕获x = 20fmt.Println("Main function is executing")
}
Main function is executing
Value of x: 10

例题1

package mainimport "fmt"// 函数 f1 返回 int 类型
func f1() int {x := 5 // 定义局部变量 x,初始化为 5// defer 语句,定义一个匿名函数,在 f1 返回之前执行defer func() {x++ // 这个函数会在 f1 返回后执行,增加 x 的值}()return x // 返回 x 的当前值,即 5
}x初始化为5,然后执行defer语句,该匿名函数会在函数执行完毕返回值前被调用。
由于return x语句会在 defer 执行之前完成,所以最终 f1 的返回值是 5。
defer 中的函数会在返回值已确定之后再执行,因此并不会影响函数的返回结果。// 函数 f2 返回一个命名返回值 x 的 int 类型
func f2() (x int) {// defer 语句,定义一个匿名函数,在 f2 返回之前执行defer func() {x++ // 这个函数会在 f2 返回后执行,增加 x 的值}()return 5 // 返回值 x 的当前值会被赋为 5
}
这里,x 是一个命名返回值,默认初始化为 0。
当执行 return 5 时,x 的值被赋为 5。然后,defer 中的匿名函数会在 f2 返回之前执行,增加 x 的值。
因此,f2 最终返回 6// 函数 f3 返回一个命名返回值 y 的 int 类型
func f3() (y int) {x := 5 // 定义局部变量 x,初始化为 5// defer 语句,定义一个匿名函数,在 f3 返回之前执行defer func() {x++ // 这个函数会在 f3 返回后执行,增加 x 的值}()return x // 返回 x 的当前值,即 5
}
这里,局部变量 x 初始化为 5,而命名返回值 y 默认初始化为 0。
当调用 return x 时,y 将得到 x 的值,也就是 5。
defer 中的函数同样会在 f3 返回之前执行,但它增加的是 x 的值而不是 y,这意味着 y 最终仍然是 5// 函数 f4 返回一个命名返回值 x 的 int 类型
func f4() (x int) {// defer 语句,定义一个匿名函数,并将 x 作为参数传递defer func(x int) {x++ // 这个函数会在 f4 返回后执行,增加参数 x 的值}(x) // 在这里,传递的是函数开头 x 的初始值return 5 // 返回值 x 的当前值会被赋为 5
}
在 f4 中,命名返回值 x 默认初始化为 0return 5 会把 x 赋值为 5。
然而,在 defer 中传递的是 x 的初始值(在函数开始时的状态),即在 defer 执行时会增加传递的值,而不会影响命名返回值 x。
因此,最终返回的值仍然是 5// 主函数
func main() {// 输出 f1 的返回值fmt.Println(f1()) // 输出: 5// 输出 f2 的返回值fmt.Println(f2()) // 输出: 6// 输出 f3 的返回值fmt.Println(f3()) // 输出: 5// 输出 f4 的返回值fmt.Println(f4()) // 输出: 5
}

例题2

package mainimport "fmt"// 定义一个计算与输出函数
func calc(index string, a, b int) int {ret := a + b // 计算 a + bfmt.Println(index, a, b, ret) // 打印当前的 index 和计算结果return ret // 返回计算结果
}func main() {x := 1 // 初始化变量 x 为 1y := 2 // 初始化变量 y 为 2// 使用 defer 语句,调用 calc 函数defer calc("AA", x, calc("A", x, y)) x = 10 // 将 x 改为 10// 使用 defer 语句,调用 calc 函数defer calc("BB", x, calc("B", x, y))y = 20 // 将 y 改为 20
}
执行过程
首先,在 main 函数开始时,x 初始化为 1,y 初始化为 2。第一个 deffered 调用:defer calc("AA", x, calc("A", x, y)) 被调用时,由于 defer 的特性,它会在 main 函数返回之前执行。
在执行 defer 语句时,calc("A", x, y) 会立即被调用,计算 calc("A", 1, 2):
此时 x=1 和 y=2,计算结果 1 + 2 = 3。
输出: A 1 2 3
函数 calc 返回值 3,因此 defer 语句会变为 defer calc("AA", x, 3)。
修改 x:接下来,x 被改为 10。当前状态为 x=10 和 y=2。
第二个 deffered 调用:defer calc("BB", x, calc("B", x, y)) 在此时被调用:
calc("B", x, y) 立即被调用,计算 calc("B", 10, 2):
此时 x=10 和 y=2,计算结果为 10 + 2 = 12。
输出: B 10 2 12
函数 calc 返回值 12,所以第二个 defer 语句变为 defer calc("BB", 10, 12)。
修改 y:最后,y 被改为 20,但这不会影响已经保存的 defer。
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4

异常机制

1. panic

panic 函数用于触发一个运行时错误,导致程序的控制流转向当前执行栈的顶部。

通常,使用 panic 来表示不可恢复的错误,例如数组越界或访问不存在的元素。

package mainimport "fmt"func causePanic() {panic("This is a panic!") // 触发一个 panic
}func main() {fmt.Println("Program started")causePanic() // 调用会导致 panic 的函数fmt.Println("This line will not be reached") // 这行不会被执行
}
Program started
panic: This is a panic!

在触发 panic 后,程序的控制流会转到调用栈的顶部,并且不会执行 causePanic 后面的任何代码。

2. recover

recover 是用来恢复因 panic 导致的程序崩溃的机制。它通常与 defer 结合使用,可以用于捕获并处理 panic。

当 recover 被调用时,它会取得 panic 传递的信息,并且程序的控制流将恢复到 recover 之后的代码。

程序在调用 causePanic 时触发了 panic,但随后通过 recover 捕获了异常,使得程序没有崩溃。package mainimport "fmt"// causePanic 函数定义
func causePanic() {panic("This is a panic!") // 触发一个 panic,程序将会崩溃,并输出信息
}// recoverFromPanic 函数定义
func recoverFromPanic() {// 使用 recover 函数捕获 panic,如果没有发生 panic,r 会是 nilif r := recover(); r != nil {fmt.Println("Recovered from panic:", r) // 如果捕获到 panic,打印出 panic 的信息}
}func main() {defer recoverFromPanic() // 使用 defer 注册 recoverFromPanic 函数,以便在 main 函数结束前调用fmt.Println("Program started") // 程序开始,输出提示信息causePanic() // 调用会导致 panic 的函数,这里会触发 panicfmt.Println("This line will not be reached") // 这行不会被执行,因为发生了 panic
}Program started
Recovered from panic: This is a panic!

3. 使用场景

当遇到不应被恢复的严重错误时,可以使用 panic,如出错状态、调用运行时错误或者不可能完成的操作。

使用 recover 处理 panic,可以防止应用程序崩溃,允许程序继续执行后续的逻辑。通常会在库或复杂的应用程序中用到。

Go语言的异常处理机制通过 panic 和 recover 提供了一种简单而有效的错误处理方式。

使用 panic 可以指示程序发生了严重错误,而使用 recover 则能够在这种情况下允许程序恢复并继续运行。

defer 是管理 recover 非常有效的工具,并使异常处理逻辑更加清晰和可控。

package mainimport "fmt"func riskyFunction() {panic("Something went wrong!") // 模拟某个出错函数
}func safeFunction() {defer recoverFromPanic() // 确保恢复函数从 panic 中恢复riskyFunction() // 调用可能触发 panic 的函数fmt.Println("This line won't be executed if panic occurs")
}func recoverFromPanic() {if r := recover(); r != nil {fmt.Println("Recovered from panic:", r) // 输出 panic 信息}
}func main() {safeFunction() // 调用安全函数fmt.Println("Program continues to run.") // 程序继续执行
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 从IPC摄像机读取视频帧解码并转化为YUV数据到转化为Bitmap
  • DeepSeek 2.5本地部署的实战教程
  • 7--SpringBoot-后端开发、原理详解(面试高频提问点)
  • Web后端开发技术:RESTful 架构详解
  • 如何在GitHub上Clone项目:一步步指南
  • js 深入理解类-class
  • 存储系统概述
  • 移动端如何实现智能语音交互
  • Java免税商品优选商城:Spring Boot实战
  • 【在Linux世界中追寻伟大的One Piece】IP分片和组装的具体过程
  • Linux:syslog文件删掉 不能自动创建
  • Cpp类和对象(中续)(5)
  • 如何将MySQL卸载干净(win11)
  • 论 JAVA 集合框架中 接口与类的关系
  • vue3(整合版)
  • [NodeJS] 关于Buffer
  • 【comparator, comparable】小总结
  • 4个实用的微服务测试策略
  • CSS盒模型深入
  • dva中组件的懒加载
  • ES10 特性的完整指南
  • golang 发送GET和POST示例
  • HTTP中的ETag在移动客户端的应用
  • JS题目及答案整理
  • Magento 1.x 中文订单打印乱码
  • Netty 4.1 源代码学习:线程模型
  • Node项目之评分系统(二)- 数据库设计
  • Python打包系统简单入门
  • SpiderData 2019年2月13日 DApp数据排行榜
  • 给第三方使用接口的 URL 签名实现
  • 入手阿里云新服务器的部署NODE
  • 探索 JS 中的模块化
  • 云栖大讲堂Java基础入门(三)- 阿里巴巴Java开发手册介绍
  • - 转 Ext2.0 form使用实例
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • ​水经微图Web1.5.0版即将上线
  • ![CDATA[ ]] 是什么东东
  • #Datawhale AI夏令营第4期#多模态大模型复盘
  • $(this) 和 this 关键字在 jQuery 中有何不同?
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)springboot金融新闻信息服务系统 毕业设计651450
  • (几何:六边形面积)编写程序,提示用户输入六边形的边长,然后显示它的面积。
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (学习日记)2024.01.09
  • (转)四层和七层负载均衡的区别
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • .NET 5.0正式发布,有什么功能特性(翻译)
  • .NET Framework与.NET Framework SDK有什么不同?
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • .NET(C#) Internals: as a developer, .net framework in my eyes
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • .NET中的Event与Delegates,从Publisher到Subscriber的衔接!