news 2026/9/3 1:01:03

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/9/2 22:10:16

永久开源承诺!科哥镜像可放心用于商业项目

永久开源承诺&#xff01;科哥镜像可放心用于商业项目 1. 引言&#xff1a;为什么这款语音识别镜像值得你关注&#xff1f; 在AI落地越来越普遍的今天&#xff0c;中文语音识别已经不再是大厂专属的技术。越来越多的中小企业、独立开发者甚至个人用户&#xff0c;都希望将语音…

作者头像 李华
网站建设 2026/8/29 16:58:18

Atmosphere EmuMMC启动故障全解析:从现象诊断到体系预防

Atmosphere EmuMMC启动故障全解析&#xff1a;从现象诊断到体系预防 【免费下载链接】Atmosphere Atmosphre is a work-in-progress customized firmware for the Nintendo Switch. 项目地址: https://gitcode.com/GitHub_Trending/at/Atmosphere "Switch开机卡在A…

作者头像 李华
网站建设 2026/8/31 2:24:26

Chatbox桌面AI助手:一站式解决编程、创作与协作的智能工具

Chatbox桌面AI助手&#xff1a;一站式解决编程、创作与协作的智能工具 【免费下载链接】chatbox Chatbox是一款开源的AI桌面客户端&#xff0c;它提供简单易用的界面&#xff0c;助用户高效与AI交互。可以有效提升工作效率&#xff0c;同时确保数据安全。源项目地址&#xff1a…

作者头像 李华
网站建设 2026/9/2 22:10:21

PaddleSpeech TTS模型下载实战:从报错修复到高效部署

PaddleSpeech TTS模型下载实战&#xff1a;从报错修复到高效部署 【免费下载链接】PaddleSpeech Easy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification Syste…

作者头像 李华
网站建设 2026/9/2 22:09:59

PowerToys Image Resizer:Windows用户必备的图片批量处理神器

PowerToys Image Resizer&#xff1a;Windows用户必备的图片批量处理神器 【免费下载链接】PowerToys Windows 系统实用工具&#xff0c;用于最大化生产力。 项目地址: https://gitcode.com/GitHub_Trending/po/PowerToys 还在为几十张图片尺寸调整而烦恼吗&#xff1f;…

作者头像 李华