函数在 Go 语言中属于“一等公民(First-Class Citizen)”拥有“一等公民”待遇的语法元素可以如下使用

  • 可以存储在变量中;
  • 可以作为参数传递给函数;
  • 可以在函数内部创建并可以作为返回值从函数返回;

1. 函数可以存储在变量中

var (myFprintf = func(w io.Writer, format string, a ...interface{}) (int, error) {return fmt.Fprintf(w, format, a...)}
)func main() {fmt.Printf("%T\n", myFprintf) // func(io.Writer, string, ...interface {}) (int, error)myFprintf(os.Stdout, "%s\n", "Hello, Go") // 输出Hello,Go
}

2. 作为参数传入函数

标准库 time 包的 AfterFunc 函数,就是一个接受函数类型参数的典型例子。你可以看看下面这行代码,这里通过 AfterFunc 函数设置了一个 2 秒的定时器,并传入了时间到了后要执行的函数。这里传入的就是一个匿名函数:

time.AfterFunc(time.Second*2, func() { println("timer fired") })
package mainimport "fmt"type CalculateType func(int, int) intfunc add(a, b int) int {return (a + b)
}func mul(a, b int) int {return (a * b)
}func Calculate(a, b int, f CalculateType) int {return f(a, b)
}
func main() {a, b := 2, 4fmt.Println(Calculate(a, b, add)) // 6fmt.Println(Calculate(a, b, mul))   // 8
}

以上例子,Calculatef 参数类型为 CalculateTypeaddmul 函数具有和 CalculateType 函数类型相同的参数和返回值,因此可以将 addmul 函数作为参数传入 Calculate 函数中。

3. 支持在函数内创建并通过返回值返回

func setup(task string) func() {println("do some setup stuff for", task)return func() {println("do some teardown stuff for", task)}
}func main() {teardown := setup("demo")defer teardown()println("do some bussiness stuff")
}

4. 拥有自己的类型

在前面讲解函数声明时,我们曾得到过这样一个结论:每个函数声明定义的函数仅仅是对应的函数类型的一个实例,就像

var a int = 13

这个变量声明语句中的 a,只是 int 类型的一个实例一样。换句话说,每个函数都和整型值、字符串值等一等公民一样,拥有自己的类型,也就是我们讲过的函数类型。

下面代码中的 HandlerFuncvisitFunc 就是 Go 标准库中,基于函数类型进行自定义的类型:

// $GOROOT/src/net/http/server.go
type HandlerFunc func(ResponseWriter, *Request)// $GOROOT/src/sort/genzfunc.go
type visitFunc func(ast.Node) ast.Visitor

Go 语言中,可以把函数作为一种变量,用 type 去定义它,那么这个函数类型就可以作为值传递,甚至可以实现方法,我们可以利用这一特性进行类型转换。

作为值传递的条件是类型具有相同的参数以及相同的返回值。Go 语言的类型转换基本格式如下:

type_name(expression)

代码实现

package mainimport "fmt"type CalculateType func(int, int)func (c *CalculateType) Server() {fmt.Println("这是函数类型")
}func add(a, b int) {fmt.Println(a + b)
}func mul(a, b int) {fmt.Println(a * b)
}func main() {a := CalculateType(add)b := CalculateType(mul)a(2, 4)b(2, 4)a.Server()b.Server()
}

输出结果:

6
8
这是函数类型
这是函数类型

5. 高阶函数

先来说说什么是高阶函数?简单地说,高阶函数可以满足下面的两个条件之一:

  • 接受其他的函数作为参数
  • 把其他的函数作为结果返回

只要满足了其中任意一个特点,我们就可以说这个函数是一个高阶函数。高阶函数也是函数式编程中的重要概念和特征。

高阶函数和闭包

  • 所谓闭包就是一个函数体内部引用了一个外部的变量
  • 高阶函数和函数式编程的特点就是 支持函数作为参数或者返回值

示例:

package mainimport ("errors""fmt"
)type operate func(x, y int) int// 方案1。
func calculate(x int, y int, op operate) (int, error) {// 函数类型属于引用类型,它的值可以为nil,而这种类型的零值恰恰就是nil。if op == nil {return 0, errors.New("invalid operation")}return op(x, y), nil
}// 方案2。
type calculateFunc func(x int, y int) (int, error)func genCalculator(op operate) calculateFunc {/*闭包定义:在一个函数中存在对外来标识符的引用。所谓的外来标识符,既不代表当前函数的任何参数或结果,也不是函数内部声明的,它是直接从外边拿过来的。*//*此处 return 函数是个闭包,它里面使用的变量op既不代表它的任何参数或结果也不是它自己声明的,而是定义它的genCalculator函数的参数,所以是一个自由变量。*/return func(x int, y int) (int, error) {if op == nil {return 0, errors.New("invalid operation")}return op(x, y), nil}
}func main() {// 方案1。x, y := 12, 23op := func(x, y int) int {return x + y}result, err := calculate(x, y, op)fmt.Printf("The result: %d (error: %v)\n",result, err)result, err = calculate(x, y, nil)fmt.Printf("The result: %d (error: %v)\n",result, err)// 方案2。x, y = 56, 78add := genCalculator(op)result, err = add(x, y)fmt.Printf("The result: %d (error: %v)\n",result, err)
}

Go 可以定义函数类型的变量,函数类型的变量可以被调用。定义函数类型变量示例代码:

package mainimport ("fmt""reflect"
)func main() {var f = func(str string) {fmt.Println("hello", str)}fmt.Println("类型是:", reflect.ValueOf(f).Kind())f("func type")
}

输出结果:

类型是: func
hello func type

等价于

package mainimport ("fmt""reflect"
)func demo(str string) {fmt.Println("hello", str)
}
func main() {var f func(str string)f = demofmt.Println("类型是:", reflect.ValueOf(f).Kind())f("func type")
}

函数类型变量也可以当做参数传递给另一个函数,然后在另一个函数中执行,示例代码如下:

package mainimport ("fmt""reflect"
)func exec(f func(str string)) {f("func type")
}func main() {var f = func(str string) {fmt.Println("hello", str)}fmt.Println("类型是:", reflect.ValueOf(f).Kind())exec(f)
}

输出结果:

类型是: func
hello func type

exec 函数接收一个函数类型的参数,那么是不是只要是函数,就可以被当做参数传入到 exec 中吗?

答案是:不行。从 exec 函数的参数列表可知,exec 接收一个函数类型参数,这个函数类型参数接收一个字符串类型的参数。

Go 学习笔记(61)— Go 高阶函数、函数作为一等公民(函数作为输入参数、返回值、变量)的写法相关推荐

  1. B站台湾大学郭彦甫|MATLAB 学习笔记|06 高阶绘图 Advanced Plot

    MATLAB学习笔记(06 高阶绘图 Advanced Plot) 如果想获得更好浏览体验的朋友可以转到下面链接 06 1. 对数图 (Logarithm Plots) x = logspace(-1 ...

  2. 学习笔记:MySQL高阶知识体系(下)——索引、锁、日志、隔离级别与MVCC

    转载自https://www.ydlclass.com/doc21xnv/database/mysqladvance/mysqlAdvance2.html MySQL高阶知识体系(下) 6. 索引 6 ...

  3. 【台大郭彦甫】Matlab入门教程超详细学习笔记六:高阶绘图(附PPT链接)

    高阶绘图 前言 一.进阶二维绘图 1. 对数图 2.一图双y轴 3. 直方图 4. 条形图 5. 饼状图 6. 极坐标图 7. 阶梯图与取样图 8. 箱线图以及误差线图 9. 填充图 二.配色 1.R ...

  4. python函数调用位置_python函数定义,调用,传参,位置参数及关键字参数,返回值

    使用函数是真正开始编程的第一步,函数y=f(x)我们并不陌生,对x进行一顿操作得到一个值y.给不同的x,进行相同的操作,得到相应的y值. 程序层面函数是执行特定任务的一段代码,将一段代码定义成函数并为 ...

  5. 学习笔记:C++初阶【C++入门、类和对象、C/C++内存管理、模板初阶、STL简介、string、vector、list、stack、queueu、模板进阶、C++的IO流】

    文章目录 前言 一.C++入门 1. C++关键字 2.命名空间 2.1 C语言缺点之一,没办法很好地解决命名冲突问题 2.2 C++提出了一个新语法--命名空间 2.2.1 命名空间概念 2.2.2 ...

  6. GD32学习笔记1(高难度工程,点亮一个LED灯)

    系列文章目录 第一章 GD32学习笔记1(高难度工程,点亮一个LED灯) 文章目录 系列文章目录 前言 一.工作流程 二.新建工程的准备工作 三.新建工程 四.工程目录管理 五.代码实现 1.初始化 ...

  7. HiveQL学习笔记(二):Hive基础语法与常用函数

    本系列是本人对Hive的学习进行一个整理,主要包括以下内容: 1.HiveQL学习笔记(一):Hive安装及Hadoop,Hive原理简介 2.HiveQL学习笔记(二):Hive基础语法与常用函数 ...

  8. python 一等公民_Python中一等公民——函数

    Python中"一等公民"--函数 Python的函数是"一等公民". 你可以将它们分配给变量,将它们存储在数据结构中,将它们作为参数传递给其他函数,甚至将它们 ...

  9. 学习大数据的第13天——Java面向对象(接口、分析参数返回值的类型不同时如何解决、包以及访问权限修饰符(public、protected、默认、private))

    学习大数据的第13天--Java面向对象(接口.分析参数返回值的类型不同时如何解决.包以及访问权限修饰符(public.protected.默认.private)) 接口 接口的基本定义: 1.1.语 ...

  10. python变量作用域图解_python笔记--作用域、高阶函数、闭包

    作用域 python中被赋值的变量的位置不同,限制了能访问到变量的范围也不同,换句话说就是"变量的作用域是由其在代码中的位置所决定的". 1.LEGB规则 L(local):局部作 ...

最新文章

  1. 对比丨深度学习库大排名:TensorFlow、Keras名列一二,Sonnet增长最快
  2. PMP之路 – 第2天 (做模拟题)
  3. 使用bc45编译ucos-II的配置过程
  4. C++Builder函数集(文件操作、获取时间、类型转换等)
  5. 8255编程c语言程序,51单片机8255驱动C程序
  6. 手机psp模拟器哪个好_功能强大,手机微信群控系统和云控哪个好?
  7. 深度学习福利入门到精通第二讲——AlexNet模型
  8. 运行catia_浅谈CATIA开发——CAA简介
  9. UVa 674 - Coin Change
  10. java 项目开发日报_CSDN日报191114:Java开发干货分享
  11. java中正则验证邮箱手机格式
  12. php公众号关注自动回复内容,微信公众号自动回复内容大全集锦
  13. 英语介绍嵌入式计算机,计算机专业的英文自我介绍
  14. oracle stdevp函数,适用于sql初学,学习sql语句的一些整理,其中大多是oracle的
  15. 现代化个人博客系统 ModStartBlog v5.7.0 简约纯白主题,富文本大升级
  16. 利用springMVC实现购物车结算功能
  17. 差分进化算法_想用遗传算法?来看看这些已为你做好的开源优化框架
  18. Linux驱动学习--wifi驱动(rtl88xx系列网卡芯片)源码分析
  19. 访问者模式Visitor
  20. 什么是模块化?为什么要模块化

热门文章

  1. Docker入门六部曲——Stack
  2. 2022-2028年中国氧化铟锡薄膜行业市场深度分析及前瞻研究报告
  3. 将文件名和文件修改时间批量输出至Excel中
  4. java实现将汉语转换为拼音
  5. 自然语言处理课程(二):Jieba分词的原理及实例操作
  6. TVM部署预定义模型
  7. x86 cpu卷积网络的自动调谐
  8. 基于区域的CNN(R-CNN)
  9. linux CentOS7 下 Docker安装
  10. HarmonyOS ListContainer 实现列表