Go语言实践[回顾]教程24--详解Go语言函数的签名、变量、匿名

Go语言实践[回顾]教程24--详解Go语言函数的类型、变量、匿名

  • 函数类型(函数签名)
  • 函数变量
  • 匿名函数

  在 Go 语言中函数也是一种类型,函数类型又叫函数签名。函数可以像其他类型一样声明变量,称之为函数变量。函数在特定场景也可以没有名字,称之为匿名函数。这节我们就重点实践一下 Go 语言的函数类型(函数签名)、函数变量、匿名函数这些方面的编程基础。

函数类型(函数签名)

  一个函数的类型,就是函数定义的首行去掉函数名、参数名和花括号,也就是只留下定义关键字 func、小括号以及类型相关的部分。查看一个函数的类型可以使用 fmt.Printf() 函数的 %T 格式化参数打印输出一个函数的类型。

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
)

func add(a, b int) int {
	return a + b
}

func sub(x int, y int) int {
	return x - y
}

// 主函数,程序入口
func main() {
	fmt.Printf("函数 add 的类型:%T\n", add)
	fmt.Printf("函数 sub 的类型:%T\n", sub)
}

  上述代码编译执行结果如下:

函数 add 的类型:func(int, int) int
函数 sub 的类型:func(int, int) int

  从打印输出的结果可以看出,虽然 add 和 sub 两个函数的名称不同、参数定义格式不同、函数体内代码不同,但是它们的类型是相同的。都是 func(int, int) int 这个函数类型。函数类型值关注函数定义关键字、参数括号、参数数量和类型、返回值的数量和类型,其它的均被忽略。

  既然函数也是类型,那么就可以使用 type 关键字定义函数类型。函数类型变量是可以作为函数的参数和返回值的。看下面的示例代码:

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
)

func add(a, b int) int {      // 声明 add 函数,类型为 func(int, int) int
	return a + b
}

func sub(x int, y int) int {  // 声明 sub 函数,类型为 func(int, int) int
	return x - y
}

type Calc func(int, int) int  // 声明类型变量 Calc 代表函数类型 func(int, int) int

func allCalc(f Calc, x, y int) int {
	return f(x, y)
}

// 主函数,程序入口
func main() {
	fmt.Println("参数为 add 时:", allCalc(add, 2, 1))
	fmt.Println("参数为 sub 时:", allCalc(sub, 2, 1))
}

  上述代码编译执行结果如下:

参数为 add 时: 3
参数为 sub 时: 1

  函数类型也是引用类型,它的指针指向函数代码的开始位置。函数的作用域与公共变量一样,在包内任何地方都可用,如果要其他包可以使用,需要将函数名首字母大写。

  第16行,自定义了一个新类型 Calc,它是 func(int, int) int 这个函数类型的别名,而上面 add、sub 两个函数的类型也都是着个类型,所以 Calc 就等于是上面两个函数的函数类型。

  第18~20行,声明了一个函数 allCalc,它的第一个参数 f 就是 Calc 类型,也就等于是一个 func(int, int) int 类型的函数,叫什么名字无所谓,函数体什么逻辑也无所谓。只要是包含两个 int 类型的参数,返回值是 int 类型的函数即可。这样做的目的就是可以将函数体内逻辑不同,但是参数及返回值相同的函数调入到这个函数内部执行,得到不同的运算逻辑。

  第19行,就是在执行外部传入进来的具体函数,具体执行的是哪个函数,取决于调用的地方给赋值的是哪个函数。第24行的调用赋值,传入的是 add 函数,那么第19行执行的就是 add 函数;第25行的调用赋值,传入的是 sub 函数,那么第19行执行的就是 sub 函数。

函数变量

  实际上前面示例中的第18行的参数 f 就是函数变量。函数变量,就是代表一个函数的变量,其值是指向函数代码起始位置的指针,所以说它也是引用类型。

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
)

func add(a, b int) int {
	return a + b
}

// 主函数,程序入口
func main() {
	f := add       // 声明一个函数变量 f,实际就是 add 函数
	a := f(2, 1)   // 通过函数变量调用函数
	b := add(2, 1) // 直接调用函数

	fmt.Println("函数变量调用结果:", a)
	fmt.Println("直接调用函数结果:", b)
}

  上述代码编译执行结果如下:

函数变量调用结果: 3
直接调用函数结果: 3

  通过示例可以看出,函数变量实际就等于给实体函数取了个别名,然后使用该函数的时候就可以通过这个变量调用。因此示例中 a 和 b 的值是相同的,因为本质上是执行了同一个函数。

  这里要特别注意,函数变量函数类型变量是不同的,函数变量代表的是一个具体函数,而函数类型变量是代表一类函数类型,千万不要混淆。

匿名函数

  在 Go语言中按照名称来区分有两种函数,命名(有名)函数和匿名函数。匿名函数,顾名思义就是没有名字的函数。需要使用的时候再定义,可以直接将匿名函数赋值给函数变量,还可以做实参给函数参数赋值以及做返回值,甚至可以直接调用(声明及调用)。

// test01 项目的 main 包,文件名 main.go
package main

import (
	"fmt"
)

// 匿名函数直接赋值给函数变量
var add = func(a, b int) int {
	return a + b
}

// 声明一个函数,参数 f 为函数类型 func(int,int)int
func allCalc(f func(int, int) int, a, b int) int {
	return f(a, b)
}

// 声明一个函数,匿名函数做返回值,根据sing不同,返回不同的函数
func selectCalc(sing string) func(int, int) int {
	switch sing {
	default:
		return nil
	case "+":
		return func(a, b int) int {
			return a + b
		}
	case "-":
		return func(a, b int) int {
			return a + b
		}
	}
}

// 主函数,程序入口
func main() {
	a := add(2, 1)                     // 通过函数变量调用匿名函数

	b := allCalc(func(a, b int) int {  // 匿名函数做为 allCalc 函数的参数
		return a + b
	}, 2, 1)                           // 这里的 2 和 1 是 allCalc 函数的第二个和第三个参数

	f := selectCalc("+")               // 变量 f 是通过 “+” 在 selectCalc 获取到其返回的函数
	c := f(2, 1)                       // 调用函数 f,实际就等于调用第24~26行的匿名函数

	d := func(x, y int) int {          // 这个匿名函数将被直接执行,无需再调用,变量 d 是函数执行后的返回值
		return x + y
	}(2, 1)                            // 这里的 (2, 1) 是这个匿名函数被立刻执行的关键

	fmt.Println("匿名函数给变量调用结果:", a)
	fmt.Println("匿名函数做参数调用结果:", b)
	fmt.Println("匿名函数做返回值调用结果:", c)
	fmt.Println("直接执行匿名函数输出结果:", d)
}

  上述代码编译执行结果如下:

匿名函数给变量调用结果: 3
匿名函数做参数调用结果: 3
匿名函数做返回值调用结果: 3
直接执行匿名函数输出结果: 3

  第9~11行,是定义了一个匿名函数,并将这个匿名函数赋值给了变量 add,这个 add 就是函数变量了。在第36行通过变量 add 调用了这个匿名函数,将函数返回值赋值给了变量 a。

  第14~16行,是将 allCalc 函数的第一个参数 f 定义为函数类型 func(int,int)int,调用 allCalc 函数是,第一个参数需要给赋值一个函数(为第38行的演示做准备)。在第38到40行调用了这个函数,第一个参数赋值的是一个匿名函数,那么 allCalc 函数体内执行的就是这个匿名函数了。匿名函数将 allCalc 函数的第二个参数和第三个参数计算后返回给了第38行的变量 b。

  第19~32行,是声明一个使用匿名函数做返回值的函数 selectCalc,根据传进来的字符串参数,决定返回哪个匿名函数,这要求返回的函数类型必须与第19行返回值定义那里的相同。第24到26行是一个匿名函数,第28到30行是一个匿名函数。在第42行调用 selectCalc 函数,传入的参数是 “+”,那 selectCalc 函数体内的 case “+” 成立,返回的就是第24到26行的匿名函数,那第42行的变量 f 就是这个匿名函数了。接着第43行使用 f 调用了这个匿名函数,将返回的计算结果赋值给了变量 c。

  第45~47行,定义的同时就调用了匿名函数,然后将结果给了变量 d。那这里的定义与其他地方的定义有什么区别吗,怎么就直接运行不需要调用了呢?怎么就不是将匿名函数赋值给了变量 d 呢?重点在第47行的花括号后面直接加上了函数调用时候需要的参数列表括号,有参的就是写在括号里,没有的就给各空括号。这种写法就是直接执行的匿名函数。第47行的 (2, 1) 就是等于给匿名函数传入了 2 和 1 两个参数,然后开始执行。
.
.
上一节:Go语言实践[回顾]教程23–详解Go语言函数的声明、变参、参数传递

下一节:Go语言实践[回顾]教程25–详解Go语言函数的延迟执行调用 defer
.

你可能感兴趣的:(Go语言,golang,Go语言教程,函数变量,函数类型,匿名函数)