5.go语言函数提纲
1 本篇前瞻
前端时间的繁忙,未曾更新go语言系列。由于函数非常重要,为此将本篇往前提一提,另外补充一些有关go新版本前面遗漏的部分。
需要恭喜你的事情是本篇学完,go语言中基础部分已经学完一半,这意味着你可以使用go语言去解决大部分的Leetcode的题,为此后面的1篇,将带领大家去巩固go语言的学习成果
2 版本拾遗
2.1 go 1.21
2.1.1 函数新增
函数min和max在go 1.21已经被实现,不过由于其建立在go1.18泛型实现的基础上,需要在后面提到泛型的时候讲到。
2.1.2 for循环的变化
从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。
这里需要提醒面试官们更新自己的面试题了,对于各位面试者来说,这个知识点反杀面试官,想想是不是很帅呢?
等会,面试官也不知道这个知识把你刷了,那么这样的公司不去也罢(这家公司根本不关注语言的更新,你想这个公司能给你多少成长?)。
2.1.3 panic函数
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)
3 函数简介
后面为了简单起见,我指导AI完成这方面的写作,如果你觉得这些知识较为简单,可以略过不看。
3.1 函数声明
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的值。
3.1.1 多返回值
在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来接收返回的和与差。然后,我们可以根据需要使用这些返回值。
多返回值功能使得函数能够更灵活地处理多种情况,并返回多个相关的信息。这在很多情况下都非常有用,比如同时获取某个操作的结果和状态码,或者同时获取多个计算结果等。
3.1.2 可变形参
在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语言中非常有用,特别是当你不确定要传递多少个参数给函数时。它们使得函数更加灵活和通用化。
3.2 匿名函数
在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。最后,我们使用了加法器函数进行计算,并将结果打印出来。
3.2.1 闭包
在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中执行不同的任务。闭包还可以用于实现函数工厂、回调函数、高阶函数等功能。由于闭包可以捕获其外部环境的变量,因此它们也是一种非常有用的工具,可以在不改变外部变量的情况下对其进行操作。
3.3 defer语句
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语句,可以更方便地处理错误和异常情况。
3.4 panic/recover函数
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函数,我们可以实现异常处理机制,以便在发生错误时优雅地处理异常情况。
4 写作拾遗
4.1 defer的性能问题
defer语句在Go语言中的性能问题是一个经常被讨论的话题。由于defer语句会将函数的执行推迟到外部函数返回之前,这意味着在外部函数执行期间,被defer的函数会一直保持在调用栈中,这可能会增加内存占用和执行时间。
问题其实早在go1.14中已经得到了完美解决。该版本能保证defer在绝大多数场景下的开销几乎为0,这就意味着无论什么情况下,我们都可以使用defer一些清理操作,比如关闭文件、释放锁等。
回顾其优化的历史,Go语言最早在go1.8对defer进行了优化处理,另外在go1.13和go1.14连续两个版本提升defer的性能,彻底解决了defer的性能问题。
4.2 多返回值/error/panic/recover
回到go语言不优雅的错误处理这边,其实我想说的是函数多返回值事实上是无奈之举,go语言没有像Java/C++那样的异常捕获机制,使得其错误处理显得很不优雅,这个可能是go语言本身支持多携程的一种妥协,利用多返回值就可以返回错误和函数结果来帮助进行错误处理。
至于panic/recover作为一种异常处理机制,PostgreSQL 数据库交互的第三方包github.com/lib/pq就利用了这点,但是需要注意的是并不是所有的错误都能通过recover恢复,也就是说recover并不是万能的。
4.3 recover不是万能的
在Go语言中,recover函数只能用于捕获并处理由panic函数引发的异常,它不能恢复由其他错误或异常情况导致的程序中断。
recover函数只能在defer函数中调用,并且通常与panic函数配合使用。当一个异常被引发时,程序执行流程会被中断,但在中断之前,Go语言会执行所有尚未执行的defer函数。在defer函数中调用recover函数可以捕获异常,并返回传递给panic函数的值。然后,程序可以继续执行,就好像没有发生异常一样。
然而,如果程序是由于其他原因而中断的,比如运行时错误、内存溢出、无效的指针引用等,那么recover函数就无法恢复程序的执行。在这些情况下,程序会立即终止,不会执行任何尚未执行的defer函数。
此外,即使在defer函数中调用了recover函数,它也只能捕获并处理当前goroutine中的异常。如果其他goroutine中发生了异常,那么该goroutine的执行会被中断,但不会影响当前goroutine的执行。
因此,在编写Go程序时,应该谨慎使用panic和recover函数,并确保它们只用于处理可预见的异常情况。对于不可预见的错误或异常情况,应该使用其他错误处理机制来处理,比如返回错误码、使用错误类型等。
5 下篇预告
使用go语言刷Leetcode题
5.go语言函数提纲的更多相关文章
- 从linux0.11中起动部分代码看汇编调用c语言函数
上一篇分析了c语言的函数调用栈情况,知道了c语言的函数调用机制后,我们来看一下,linux0.11中起动部分的代码是如何从汇编跳入c语言函数的.在LINUX 0.11中的head.s文件中会看到如下一 ...
- C语言(函数)学习之strstr strcasestr
C语言(函数)学习之[strstr]&[strcasestr]一.strstr函数使用[1]函数原型char*strstr(constchar*haystack,constchar*needl ...
- C语言函数sscanf()的用法
从文件读取数据是一件很麻烦的事,所幸有sscanf()函数. C语言函数sscanf()的用法 sscanf() - 从一个字符串中读进与指定格式相符的数据. 函数原型: int sscanf( st ...
- 不可或缺 Windows Native (6) - C 语言: 函数
[源码下载] 不可或缺 Windows Native (6) - C 语言: 函数 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 函数 示例cFunction.h # ...
- C#委托与C语言函数指针及函数指针数组
C#委托与C语言函数指针及函数指针数组 在使用C#时总会为委托而感到疑惑,但现在总新温习了一遍C语言后,才真正理解的委托. 其实委托就类似于C/C++里的函数指针,在函数传参时传递的是函数指针,在调用 ...
- swift1.2语言函数和闭包函数介绍
swift1.2语言函数和闭包函数介绍 在编程中,随着处理问题的越来越复杂,代码量飞速增加.其中,大量的代码往往相互重复或者近似重复.如果不采有效方式加以解决,代码将很难维护. swift1.2语言函 ...
- Swift 1.1语言函数参数的特殊情况本地参数名外部参数名
Swift 1.1语言函数参数的特殊情况本地参数名外部参数名 7.4 函数参数的特殊情况 声明定义有参函数时,为函数的每一个参数都定义了参数名称.根据参数名定义的形式不同,函数参数包括本地参数和外部 ...
- C语言函数指针基础
本文写的非常详细,因为我想为初学者建立一个意识模型,来帮助他们理解函数指针的语法和基础.如果你不讨厌事无巨细,请尽情阅读吧. 函数指针虽然在语法上让人有些迷惑,但不失为一种有趣而强大的工具.本文将从C ...
- 动态修改 C 语言函数的实现
Objective-C 作为基于 Runtime 的语言,它有非常强大的动态特性,可以在运行期间自省.进行方法调剂.为类增加属性.修改消息转发链路,在代码运行期间通过 Runtime 几乎可以修改 O ...
- keil or c51 汇编调用c语言函数 容易忽视的问题
最近,在用keil 写一个小程序时,想实践一下从汇编调用 C语言函数,我们都知道C语言调用汇编函数讨论得较多,但反过来,从汇编中调用C语言的函数未见深入分析:在开始的时候,还是忽视了一个问题,就是对现 ...
随机推荐
- 浅析开源容器标准——OCI
1.导语 容器技术火起来了以后,Docker的容器镜像和容器运行时已然成为行业的标准.此后,为了推进容器生态的健康发展.在Linux基金会的主导下,Docker和各大云厂商Google, Amazon ...
- 快速取模算法(Barrett Reduction)
原理:取模运算低效的原因本质是除法运算的低效.如果能将除法变成其它运算就可以加速.具体地,将除以任意数转化成"乘一个数.除以一个 \(2^k\) "(取 \(2^{62}\) 即可 ...
- C++面试八股文:用过STL吗?
某日二师兄参加XXX科技公司的C++工程师开发岗位第21面: 面试官:用过STL吗? 二师兄:(每天都用好吗..)用过一些. 面试官:你知道STL是什么? 二师兄:STL是指标准模板库(Standar ...
- List 接口及其常用方法
List 接口基本介绍 List接口是Collection接口的子接口,其主要特点如下: List中元素有序,是按照元素的插入顺序进行排序的.每个元素都有一个与之关联的整数型索引(索引从 0 开始), ...
- pip install lxml 总是失败
- .NetCore3.1+微服务架构技术栈
目标 目标系统架构演变,单体-分布式-微服务-中台 微服务架构核心解决,横向对比1.0.2.0.3.0 践行微服务架构,全组件解读! 也谈中台 单体架构Monolithic 单体应用时代:应用程序就是 ...
- 深入JS——理解闭包可以看作是某种意义上的重生
JS中有一个非常重要但又难以完全掌握的概念,那就是闭包.很多JS程序员自以为已经掌握了闭包,但实质上是一知半解,就像"JS中万物皆为对象"这个常见的错误说法一样,很多前端开发者到现 ...
- P5752 [NOI1999] 棋盘分割题解
本文来自我的洛谷博客. 这个题解思路虽然与其他人的思路相同, 但力求使用清晰易懂的图片和文字,讲解最简洁的道理. 请大家耐心地看完,注意要结合图片一起哦~~ 2022-8-24 更改了格式与错别字. ...
- 巧用 awk 批量杀进程
今天遇到线上的一个问题: 我需要批量杀死某台机器的 PHP 进程,该怎么办? 注意,不是 php-fpm,是常驻任务. 如果是一个进程,那就好办了,ps -ef | grep php,找到 PID 然 ...
- Java Maven Settings配置参考
介绍 快速概览 settings.xml文件中的 settings 元素包含用于定义以各种方式配置Maven执行的值的元素,如pom.xml,但不应绑定到任何特定项目或分发给受众.这些值包括本地仓库位 ...