当前位置: 首页 > news >正文

Go function - 有关function我能告诉你的一切

官方参考:

  • Go Spec
    • Built-in functions
    • Method_declarations

写 Go 的时候,函数是最先见到、也最容易被低估的角色。
它表面上只是func关键字后面跟一段代码,但真正写起来你会发现:Go 把函数设计得非常“务实”。它可以有多个返回值,可以被当作值传来传去,可以临时组装成闭包,还能摇身一变成为某个类型的方法。下面就来一起了解function有哪些基础的使用方法以及技巧

1. Function 基础

Go 里函数用func关键字定义:

func函数名(参数列表)返回值类型{// 函数体}

例如:

funcadd(aint,bint)int{returna+b}// 可以将同类型参数合并声明funcadd(a,bint)int{returna+b}

1.1 多返回值

多返回值是 Go 很有辨识度的特性。
在很多语言里,一个函数想返回两个结果,往往要塞进对象、数组、元组,或者靠异常去表达错误。Go 更直接:既然函数可能天然产出多个东西,那就把它们摊开放在返回值里。

funcdivide(a,bint)(int,int){returna/b,a%b}funcmain(){q,r:=divide(10,3)q2,_:=divide(10,3)// 也可以忽略某个返回值:}

1.2 命名返回值(Go独有)

返回值可以提前命名。
这有点像在函数出口提前摆好几个盒子,函数体里只负责往盒子里放东西,最后一句return就能把盒子一起端出去:

funcrectangle(w,hint)(areaint){area=w*hreturn}funcrectangle(w,hint)int{// 等价area:=w*hreturnarea}

多返回值也可以命名:

funccalc(a,bint)(sumint,diffint){sum=a+b diff=a-breturn}

不过实际开发里不要滥用裸return,函数长了会降低可读性。
此外再遇上defer+闭包的情况,可能会产生一些意想不到的输出结果

functest()(resultint){deferfunc(){result++}()return0}

很多人第一反应是0,但它的实际输出是1

官方文档这样解释:

For instance, if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned. If the deferred function has any return values, they are discarded when the function completes. (See also the section on handling panics.)
例如,如果延迟函数是一个函数字面量 ,并且其外层函数具有命名的结果参数 ,且这些参数的作用域位于该字面量内部,则延迟函数可以在结果参数返回之前访问并修改它们。如果延迟函数有任何返回值,则这些返回值会在函数执行完毕时被丢弃。(另请参阅关于处理 panic 的部分。)

2. Function 核心用法

2.1 main 与 init 函数

Go 程序启动时,并不是一上来就冲进main
它会先把包依赖、全局变量、init函数这些“开场准备”处理完,再把舞台交给main

funcmain(){fmt.Println("Hello Go")}
funcinit(){fmt.Println("init")}

程序运行时会先初始化包依赖,执行所有init,最后执行main

对比点initmain
是否自动执行
是否建议手动调用
数量多个只能一个
执行时机程序启动前最后执行
所在包任意包只能 main 包
用途初始化程序入口

2.2 立即执行函数

对于写过 Js,对这一套应该很眼熟。函数刚定义完,后面立刻跟一对括号执行。

func(){fmt.Println("run immediately")}()func(namestring){fmt.Println("Hello",name)}("XiaoYi")

它在并发示例中经常出现,用来保证每个 goroutine 拿到独立变量,避免闭包捕获同一个i
在并发场景里相当于给变量拍一张当时的快照:

fori:=0;i<n;i++{gofunc(iint){// 使用 i}(i)}

2.3 函数也是值

这个用法让我想到了C++的函数指针传递函数,总之这一套函数操作自由度极高

Go 的函数可以作为值传递。

更有意思的是,当函数引用了外部变量时,它带走的不只是函数本体,还包括它需要的那部分环境。
这就是闭包的味道:函数像是背了一个小包,把路上需要的变量也一起带着。

函数可以赋值给变量:

add:=func(a,bint)int{// 匿名函数作为值传递returna+b}result:=add(1,2)

要注意的是内置函数(例如slice的len,cap等)不能作为值在变量之间传递:

Built-in functions
The built-in functions do not have standard Go types, so they can only appear in call expressions; they cannot be used as function values.

2.3.1 函数作为参数

函数作为参数时,我们可以把“要做什么”交给调用方决定。
外层函数只负责搭台,真正的计算逻辑由传进来的函数登场:

funcoperate(a,bint,fnfunc(int,int)int)int{returnfn(a,b)}result:=operate(3,4,func(x,yint)int{returnx+y})
2.3.2 函数作为返回值

函数作为返回值时,外层函数就像一个工厂:根据传入参数,生产出一个定制版函数。

funcmakeAdder(baseint)func(int)int{returnfunc(xint)int{returnbase+x}}

使用:

add10:=makeAdder(10)fmt.Println(add10(5))// 15

这里内部函数引用了外部变量base
makeAdder(10)返回的不是一个普通加法器,而是一个已经记住base = 10的加法器,这就是闭包。

2.4 闭包

闭包可以记住外部变量。
如果普通函数像一次性工具,用完就散场;闭包更像带记忆的工具,它能把某些状态留在身上,下次调用时继续用。

funccounter()func()int{count:=0returnfunc()int{count++returncount}}

使用:

c:=counter()fmt.Println(c())// 1fmt.Println(c())// 2fmt.Println(c())// 3

这里的count明明定义在counter里面,但counter返回以后它并没有立刻消失。
因为返回的匿名函数还需要它,所以这个变量会继续活着。每次调用c(),都是在修改同一个被记住的count

2.5 defer 让函数延迟执行

defer标记的函数会在当前函数结束前执行
它特别适合做“收尾动作”:打开文件后记得关闭,加锁后记得解锁,申请资源后记得释放。

funcreadFile(){deferfmt.Println("close file")fmt.Println("read file")}

输出:

read file close file

多个defer是后进先出,可以想象成栈的结构

functest(){deferfmt.Println("A")deferfmt.Println("B")deferfmt.Println("C")}

输出:

C B A

3. Function 进阶

3.1 面向对象

Go 没有class关键字,但这并不影响它表达“对象行为”。
它没有把方法塞进类里,而是把方法绑定到类型上:类型负责数据,方法负责行为,两者组合起来也能写出清晰的对象模型。

对象方法:本质是带接收者的函数

官方文档:Method_declarations

Go 可以给类型定义方法:

MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] . Receiver = Parameters .

官方定义里面,将接收者插在了funcMethodName之间。
下面这段代码看起来像成员方法,但本质仍然是一种带接收者的函数:

typePlayerstruct{NamestringHPint}func(p Player)SayName(){fmt.Println(p.Name)}

调用:

player:=Player{Name:"Knight",HP:100}player.SayName()

下面有几点注意事项:

  1. receiver 只能有一个,不能是可变参数。
  2. receiver 必须绑定到当前包里的 defined type。
  3. 方法名绑定到 receiver base type,不是绑定到某个实例。
  4. 泛型类型可以有方法,但 receiver 的类型参数声明有严格规则。

值接收者和指针接收者是 Go 方法里非常关键的一道分水岭。
值接收者会复制一份对象,像是拿到一份复印件;指针接收者拿到的是原对象的地址,修改会真正落到原对象身上。
可以通过下面一个例子来证明:

type Player struct { HP int } func (p Player) ValueChange() { p.HP = 0 } func (p *Player) PointerChange() { p.HP = 0 } func main() { p := Player{HP: 100} p.ValueChange() fmt.Println(p.HP) // 100 p.PointerChange() fmt.Println(p.HP) // 0 }

虽然方法接收者是*Player,但 Go 通常允许你直接用player.TakeDamage(),不用手动写(&player).TakeDamage()
这是 Go 在可寻址变量上提供的一点语法照顾,写起来更像普通方法调用:

Effective Go: methods
值接收者方法:T*T都能调用
指针接收者方法:原则上只有*T能调用
但如果T是“可寻址的变量”,Go 会自动帮你取地址

3.2 可变参数函数

可变参数函数适合处理“参数数量不固定”的场景。
语法是...类型,意思是:这里可以接 0 个、1 个,也可以接很多个同类型参数。

funcsum(nums...int)int{total:=0for_,n:=rangenums{total+=n}returntotal}

调用:

sum(1,2,3)sum()

如果已经有 slice,可以用...展开。
这一步有点像把一整盒零件倒出来,一个个交给函数:

nums:=[]int{1,2,3}sum(nums...)

注意:可变参数必须放在参数列表最后。

funclog(prefixstring,values...int){}

3.3 函数类型:给函数起一个别名

可以给函数签名起别名,让意图更清楚:

typeOperationfunc(int,int)intfunccalculate(a,bint,op Operation)int{returnop(a,b)}

4. 小结

到这里,Go function 的主要地图基本展开了。
它不是只有“输入参数,返回结果”这么简单,而是一整套围绕代码复用、行为传递、状态保存、资源收尾组织起来的工具。

看看下面这些函数写法,你是否都能一眼读出含义?

funcf()funcf(aint)funcf(a,bint)funcf(aint,bstring)funcf()intfuncf()(int,string)funcf(aint)(resultint)funcf(nums...int)funcf(fnfunc(int)int)funcf()func(int)int

如果前面的内容都能读懂,再看下面这个综合案例就会顺很多。

packagemainimport"fmt"typePlayerstruct{NamestringHPint}funcNewPlayer(namestring)*Player{return&Player{Name:name,HP:100,}}func(p*Player)TakeDamage(damageint){p.HP-=damage}func(p Player)IsAlive()bool{returnp.HP>0}funcmain(){player:=NewPlayer("Cat Knight")player.TakeDamage(30)fmt.Println(player.Name)fmt.Println(player.HP)fmt.Println(player.IsAlive())}

输出:

Cat Knight 70 true
http://www.jsqmd.com/news/769287/

相关文章:

  • 神经网络参数化缩放(µP)原理与实践指南
  • Claude Code 免费使用指南:free-claude-code 代理方案全解析(2026)
  • 2026年贵阳装修公司排名指南:预算透明+环保可信的五大靠谱品牌深度横评 - 年度推荐企业名录
  • 模型量化鲁棒性优化:学习率调度与权重平均技术
  • dnSpy配置管理实战:从个人工作流到团队协作的进阶指南
  • 蓝牙5.3到底升级了啥?手把手教你为IoT设备选型避坑
  • 2026年想找靠谱重庆除甲醛供应商?哪个才是你的最优之选? - 速递信息
  • 终极指南:如何构建和使用MPC-BE开源媒体播放器
  • ComfyUI-Impact-Pack:AI图像增强插件的完整使用指南
  • 每年母亲节临近,很多人都会陷入同一个烦恼:送妈妈什么礼物才不踩雷? - 速递信息
  • 关于在网页中使用选择器的方式
  • 纯Java大模型推理引擎gemma4.java:零依赖、高性能部署实践
  • 如何在5分钟内完成专业级AI换脸:roop-unleashed终极指南
  • Arm Cortex-R82 ETM调试技术详解与应用实践
  • 热式质量流量计厂家怎么选?2026 十大品牌推荐榜单 - 陈工日常
  • markdownReader:浏览器中优雅阅读Markdown文档的完美解决方案
  • 别再手动改Word了!用Python的python-docx库,5分钟批量生成100份报告
  • 体验 Taotoken 官方价折扣后,在 Ubuntu 项目中的实际 token 花费变化
  • 2026年贵阳装修公司排名指南:预算透明、整装一站式、口碑靠谱品牌深度横评 - 年度推荐企业名录
  • Wand-Enhancer终极指南:零成本解锁WeMod专业版功能的完整教程
  • 重新定义工作空间:智能桌面分区系统的创新实践
  • 2026年贵阳装修公司排名完全指南:预算透明零增项、整装一站式解决方案对比评测 - 年度推荐企业名录
  • 2026 宁波彩钢瓦金属屋面厂房防水防腐公司排名|5 家正规企业推荐 + 避坑指南 - 速递信息
  • 3分钟理解Legacy iOS Kit:让旧iPhone重获新生的终极方案
  • 号外号外~2026年最新卖家精灵折扣码更新啦 它最大的优势 - 易派
  • 为 Claude Code 编程助手配置 Taotoken 作为后端大模型服务提供方
  • 如何快速完整地下载任何网站:WebSite-Downloader终极指南
  • 使用AutoHotKey实现自动化
  • 2026年扭矩测试仪优质厂家指南:国内外靠谱品牌与供应商全景推荐 - 品牌推荐大师
  • 初次使用大模型API,如何通过Taotoken模型广场快速了解与选型