go从零单排之方法

一、Go 方法

Go 中的方法(Method) 是「绑定到特定类型的函数」,可以把它理解为:给自定义类型(结构体 / 基本类型)"新增" 的专属函数,核心作用是让代码更符合面向对象的 "封装" 思想,同时保持 Go 语言的简洁性。

1. 方法与函数的核心区别
特性 函数(Function) 方法(Method)
绑定关系 无绑定,属于全局 / 包级别 绑定到特定类型(接收者),属于该类型
定义语法 func 函数名(参数) 返回值 {} func (接收者) 方法名(参数) 返回值 {}
调用方式 函数名(参数) 实例.方法名(参数)
2. 方法示例
复制代码

package main import "fmt" // 1. 定义自定义类型(以结构体为例) type Person struct { Name string Age int } // 2. 为 Person 类型定义方法(值接收者) // (p Person) 是接收者:表示该方法绑定到 Person 类型 func (p Person) SayHello() { fmt.Printf("大家好,我是%s,今年%d岁\n", p.Name, p.Age) } // 3. 为 Person 定义修改属性的方法(指针接收者) func (p *Person) Grow() { p.Age++ // 修改接收者的属性 } func main() { // 4. 创建实例并调用方法 p := Person{Name: "张三", Age: 20} p.SayHello() // 输出:大家好,我是张三,今年20岁 p.Grow() // 调用指针接收者方法,修改Age p.SayHello() // 输出:大家好,我是张三,今年21岁 }

关键拆解

  • 接收者(Receiver) :方法定义中 (p Person) 或 (p *Person) 是核心,p 是接收者变量(可自定义命名,通常用类型首字母小写),Person/*Person 是接收者类型;
  • 调用逻辑:方法必须通过「类型实例」调用,不能像函数一样直接调用;
  • 核心价值:把 "数据(结构体)" 和 "操作数据的逻辑(方法)" 绑定,实现封装。
3. 接收者的两种类型(值接收者 vs 指针接收者)

这是 Go 方法最核心的知识点,直接决定方法能否修改原实例:

接收者类型 语法示例 本质 能否修改原实例 适用场景
值接收者 (p Person) 接收实例的拷贝 不能 只读操作、简单类型、避免拷贝
指针接收者 (p *Person) 接收实例的指针 修改实例、大结构体、避免拷贝

实战对比示例

复制代码

package main import "fmt" type Student struct { Score int } // 值接收者:操作的是拷贝,原实例不变 func (s Student) AddScore1(score int) { s.Score += score } // 指针接收者:操作的是原实例,会修改值 func (s *Student) AddScore2(score int) { s.Score += score } func main() { stu := Student{Score: 80} stu.AddScore1(10) fmt.Println(stu.Score) // 输出 80(值接收者,原实例未变) stu.AddScore2(10) fmt.Println(stu.Score) // 输出 90(指针接收者,原实例修改) }

重要补充:Go 会自动处理值和指针的转换,调用方法时无需手动取地址 / 解引用:

  • 值实例调用指针接收者方法:stu.AddScore2(10) 等价于 (&stu).AddScore2(10);
  • 指针实例调用值接收者方法:(&stu).AddScore1(10) 等价于 stu.AddScore1(10)。
4. 为非结构体类型定义方法

Go 允许为「自定义的基本类型别名」定义方法(不能直接为原生基本类型如 int 定义):

复制代码

package main import "fmt" // 定义int的别名类型 type MyInt int // 为MyInt定义方法 func (m MyInt) Double() MyInt { return m * 2 } func main() { var num MyInt = 5 fmt.Println(num.Double()) // 输出 10 }


二、Go 方法的核心特性

1. 封装性(面向对象核心)

方法可以访问接收者的私有字段(结构体中首字母小写的字段),外部包无法直接访问,只能通过方法操作,实现数据封装:

复制代码

// model包 - model/person.go package model // Person 结构体(导出) type Person struct { Name string // 导出字段(首字母大写) age int // 私有字段(首字母小写) } // SetAge 方法(导出):外部包只能通过该方法修改age func (p *Person) SetAge(a int) { if a > 0 && a < 150 { // 增加校验逻辑 p.age = a } } // GetAge 方法(导出):外部包只能通过该方法读取age func (p *Person) GetAge() int { return p.age } // main包 package main import ( "fmt" "your-project/model" ) func main() { p := &model.Person{Name: "李四"} p.SetAge(25) // 合法:通过方法修改私有字段 fmt.Println(p.GetAge()) // 输出 25 // p.age = 30 // 编译错误:无法访问私有字段 }

2. 方法集(Method Set)

每个类型都有对应的「方法集」,决定该类型的实例 / 指针能调用哪些方法:

  • 值类型(T) 的方法集:包含所有「值接收者」方法;
  • *指针类型(T) 的方法集:包含所有「值接收者 + 指针接收者」方法;

通俗理解:指针类型能调用所有方法,值类型只能调用值接收者方法(Go 会自动转换,但反向不行)。

3. 方法重载?No!

Go 不支持方法重载(同一类型不能定义同名但参数不同的方法),这是 Go 简洁性的体现,若需不同逻辑,可通过参数可选、多返回值实现。

4. 方法与接口的配合(核心应用)

方法是实现接口的唯一方式:只要类型实现了接口的所有方法,就隐式实现了该接口,无需显式声明(Go 接口的 "鸭子类型"):

复制代码

package main import "fmt" // 定义接口 type Speaker interface { Speak() string } // 定义类型1 type Dog struct{} // 实现Speaker接口的Speak方法 func (d Dog) Speak() string { return "汪汪汪" } // 定义类型2 type Cat struct{} // 实现Speaker接口的Speak方法 func (c Cat) Speak() string { return "喵喵喵" } // 通用函数:接收Speaker接口 func MakeSound(s Speaker) { fmt.Println(s.Speak()) } func main() { MakeSound(Dog{}) // 输出 汪汪汪 MakeSound(Cat{}) // 输出 喵喵喵 }


三、Go 方法的典型业务场景

场景 1:结构体封装

最核心场景:为业务实体(如用户、订单、商品)定义方法,封装数据和操作逻辑:

复制代码

package main import ( "fmt" "time" ) // 订单结构体 type Order struct { OrderID string Amount float64 Status string // 状态:pending/payed/shipped/completed CreateTime time.Time } // 支付订单(指针接收者,修改状态) func (o *Order) Pay() error { if o.Status != "pending" { return fmt.Errorf("订单%s状态异常,无法支付", o.OrderID) } o.Status = "payed" return nil } // 发货(指针接收者) func (o *Order) Ship() error { if o.Status != "payed" { return fmt.Errorf("订单%s未支付,无法发货", o.OrderID) } o.Status = "shipped" return nil } // 获取订单创建时间字符串(值接收者,只读) func (o Order) GetCreateTime() string { return o.CreateTime.Format("2006-01-02 15:04:05") } func main() { order := Order{ OrderID: "ORD20260320001", Amount: 99.9, Status: "pending", CreateTime: time.Now(), } if err := order.Pay(); err != nil { fmt.Println(err) } else { fmt.Println("支付成功,订单状态:", order.Status) // 输出 payed } fmt.Println("订单创建时间:", order.GetCreateTime()) }

场景 2:工具类封装(基本类型扩展)

为基本类型别名定义方法,实现工具类逻辑(如字符串、数字的常用操作):

复制代码

package main import "strings" // 字符串别名 type MyString string // 去除空格并转小写 func (s MyString) TrimAndLower() MyString { return MyString(strings.TrimSpace(string(s))) } // 判断是否包含子串 func (s MyString) Contains(sub string) bool { return strings.Contains(string(s), sub) } func main() { var str MyString = " Hello World " fmt.Println(str.TrimAndLower()) // 输出 hello world fmt.Println(str.Contains("World")) // 输出 true }

场景 3:接口实现(多态)

通过方法实现接口,实现多态逻辑(如不同数据源的读取、不同支付方式的处理):

复制代码

package main import "fmt" // 支付接口 type Payment interface { Pay(amount float64) string } // 微信支付 type WeChatPay struct{} func (w WeChatPay) Pay(amount float64) string { return fmt.Sprintf("微信支付%.2f元成功", amount) } // 支付宝支付 type AliPay struct{} func (a AliPay) Pay(amount float64) string { return fmt.Sprintf("支付宝支付%.2f元成功", amount) } // 统一支付函数 func UnifiedPay(p Payment, amount float64) { fmt.Println(p.Pay(amount)) } func main() { UnifiedPay(WeChatPay{}, 100.0) // 输出 微信支付100.00元成功 UnifiedPay(AliPay{}, 200.0) // 输出 支付宝支付200.00元成功 }

场景 4:链式调用

方法返回接收者本身(指针),实现链式调用:

复制代码

package main import "fmt" type Builder struct { content string } func (b *Builder) AddTitle(title string) *Builder { b.content += fmt.Sprintf("<h1>%s</h1>", title) return b // 返回指针,支持链式调用 } func (b *Builder) AddContent(content string) *Builder { b.content += fmt.Sprintf("<p>%s</p>", content) return b } func (b *Builder) Build() string { return b.content } func main() { // 链式调用:简洁直观 html := (&Builder{}). AddTitle("Go方法教程"). AddContent("方法是绑定到类型的函数"). Build() fmt.Println(html) // 输出:<h1>Go方法教程</h1><p>方法是绑定到类型的函数</p> }


总结

  1. 方法本质:绑定到特定类型的函数,核心是「接收者」,分为值接收者(只读)和指针接收者(可修改);
  1. 核心特性:支持封装、配合接口实现多态,无方法重载,指针类型方法集包含所有方法;
  1. 典型场景:结构体业务建模、工具类扩展、接口实现、链式调用,是 Go 面向对象编程的核心载体。

Go 方法的设计既保留了函数的简洁性,又实现了面向对象的核心思想(封装、多态),是日常开发中处理 "数据 + 逻辑" 的最优方式。

相关推荐
Jay_Franklin2 小时前
Python一站式科研工作流:从数据分析到报告生成
开发语言·python·论文笔记
小堃学编程2 小时前
【项目实战】基于protobuf的发布订阅式消息队列(1)—— 准备工作
java·大数据·开发语言
setmoon2142 小时前
C++中的构建器模式
开发语言·c++·算法
2301_815482932 小时前
C++中的桥接模式变体
开发语言·c++·算法
ZHOUPUYU2 小时前
PHP性能分析与调优:从定位瓶颈到实战优化
开发语言·后端·html·php
yunyun321232 小时前
C++与量子计算模拟
开发语言·c++·算法
weixin_462901972 小时前
ESP32电压显示
开发语言·javascript·css·python
稻草猫.2 小时前
MyBatis-Plus高效开发全攻略
java·数据库·后端·spring·java-ee·mybatis·mybatis-plus
探序基因2 小时前
R语言读取单细胞转录组基因表达矩阵loom文件
开发语言·r语言