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.") // 程序继续执行
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/880011.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

re题(39)BUUCTF-[FlareOn3]Challenge1

BUUCTF在线评测 (buuoj.cn) 查壳是32位&#xff0c;ida打开&#xff0c;进入main函数&#xff0c;进入sub_401260看看 查看byte_413000存的字符串 _BYTE *__cdecl sub_401260(int a1, unsigned int a2) {int v3; // [espCh] [ebp-24h]int v4; // [esp10h] [ebp-20h]int v5; //…

python selenium网页操作

一、安装依赖 pip install -U seleniumselenium1.py&#xff1a; from selenium import webdriver from selenium.webdriver.common.by import Bydriver webdriver.Chrome() driver.get("https://www.selenium.dev/selenium/web/web-form.html") title driver.ti…

https的连接过程

根证书: 内置在操作系统和浏览器中,可手动添加,下级是中间证书或服务器证书,只有当中间证书或服务器证书关联到已存在的根证书时,中间证书或服务器证书才视为有效 中间证书: 位于根证书和服务器证书之间,他们之间也可以没有中间证书,作用是对根证书增加一个下级,方便管理,由根…

整合多方大佬博客以及视频 一文读懂 servlet

参考文章以及视频 文章&#xff1a; 都2023年了&#xff0c;Servlet还有必要学习吗&#xff1f;一文带你快速了解Servlet_servlet用得多吗-CSDN博客 【计算机网络】HTTP 协议详解_3.简述浏览器请求一个网址的过程中用到的网络协议,以及协议的用途(写关键点即可)-CSDN博客 【…

yolov8旋转目标检测之绝缘子检测-从数据加载到模型训练、部署

YOLOv8 是 YOLO (You Only Look Once) 系列目标检测算法的最新版本&#xff0c;以其高速度和高精度而著称。在电力行业中&#xff0c;绝缘子是电力传输线路上的重要组件之一&#xff0c;它们用于支撑导线并保持电气绝缘。由于长期暴露在户外环境中&#xff0c;绝缘子容易出现损…

【JavaEE】多线程编程引入——认识Thread类

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c;你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;希望本文内容能帮到你&#xff01; 目录 引入&#xff1a; 一&#xff1a;Thread类 1&#xff1a;Thread类可以直接调用 2&#xff1a;run方法 &a…

【25.6】C++智能交友系统

常见错误总结 const-1 如下代码会报错 原因如下&#xff1a; man是一个const修饰的对象&#xff0c;即man不能修改任何内容&#xff0c;但是man所调用的play函数只是一个普通的函数&#xff0c;所以出现了报错。我们需要在play函数中加上const修饰&#xff0c;或者删除man对…

【计算机网络 - 基础问题】每日 3 题(十八)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

SpringBoot环境配置(Spring Boot Profile)

一、介绍 在Spring Boot中&#xff0c;spring.profiles 配置用于定义不同环境下的配置文件。这使得应用可以在不同的环境中使用不同的配置&#xff0c;比如开发环境、测试环境和生产环境等。这种方式可以避免在代码中硬编码配置信息&#xff0c;并且能够更灵活地管理应用的环境…

SpringBootWeb增删改查入门案例

前言 为了快速入门一个SpringBootWeb项目&#xff0c;这里就将基础的增删改查的案例进行总结&#xff0c;作为对SpringBootMybatis的基础用法的一个巩固。 准备工作 需求说明 对员工表进行增删改查操作环境搭建 准备数据表 -- 员工管理(带约束) create table emp (id int …

计算机毕业设计公交站点线路查询网站登录注册搜索站点线路车次/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序

选题背景‌&#xff1a; 随着城市化进程的加快&#xff0c;公共交通成为城市居民出行的重要方式。然而&#xff0c;传统的公交站点线路查询方式往往依赖于纸质地图或简单的电子显示屏&#xff0c;查询效率低下且信息更新不及时。因此&#xff0c;开发一个功能全面、易于使用的…

OpenMV学习第一步安装IDE_2024.09.20

用360浏览器访问星瞳科技官网&#xff0c;一直提示访问不了。后面换了IE浏览器就可以访问。第一个坑。

基于springboot的智慧社区微信小程序

文未可获取一份本项目的java源码和数据库参考。 本课题研究目标 本文主要对小区生活服务平台的功能和非功能需求进行了分析&#xff0c;系统除了提供物业保修、小区资讯、投诉留言、常用电话等基础功能外&#xff0c;为了满足用户的多样化需求&#xff0c;还提供邻里圈子和有…

238 除自身以外数组的乘积

解题思路&#xff1a; \qquad 这道题要求在 O ( n ) O(n) O(n) 时间内解决&#xff0c;但是不能使用除法。仅使用乘法的话&#xff0c;看上去很难在一次遍历中得出想要的结果&#xff0c;但是没关系&#xff0c;一次遍历不行的话那就试试两次、三次&#xff0c;重要的是分析在…

Python--TCP/UDP通信

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 一.客户端与服务端通信原理 1. 服务器端 服务器端的主要任务是监听来自客户端的连接请求&#xff0c;并与之建立连接&#xff0c;然后接收和发送数据。 创建套接字&#xff1a;首先&#xff0…

【数据库】常用数据库简介

目录 &#x1f354; 常用的关系型数据库 &#x1f354; Mysql简介 &#x1f354; SQL 简介 SQL语句的分类 SQL 写法 SQL 常用的数据类型 &#x1f354; DDL语句 对数据库的操作 对数据表的操作 &#x1f354; DML语句 插入数据 insert into 修改数据 update 删除数…

css基础知识笔记

一言&#xff1a; “放任误解就是撒谎。” 文章目录 前言文章有误敬请斧正 不胜感恩&#xff01;CSS基础教程0.文本样式基础1. CSS选择器2. CSS布局技巧3. 响应式设计4. Emmet语法 总结 前言 写在开始&#xff1a; 今天来看一眼CSS基础知识。 好几天没更新了 先更一篇 文章有…

DataGrip在Windows和MacOS平台上的快捷键

0. 背景信息 No.说明1测试DataGrip版本号 : 2024.2.2 1. Windows下快捷键 2. MacOS下快捷键

Java流程控制语句——跳转语句详解:break 与 continue 有什么区别?

&#x1f310;在Java编程中&#xff0c;break和continue是两个重要的控制流语句&#xff0c;它们允许开发者根据特定条件改变程序的执行流程。虽然两者都用于中断当前的行为&#xff0c;但它们的作用方式不同。本文将通过生动的例子来详细解释这两个语句&#xff0c;并使用流程…

C++/Qt 集成 AutoHotkey

C/Qt 集成 AutoHotkey 前言AutoHotkey 介绍 方案一&#xff1a;子进程启动编写AutoHotkey脚本准备 AutoHotkey 运行环境编写 C/Qt 代码 方案二&#xff1a;显式动态链接方案探索编译动态链接库集成到C工程关于AutoHotkeyDll.dll中的函数原型 总结 前言 上一篇介绍了AutoHotkey…