【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 的十进制值为ÿ…...
用OpenCV搭建可落地的图像数据采集系统
1. 项目概述:用 OpenCV 搭建轻量级图像采集工作站,不是写个 demo 而是建一套能落地的数据生产线你有没有遇到过这种场景:刚立项一个手势识别项目,团队兴奋地讨论模型结构、损失函数、训练策略,结果一问“数据呢&#x…...
从ONOS 1.10.0升级到1.15.0,我踩了这些坑:日志命令、GUI激活与依赖项变化全记录
从ONOS 1.10.0升级到1.15.0的实战避坑指南 当你从ONOS 1.10.0升级到1.15.0时,可能会遇到一系列意料之外的"惊喜"。作为一个刚从这场升级大战中幸存下来的老兵,我想分享一些血泪教训和实用技巧,帮助后来者少走弯路。 1. 升级前的准备…...
硬件工程师必读:九大核心算法如何重塑芯片与系统设计
1. 项目概述:一次关于算法之美的深度阅读作为一名在电子工程和数字设计领域摸爬滚打了十几年的工程师,我的日常工作就是和FPGA、ASIC、各种EDA工具以及层出不穷的硬件描述语言打交道。我们这行,天天谈的是时序收敛、功耗优化、面积利用&#…...
计算机视觉与3D重建:模型加速与质量优化的全栈实践
1. 项目概述:当计算机视觉遇见效率与精度革命最近,微软研究院在计算机视觉领域的两项进展引起了我的注意。一项是关于如何让模型“看”得更快更准,另一项则是关于如何让3D扫描模型从“毛坯”变成“精装”。这听起来像是两个独立的方向&#x…...
Lua RTOS在ESP32上的应用:从架构解析到物联网项目实战
1. 项目概述:当Lua遇上RTOS,为ESP32注入灵魂 如果你玩过ESP32,大概率用过Arduino框架或者乐鑫官方的ESP-IDF。前者简单易上手,但深度定制和实时性有限;后者功能强大专业,但C语言开发门槛不低,调…...
reverse-geocoder未来展望:AI增强地理编码与智能位置预测
reverse-geocoder未来展望:AI增强地理编码与智能位置预测 【免费下载链接】reverse-geocoder A fast, offline reverse geocoder in Python 项目地址: https://gitcode.com/gh_mirrors/re/reverse-geocoder 在当今数据驱动的世界中,地理编码技术已…...
潜变量模型完全指南:从高斯混合模型到变分自编码器
潜变量模型完全指南:从高斯混合模型到变分自编码器 【免费下载链接】bayesian-machine-learning Notebooks about Bayesian methods for machine learning 项目地址: https://gitcode.com/gh_mirrors/ba/bayesian-machine-learning 潜变量模型是机器学习领域…...
Vex:VS Code向量数据库管理扩展,提升AI开发效率
1. 项目概述:Vex,一个为开发者设计的向量数据库管理利器如果你正在用 VS Code 开发 AI 应用,并且和向量数据库(比如 Milvus 或 ChromaDB)打交道,那你大概率经历过这样的场景:为了插入几条测试向…...
量化研究实战:从数据到策略的Python框架与机器学习应用
1. 从零到一:量化研究实战框架搭建心路如果你和我一样,对金融市场既着迷又敬畏,总想用理性和数据去解读那些看似随机的价格波动,那么“量化研究”这个词对你来说一定不陌生。它听起来高大上,仿佛是高学历精英们在华尔街…...
给 Agent 用的搜索:Cloudflare AI Search 是什么,怎么工作的
原文:AI Search: the search primitive for your agents 发布时间:2026 年 4 月 16 日 作者:Gabriel Massadas、Miguel Cardoso、Anni Wang 每个 Agent 都需要搜索,但自己搭很麻烦 编码 Agent 要检索数百万个文件,客服…...
