【Go语言圣经】第六节:方法
第六章:方法
6.1 方法声明
在函数声明时,在其名字之前放上一个变量,这就是声明了变量对应类型的一个方法,相当于为这种类型定义了一个独占的方法。
下例为 Point 类型声明了计算两个点之间距离的方法:
package mainimport "math"type Point struct {X, Y float64
}func (p Point) Distance(q Point) float64 {return math.Hypot(q.X - p.X, q.Y - p.Y)
}
上述代码中的 p,叫做方法的接收器(receiver)。在 Golang 当中,不会像其他语言那样使用 this 或 self 作为接收器,可以任意选择作为接收器的名字。
下例尝试使用方法:
package mainimport ("fmt""math"
)type Point struct {X, Y float64
}func (p Point) Distance(q Point) float64 {return math.Hypot(q.X-p.X, q.Y-p.Y)
}func main() {p := Point{1, 2}q := Point{4, 5}distance := p.Distance(q)fmt.Println(distance)
}
需要注意的是,此处的p.Distance
是方法的调用,我们当然可以声明一个Distance
同名函数,接受p
和q
作为参数,计算二者之间的距离。函数和方法是不冲突的,可以同时调用。
但是需要注意的话,如果我们声明了一个名为X
的方法,编译器会报错,因为产生了歧义,因为X
已经是Point
的成员变量了。
下例定义了一个 Path 类型,Path 代表一个线段的集合,并且同样为 Path 定义一个名为 Distance 的方法:
type Path []Pointfunc (path Path) Distance() float64 {sum := 0.0for i := range path {if i > 0 {sum += path[i - 1].Distance((path[i]))}}return sum
}
Path 是一个命名的 slice 类型,但是我们仍然可以为其定义方法。在 Golang 当中,我们可以给同一个包内的任意命名类型定义方法,只要这个命名类型的底层类型不是指针或 interface。
下例调用新方法,计算三角形周长:
func main() {perim := Path{{1, 1},{5, 1},{5, 4},{1, 1},}fmt.Println(perim.Distance())
}
6.2 基于指针对象的方法
当调用一个函数时,会对其每一个参数值进行拷贝,如果一个函数需要更新一个变量,或者函数的其中一个参数是在太大我们希望能够避免进行这种默认的拷贝,该情况下我们就需要用到指针了:
func (p *Point) ScaleBy(factor float64) {p.X *= factorp.Y *= factor
}
上述方法的名字是(*Point).ScaleBy
,括号是必须的,没有括号的话这个表达式可能会被理解为*(Point.ScaleBy)
,即类型方法的指针。
在现实的程序当中,一般会约定,如果 Point 这个类有一个指针作为接收器的方法,那么所有 Point 的方法都必须有一个指针接收器,即使是那些并不需要这个指针接收器的函数。
只有类型(比如 Point)和指向它们的指针(*Point
),才可能出现在接收器声明里的两种接收器。此外,为了避免歧义,在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器当中的,比如:
type P *int
func (P) f() { /* ... ... ... */ } // Compile Error
想要调用指针类型方法(*Point).Salary
,只需要提供一个 Point 类型的指针即可:
func main() {r := &Point{1, 2}r.ScaleBy(2) fmt.Println(*r) // {2, 4}
}
或者:
p := Point{1, 2}
(&p).ScaleBy(2)
不过上述两种方法稍显笨拙,Golang 本身在这种地方会帮助我们。如果接收器 p 是一个 Point 类型的变量,并且其方法需要一个 Point 指针作为接收器,可以用下面这种简短的写法:
p.ScaleBy(2)
编译器会隐式地帮助我们用&p
去调用 ScaleBy 方法。这种简化写法只适用于“变量”,包括 struct 里的字段,比如p.X
,以及 array 和 slice 内的元素。不能通过一个无法取到地址的接收器来调用指针方法,比如临时变量的内存地址无法获取:
Point{1, 2}.ScaleBy(2) // Compile Error
我们可以用一个*Point
这样的接收器来调用 Point 的方法,因为我们可以通过地址找到这个变量,只要用解引用符号*
获取变量即可。同样地,如果是一个指针想要调用其所指对象类型的非指针接收器方法时,Golang 的编译器会为我们隐式地插入解引用符:
pptr.Distance(q)
// 等价于
(*pptr).Distance(q)
6.2.1 nil 也是一个合法的接收器类型
就像一些函数允许 nil 指针作为参数一样,方法理论上也可以使用 nil 指针作为接收器,尤其当 nil 对于对象来说是合法的零值时,比如 map 或 slice。在下面的简单 int 链表的例子里,nil 代表的是空链表:
type IntList struct {Value intTail *IntList
}
func (list *IntList) Sum() int {if list == nil {return 0}return list.Value + list.Tail.Sum()
} // 使用递归的方法计算链表值的综合
不必担心此时 list 是否为 nil,就算 list 为 nil,它也可以调用 IntList 的方法。
6.3 通过嵌入结构体来扩展类型
下例定义了一个 ColoredPoint 类型:
package mainimport "image/color"type Point struct {X, Y float64
}type ColoredPoint struct {PointColor color.RGBA
}
我们完全可以将 ColoredPoint 定义为一个有三个字段的 struct,但是我们却将 Point 这个类型嵌入到了 ColoredPoint 来提供 X 和 Y 这两个字段。
结构当中内嵌其它结构可以使我们在定义 ColoredPoint 时得到一种句法上的简写形式,并使其包含 Point 类型所具有的一切字段,然后再定义一些自己的。我们可以直接认为通过嵌入的字段就是 ColoredPoint 自身的字段。
对于 Point 中的方法我们也有类似的用法,可以将 ColoredPoint 类型当作接收器来调用 Point 里的方法,即使 ColoredPoint 里没有声明这些方法:
func main() {red := color.RGBA{255, 0, 0, 255}blue := color.RGBA{0, 0, 255, 255}var p = ColoredPoint{Point{1, 1}, red}var q = ColoredPoint{Point{5, 4}, blue}fmt.Println(p.Distance(q.Point))p.ScaleBy(2)q.ScaleBy(2)fmt.Println(p.Distance(q.Point))
}
Point 类的方法也被引入到了 ColoredPoint 当中。一个 ColoredPoint 并不是一个 Point,但它 “has a Point”,并且它有从 Point 类里引入的 Distance 和 ScaleBy 方法。
在类型中内嵌的匿名字段也可能是一个命名类型的指针,这种情况下字段和方法会被间接地引入到当前类型当中。添加这一层间接关系让我们可以共享通用的结构并动态地改变对象之间的关系:
type ColoredPoint struct {*PointColor color.RGBA
}func main() {red := color.RGBA{255, 0, 0, 255}blue := color.RGBA{0, 0, 255, 255}var p = ColoredPoint{&Point{1, 1}, red}var q = ColoredPoint{&Point{5, 4}, blue}fmt.Println(p.Distance(*q.Point))q.Point = p.Point // p 和 q 共享同一个 Pointp.ScaleBy(2)fmt.Println(*p.Point, *q.Point)
}
一个 struct 类型也可以有多个匿名字段,比如:
type ColoredPoint struct {Pointcolor.RGBA
}
6.4 方法值和方法表达式
执行一个方法常见的形式是p.Distance()
,实际上这一步可以被拆分为两步来执行。p.Distance
叫做“选择器”,选择器返回一个值,这个值是将方法Point.Distance
绑定到特定接受其变量的函数。
这个函数可以不通过指定其接收器即可被调用,原因在于p.Distance
这条语句已经将Distance
方法与p
绑定了。只需要传入函数的参数即可:
P := Point{1, 2}
q := Point{4, 6}distanceFromP := p.Distance
fmt.Println(distanceFromP(q))
var origin Point
fmt.Println(distanceFromP(origin))
在一个包的API需要一个函数值,且调用方法希望操作的是某一个绑定了对象的方法的话,方法“值”会非常有用。例如,下例中的time.AfterFunc
这个函数的功能是在指定的延迟时间之后来执行一个函数。且这个函数操作的是一个Rocket 对象 r:
type Rocket struct { /* … … … */ }
func (r *Rocket) Launch() { /* … … … */ }
r := new(Rocket)
time.AfterFunc(10 * time.Second, func() { r.Launch() })
直接将方法的值传入 AfterFunc 的话可以更简短:
time.AfterFunc(10 * time.Second, r.Launch)
与调用一个普通的函数相比,调用一个方法时,必须要用选择器语法来指定方法的接收器。当 T 是一个类型时,方法表达式可能会写作T.f
或(*T).f
,会返回一个函数“值”,这种函数会将其第一个参数用作接收器,所以可以用类似于函数调用的方式来对方法表达式进行调用(意思是,直接将类型.方法
与某个变量绑定,此时这个变量类似于一个函数,加入原先的方法有一个参数,此时的函数有两个参数,第一个参数将会被视为选择器参数将第一个传入该函数的变量与方法绑定):
P := Point{1, 2}
q := Point{4, 6}
distance := Point.Distance // method expression
fmt.Println(distance(p, q)) // “5”
fmt.Printf(“%T\n”, distance)// “func(Point, Point) float64”scale := (*Point).ScaleBy
scale(&p, 2)
fmt.Println(p) // “{2, 4}”
fmt.Printf(“%T\n”, scale) // “func(*Point, float64)”
下例是一个更复杂的例子:
type Point struct{ X, Y float64 }func (p Point) Add(q Point) Point { return Point{p.X + q.X, p.Y + q.Y} }
func (p Point) Sub(q Point) Point { return Point{p.X - q.X, p.Y - q.Y} }type Path []Pointfunc (path Path) TranslateBy(offset Point, add bool) {var op func(p, q Point) Pointif add {op = Point.Add} else {op = Point.Sub}for i := range path {path[i] = op(path[i], offset)}
}
6.6 封装
“封装”指的是一个对象的变量或方法对调用方是不可见的,是 OOP 最关键的一个概念。
Golang 只有一种控制可见性的手段:大写首字母的标识符会从定义它们的包中被导出,小写字母的则不会。这种限制包内成员的方式同样适用于 struct 或一个类型的方法。因而如果我们想要封装一个对象,必须将其定义为一个 struct。
一个例子如下:
type IntSet struct {words []uint64
}
当然我们也可以直接将 IntSet 定义为 slice 类型,这样我们就需要把代码中所有方法里的s.word
用*s
替换掉:
type IntSet []uint64
上述这种基于名字的手段使得在 Golang 中最小的封装单元是 package,而不是像其他语言一样的类型。一个 struct 类型的字段对同一个包的所有代码都有可见性,无论你的代码是写在一个函数还是一个方法里。
封装最重要的优点是阻止了外部调用方对对象内部的值进行修改:
type Counter struct { n int }
func (c *Counter) N() int { return c.n }
func (c *Counter) Increment() { c.n++ }
func (c *Counter) Reset() { c.n = 0 }
只用来访问或修改内部变量的函数被称为 setter 或 getter。
注意:Golang 的编码风格不禁止直接导出字段。当然,一旦进行了导出,就没有办法在保证 API 兼容的情况下去除对其的导出,所以在一开始的选择一定要经过深思熟虑并且要考虑到包内部的一些不变量的保证,未来可能的变化,以及调用方的代码质量是否会因为包的一点修改而变差。
相关文章:
【Go语言圣经】第六节:方法
第六章:方法 6.1 方法声明 在函数声明时,在其名字之前放上一个变量,这就是声明了变量对应类型的一个方法,相当于为这种类型定义了一个独占的方法。 下例为 Point 类型声明了计算两个点之间距离的方法: package mai…...
【Leetcode刷题记录】45. 跳跃游戏 II--贪心算法
45. 跳跃游戏 II 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i j] 处: 0 < j < nums[i]i j < n 返回到达 num…...

mysql_init和mysql_real_connect的形象化认识
解析总结 1. mysql_init 的作用 mysql_init 用于初始化一个 MYSQL 结构体,为后续数据库连接和操作做准备。该结构体存储连接配置及状态信息,是 MySQL C API 的核心句柄。 示例: MYSQL *conn mysql_init(NULL); // 初始化连接句柄2. mysql_…...

Qt网络相关
“ 所有生而孤独的人,葆有的天真 ” 为了⽀持跨平台, QT对⽹络编程的 API 也进⾏了重新封装。本章会上手一套基于QT的网络通信编写。 UDP Socket 在使用Qt进行网络编程前,需要在Qt项目中的.pro文件里添加对应的网络模块( network ). QT core gui net…...
deepseek接入pycharm 进行AI编程
要将DeepSeek接入PyCharm进行AI编程,可以按照以下步骤操作: ### 1. 获取DeepSeek API访问权限 DeepSeek通常以API的形式对外提供服务,你需要在其官方网站注册账号,申请API访问权限。在申请通过后,会获得API密钥(API Key),这是后续调用API的关键凭证。 ### 2. 安装必要…...

Verilog基础(三):过程
过程(Procedures) - Always块 – 组合逻辑 (Always blocks – Combinational) 由于数字电路是由电线相连的逻辑门组成的,所以任何电路都可以表示为模块和赋值语句的某种组合. 然而,有时这不是描述电路最方便的方法. 两种always block是十分有用的&am…...

生成式AI安全最佳实践 - 抵御OWASP Top 10攻击 (上)
今天小李哥将开启全新的技术分享系列,为大家介绍生成式AI的安全解决方案设计方法和最佳实践。近年来,生成式 AI 安全市场正迅速发展。据 IDC 预测,到 2025 年全球 AI 安全解决方案市场规模将突破 200 亿美元,年复合增长率超过 30%…...
.Net WebAPI -[HttpPut(“{fileServiceId:int}“)]
[HttpPut("{fileServiceId:int}")] 这个写法是 ASP.NET Core 中的一个路由特性,用于定义一个 HTTP PUT 请求的路由,并指定路由参数的类型。 解析 HttpPut [HttpPut]: 这是一个 ASP.NET Core 的路由特性,用于标记一个方…...

[EAI-027] RDT-1B,目前最大的用于机器人双臂操作的机器人基础模型
Paper Card 论文标题:RDT-1B: a Diffusion Foundation Model for Bimanual Manipulation 论文作者:Songming Liu, Lingxuan Wu, Bangguo Li, Hengkai Tan, Huayu Chen, Zhengyi Wang, Ke Xu, Hang Su, Jun Zhu 论文链接:https://arxiv.org/ab…...

C基础寒假练习(7)
一、有 1、2、3、4个数字,能组成多少互不相同且无重复的三位? 都是多少? #include <stdio.h> int main() {// 定义数字数组int digits[] {1, 2, 3, 4};int n sizeof(digits) / sizeof(digits[0]);// 嵌套循环遍历所有排列for (int …...
Ajax:重塑Web交互体验的人性化探索
在数字化时代,网页的交互性和响应速度已成为衡量用户体验的关键指标。Ajax(Asynchronous JavaScript and XML),作为前端与后端沟通的桥梁,凭借其异步通信的能力,极大地提升了网页的动态性和用户友好度&…...

【DeepSeek背后的技术】系列二:大模型知识蒸馏(Knowledge Distillation)
目录 1 引言2 操作步骤和公式说明2.1 准备教师模型(Teacher Model)和学生模型(Student Model)2.2 生成软标签(Soft Labels)2.3 定义蒸馏损失函数2.4 训练学生模型2.5 调整超参数2.6 评估与部署 3 其他知识蒸…...

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.14 内存映射:处理超大型数组的终极方案
2.14 内存映射:处理超大型数组的终极方案 目录 #mermaid-svg-G91Kn9O4eN2k8xEo {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-G91Kn9O4eN2k8xEo .error-icon{fill:#552222;}#mermaid-svg-G91Kn9O4eN2k…...

【C++】STL——vector的使用
目录 💕1.vector介绍 💕2.vector的基本用法 💕3.vector功能的具体用法 (讲解) 💕4.vector——size,capacity函数的使用 (简单略讲) 💕5.resizeÿ…...
springboot/ssm互联网智慧医院体检平台web健康体检管理系统Java代码编写
springboot/ssm互联网智慧医院体检平台web健康体检管理系统Java代码编写 基于springboot(可改ssm)vue项目 开发语言:Java 框架:springboot/可改ssm vue JDK版本:JDK1.8(或11) 服务器:tomcat 数据库&am…...

介绍一下Mybatis的Executor执行器
Executor执行器是用来执行我们的具体的SQL操作的 有三种基本的Executor执行器: SimpleExecutor简单执行器 每执行一次update或select,就创建一个Statement对象,用完立刻关闭Statement对象 ReuseExecutor可重用执行器 可重复利用Statement…...

Wide Deep 模型:记忆能力与泛化能力
实验和完整代码 完整代码实现和jupyter运行:https://github.com/Myolive-Lin/RecSys--deep-learning-recommendation-system/tree/main 引言 Wide & Deep 模型是一种结合了线性模型(Wide)和深度神经网络(Deep)的混…...

Hot100之矩阵
73矩阵置零 题目 思路解析 收集0位置所在的行和列 然后该行全部初始化为0 该列全部初始化为0 代码 class Solution {public void setZeroes(int[][] matrix) {int m matrix.length;int n matrix[0].length;List<Integer> list1 new ArrayList<>();List<…...
Python语言的安全开发
Python语言的安全开发 引言 在信息技术迅速发展的今天,网络安全问题愈发凸显。随着Python语言的广泛应用,尤其是在数据分析、人工智能、Web开发等领域,其安全问题越来越受到重视。Python作为一门高效且易于学习的编程语言,虽然在…...

蓝桥杯刷题DAY3:Horner 法则 前缀和+差分数组 贪心
所谓刷题,最重要的就是细心 📌 题目描述 在 X 进制 中,每一数位的进制不固定。例如: 最低位 采用 2 进制,第二位 采用 10 进制,第三位 采用 8 进制, 则 X 进制数 321 的十进制值为ÿ…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...