命名函数和匿名函数

在线运行open in new window启动 AI 助手open in new window

func main() {
	func() {
		fmt.Println("no name")
	}()

	namedFunc := func() {
		fmt.Println("named")
	}

	fmt.Printf("%T\n", namedFunc) // func()

	namedFunc()
	namedFunc()
}

程序代码演示了匿名函数和具名函数的用法。首先,func main() 是程序的入口点,所有 Go 程序都从 main 函数开始执行。接着,我们有一个匿名函数 func() { fmt.Println("no name") }(),它在定义的同时立即被调用,打印 "no name" 字符串。然后,我们将一个匿名函数赋值给一个变量 namedFunc,这样我们就可以通过这个变量来调用该函数,在这个函数中,它只打印 "named" 字符串。接下来,fmt.Printf("%T\n", namedFunc) 打印 namedFunc 的类型,输出将是 func(),表明 namedFunc 是一个无参数且无返回值的函数。最后,程序调用了具名函数 namedFunc 两次,所以 "named" 字符串将被打印两次。

定义新的函数类型

在线运行open in new window启动 AI 助手open in new window

// 新的类型,接受 string 返回 error 的函数
type handlerFunc func(name string) error

func main() {
	hf := func(name string) error {
		if name != "zhangsan" {
			return errors.New("not expected name")
		}

		return nil
	}

	fmt.Printf("%#v\n", hf("zhangsan"))
	fmt.Printf("%#v\n", hf("lisi"))
}

代码定义了一个类型 handlerFunc,它是一个接受一个字符串参数并返回一个错误的函数类型。接着定义了一个名为 hf 的函数变量,它的类型是 handlerFunc,并赋值为一个函数字面量。

该函数字面量会检查参数 name 是否等于 "zhangsan",如果不是,则返回一个错误,否则返回 nil。

最后,该程序调用了两次 hf 函数,第一次传入的参数是 "zhangsan",第二次传入的参数是 "lisi"。程序使用 %#v 格式化输出了每次调用的结果。

第一次调用 hf("zhangsan") 时传入了期望的参数,因此该函数返回了 nil;而第二次调用 hf("lisi") 时传入了不符合期望的参数,因此该函数返回了一个错误,错误的信息为 "not expected name"。

函数不定参数的本质

在线运行open in new window启动 AI 助手open in new window

func handle(key string, args ...interface{}) {
	fmt.Printf("key: %s\n", key)
	fmt.Printf("args size: %d\n", len(args))

    // 不定参数等效于切片
	for idx, arg := range args {
		fmt.Printf("args[%d] = %#v\n", idx, arg)
	}

	fmt.Println("-------------------")
}

func main() {
	handle("zero")
	handle("one", 0)
	handle("two", 1, 2)
	handle("three", "1", "2", "3")
}

代码定义了一个名为 handle 的函数,它接受一个字符串参数 key 和若干个 interface{} 类型的不定参数 args。该函数会依次打印出 key 和 args 的长度,并遍历 args 打印出每个元素的值和索引。最后,它会输出一行分隔符以方便区分不同的调用。

在 main 函数中,该程序调用了 handle 函数四次,每次传入不同的参数。第一次调用只传入了一个字符串参数,第二次调用传入了一个整数 0,第三次调用传入了两个整数 1 和 2,第四次调用传入了三个字符串参数 "1"、"2" 和 "3"。

可以看出,函数 handle 可以接受不同类型、不定数量的参数,并通过遍历 args 来访问它们。此外,通过 len 函数可以方便地获取 args 的长度。

高阶函数之函数参数

在线运行open in new window启动 AI 助手open in new window

func nameWithSep(first, last string, loadSep func() string) string {
	return strings.Join([]string{last, first}, loadSep())
}

func main() {
	f1 := func() string { return "-" }
	f2 := func() string { return "_" }
	f3 := func() string { return "@" }

	fmt.Println(nameWithSep("Jacky", "Li", f1)) // Li-Jacky
	fmt.Println(nameWithSep("Jacky", "Li", f2)) // Li_Jacky
	fmt.Println(nameWithSep("Jacky", "Li", f3)) // Li_Jacky

	fmt.Println(nameWithSep("Jacky", "Li", func() string { return "#" })) // Li#Jacky
}

代码定义了一个名为 nameWithSep 的函数,它接受两个字符串参数 first 和 last,代表一个人的名字和姓氏,以及一个返回分隔符字符串的函数 loadSep。该函数使用 loadSep 返回的分隔符字符串将姓氏和名字连接在一起。

main 函数使用 nameWithSep 函数,使用不同的分隔符函数 f1、f2 和 f3,将不同组合的名字和姓氏连接起来。然后,它使用返回分隔符字符串 # 的匿名函数调用 nameWithSep。

第一次调用 nameWithSep 使用 f1 函数返回的分隔符字符串 -,将 "Jacky" 和 "Li" 连接在一起,得到 "Li-Jacky"。

第二次调用 nameWithSep 使用 f2 函数返回的分隔符字符串 _,将 "Jacky" 和 "Li" 连接在一起,得到 "Li_Jacky"。

第三次调用 nameWithSep 使用 f3 函数返回的分隔符字符串 @,将 "Jacky" 和 "Li" 连接在一起,得到 "Li@Jacky"。

第四次调用 nameWithSep 使用一个匿名函数返回分隔符字符串 #,将 "Jacky" 和 "Li" 连接在一起,得到 "Li#Jacky"。

高阶函数之返回函数

在线运行open in new window启动 AI 助手open in new window

type calcFunc func(a, b int) int

// func getCalcMethod(name string) func(a, b int) int { ... }

func getCalcMethod(name string) calcFunc {
	switch name {
	case "+":
		return func(a, b int) int { return a + b }
	case "-":
		return func(a, b int) int { return a - b }
	case "/":
		return func(a, b int) int { return a / b }
	case "*":
		return func(a, b int) int { return a * b }
	}

	return nil
}

func main() {
	add := getCalcMethod("+")
	sub := getCalcMethod("-")
	div := getCalcMethod("/")
	mul := getCalcMethod("*")

	fmt.Println(add(10, 5))
	fmt.Println(sub(10, 5))
	fmt.Println(div(10, 5))
	fmt.Println(mul(10, 5))
}

代码定义了一个类型 calcFunc,代表一个接收两个整数参数并返回一个整数的函数类型。然后,定义了一个函数 getCalcMethod,它接受一个字符串参数 name,并根据参数值返回不同的函数,这些函数执行加法、减法、除法或乘法操作。如果参数值不是这些运算符之一,则函数返回 nil。

在 main 函数中,分别使用 getCalcMethod 函数获取加法、减法、除法和乘法操作的函数,并将它们分别赋值给 add、sub、div 和 mul 变量。然后,分别调用这些函数,并传递两个整数参数。

第一次调用 add 函数,传入参数 10 和 5,得到 15。第二次调用 sub 函数,传入参数 10 和 5,得到 5。第三次调用 div 函数,传入参数 10 和 5,得到 2。最后一次调用 mul 函数,传入参数 10 和 5,得到 50。

捕获变量的闭包函数

在线运行open in new window启动 AI 助手open in new window

func join(old string) func(string) string {
	// old := "prefix"

	concat := func(ns string) string {
		// 注意闭包函数捕获了 old 并修改
		old = fmt.Sprintf("%s_%s", old, ns)
		return old
	}

	return concat
}

func main() {
	f1 := join("prefix")
	f2 := join("prefix")

	f1("one")
	f2("1")

	f1("two")
	f2("2")

	f1("three")
	f2("3")

	// f1 捕获的 old 和 f2 捕获的 old 是隔离的
	fmt.Println(f1("!")) // prefix_one_two_three_!
	fmt.Println(f2("!")) // prefix_1_2_3_!
}

代码定义了一个名为 join 的函数,它接受一个字符串参数 old,并返回一个函数 concat。函数 concat 接受一个字符串参数 ns,并使用闭包修改 old 变量,然后返回 old 变量的值。

在 main 函数中,使用 join 函数分别创建两个闭包函数 f1 和 f2。然后,分别调用这两个函数多次,并传递不同的字符串参数。最后,打印 f1 和 f2 的返回值。

第一次调用 f1 函数,传入参数 "one",闭包函数 concat 修改了 old 变量,old 的值变成了 "prefix_one"。第一次调用 f2 函数,传入参数 "1",闭包函数 concat 修改了新的 old 变量,old 的值变成了 "prefix_1"。

接下来,分别调用 f1 和 f2 函数两次,传入不同的参数。在每次调用中,闭包函数 concat 修改了其捕获的 old 变量,并返回新的 old 变量的值。

最后,分别打印 f1("!") 和 f2("!") 的返回值。因为 f1 和 f2 捕获的 old 变量是隔离的,所以 f1 的返回值包含了所有调用 f1 的参数,而 f2 的返回值包含了所有调用 f2 的参数。

Last Updated:
Contributors: Bob Wang