前言
你是否听说过 Go
语言中的函数是一等公民?如果没有,那么恭喜你,本文将带你一起揭开这个神秘的面纱。如果你已经了解这个概念,你是否知道为什么 Go
语言中的函数被称为一等公民?不管你的答案是什么,通过阅读本文,你将对这个概念有更深入的了解。
准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。
一等公民
In a given programming language design, a first-class citizen is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, and assigned to a variable.
什么是一等公民?上面引文来自维基百科,直译过来的意思是:“在给定的编程语言设计中,一等公民是指支持所有通常可用于其他实体的操作的实体。这些操作通常包括作为参数传递、从函数返回和赋值给变量。”。
直译过来似乎有点难以理解,没关系,我们可以用更简单的方式来理解:在编程语言设计中,被称为一等公民的元素可以自由地进行常见的操作,如作为参数传递、从函数返回和赋值给变量。
在 Go
语言中,函数具备这些特性,可以赋值给变量、作为参数传递,并且可以作为函数的返回值。
函数作为一等公民的实际运用示例
当我们理解了 Go
语言中的函数为什么被视为一等公民之后,让我们来探索一下它作为一等公民的实际运用吧。
赋值给变量
在 Go
语言中,函数是一种类型,它可以像其他类型(如 int64
、string
等)一样被赋值给变量,这意味着我们可以创建一个变量,将函数赋值给它,然后通过该变量来调用函数。
将普通函数赋值给变量
我们可以将普通函数赋值给变量,以便通过变量调用该函数。下面是一个示例代码:
代码语言:go复制import (
"fmt"
)
func SayHello(s string) {
fmt.Println(s)
}
func main() {
sayHelloFunc := SayHello
sayHelloFunc("你好,我是陈明勇") // 你好,我是陈明勇
}
在上面的例子中,首先我们定义了一个普通函数 SayHello(s string)
,该函数接受一个字符串参数 s
,并在函数体中使用 fmt.Println
函数打印字符串;
然后在 main
函数中,我们将该函数赋值给变量 sayHelloFunc
,通过这个变量,我们可以调用 SayHello
函数,实现相同的功能。这种方式可以在需要动态选择函数的情况下使用,使得代码更加 灵活 和 可复用。
创建匿名函数并赋值给变量
除了将普通函数赋值给变量,我们还可以通过创建匿名函数的形式并将其赋值给变量。下面是一个示例代码:
代码语言:go复制import (
"fmt"
)
func main() {
sayHelloFunc := func(s string) {
fmt.Println(s)
}
sayHelloFunc("你好,我是陈明勇") // 你好,我是陈明勇
}
在上述代码中,我们使用 func
关键字创建了一个匿名函数,该函数也是接受一个字符串参数 s
,并在函数体中使用 fmt.Println
函数打印字符串;然后,我们将该匿名函数赋值给 sayHelloFunc
变量。通过 sayHelloFunc
变量,我们可以调用匿名函数并传入相应的参数,实现相同的功能。
匿名函数的创建方式灵活且简洁,特别适用于一次性的函数需求或需要在不同的上下文中定义函数的场景。
作为参数传递
在 Go
语言中,函数可以作为函数参数传递给其他函数,这使得函数可以更加灵活的操作和组合。我们来看看一个时间转换的例子;
import (
"fmt"
"time"
)
// ApplyFormatTimeToStringFunc 根据参数 t 和 operation,将时间类型转成对应格式的字符串类型,字符串的格式由参数 operation 决定
// 如果参数 t 为零值,则返回空字符串
func ApplyFormatTimeToStringFunc(t time.Time, operation func(t time.Time) string) string {
if t.IsZero() {
return ""
}
return operation(t)
}
// FormatTimeToString 将时间转成 yyyy-MM-dd 的形式
func FormatTimeToString(t time.Time) string {
return t.Format(time.DateOnly)
}
// FormatDateTimeToString 将时间转成 yyyy-MM-dd HH:mm:ss 的形式
func FormatDateTimeToString(t time.Time) string {
return t.Format(time.DateTime)
}
func main() {
// yyyy-MM-dd
formatTimeToString := ApplyFormatTimeToStringFunc(time.Now(), FormatTimeToString)
fmt.Println(formatTimeToString) // 2023-07-18
// yyyy-MM-dd HH:mm:ss
formatDateTimeToString := ApplyFormatTimeToStringFunc(time.Now(), FormatDateTimeToString)
fmt.Println(formatDateTimeToString) // 2023-07-18 00:00:00
}
在上述例子中,首先我们定义了一个 ApplyFormatTimeToStringFunc
函数,该函数接收一个时间类型参数 t
和一个函数类型参数 operation
,根据参数 t
和 operation
,将时间类型转成字符串类型,字符串的格式由参数 operation
决定;
然后定义两个操作函数 FormatTimeToString
和 FormatDateTimeToString
,这两个函数分别将时间转换为 yyyy-MM-dd
和 yyyy-MM-dd HH:mm:ss
的格式;
最后在 main
函数中,我们通过将不同的操作函数作为参数传递给 ApplyFormatTimeToStringFunc
函数来格式化当前时间。通过使用函数作为参数传递给另一个函数,动态改变函数的行为,使得我们可以根据需要选择不同的格式化方式来处理时间,提高代码的灵活性和可复用性。
作为函数的返回值
在 Go
语言中,函数除了可以赋值给变量和作为参数进行传递以外,它还可以作为函数的返回值进行使用。以下是示例代码:
import (
"fmt"
)
func CreateDialogueFormatter(name string) func(string) string {
return func(s string) string {
return fmt.Sprintf("[%s]: ", name) s
}
}
func main() {
DialogueOfCmy := CreateDialogueFormatter("陈明勇")
fmt.Println(DialogueOfCmy("你好")) // [陈明勇]: 你好
DialogueOfGopher := CreateDialogueFormatter("Gopher")
fmt.Println(DialogueOfGopher("你好")) // [Gopher]: 你好
}
在上面的例子中,首先我们定义了 CreateDialogueFormatter
函数,该函数接收一个 name
参数,用于设置对话人昵称,并返回一个可定制化的对话函数;
然后在 main
函数中,通过调用 CreateDialogueFormatter
函数并传入不同的昵称,可以创建多个针对不同对话人的对话函数。
通过将函数作为返回值,我们可以在运行时动态地生成函数,从而使函数更具灵活性和可定制性。
小结
函数作为一等公民在 Go
语言中非常重要,借助其三大特性,我们能够实现高阶函数编程,提升代码的灵活性和可复用性。
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!