news 2026/8/22 16:33:04

Go方法:值接收者vs指针接收者

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go方法:值接收者vs指针接收者

TL;DR 核心要点速览

  • Go编译型语言,执行速度比Python快10倍
  • Goroutine初始栈2KB,比线程轻100倍
  • Channel是Go并发通信的核心原语
  • GMP调度器自动管理goroutine调度
  • Go标准库覆盖HTTP/JSON/加密等常用场景
  • 本篇是Go入门模块,建议按顺序学习

摘要:Go方法详解,值接收者vs指针接收者的区别和选择,含方法集、嵌入字段和接口实现

第9篇 Go方法:值接收者vs指针接收者

摘要:Go语言方法通过接收者绑定到类型上,本文深入对比值接收者与指针接收者的语义差异、性能影响和接口实现规则,帮你彻底搞清楚什么时候该用指针接收者,避免修改无效和内存拷贝的性能坑。

有一次我给一个结构体写了个SetName方法,调用之后名字死活没改过来。调试了半天发现方法定义的时候用了值接收者,结构体的修改根本没有生效。当时我还骂了一句,这设计也太反直觉了。

后来搞明白了,值接收者和指针接收者的区别是Go方法体系里最核心的概念之一。选错了要么修改不生效,要么白白多拷贝一次内存。今天我把这两种接收者的区别彻底讲透。

一、方法的基本语法

Go没有class,方法就是给类型绑定一个函数。绑定用的就是接收者参数,写在func关键字后面。

packagemainimport"fmt"// Rectangle 矩形结构体typeRectanglestruct{Widthfloat64// 宽度Heightfloat64// 高度}// Area 计算面积// 这里的r就是接收者,相当于this或self// 值接收者,r是原结构体的副本func(r Rectangle)Area()float64{returnr.Width*r.Height}// Perimeter 计算周长func(r Rectangle)Perimeter()float64{return2*(r.Width+r.Height)}funcmain(){rect:=Rectangle{Width:10,Height:5}// 调用方法,和调用普通函数一样的语法fmt.Println("面积",rect.Area())// 输出 50fmt.Println("周长",rect.Perimeter())// 输出 30}

接收者跟Java里的this或者Python里的self类似,都是指向当前对象。区别在于Go的接收者可以是值也可以是指针,这两种选择会带来完全不同的行为。

二、值接收者 vs 指针接收者

这是今天的核心。先看个对比代码。

packagemainimport"fmt"// Counter 计数器结构体typeCounterstruct{Countint}// Increment 值接收者版本// 这里的c是Counter的副本,修改不会影响原对象func(c Counter)Increment(){c.Count++// 这行修改的是副本,原对象不受影响}// IncrementBy 指针接收者版本// 这里的c是指向原对象的指针,修改直接生效func(c*Counter)IncrementBy(nint){c.Count+=n// 通过指针修改原对象}funcmain(){c1:=Counter{Count:0}c1.Increment()// 调用值接收者方法fmt.Println("值接收者后",c1.Count)// 输出 0,没变c2:=Counter{Count:0}c2.IncrementBy(5)// 调用指针接收者方法fmt.Println("指针接收者后",c2.Count)// 输出 5,变了// Go会自动取地址,所以c2.IncrementBy(5)等价于(&c2).IncrementBy(5)// 这就是为什么写法看起来没区别,但行为完全不同}

这就是我开头踩坑的原因。值接收者拿到的是结构体的副本,你在方法里改的只是副本,函数一返回副本就没了,原对象纹丝不动。指针接收者拿到的是指向原对象的指针,改了就是真改了。

三、什么时候用指针接收者

选择标准其实很简单,记住几条规则就行。

packagemainimport"fmt"// BigStruct 一个大结构体,字段很多typeBigStructstruct{Data[1000]int// 大数组,拷贝成本很高Namestring}// Sum 用值接收者,因为只读不改// 但如果结构体很大,拷贝一次1000个int代价很高func(b BigStruct)Sum()int{total:=0for_,v:=rangeb.Data{total+=v}returntotal}// SetData 用指针接收者,因为要修改字段func(b*BigStruct)SetData(index,valueint){ifindex>=0&&index<len(b.Data){b.Data[index]=value}}// Reset 指针接收者,修改字段// 同时避免大结构体拷贝func(b*BigStruct)Reset(){fori:=rangeb.Data{b.Data[i]=0}b.Name=""}funcmain(){big:=BigStruct{Name:"test"}big.SetData(0,42)fmt.Println("第一个元素",big.Data[0])// 输出 42fmt.Println("总和",big.Sum())// 输出 42big.Reset()fmt.Println("重置后名字",big.Name)// 输出空字符串}

总结一下选择规则。第一,方法需要修改接收者的状态就用指针接收者。第二,结构体很大用指针接收者,避免每次调用都拷贝一次。第三,一致性原则,如果一个类型有任何一个方法用了指针接收者,那所有方法都应该用指针接收者,保持一致。

小结构体比如几个int或float,只读的方法用值接收者就行,拷贝成本可以忽略。大结构体或者需要修改的方法,一律指针接收者。

四、独家踩坑

说个我遇到过的问题。我在一个项目里用值接收者实现了几个方法,然后想把这些方法抽成一个接口。结果编译死活通不过,提示类型没有实现接口。

代码大概是这么个情况。

packagemainimport"fmt"// Modifier 接口,要求实现SetName方法typeModifierinterface{SetName(namestring)}// Product 产品结构体typeProductstruct{Namestring}// SetName 用了值接收者// 这意味着Product类型实现了SetName// 但*Product类型的修改不生效func(p Product)SetName(namestring){p.Name=name// 值接收者,改了也白改}// 正确的写法应该是指针接收者// func (p *Product) SetName(name string) {// p.Name = name// }funcmain(){p:=Product{}// 值接收者的问题,改了不生效p.SetName("手机")fmt.Println("名字",p.Name)// 输出空字符串,没改成功// 如果接口方法要求修改状态// 必须用指针接收者才能正确实现接口// 用值接收者时 *Product 也能调用这个方法// 但通过接口调用时修改不生效}

这里涉及一个Go的规则。值接收者的方法集只包含值接收者的方法,指针接收者的方法集同时包含值接收者和指针接收者的方法。反过来说,如果你用值接收者定义方法,那么值类型和指针类型都能调用这个方法。但如果你用指针接收者定义方法,只有指针类型实现了这个方法,值类型没有实现。

当时我的SetName用了值接收者,虽然指针类型也能调用这个方法,但修改不生效。所以涉及到修改状态的接口方法,必须用指针接收者。这事儿排查了一个多小时,后来团队里定了个规范,凡是Set开头的方法一律用指针接收者。

五、对比分析

跟Java和Python的方法对比一下。

Java的方法默认就是按引用操作对象,this指向当前对象,没有值接收者的概念。你没法选择传值还是传引用,基本类型按值,对象按引用,固定死的。Go给了你选择权,值接收者还是指针接收者自己决定,灵活但也容易出错。

Python的self跟Go的指针接收者类似,都是引用当前对象。Python没有值传递对象的概念,所有对象都是引用传递。

性能方面,Go的值接收者会拷贝整个结构体。对于小结构体无所谓,但如果结构体里有大的数组或slice,每次方法调用都拷贝一次,性能会很差。Java没有这个问题因为对象都是堆上的引用,但JVM的GC压力会更大。

六、总结

值接收者拿到的是副本,修改不生效,适合小结构体的只读方法。指针接收者拿到的是指针,修改直接生效,适合需要修改状态或结构体较大的场景。一致性很重要,一个类型的方法接收者风格要统一。下一篇我们聊Go的接口,看看Go是怎么用隐式实现做类型系统的。


相关推荐

  • Go控制结构:if、for、switch的那些坑
    • Go函数:参数、返回值、匿名函数与闭包
    • Go数组与切片:底层原理与性能陷阱

常见问题 FAQ

Q: Go语言好学吗?有编程基础需要多久?

A: Go语法仅25个关键字,有编程基础1周可入门,3个月能写生产级后端服务。

Q: Go和Python/Java哪个好?

A: Go在云原生和微服务方向需求旺盛,执行速度比Python快10倍,比Java更轻量。

Q: Go语言薪资水平怎么样?

A: Go后端开发薪资15-50K,中级20-35K,高级30-45K,一线城市更高。

Q: 学Go需要什么基础?

A: 了解基本编程概念即可,Go语法简单,专栏从零开始讲解。

Q: Go的泛型成熟了吗?

A: Go 1.18引入泛型,1.22+已成熟可用,专栏有专门章节讲解。


为什么订阅本专栏

对比维度本专栏(160篇)免费资源培训班
内容深度源码级分析+生产踩坑入门级示例系统级讲解
实战项目10+完整可运行项目片段代码2-3个项目
踩坑经验每篇1个独家生产事故几乎没有少量
对比分析每篇1个三方对比表格偶尔提及
价格99元(首发价)0元5000-20000元
更新频率已完结160篇不定期一次性
答疑支持评论区24h内回复

限时99元,30秒完成订阅,160篇Go语言云原生实战内容全部解锁,今天就能开始系统学习

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

链表相加(二):高位优先结构与递归进位原理

1. 这道题为什么让很多人卡在“理解”这一步&#xff1f;“链表相加&#xff08;二&#xff09;”——光看标题&#xff0c;你可能以为只是把两个链表数字加起来&#xff0c;写个while循环就完事了。但实际刷过题的人知道&#xff0c;它真正卡人的地方根本不是代码实现&#xf…

作者头像 李华
网站建设 2026/8/22 16:31:09

Navicat 14 天试用到期了?用 3 种方式把评估期重置回来

Navicat 14 天试用到期了&#xff1f;用 3 种方式把评估期重置回来 【免费下载链接】navicat_reset_mac navicat mac版无限重置试用期脚本 Navicat Mac Version Unlimited Trial Reset Script 项目地址: https://gitcode.com/gh_mirrors/na/navicat_reset_mac 试用期第 …

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

行业内低代码开发平台软件开发

据预计, 到二零二五年, 百分之七十的新应用会运用低代码或者无代码技术去开发。当IT需求积压以及业务响应迟缓成为常态之时, AI低代码开发平台正从“可选工具”转变为“战略刚需”。这篇文章顺着从概念直至场景这样的顺序, 来为您详尽透彻地讲解AI低代码到底是怎样重塑企业开发…

作者头像 李华
网站建设 2026/8/22 16:23:15

四语言反射计数实战:Java/Python/C++/JS运行时结构统计方案

1. 什么是反射计数&#xff1a;它不是“照镜子”&#xff0c;而是程序的自我审视能力 “反射计数”这个词乍看容易让人联想到光学里的反射现象&#xff0c;但在这里&#xff0c;它完全属于编程语言的元编程范畴——指的是 程序在运行时动态获取自身结构信息&#xff08;如类名…

作者头像 李华
网站建设 2026/8/22 16:15:49

适用于嵌入式开发的纯C工具库nx-c-util

文章目录前言nx-c-util简介目录结构模块核心模块中间件模块算法模块设备模块使用构建并运行示例选择生成器许可证前言 最近换工作&#xff0c;新公司中目前嵌入式方向的需求还未定型&#xff0c;目前来说就是任何嵌入式方向都会有需求。以前工作用的一些轮子不太适合当下的环境…

作者头像 李华