news 2026/10/30 5:52:22

Go进阶之习惯函数一等公民

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go进阶之习惯函数一等公民

在Go语言中.函数是唯一一种基于特定输入 实现特定任务并可反馈任务执行结果的代

码块.本质上可以说Go程序就是一组函数的集合.

1.函数特点:

1).以func关键字开头.

2).支持多返回值.

3).支持具名返回值.

4).支持递归调用.

5).支持同类型的可变参数.

6).支持defer.实现函数优雅返回.

2.一等公民概念(wiki发明人 c2站点作者的诠释):

如果一门编程语言对某种语言元素的创建和使用没有限制.我们可以像对待值(value)

一样对待这种元素语法.那么我们就称这种语法元素是这门编程语言的一等公民.拥有

一等公民待遇的语法元素可以存储在变量中.可以作为参数传递给函数.可以在函数内

部创建并可以作为返回值从函数返回.在动态类型语言中.语言运行还支持对一等公民

类型检查.

3.Go对一等公民的诠释:

3.1正常创建:

我们可以在源码底层正常创建一个函数.如下所示:

源码位置:src/fmt/print.go:newPrinter

// newPrinter allocates a new pp struct or grabs a cached one. func newPrinter() *pp { p := ppFree.Get().(*pp) p.panicking = false p.erroring = false p.wrapErrs = false p.fmt.init(&p.buf) return p }
3.2在函数内创建:

在Go语言中我们可以在函数内定义一个新函数.如下在hexdumpWords函数内部定

义一个匿名函数(被赋值给变量p1).

源码位置:src/runtime/print.go:

// hexdumpWords prints a word-oriented hex dump of [p, end). // // If mark != nil, it will be called with each printed word's address // and should return a character mark to appear just before that // word's value. It can return 0 to indicate no mark. func hexdumpWords(p, end uintptr, mark func(uintptr) byte) { printlock() var markbuf [1]byte markbuf[0] = ' ' minhexdigits = int(unsafe.Sizeof(uintptr(0)) * 2) for i := uintptr(0); p+i < end; i += goarch.PtrSize { if i%16 == 0 { if i != 0 { println() } print(hex(p+i), ": ") } if mark != nil { markbuf[0] = mark(p + i) if markbuf[0] == 0 { markbuf[0] = ' ' } } gwrite(markbuf[:]) val := *(*uintptr)(unsafe.Pointer(p + i)) print(hex(val)) print(" ") // Can we symbolize val? fn := findfunc(val) if fn.valid() { print("<", funcname(fn), "+", hex(val-fn.entry()), "> ") } } minhexdigits = 0 println() printunlock() }
3.3作为类型:

可以使用函数自定义类型.示例如下:

源码位置:src/net/http/server.go

// The HandlerFunc type is an adapter to allow the use of // ordinary functions as HTTP handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // [Handler] that calls f. type HandlerFunc func(ResponseWriter, *Request)
3.4存储到变量:

可以将定义好的函数存储到一个变量中.

源码位置:src/runtime/vdso_linux.go

3.5作为参数传入函数:

可以将函数作为参数传入函数.

源码位置:src/time/sleep.go

// AfterFunc waits for the duration to elapse and then calls f // in its own goroutine. It returns a [Timer] that can // be used to cancel the call using its Stop method. // The returned Timer's C field is not used and will be nil. func AfterFunc(d Duration, f func()) *Timer { return (*Timer)(newTimer(when(d), 0, goFunc, f, nil)) }
3.6作为返回值从函数返回:

函数可以作为返回值从函数返回.

总结:

Go中的函数可以像普通整型那样被创建和使用.

除了上面的情况.函数还可以被放入数组 切片或map等结构.可以像其他类型变量一

样赋值个interface{}.示例如下:

package main import ( "fmt" "gomodule/data" _ "gomodule/pubsub" "time" ) type binaryFunc func(int, int) int func main() { var i interface{} = binaryFunc(func(a, b int) int { return a + b }) c := make(chan func(int2, int3 int) int, 10) fns := []binaryFunc{ func(a, b int) int { return a + b }, func(a, b int) int { return a - b }, func(a, b int) int { return a * b }, } c <- func(int2, int3 int) int { return int2 + int3 } fmt.Println(fns[0](5, 6)) f := <-c fmt.Println(f(5, 6)) v, ok := i.(binaryFunc) if !ok { fmt.Println("type assertion error") return } fmt.Println(v(5, 6)) }

4.特殊作用:

4.1像对整型变量那样对函数进行显示类型转换.

Go是类型安全的语言.不允许隐式类型转换.

var a int = 5 var b int32 = 6 //违法操作.无法编译. fmt.Println(a + b) //可以编译 fmt.Println(a + int(b))

对整型变量进行的操作也可以用在函数上.函数也可以被显示类型转换.这样的类型转

换在特定的领域有奇妙的作用.示例如下:

package main import ( "fmt" "gomodule/data" _ "gomodule/pubsub" "net/http" "time" ) func main() { http.ListenAndServe(":8080", http.HandlerFunc(greeting)) } func greeting(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello World!") }

这是一个常见的webserver例子.这里用到了一等公民函数的特性.

ListenAndServe源码如下:

// ListenAndServe listens on the TCP network address addr and then calls // [Serve] with handler to handle requests on incoming connections. // Accepted connections are configured to enable TCP keep-alives. // // The handler is typically nil, in which case [DefaultServeMux] is used. // // ListenAndServe always returns a non-nil error. func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }

ListenAndServe会将来自客户端的http请求交给handler处理.这里的handler参

数的类型http.handler接口如下:

type Handler interface { ServeHTTP(ResponseWriter, *Request) }

该接口有一个serveHttp方法.原型为

func(http.ResponseWrite,*http.Request).与我们定义的方法类型一致.但是不

能直接传入.因为greeting没有实现handler接口的方法.所以无法直接传入.

因为main方法中把函数传给了ListenAndServe.看看http.HandlerFunc是什么.

// The HandlerFunc type is an adapter to allow the use of // ordinary functions as HTTP handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // [Handler] that calls f. type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }

handlerFunc是一个基于函数的新类型.然后实现了ServeHttp方法.也就是实现了

Handler接口.所以说就是把传入的函数显示的转换成了HandlerFunc类型.然后后

者实现了Handler接口.这样就满足了要求.

4.2函数式编程:

Go语言演进至今.对多种编程范式或多或少都有支持.

柯里化函数:

是把接收的多个参数函数变换成接收一个单一参数(原函数的第一个函数)的函数.并

返回接受余下的参数和返回结果的新函数技术.

package main import ( "fmt" "gomodule/data" _ "gomodule/pubsub" "time" ) func times(x, y int) int { return x + y } func partialTimes(x int) func(int) int { return func(y int) int { return times(x, y) } } func main() { timesTwo := partialTimes(2) timesThree := partialTimes(3) timesFour := partialTimes(3) fmt.Println(timesTwo(5)) fmt.Println(timesThree(5)) fmt.Println(timesFour(5)) }

这个例子运用函数的两点性质.在函数中定义.通过返回值返回.闭包.

闭包是在函数内部定义的匿名函数.并且允许该匿名函数访问定义它的外部函数的作

用域.本质上.闭包是将函数内部和函数外部连接起来的桥梁.

函子:

函子需要满足两个条件.

函子本身是一个容器类型.以GO语言为例子.这个容器可以是切片 map 甚至

channel.

该容器类型需要实现一个方法.该方法接受一个类型参数.并在容器的每个元素上应用

这个函数.得到一个新函子.原函子容器内部元素不受影响.

package main import ( "fmt" "gomodule/data" _ "gomodule/pubsub" "time" ) type IntSliceFunctor interface { Fmap(fn func(int) int) IntSliceFunctor } type IntSliceFunctorImpl struct { ints []int } func (isf IntSliceFunctorImpl) Fmap(fn func(int) int) IntSliceFunctor { newInts := make([]int, len(isf.ints)) for i, elt := range isf.ints { retInt := fn(elt) newInts[i] = retInt } return IntSliceFunctorImpl{newInts} } func NewIntsSliceFunctor(slice []int) IntSliceFunctor { return IntSliceFunctorImpl{slice} } func main() { //原切片. intSlice := []int{1, 2, 3, 4} functor := NewIntsSliceFunctor(intSlice) mapperFunc1 := func(i int) int { return i + 10 } fmap := functor.Fmap(mapperFunc1) mapperFunc2 := func(i int) int { return i + 3 } fmap.Fmap(mapperFunc2) fmt.Println(functor) }

1).定义了一个intSliceFunctorImpl类型.用来作为函子的载体.

2).函子要实现的方法命名为Fmap.intSliceFunctorImpl类型实现了该方法.

该方法也是intSliceFunctor唯一的方法.

3).定义创建了intSliceFunctor的函数NewIntSliceFunctor.通过该函数初始化一

个切片.

4).在main方法中定义了两个转换函数.这两个函数应用到上述函子例子.得到新的函

子的内部容器元素值是元容器的元素值经过转换得到的.

5).无论如何应用转换函数.原函子容器的值不受影响.

4.2.3延续传递式:

函数式离不开递归.以求阶乘为例.

package main import ( "fmt" "gomodule/data" _ "gomodule/pubsub" "time" ) func factorial(n int) int { if n == 1 { return 1 } else { return n * factorial(n-1) } } func main() { fmt.Println(factorial(5)) }

上述例子未用到一等公民的性质.函数式编程有一种被称为延续式传递的编程风格可

以充分运用函数作为一等公民的性质.

在CPS风格中.函数是不允许有返回值.一个函数A应该将其想返回的值显示传给一个

continuation函数(一般接受一个参数).而这个函数自身是函数A的一个参数.示例如

下.

func CpsMax(a, b int) int { if a > b { return a } else { return b } } func main() { fmt.Println(CpsMax(5, 6)) }

max函数看作上面定义中的A函数在CPS转化之前的状态.根据CPS定义将其转换风

格.

1).去掉max的返回值.并为其添加一个函数类型的参数f(这个函数就是continute函

数):

func Max(x, y int, f func(int)) { }

2).将返回结果传递给continuation函数.把return语句替换为对f函数的调用.

func CpsMax(x, y int, f func(int)) { if x > y { f(x) } else { f(y) } } func main() { CpsMax(5, 6, func(i int) { fmt.Println(i) }) }

总结:

Go函数可以像变量值那样赋值给变量.作为函数传递 作为返回值返回以及在函数内部

创建.

函数可以像变量那样被显示转换.

基于函数特质.了解了Go中几种有用的编程风格.

不要为了符合特定风格滥用函数特质.

星星都失重了.

如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/29 13:20:54

Midscene.js终极指南:5分钟掌握AI自动化测试核心配置

Midscene.js终极指南&#xff1a;5分钟掌握AI自动化测试核心配置 【免费下载链接】midscene Let AI be your browser operator. 项目地址: https://gitcode.com/GitHub_Trending/mid/midscene 想要让AI成为你的得力测试助手&#xff0c;却又被复杂的配置流程吓退&#x…

作者头像 李华
网站建设 2026/10/27 3:38:15

YOLOv9官方镜像发布,目标检测进入新时代

YOLOv9官方镜像发布&#xff0c;目标检测进入新时代 在智能工厂的质检线上&#xff0c;每分钟有上千个产品飞速流转&#xff0c;传统检测方法还在为“是否漏检”而反复确认时&#xff0c;新一代目标检测模型已经完成了整条产线的实时扫描——这不是未来构想&#xff0c;而是YO…

作者头像 李华
网站建设 2026/10/27 10:43:54

实测科哥构建的ASR镜像:不同音频格式兼容性大考验

实测科哥构建的ASR镜像&#xff1a;不同音频格式兼容性大考验 在语音识别技术日益普及的今天&#xff0c;一个稳定、高效且兼容性强的本地化ASR&#xff08;自动语音识别&#xff09;系统显得尤为重要。最近&#xff0c;由“科哥”基于阿里云FunASR项目二次开发的 Speech Seac…

作者头像 李华
网站建设 2026/10/29 22:50:25

Chronos时序预测技术深度解析:外部特征融合的终极指南

Chronos时序预测技术深度解析&#xff1a;外部特征融合的终极指南 【免费下载链接】chronos-forecasting 项目地址: https://gitcode.com/GitHub_Trending/ch/chronos-forecasting Chronos时序预测是Amazon推出的革命性深度学习预测框架&#xff0c;通过整合外部特征信…

作者头像 李华
网站建设 2026/10/29 10:10:06

声纹识别技术演进分析:从i-vector到CAM++深度学习

声纹识别技术演进分析&#xff1a;从i-vector到CAM深度学习 1. 引言&#xff1a;声纹识别的现实意义与技术挑战 你有没有想过&#xff0c;为什么手机能通过“语音助手”识别你的声音&#xff1f;或者银行客服系统如何判断打电话的人是不是账户本人&#xff1f;这背后的核心技…

作者头像 李华
网站建设 2026/10/18 7:55:01

3步解锁专业级音乐播放器:foobox-cn深度定制指南

3步解锁专业级音乐播放器&#xff1a;foobox-cn深度定制指南 【免费下载链接】foobox-cn DUI 配置 for foobar2000 项目地址: https://gitcode.com/GitHub_Trending/fo/foobox-cn 厌倦了千篇一律的音乐播放器界面&#xff1f;foobox-cn基于foobar2000的DUI配置框架&…

作者头像 李华