06-揭开神秘面纱:Golang method的魅力解析

在这里插入图片描述

📃个人主页:个人主页
🔥系列专栏:Golang基础
💬Go(又称Golang)是由Google开发的开源编程语言。它结合了静态类型的安全性和动态语言的灵活性,拥有高效的并发编程能力和简洁的语法。Go被设计用于构建可扩展、高性能的软件系统,具有优秀的内存管理和快速的编译速度,适用于Web开发、系统编程和云计算等领域。

文章目录

  • method
  • 指针作为receiver
  • method继承
  • method重写

函数的另一种形态,带有接收者的函数,称为 method

method

现在假设有这么一个场景,定义了一个 struct 叫做长方形,现在想要计算他的面积,那么按照一般的思路应该会用下面的方式来实现:

package main
import "fmt"

type Rectangle struct {
	width, height float64
}
func area(r Rectangle) float64 {
	return r.width*r.height
}
func main() {
	r1 := Rectangle{12, 2}
	r2 := Rectangle{9, 4}
	fmt.Println("Area of r1 is: ", area(r1))
	fmt.Println("Area of r2 is: ", area(r2))
}

这段代码可以计算出来长方形的面积,但是 area() 不是作为 Rectangle 的方法实现的(类似面向对象里面的方法),而是将Rectangle 的对象(如 r1,r2 )作为参数传入函数计算面积的。
这样实现当然没有问题,但是当需要增加圆形、正方形、五边形甚至其它多边形的时候,想计算他们的面积的时候怎么办?
那就只能增加新的函数,但是函数名就必须要跟着换了,变成 area_rectangle, area_circle , area_triangle
椭圆代表函数, 而这些函数并不从属于 struct (或者以面向对象的术语来说,并不属于 class ),他们是单独存在于 struct 外围,而非在概念上属于某个 struct 的。
很显然,这样的实现并不优雅,并且从概念上来说"面积"是"形状"的一个属性,它是属于这个特定的形状的,就像长方形的长和宽一样。
基于上面的原因所以就有了 method 的概念, method 是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在 func 后面增加了一个 receiver (也就是 method 所依从的主体)。
用上面提到的形状的例子来说,method area() 是依赖于某个形状(比如说 Rectangle )来发生作用的。
Rectangle.area() 的发出者是 Rectanglearea() 是属于 Rectangle 的方法,而非一个外围函数。
更具体地说,Rectangle 存在字段 heightwidth, 同时存在方法 area() , 这些字段和方法都属于 Rectangle
用Rob Pike的话来说就是:
“A method is a function with an implicit first argument, called a receiver.”
(方法是一个具有隐式第一个参数的函数,称为接收器。)
method的语法如下:

func (r ReceiverType) funcName(parameters) (results)

下面用最开始的例子用 method 来实现:

package main
import (
	"fmt"
	"math"
)

type Rectangle struct {
	width, height float64
}
type Circle struct {
	radius float64
}
func (r Rectangle) area() float64 {
	return r.width*r.height
}
func (c Circle) area() float64 {
	return c.radius * c.radius * math.Pi
}
func main() {
	r1 := Rectangle{12, 2}
	r2 := Rectangle{9, 4}
	c1 := Circle{10}
	c2 := Circle{25}
	fmt.Println("Area of r1 is: ", r1.area())
	fmt.Println("Area of r2 is: ", r2.area())
	fmt.Println("Area of c1 is: ", c1.area())
	fmt.Println("Area of c2 is: ", c2.area())
}

在使用 method 的时候重要注意几点:

  • 虽然method的名字一模一样,但是如果接收者不一样,那么method就不一样
  • method里面可以访问接收者的字段
  • 调用method通过 . 访问,就像struct里面访问字段一样

在上例,method area() 分别属于 RectangleCircle , 于是他们的 Receiver 就变成了 RectangleCircle, 或者说,这个 area() 方法 是由 Rectangle/Circle 发出的。
值得说明的一点是,此处方法的 Receiver 是以值传递,而非引用传递,是的,Receiver 还可以是指针, 两者的差别在于, 指针作为 Receiver 会对实例对象的内容发生操作,而普通类型作为 Receiver 仅仅是以副本作为操作对象,并不对原实例对象发生操作,后文对此会有详细论述。
那是不是 method 只能作用在 struct 上面呢?当然不是,他可以定义在任何 自定义的类型、内置类型、struct 等各种类型上面。
什么叫自定义类型,自定义类型不就是 struct ,其实不是这样的,struct只是自定义类型里面一种比较特殊的类型而已,还有其他自定义类型申明,可以通过如下这样的申明来实现:

type typeName typeLiteral

请看下面这个申明自定义类型的代码:

type ages int
type money float32
type months map[string]int
m := months {
	"January":31,
	"February":28,
	...
	"December":31,
}

这样就可以在自己的代码里面定义有意义的类型了,实际上只是一个定义了一个别名,有点类似于c中的 typedef,例如上面 ages 替代了 int,回到 method 可以在任何的自定义类型中定义任意多的 method ,接下来让看一个复杂一点的例子:

package main
import "fmt"

const(
	WHITE = iota
	BLACK
	BLUE
	RED
	YELLOW
)
type Color byte
type Box struct {
	width, height, depth float64
	color Color
}
type BoxList []Box //a slice of boxes
func (b Box) Volume() float64 {
	return b.width * b.height * b.depth
}
func (b *Box) SetColor(c Color) {
	b.color = c
}
func (bl BoxList) BiggestColor() Color {
	v := 0.00
	k := Color(WHITE)
	for _, b := range bl {
		if bv := b.Volume(); bv > v {
			v = bv
			k = b.color
		}
	}
	return k
}
func (bl BoxList) PaintItBlack() {
	for i := range bl {
		bl[i].SetColor(BLACK)
	}
}
func (c Color) String() string {
	strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
	return strings[c]
}
func main() {
	boxes := BoxList {
		Box{4, 4, 4, RED},
		Box{10, 10, 1, YELLOW},
		Box{1, 1, 20, BLACK},
		Box{10, 10, 1, BLUE},
		Box{10, 30, 1, WHITE},
		Box{20, 20, 20, YELLOW},
	}
	fmt.Printf("We have %d boxes in our set\n", len(boxes))
	fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
	fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
	fmt.Println("The biggest one is", boxes.BiggestColor().String())
	fmt.Println("Let's paint them all black")
	boxes.PaintItBlack()
	fmt.Println("The color of the second one is", boxes[1].color.String())
	fmt.Println("Obviously, now, the biggest one is",
	boxes.BiggestColor().String())
}

上面的代码通过 const 定义了一些常量,然后定义了一些自定义类型

  • Color作为byte的别名
  • 定义了一个struct:Box,含有三个长宽高字段和一个颜色属性
  • 定义了一个slice:BoxList,含有Box

然后以上面的自定义类型为接收者定义了一些 method

  • Volume()定义了接收者为Box,返回Box的容量
  • SetColor(c Color),把Box的颜色改为c
  • BiggestColor()定在在BoxList上面,返回list里面容量最大的颜色
  • PaintItBlack()把BoxList里面所有Box的颜色全部变成黑色
  • String()定义在Color上面,返回Color的具体颜色(字符串格式)

上面的代码通过文字描述出来之后是不是很简单?
一般解决问题都是通过问题的描述,去写相应的代码实现。

指针作为receiver

现在让回过头来看看 SetColor 这个 method ,它的 receiver 是一个指向 Box 的指针,可以使用 *Box
定义 SetColor 的真正目的是想改变这个 Box 的颜色,如果不传 Box 的指针,那么 SetColor 接受的其实是
Box 的一个copy,也就是说 method 内对于颜色值的修改,其实只作用于 Box 的copy,而不是真正的 Box
所以需要传入指针。
这里可以把 receiver 当作 method 的第一个参数来看,然后结合前面函数讲解的传值和传引用就不难理解。
这里也许会问 SetColor 函数里面应该这样定义 *b.Color=c ,而不是 b.Color=c ,需要读取到指针相应的
值。
其实Go里面这两种方式都是正确的,当用指针去访问相应的字段时(虽然指针没有任何的字段),Go知道要通过指针去获取这个值。PaintItBlack 里面调用 SetColor 的时候是不是应该写成 (&bl[i]).SetColor(BLACK) ,因为 SetColorreceiver*Box,而不是 Box
这两种方式都可以,因为Go知道 receiver 是指针,他自动转了。
也就是说如果一个 methodreceiver*T ,可以在一个 T类型 的实例变量 V 上面调用这个 method,而不需要 &V 去调用这个 method
类似的如果一个 methodreceiverT,可以在一个 *T类型 的变量 P 上面调用这个 method ,而不需要 *P去调用这个 method
所以不用担心是调用的指针的 method 还是不是指针的 method,Go知道要做的一切。

method继承

通过字段的继承的学习,发现Go的一个神奇之处,method 也是可以继承的。如果匿名字段实现了一个 method,那么包含这个匿名字段的 struct 也能调用该 method
来看下面这个例子:

package main
import "fmt"

type Human struct {
	name string
	age int
	phone string
}
type Student struct {
	Human //匿名字段
	school string
}
type Employee struct {
	Human //匿名字段
	company string
}
//在human上面定义了一个method
func (h *Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
func main() {
	mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
	sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
	mark.SayHi()
	sam.SayHi()
}

method重写

上面的例子中,如果 Employee 想要实现自己的 SayHi ,怎么办?
简单,和匿名字段冲突一样的道理,可以在 Employee 上面定义一个 method ,重写了匿名字段的方法。
请看下面的例子:

package main
import "fmt"

type Human struct {
	name string
	age int
	phone string
}
type Student struct {
	Human //匿名字段
	school string
}
type Employee struct {
	Human //匿名字段
	company string
}
//Human定义method
func (h *Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
//Employee的method重写Human的method
func (e *Employee) SayHi() {
	fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
	e.company, e.phone) //Yes you can split into 2 lines here.
}
func main() {
	mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
	sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
	mark.SayHi()
	sam.SayHi()
}

通过这些内容,可以设计出基本的面向对象的程序了,但是Go里面的面向对象是如此的简单,没有任何的私有、公有关键字,通过大小写来实现(大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/30650.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

安卓高通机型的基带移植 修改 编译的相关 增加信号 支持5G等【二】

安卓高通机型的基带移植 修改 编译的相关 增加信号 支持5G等【一】 前面分享了这篇帖子,很多友友希望更新下新机型的基带替换方法。今天对其中做一些补充说明。由于安卓机型跨版本幅度较大。有的机型从出厂安卓8有可能官方目前已经更新到安卓12 13等等。所以任何的教…

Visual ChatGPT原理解读——大模型论文阅读笔记四

论文:https://arxiv.org/abs/2303.04671 代码:https://github.com/microsoft/TaskMatrix 一. 整体框架 如图所示,用户上传一张黄花的图像并输入一个复杂的语言指令“请根据该图像的预测深度生成一朵红花,然后逐步使其像卡通一样”…

5G技术学习——5GNR帧结构和空口资源

这里写目录标题 4G时域定义:资源划分 5GNR中时域 频域 与空域资源 循环前缀CP:背景和原理5G帧结构:基本框架5G slot分类 5G 频域资源5G频域资源基本概念信道带宽与传输带宽BWP定义及其应用场景 4G 时域定义: 帧:10ms,…

【使用Hystrix实现服务容错和熔断】—— 每天一点小知识

💧 使用 H y s t r i x 实现服务容错和熔断 \color{#FF1493}{使用Hystrix实现服务容错和熔断} 使用Hystrix实现服务容错和熔断💧 🌷 仰望天空,妳我亦是行人.✨ 🦄 个人主页——微风撞见云的博客&#x1f390…

SpringBoot + Vue前后端分离项目实战 || 一:Vue前端设计

文章目录 环境配置开发工具下载Vue前端模板前端项目启动前端说明及修改修改导航栏自定义菜单与子菜单增加导航标签功能 前端数据格式 B站视频讲解:2023全网最简单但实用的SpringBootVue前后端分离项目实战 不想看视频可浏览此文章笔记,比较详细 环境配置…

Java版企业工程项目管理系统源码+java版本+项目模块功能清单+spring cloud +spring boot

工程项目各模块及其功能点清单 一、系统管理 1、数据字典:实现对数据字典标签的增删改查操作 2、编码管理:实现对系统编码的增删改查操作 3、用户管理:管理和查看用户角色 4、菜单管理:实现对系统菜单的增删改查操…

青大数据结构【2020】【三分析计算】

关键字: 无相连通图、Prim算法最小生成树、哈希函数、线性探测法、平均查找长度 1.对于一个带权连通无向图G,可以采用Prim算法构造出从某个顶点v出发的最小生成树,问该最小生成树是否一定包含从顶点v到其他所有顶点的最短路径。如果回答是&a…

kafka 报错 - Cannot assign requested address

背景 在华为云服务器上跑了 zookeeper 和 kafka 的 broker,想内外网分流,重点就是做不到从外网去消费,比如用自己的 windows 笔记本去消费。 配置 server.properties 的 listener 为 broker 所在机子的的内网 IP 后,终于能 star…

Vulnhub项目:Aragog

1、靶机地址: HarryPotter: Aragog (1.0.2) ~ VulnHub 死亡圣器三部曲之第一部,Aragog是海格养的蜘蛛的名字, 并且又牵扯到了密室 2、渗透过程 确定靶机ip,攻击机ip,扫描靶机开放端口 只有22,80端口&a…

数学建模常用模型(一):灰色预测法

数学建模常用模型(一):灰色预测法 灰色预测法是一种用于处理少量数据、数据质量较差或者缺乏历史数据的预测方法。它适用于一些非线性、非平稳的系统,尤其在短期预测和趋势分析方面有着广泛的应用。灰色预测法作为一种强大的数学…

辅助驾驶功能开发-功能算法篇(3)-ACC-弯道速度辅助

1、功能架构:ACC弯道速度辅助(CSA) 2、CSA功能控制 2.1 要求 2.1.1 CSA ASM:弯道速度辅助 1. 模式管理器:驾驶员应能够激活/关闭功能 应存在处理 CSA 功能的模式管理器。模式管理器由驾驶员输入和系统状态控制。 模式管理器有两个由 CSAStatus 定义的状态。状态转换定义…

RabbitMQ高阶使用消息推送

目录 1 从打车开始说起1.1 需要解决的问题1.2 消息推送 2 消息推送2.1 什么是消息推送2.2 方案介绍2.2.1 ajax短轮询2.2.2 长轮询2.2.3 WebSocket 2.3 WS实现消息推送2.3.1 架构介绍2.3.2 暂存数据2.3.2.1 什么是MongoDB2.3.2.2 插入数据2.3.2.3 查询数据 2.4.1 轮询任务2.4.1.…

软件工程导论期末急救包(上)

目录 什么是软件工程?它的目标和内容是什么? 软件文档作用及包含 软件过程模型 瀑布模型 快速原型模型 增量模型 螺旋模型 喷泉模型 软件生存周期 需求分析阶段的基本任务是什么? 可行性研究的任务是什么? 软件是什…

vue+el-select下拉实现:全选、反选、清空功能

问题描述: el-select下拉框要求实现全选功能。具体功能包括: 当选择【全选】时,所有选项全部被勾选;当选择【反选】时,已选择选项变为未选择选项,未选项变为已选项当选择【清空】时,所有选项变…

带你用Python制作7个程序,让你感受到端午节的快乐

名字:阿玥的小东东 学习:Python、C/C 主页链接:阿玥的小东东的博客_CSDN博客-python&&c高级知识,过年必备,C/C知识讲解领域博主 目录 前言 程序1:制作粽子 程序2:龙舟比赛 程序3:艾草挂 程序4…

【人脸检测0】视频分解图片与图片合成视频

一,引言 目标:这小节主要通过两个demo熟悉视频分解图片与图片合成视频的OpenCV的应用 环境:python3.6+OpenCV3.3.1 二,示例 Demo1:视频分解图片 目标: 1.指定文件夹中读取视频文件 2.将视频文件分解为图片 3.将图片保存在指定文件夹中 # -*-coding:utf-8-*- #auth…

澳洲学生用ChatGPT代写?澳洲多所高校使用全新反击工具检测

朋友们听句劝 ChatGPT可太危险了 ChatGPT有多火?据2月1日瑞银发布的一项研究报告显示,仅仅发布两个月,ChatGPT月活跃用户已达1亿,这是历史上增长速度最快的应用。要知道达成1亿用户的时间,Instagram用了2.5年&#xf…

合宙Air724UG Cat.1模块硬件设计指南--SDIO接口

SDIO接口 简介 SDIO(Secure Digital Input and Output)全称为安全数字输入输出接口,在协议上和SPI类似是一种串行的硬件接口,通信的双方一个作为HOST,另一端是Device,所有的通信都是由HOST端发送命令开始的,Device端只…

Stable Diffusion实操示例

一、负向提示词 解决问题:生成的图片存在瑕疵,比如多只眼睛、多只手指等情况。通过embeddings可以避免一些常用的不好结果。 方法:从https://civitai.com/?utm_sourcenettsz.com 中下载负向提示词的embeddings模型, EasyNegat…

广角积分球均匀光源

现阶段,摄影测量技术已涉及多行多业,其在交通、考古以及景物三维重建中的应用尤为显著,但是普通相机取景范围有限,不能全面捕获整个空间信息,因此一种新型相机--全景相机逐步被应用到实际当中。80年代初,国…