Go语言开发(六)、Go语言闭包

Go语言开发(六)、Go语言闭包

一、函数式编程

1、函数式编程简介

函数式编程是一种编程模型,将计算机运算看作是数学中函数的计算,并且避免了状态以及变量的概念。
在面向对象思想产生前,函数式编程已经有数十年的历史。随着硬件性能的提升以及编译技术和虚拟机技术的改进,一些曾被性能问题所限制的动态语言开始受到关注,Python、Ruby和Lua等语言都开始在应用中崭露头角。动态语言因其方便快捷的开发方式成为很多人喜爱的编程语言,伴随动态语言的流行,函数式编程也开始流行。

2、函数式编程的特点

函数式编程的主要特点如下:
A、变量的不可变性: 变量一经赋值不可改变。如果需要改变,则必须复制出去,然后修改。
B、函数是一等公民: 函数也是变量,可以作为参数、返回值等在程序中进行传递。
C、尾递归:如果递归很深的话,堆栈可能会爆掉,并导致性能大幅度下降。而尾递归优化技术(需要编译器支持)可以在每次递归时重用stack。

3、高阶函数

在函数式编程中,函数需要作为参数传递,即高阶函数。在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
A、函数可以作为参数被传递
B、函数可以作为返回值输出

二、匿名函数

1、匿名函数简介

匿名函数是指不需要定义函数名的一种函数实现方式,匿名函数由一个不带函数名的函数声明和函数体组成。C和C++不支持匿名函数。

func(x,y int) int {
    return x + y
}

2、匿名函数的值类型

在Go语言中,所有的函数是值类型,即可以作为参数传递,又可以作为返回值传递。
匿名函数可以赋值给一个变量:

f := func() int {
    ...
}

定义一种函数类型:
type CalcFunc func(x, y int) int
函数可以作为值传递:

func AddFunc(x, y int) int {
return x + y
}

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

...

func OperationFunc(x, y int, calcFunc CalcFunc) int {
   return calcFunc(x, y)
}

func main() {
   sum := OperationFunc(1, 2, AddFunc)
   difference := OperationFunc(1, 2, SubFunc)
   ...
}

函数可以作为返回值:

// 第一种写法
func add(x, y int) func() int {
   f := func() int {
      return x + y
   }
   return f
}

// 第二种写法
func add(x, y int) func() int {
   return func() int {
      return x + y
   }
}

当函数返回多个匿名函数时建议采用第一种写法:

func calc(x, y int) (func(int), func()) {
   f1 := func(z int) int {
      return (x + y) * z / 2
   }

   f2 := func() int {
      return 2 * (x + y)
   }
   return f1, f2
}

匿名函数的调用有两种方法:

// 通过返回值调用
func main() {
   f1, f2 := calc(2, 3)
   n1 := f1(10)
   n2 := f1(20)
   n3 := f2()
   fmt.Println("n1, n2, n3:", n1, n2, n3)
}

// 在匿名函数定义的同时进行调用:花括号后跟参数列表表示函数调用
func safeHandler() {
   defer func() {
      err := recover()
      if err != nil {
         fmt.Println("some exception has happend:", err)
      }
   }()
   ...
}

三、闭包

1、闭包的定义

函数可以嵌套定义(嵌套的函数一般为匿名函数),即在一个函数内部可以定义另一个函数。Go语言通过匿名函数支持闭包,C++不支持匿名函数,在C++11中通过Lambda表达式支持闭包。
闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)。
闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,函数代码在函数被定义后就确定,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
所谓引用环境是指在程序执行中的某个点所有处于活跃状态的约束所组成的集合。约束是指一个变量的名字和其所代表的对象之间的联系。由于在支持嵌套作用域的语言中,有时不能简单直接地确定函数的引用环境,因此需要将引用环境与函数组合起来。

2、闭包的本质

闭包是包含自由变量的代码块,变量不在代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。由于自由变量包含在代码块中,所以只要闭包还被使用,那么自由变量以及引用的对象就不会被释放,要执行的代码为自由变量提供绑定的计算环境。
闭包可以作为函数对象或者匿名函数。支持闭包的多数语言都将函数作为第一级对象,即函数可以存储到变量中作为参数传递给其它函数,能够被函数动态创建和返回。

func add(n int) func(int) int {
   sum := n
   f := func(x int) int {
      var i int = 2
      sum += i * x
      return sum
   }
   return f
}

add函数中函数变量为f,自由变量为sum,同时f为sum提供绑定的计算环境,sum和f组成的代码块就是闭包。add函数的返回值是一个闭包,而不仅仅是f函数的地址。在add闭包函数中,只有内部的匿名函数f才能访问局部变量i,而无法通过其它途径访问,因此闭包保证了i的安全性。
当分别用不同的参数(10, 20)注入add函数而得到不同的闭包函数变量时,得到的结果是隔离的,即每次调用add函数后都将生成并保存一个新的局部变量sum。
在函数式语言中,当内嵌函数体内引用到体外的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体(闭包)返回。
当每次调用add函数时都将返回一个新的闭包实例,不同实例之间是隔离的,分别包含调用时不同的引用环境现场。不同于函数,闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
从形式上看,匿名函数都是闭包。
函数只是一段可执行代码,编译后就固定,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数。
对象是附有行为的数据,而闭包是附有数据的行为。

3、闭包的使用

闭包经常用于回调函数,当IO操作(例如从网络获取数据、文件读写)完成的时候,会对获取的数据进行某些操作,操作可以交给函数对象处理。
除此之外,在一些公共的操作中经常会包含一些差异性的特殊操作,而差异性的操作可以用函数来进行封装。

package main

import "fmt"

func adder() func(int) int {
   sum := 0
   f := func(x int) int {
      sum += x
      return sum
   }
   return f
}

func main() {
   sum := adder()
   for i := 0; i < 10; i++ {
      fmt.Println(sum(i))
   }
}

四、闭包的应用

package main

import "fmt"

//普通闭包
func adder() func(int) int {
   sum := 0
   return func(v int) int {
      sum += v
      return sum
   }
}

//无状态、无变量的闭包
type iAdder func(int) (int, iAdder)
func adder2(base int) iAdder {
   return func(v int) (int, iAdder) {
      return base + v, adder2(base + v)
   }
}

//使用闭包实现斐波那契数列
func Fibonacci() func() int {
   a, b := 0, 1
   return func() int {
      a, b = b, a+b
      return a
   }
}

func main() {
   //普通闭包调用
   a := adder()
   for i := 0; i < 10; i++ {
      var s int =a(i)
      fmt.Printf("0 +...+ %d = %d\n",i, s)
   }
   //状态 无变量的闭包 调用
   b := adder2(0)
   for i := 0; i < 10; i++ {
      var s int
      s, b = b(i)
      fmt.Printf("0 +...+ %d = %d\n",i, s)
   }

   //调用斐波那契数列生成
   fib:=Fibonacci()
   fmt.Println(fib(),fib(),fib(),fib(),fib(),fib(),fib(),fib())
}

原文地址:http://blog.51cto.com/9291927/2130303

时间: 2024-10-11 06:14:11

Go语言开发(六)、Go语言闭包的相关文章

Go语言开发(二)、Go语言基础

Go语言开发(二).Go语言基础 一.Go语言程序结构 Go语言程序基本结构如下:A.包声明B.引入包C.函数D.变量E.语句 & 表达式F.注释 package main //包声明 import "fmt" //引入包 func main(){ //main函数 //打印Hello World fmt.Print("Hello World!") } 二.Go语言基础语法 1.Go语言标记 Go语言程序由多个标记组成,可以是关键字.标识符.常量.字符串.符

Go语言开发学习教程

Go语言开发学习教程 Go语言开发学习教程目录如下: Go语言开发(一).Go语言简介http://blog.51cto.com/9291927/2126775Go语言开发(二).Go语言基础http://blog.51cto.com/9291927/2127825Go语言开发(三).Go语言内置容器http://blog.51cto.com/9291927/2129969Go语言开发(四).Go语言面向对象http://blog.51cto.com/9291927/2130132Go语言开发(

001-iOS开发前奏-C语言笔记

学习目标 1.[了解]操作系统 2.[了解]应用软件 3.[了解]操作系统的分类和市场占有份额 4.[了解]iOS操作系统 5.[了解]应用软件开发的分类 6.[了解]UNIX常用命令 7.[掌握]如何开发第一个C语言程序 一.操作系统 我们的计算机是由很多种硬件设备组成的,比如CPU.内存.硬盘.网卡.主板.声卡.......如果计算机只是仅仅有这些硬件设备,这样能不能正常使用? CPU:负责计算.处理数据 内存:存储数据 (临时) 硬盘:存储数据 (永久) 网卡:接收.发送网络数据 声卡:输

「C语言」Windows+EclipseCDT下的C语言开发环境准备

之前写过一篇 「C语言」在Windows平台搭建C语言开发环境的多种方式 ,讨论了如何在Windows下用DEV C++.EclipseCDT.VisualStudio.Sublime Test.Clion等IDE/编辑器搭建C语言开发环境,但也只是点到为止的介绍,对每一个开发环境的选择没有详细的步骤与过程: 这次借助C语言期末课程设计文档上介绍用Eclipse开发C语言的时机,逐步图文论证如何用Eclipse从安装到输出自己的第一个C语言Hello World: 欢迎探讨,欢迎互粉: 目录:

Go语言开发(一)、Go语言简介

Go语言开发(一).Go语言简介 一.Go语言简介 1.Go语言简介 Go,全称golang,是Google开发的一种静态强类型.编译型.并发型并具有垃圾回收功能的编程语言. Go从2007年末由Robert Griesemer.Rob Pike.Ken Thompson(C语言发明者)主持开发,于2009年11月正式宣布成为开放源代码项目,并在Linux及Mac OS X平台上进行了实现,后续增加了Windows平台的实现.2012年初,Go语言官方发布了Go 1.0稳定版本,目前Go语言基于

使用 Go 语言开发大型 MMORPG 游戏服务器怎么样?(非常稳定、捕获所有异常、非常适合从头开始,但大公司已经有现成的C++框架、所以不会使用)

使用 Go 语言开发大型 MMORPG 游戏服务器怎么样?和C Socket服务器比起来有什么优劣?可行性怎么样? 从2013年起,经朋友推荐开始用Golang编写游戏登陆服务器, 配合C++做第三方平台验证. 到编写独立工具导表工具GitHub - davyxu/tabtoy: 跨平台的高性能便捷电子表格导出器. 以及网络库GitHub - davyxu/cellnet: 简单,方便,高效的Go语言的游戏服务器底层. 最终使用这些工具及库编写整个游戏服务器框架, 我的感受是很不错的 细节看来,

Go语言开发(九)、Go语言并发编程

Go语言开发(九).Go语言并发编程 一.goroutine简介 1.并发与并行简介 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行.并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行.并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要

Go语言开发(十五)、Go语言常用标准库五

Go语言开发(十五).Go语言常用标准库五 一.md5 1.md5简介 md5在crypto/md5包中,md5包提供了New和Sum方法. func New() hash.Hash func Sum(data []byte) [Size]byte hash.Hash继承了io.Writer,因此可以将其当成一个输入流进行内容的更新. type Writer interface { Write(p []byte) (n int, err error) } Write方法将p中的内容读入后存入到h

为什么跨语言开发不是件难事

经常会有同学遇到跨语言开发.写.net的要转型写PHP,写PHP的要转型写Java,写Java要转型写GO,写Lua的要转型写JavaScript,写JavaScript要转型写Typescript等等.每当遇到这种公司层面的全面转型,新同学就比较焦虑,到底能不能转型成功,会不会做不好被公司裁了. 个人经历 先说下个人经历.我是08年底开始用PHP写代码,09年底的时候,有同事说隔壁用actionscript写网页游戏的程序员,半年经验就一万多了(我那时只有六七千),我当时一想老子特么也要写ac