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

Go 中实现“面向对象”

在 Go 中实现“面向对象”的核心是两个概念的结合:

  1. 结构体 (Struct): 用来封装数据,类似于 PHP 类中的属性
  2. 方法 (Method): 本质上是一个函数,但它与一个特定的类型(我们称之为接收者 Receiver)绑定。类似于 PHP 类中的方法

我们先定义一个数据结构:

// 一个表示用户的 Struct
type User struct {FirstName stringLastName  stringAge       int
}

现在,我们想创建一个函数来获取用户的全名。

如果是用我们之前学的普通函数,我们会这样做:

// 普通函数,把 User 对象作为参数传入
func GetFullName(user User) string {return user.FirstName + " " + user.LastName
}// 调用时:
// u := User{FirstName: "John", LastName: "Doe"}
// fullName := GetFullName(u)

这种写法完全没问题,但它没有体现出 GetFullName 这个行为和 User 这个数据类型之间的强关联。


定义“方法”

而“方法”的写法,通过增加一个“接收者”,把这种关联变得非常明确。

// (u User) 就是接收者,它让这个函数成为了 User 类型的一个方法
//    |
//    v
func (u User) FullName() string {return u.FirstName + " " + u.LastName
}

我们来分解一下这个语法:

  • func:还是那个我们熟悉的函数关键字。
  • (u User): 这是最关键的新增部分!它被称为接收者 (Receiver)
    • User: 表示这个方法是绑定在 User 这个类型上的。
    • u: 是接收者变量的名字,类似于 PHP 中的 $this。它代表调用这个方法的具体实例。你可以给它取任何名字(比如 thisself),但 Go 社区的惯例是使用该类型的单字母或双字母缩写(如 u for User)。
  • FullName(): 方法名和参数列表,和普通函数一样。
  • string: 返回值类型。

如何调用这个方法?
现在,调用方式变得和你熟悉的面向对象语法一模一样了:

func main() {// 创建一个 User 实例user := User{FirstName: "Jane",LastName:  "Doe",Age:       30,}// 使用 . 语法调用绑定在 user 上的方法fullName := user.FullName()fmt.Println(fullName) // 输出: Jane Doe
}

通过这种方式,我们把数据(User 结构体)和操作数据的行为(FullName 方法)清晰地绑定在了一起,让代码的结构变得更加内聚和易于理解。


现在轮到你了。请你模仿 FullName 方法的格式,为 User 类型再添加一个新方法,名为 CanVote(),它判断用户的年龄(Age)是否大于等于 18 岁,并返回一个布尔值 (bool)。

你的直觉完全正确!不能

你说“只读吧”,这正是问题的核心。

为什么不行?
这要回到我们之前提到的 Go 的一个基本规则:Go 默认总是“按值传递” (pass-by-value)

当我们这样定义方法时:func (u User) HaveBirthday(),接收者 u User 的意思是:

“当调用这个方法时,请把那个 User 实例完整地复制一份,然后把这份副本命名为 u,交给方法使用。”

所以,在 HaveBirthday 方法内部,u.Age = u.Age + 1 这行代码修改的只是那个副本的年龄,而原始的 user 变量毫发无损。


你的尝试非常棒,已经完全掌握了方法的结构!

这里只需要做两个微小的调整,就能完全符合要求了:

  1. 返回值类型: 方法需要返回一个布尔值(truefalse),所以返回值类型应该是 bool,而不是 int
  2. 判断逻辑: 投票的条件通常是“大于等于18岁”,所以我们应该用 >= 操作符。

修正后的版本是这样的:

func (u User) CanVote() bool {return u.Age >= 18
}

你已经成功创建了一个读取结构体数据的方法。我们来看看它的实际效果:

func main() {user1 := User{"Jane", "Doe", 30}user2 := User{"Peter", "Pan", 17}fmt.Printf("%s 能投票吗: %t\n", user1.FullName(), user1.CanVote())fmt.Printf("%s 能投票吗: %t\n", user2.FullName(), user2.CanVote())
}
// 输出:
// Jane Doe 能投票吗: true
// Peter Pan 能投票吗: false

这引出了我们关于方法的最后一个、也是最重要的概念。到目前为止,我们的方法都只是读取数据。那如果我们想要一个修改数据的方法呢?

比如说,我们想创建一个 HaveBirthday 方法,用来给用户增加一岁。

你觉得下面这段代码,能成功地改变用户的年龄吗?为什么?

// 这个方法能成功地改变用户的年龄吗?
func (u User) HaveBirthday() {u.Age = u.Age + 1
}

解决方案:指针接收者 (Pointer Receiver)

那么,如何才能修改原始的数据呢?答案就是使用指针

我们需要把方法的接收者从一个 User 值,改成一个指向 User指针 *User

语法上,只需要加一个 * 号:

// 注意接收者类型的变化:User -> *User
func (u *User) HaveBirthday() {u.Age = u.Age + 1
}

现在,接收者 u *User 的意思是:

“当调用这个方法时,请把那个 User 实例的内存地址(也就是一个指针)交给我,我把它命名为 u。”

因为方法现在拿到了原始数据的“地址”,它就可以顺着地址找到那个唯一的、原始的 User 实例,并直接修改它的内容。

我们来看一下完整的、可以成功修改年龄的代码:

func main() {user := User{FirstName: "Jane",LastName:  "Doe",Age:       30,}fmt.Println("生日前的年龄:", user.Age) // 输出: 30// 调用带有指针接收者的方法// Go 很聪明,你不需要写 (&user).HaveBirthday()// 直接写 user.HaveBirthday() 即可,Go 会自动帮你转换user.HaveBirthday()fmt.Println("生日后的年龄:", user.Age) // 输出: 31
}

成功了!


何时使用值接收者 vs. 指针接收者?

这是一个简单的经验法则:

  • 如果你的方法不需要修改原始数据,只是读取它(像 FullNameCanVote),那么使用值接收者 (u User)
  • 如果你的方法需要修改原始数据(像 HaveBirthday),那么必须使用指针接收者 (u *User)

在实际开发中,为了保持一致性,如果一个类型中有一个方法使用了指针接收者,那么该类型的其他所有方法通常也会统一使用指针接收者。

至此,你已经学完了 Go 函数和方法的所有核心概念。从基础语法,到函数式编程,再到 Go 风格的面向对象编程,你已经建立了一个非常完整和坚实的知识体系。恭喜你!

http://www.dtcms.com/a/419523.html

相关文章:

  • 富阳做网站广州专业做网站多少钱
  • 威海网站开发公司电话手机软件怎么做出来的
  • 企业系统有哪些南通网站流量优化
  • nginx 的root跟alias的区别
  • 到底什么是智能网联汽车??第三期——汽车总线及车载网络系统
  • 网站做跳转影响排名吗wordpress在线考试插件
  • 网站开发行业推广网站开发合同是否专属管辖
  • 网站建设招聘启事太原城市建设招标网站
  • 做淘宝客为什么要做网站wordpress中文清爽博客主题:jishuzh主题分享
  • Vue表格多选后,将勾选数据返现到弹框中列表,部分数据出现丢失情况
  • CKAD-CN 考试知识点分享(16) 修改 container 名称
  • 东营优化网站中国石油大学网页设计与网站建设
  • 机器视觉:基于MTCNN与Caffe模型的人脸性别年龄统计系统实现
  • 手机网站开发升上去专门做消防器材的网站
  • Docker进程中的守护进程原理解析
  • ApplicationContext接口实现(四)
  • PyQt python 异步任务,多线程,进阶版
  • 磁盘物理坏块与逻辑坏块的区别
  • net asp网站开发长春哪有做网站公司
  • 【机器学习】监督学习 —— 决策树(Decision Tree)
  • (基于江协科技)51单片机入门:5.定时器
  • 怎么制作个人门户网站东莞常平中转场
  • 强化学习原理(四)
  • 做网站 毕业设计长沙企业网页设计哪家专业
  • 菊风可视化回溯解决方案,为金融业务合规打造全流程“可回溯”能力
  • 蜜度AI审校从技术到服务全面突破 为出版内容校对注入新活力
  • 单一索引,覆盖索引,联合索引
  • BentoML推出llm-optimizer开源框架:让LLM性能调优变简单?
  • Cherry Studio实战使用
  • Python 类型提示:Literal 类型