Go中函数与方法本质不同:函数是独立代码块,方法绑定类型并隐式传递接收者;内建类型不可直接定义方法,需先定义别名类型;接收者用值或指针取决于是否修改字段及性能考量;参数始终传值,指针传递实为地址值传递;编译器自动处理取址/解引用,但仅限变量;接口实现依赖方法集规则。

Go 里函数和方法根本不是一回事
函数是独立的代码块,方法是绑定到某个类型上的函数。关键区别不在语法糖,而在调用时的隐式接收者传递机制。你写 user.GetName(),编译器实际帮你补上了 GetName(user) 这个参数;而普通函数 GetName(user) 必须显式传参,没得商量。
方法接收者必须是「本包定义的类型」或「指针/值类型」
常见错误:func (s string) ToUpper() string 在非 string 所在包中会报错 invalid receiver type string (string is not a defined type)。Go 不允许为内建类型(如 string、[]int)或别名以外的外部类型添加方法。
- 正确做法:先定义自己的类型,哪怕只是别名:
type MyString string,再写func (s MyString) ToUpper() string - 接收者用值还是指针?改结构体字段必须用指针接收者(
*User),只读计算可用值接收者(User);但若结构体较大,值接收会引发不必要的拷贝 - 同一个类型不能同时存在值接收者和指针接收者的方法——不是语法错误,但会导致调用歧义,编译器会拒绝
参数传递全是值传递,但「传指针」效果等价于引用传递
Go 没有引用传递。所谓“引用传递”只是把指针变量的值(即内存地址)传过去。所以 func modify(s *string) 能改原值,是因为你传的是地址的副本,它仍指向同一块内存;而 func modify(s string) 改的只是副本,不影响调用方。
- 切片、map、channel、func 类型本身已含指针语义,传它们时不用额外加
*就能修改底层数组或哈希表 - 结构体作为参数:小结构体(如两个
int)传值更高效;大结构体(含 slice 或大量字段)建议传指针,避免拷贝开销 - 方法接收者也遵循同样规则:值接收者拿到的是整个结构体的副本;指针接收者拿到的是结构体地址的副本
调用时的自动解引用和取地址容易让人迷糊
Go 会在调用方法时自动处理 & 和 *,但这不是魔法,而是编译器根据接收者类型和实参类型做的隐式转换。比如 u := User{}; u.Print() 调用指针接收者方法,编译器悄悄转成 (&u).Print();反过来,up := &User{}; up.Print() 调用值接收者方法,也会自动转成 (*up).Print()。
立即学习“go语言免费学习笔记(深入)”;
- 这种自动转换只适用于变量,不适用于字面量或表达式:
User{}.Print()无法调用指针接收者方法,因为没地方取地址 - 接口实现判断看的是方法集:值类型
T的方法集只包含值接收者方法;*T的方法集包含值和指针接收者方法。所以想让T满足某个含指针接收者方法的接口,必须传&t - 最容易漏掉的点:日志、调试时打印接收者地址,你会发现值接收者方法里的
fmt.Printf("%p", &t)和调用方的地址不同——它真是副本










