- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
前端时间的繁忙,未曾更新go语言系列。由于函数非常重要,为此将本篇往前提一提,另外补充一些有关go新版本前面遗漏的部分.
需要恭喜你的事情是本篇学完,go语言中基础部分已经学完一半,这意味着你可以使用go语言去解决大部分的 Leetcode 的题,为此后面的1篇,将带领大家去巩固go语言的学习成果 。
函数min 和 max 在 go 1.21 已经被实现,不过由于其建立在 go1.18 泛型实现的基础上,需要在后面提到泛型的时候讲到.
从 go1.1 时代一直这样一个坑或者面试题是这样的:
题目1. 。
s := []int{1, 2, 3, 4}
var prints []func()
for _, v := range s {
prints = append(prints, func() { fmt.Println(v) })
}
for _, print := range prints {
print()
}
或者是 。
题目2 。
s := []int{1, 2, 3, 4}
var ps []*int
for _, v := range s {
ps = append(ps,&v)
}
for _, v := range ps {
fmt.Println(*v)
}
或者是 。
题目3 。
var prints []func()
for i:= 1; i <= 4; i++{
prints = append(prints, func() { fmt.Println(i) })
}
for _, print := range prints {
print()
}
题目1,2,3的答案都不是 1,2,3,4 ,而分别是反直觉的 4个4 , 4个4 和 4个5 。
在 go1.21 时代中,你加入环境变量 GOEXPERIMENT=loopvar ,这些题目的答案就统一变为 1,2,3,4 .
这里需要提醒面试官们更新自己的面试题了,对于各位面试者来说,这个知识点反杀面试官,想想是不是很帅呢?
等会,面试官也不知道这个知识把你刷了,那么这样的公司不去也罢( 这家公司根本不关注语言的更新,你想这个公司能给你多少成长? ).
panic在 go1.21 后函数 panic 的参数时 nil ,那么 recover 不会再受到 nil ,而是类型为 *runtime.PanicNilError 的错误.
defer func() {
if err := recover(); err != nil {
fmt.Println("error:", err)
}
fmt.Println("end")
}()
panic(nil)
后面为了简单起见,我指导AI完成这方面的写作,如果你觉得这些知识较为简单,可以略过不看.
Go语言的函数结构声明简单明了,由关键字 func 、函数名、参数列表、返回类型以及函数体组成。下面是一个基本的Go语言函数的结构示例:
func functionName(parameter1 type1, parameter2 type2) returnType {
// 函数体
// 执行语句
return value
}
让我们来详细了解一下每个部分的作用:
func
:这是Go语言中定义函数的关键字,它表示接下来要定义一个函数。 functionName
:这是函数的名称,你可以根据自己的需求为函数命名。函数名应该具有描述性,能够清晰地表达函数的功能。 parameter1, parameter2
:这些是函数的参数,你可以根据需要定义任意数量的参数。每个参数都有一个名称和一个类型。在函数体内,你可以通过这些参数名称来访问传递给函数的值。 type1, type2
:这些参数的类型,指定了传递给函数的值的类型。Go语言是一种静态类型语言,因此在定义函数时,你需要明确指定每个参数的类型。 returnType
:这是函数的返回类型,表示函数执行完成后返回的数据类型。如果函数不需要返回任何值,则返回类型可以为空。 return value
:这是函数的返回语句,用于返回函数的执行结果。返回值的类型必须与函数的返回类型相匹配。 这是一个简单的Go语言函数示例,用于计算两个整数的和:
func add(a int, b int) int {
sum := a + b
return sum
}
在这个示例中, add 是函数的名称,它接受两个整数类型的参数 a 和 b ,并返回一个整数类型的结果。函数体内将 a 和 b 相加,并将结果赋值给变量 sum ,然后通过 return 语句返回 sum 的值.
在Go语言中,函数可以返回多个值。这是Go语言的一项非常强大的功能,使得函数能够更灵活地处理多种情况并返回多个相关的信息.
要在函数中返回多个值,只需在函数签名中指定多个返回类型,并在函数体中使用逗号分隔的值列表来返回这些值。下面是一个简单的示例:
func calculate(a, b int) (int, int) {
sum := a + b
diff := a - b
return sum, diff
}
在上面的示例中, calculate 函数接受两个整数参数 a 和 b ,并返回它们的和与差。函数签名中指定了两个返回类型 int ,分别对应和与差的结果。在函数体内,我们计算了和与差,并使用 return 语句返回这两个值.
要调用返回多个值的函数,可以使用多个变量来接收返回值。例如:
result1, result2 := calculate(10, 5)
fmt.Println(result1) // 输出:15
fmt.Println(result2) // 输出:5
在上面的代码中,我们调用了 calculate 函数,并使用两个变量 result1 和 result2 来接收返回的和与差。然后,我们可以根据需要使用这些返回值.
多返回值功能使得函数能够更灵活地处理多种情况,并返回多个相关的信息。这在很多情况下都非常有用,比如同时获取某个操作的结果和状态码,或者同时获取多个计算结果等.
在Go语言中,函数的参数列表可以使用省略号(...)来表示接受可变数量的参数。这样的函数被称为可变形参函数.
可变形参函数可以接受任意数量的参数,包括零个参数。这些参数在函数内部可以通过一个切片来访问,切片的长度等于传递给函数的参数数量.
下面是一个简单的示例,演示了如何在Go语言中定义和使用可变形参函数:
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
在上面的示例中, sum 函数接受一个可变数量的整数参数,并返回它们的和。函数签名中的省略号表示参数列表是可变的。在函数内部,我们使用一个切片 numbers 来访问传递给函数的参数。然后,我们遍历切片中的每个元素,并将它们累加到 total 变量中。最后,我们返回 total 的值作为函数的结果.
要调用可变形参函数,可以在函数调用时使用逗号分隔的参数列表,或者直接传递一个切片。下面是两种调用方式的示例:
// 使用逗号分隔的参数列表调用函数
result := sum(1, 2, 3, 4, 5)
fmt.Println(result) // 输出:15
// 使用切片调用函数
numbers := []int{6, 7, 8, 9, 10}
result = sum(numbers...)
fmt.Println(result) // 输出:40
在第一个示例中,我们使用逗号分隔的参数列表调用了 sum 函数,并传递了5个整数参数。在第二个示例中,我们首先创建了一个包含5个整数的切片 numbers ,然后使用切片调用了 sum 函数。注意,在传递切片给可变形参函数时,需要使用省略号来展开切片中的元素.
可变形参函数在Go语言中非常有用,特别是当你不确定要传递多少个参数给函数时。它们使得函数更加灵活和通用化.
在Go语言中,可以使用匿名函数(也称为闭包函数)来创建没有名称的函数。匿名函数可以直接赋值给变量,或者作为参数传递给其他函数,或者作为函数的返回值.
下面是一个简单的示例,演示了如何在Go语言中定义和使用匿名函数:
// 定义一个匿名函数,并将其赋值给变量add
add := func(a, b int) int {
return a + b
}
// 调用匿名函数
result := add(3, 4)
fmt.Println(result) // 输出:7
在上面的示例中,我们定义了一个匿名函数,并将其赋值给变量 add 。匿名函数接受两个整数参数 a 和 b ,并返回它们的和。然后,我们调用了匿名函数,并将结果赋值给变量 result 。最后,我们打印了 result 的值,可以看到输出结果为7.
匿名函数可以作为参数传递给其他函数。例如,你可以将匿名函数传递给排序函数,以便自定义排序逻辑。下面是一个示例:
// 定义一个切片
numbers := []int{5, 2, 4, 6, 1, 3}
// 使用匿名函数作为参数传递给排序函数
sort.Slice(numbers, func(i, j int) bool {
return numbers[i] < numbers[j]
})
// 打印排序后的切片
fmt.Println(numbers) // 输出:[1 2 3 4 5 6]
在上面的示例中,我们定义了一个切片 numbers ,然后使用匿名函数作为参数传递给了 sort.Slice 函数。匿名函数定义了排序逻辑,根据切片元素的大小进行比较。最后,我们打印了排序后的切片.
匿名函数还可以作为函数的返回值。例如,你可以定义一个函数,它返回一个匿名函数,以便在需要时动态创建函数。下面是一个示例:
// 定义一个函数,返回一个匿名函数
func createAdder(x int) func(int) int {
return func(y int) int {
return x + y
}
}
// 调用createAdder函数,获取一个加法器函数
adder := createAdder(5)
// 使用加法器函数进行计算
result := adder(3)
fmt.Println(result) // 输出:8
在上面的示例中,我们定义了一个函数 createAdder ,它接受一个整数参数 x ,并返回一个匿名函数。匿名函数接受一个整数参数 y ,并返回 x+y 的结果。然后,我们调用了 createAdder 函数,获取了一个加法器函数 adder 。最后,我们使用了加法器函数进行计算,并将结果打印出来.
在Go语言中,闭包(Closure)是指一个函数值(function value),它引用了自己函数体之外的变量。换句话说,闭包是由函数及其相关的引用环境组合而成的实体.
闭包在Go语言中有很多实际的应用场景,比如在并发编程中常用的goroutine和匿名函数等。闭包可以让函数访问并操作其词法环境中的变量,即使函数是在其定义的词法环境之外调用的.
下面是一个简单的Go语言闭包示例:
func main() {
// 外部函数
outer := func() {
// 内部函数引用了外部函数的变量
count := 0
inner := func() {
count++
fmt.Println(count)
}
// 调用内部函数
inner()
}
// 调用外部函数
outer() // 输出:1
}
在上面的示例中, outer 函数是一个闭包,它包含了一个内部函数 inner 。 inner 函数引用了 outer 函数中的 count 变量。在 outer 函数被调用时,会创建一个新的 count 变量,并在 inner 函数中对其进行操作。每次调用 outer 函数时,都会创建一个新的闭包实例,并且每个闭包实例都有自己的 count 变量。在上述代码中,我们只调用了一次 outer 函数,所以只有一个闭包实例,并且输出为1.
闭包在Go语言中有很多用途,比如在并发编程中可以使用闭包来创建goroutine,以便在每个goroutine中执行不同的任务。闭包还可以用于实现函数工厂、回调函数、高阶函数等功能。由于闭包可以捕获其外部环境的变量,因此它们也是一种非常有用的工具,可以在不改变外部变量的情况下对其进行操作.
defer 是Go语言中的一个关键字,用于延迟(defer)一个函数的执行,直到包含它的函数(也称为外部函数)执行完毕之前。 defer 语句会将函数的执行推迟到外部函数返回之前,无论外部函数是通过正常返回还是由于发生panic异常而返回.
defer 语句的语法形式如下:
defer function_call
其中, function_call 是一个函数调用表达式,可以是任意的函数调用,包括内置函数、用户自定义函数或方法调用等.
当包含 defer 语句的函数执行到其定义的末尾时,被 defer 的函数会被推迟执行。推迟执行的函数可以访问其外部函数的变量,即使外部函数已经返回。这意味着 defer 语句可以用于释放资源、关闭文件、解锁互斥锁等操作,以确保在函数返回之前这些操作一定会执行.
下面是一个简单的示例,演示了 defer 语句的用法:
func main() {
fmt.Println("Start")
defer fmt.Println("Middle")
fmt.Println("End")
}
在上面的示例中,当 main 函数执行到 defer fmt.Println("Middle") 时, fmt.Println("Middle") 函数的执行会被推迟。然后,程序会继续执行 fmt.Println("End") ,最后当 main 函数执行完毕之前,被推迟的 fmt.Println("Middle") 函数会被执行。因此,上述代码的输出结果为:
Start
End
Middle
可以看到, defer 语句改变了函数的执行顺序,使得被推迟的函数在外部函数返回之前执行.
除了用于释放资源和执行清理操作之外, defer 语句还可以用于实现一些高级功能,比如错误处理和恢复(panic/recover)机制等。通过使用 defer 语句,可以更方便地处理错误和异常情况.
panic 和 recover 是Go语言中的两个内置函数,用于处理异常情况。它们一起构成了Go语言的异常处理机制.
panic 函数用于引发一个异常,它会中断当前的程序执行流程,并向上层调用栈传播panic,直到被捕获或程序终止。 panic 函数接受一个任意类型的参数,该参数会被传递给捕获异常的代码,通常用于传递错误信息.
recover 函数用于捕获并处理异常。它只能在 defer 函数中调用,并且通常与 panic 函数配合使用。当一个异常被引发时,程序执行流程会被中断,但在中断之前,Go语言会执行所有尚未执行的 defer 函数。在 defer 函数中调用 recover 函数可以捕获异常,并返回传递给 panic 函数的值。如果没有异常发生,或者 recover 函数不是在 defer 函数中调用的,那么 recover 函数会返回nil.
下面是一个简单的示例,演示了 panic 和 recover 函数的用法:
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Recovered:", err)
}
}()
panic("an error occurred")
}
在上面的示例中,我们使用了一个匿名函数作为 defer 函数的参数,并在匿名函数中调用了 recover 函数。当程序执行到 panic("an error occurred") 时,会引发一个异常,程序执行流程会被中断,但在中断之前,Go语言会执行尚未执行的 defer 函数。在 defer 函数中,我们调用了 recover 函数来捕获异常,并打印出传递给 panic 函数的错误信息。因此,上述代码的输出结果为:
makefile复制代码
Recovered: an error occurred
可以看到,通过使用 panic 和 recover 函数,我们可以实现异常处理机制,以便在发生错误时优雅地处理异常情况.
defer 语句在Go语言中的性能问题是一个经常被讨论的话题。由于 defer 语句会将函数的执行推迟到外部函数返回之前,这意味着在外部函数执行期间,被 defer 的函数会一直保持在调用栈中,这可能会增加内存占用和执行时间.
问题其实早在 go1.14 中已经得到了完美解决。该版本能保证 defer 在绝大多数场景下的开销几乎为0,这就意味着无论什么情况下,我们都可以使用 defer 一些清理操作,比如关闭文件、释放锁等.
回顾其优化的历史,Go语言最早在 go1.8 对 defer 进行了优化处理,另外在 go1.13 和 go1.14 连续两个版本提升 defer 的性能,彻底解决了 defer 的性能问题.
回到go语言不优雅的错误处理这边,其实我想说的是函数多返回值事实上是无奈之举,go语言没有像Java/C++那样的异常捕获机制,使得其错误处理显得很不优雅,这个可能是go语言本身支持多携程的一种妥协,利用多返回值就可以返回错误和函数结果来帮助进行错误处理.
至于panic/recover作为一种异常处理机制,PostgreSQL 数据库交互的第三方包 github.com/lib/pq 就利用了这点,但是需要注意的是并不是所有的错误都能通过recover恢复,也就是说recover并不是万能的.
在Go语言中, recover 函数只能用于捕获并处理由 panic 函数引发的异常,它不能恢复由其他错误或异常情况导致的程序中断.
recover 函数只能在 defer 函数中调用,并且通常与 panic 函数配合使用。当一个异常被引发时,程序执行流程会被中断,但在中断之前,Go语言会执行所有尚未执行的 defer 函数。在 defer 函数中调用 recover 函数可以捕获异常,并返回传递给 panic 函数的值。然后,程序可以继续执行,就好像没有发生异常一样.
然而,如果程序是由于其他原因而中断的,比如运行时错误、内存溢出、无效的指针引用等,那么 recover 函数就无法恢复程序的执行。在这些情况下,程序会立即终止,不会执行任何尚未执行的 defer 函数.
此外,即使在 defer 函数中调用了 recover 函数,它也只能捕获并处理当前goroutine中的异常。如果其他goroutine中发生了异常,那么该goroutine的执行会被中断,但不会影响当前goroutine的执行.
因此,在编写Go程序时,应该谨慎使用 panic 和 recover 函数,并确保它们只用于处理可预见的异常情况。对于不可预见的错误或异常情况,应该使用其他错误处理机制来处理,比如返回错误码、使用错误类型等.
使用go语言刷Leetcode题 。
最后此篇关于5.go语言函数提纲的文章就讲到这里了,如果你想了解更多关于5.go语言函数提纲的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有一个 html 结构,看起来像这样 hello activeElement 始终是“a”,但我想在“a”处于事件状态时给出轮廓以跨越。 (我所说的事件是指当我们
我是一名优秀的程序员,十分优秀!