go学习 6、方法
6、方法
面向对象编程(OOP),封装、组合。
6.1 方法声明
在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。
package geometryimport "math"type Point struct{ X, Y float64 }// traditional function
func Distance(p, q Point) float64 {return math.Hypot(q.X-p.X, q.Y-p.Y)
}// same thing, but as a method of the Point type
func (p Point) Distance(q Point) float64 {return math.Hypot(q.X-p.X, q.Y-p.Y)
}
附加的参数p,叫做方法的接收器(receiver)。
在方法调用过程中,接收器参数一般会在方法名之前出现。这和方法声明是一样的,都是接收器参数在方法名字之前。
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // "5", function call
fmt.Println(p.Distance(q)) // "5", method call
第一个Distance的调用 实际上用的是包级别的函数geometry.Distance,而第二个则是使用刚刚声明的Point,调用的 是Point类下声明的Point.Distance方法。
这种p.Distance的表达式叫做选择器,选择合适的对应p这个对象的Distance方法来执行。
选择器也会被用来选择一个struct类型的字段,比如p.X。由于方法和字段都是在同一命名空间,所以如果我们在这里声明一个X方法的话,编译器会报错,因为在调用p.X时会有歧义。
因为每种类型都有其方法的命名空间,我们在用Distance这个名字的时候,不同的Distance调 用指向了不同类型里的Distance方法。
// A Path is a journey connecting the points with straight lines.
type Path []Point
// Distance returns the distance traveled along the path.
func (path Path) Distance() float64 {sum := 0.0for i := range path {if i > 0 {sum += path[i-1].Distance(path[i])} }return sum
}
我们可以给同一个包内的任意命名类型定义方法,只要这个命名类型的底层类型(这个例子里,底层类型是指[]Point这个slice,Path就是命名类型)不是指针或者interface。
perim := Path{{1, 1},{5, 1},{5, 4},{1, 1},
}
fmt.Println(perim.Distance()) // "12"
对于一个给定的类型,其内部的方法都必须有唯一的方法名,但是不同的类型却可以有同样的方法名。
方法比之函数的一些好处:方法名可以简短。
import "gopl.io/ch6/geometry"
perim := geometry.Path{{1, 1}, {5, 1}, {5, 4}, {1, 1}} fmt.Println(geometry.PathDistance(perim)) // "12", standalone function fmt.Println(perim.Distance()) // "12", method of geometry.Path
6.2 基于指针对象的方法
当这个接受者变量本身比较大时,我们就可以用其指针而不是对象来声明方法。
func (p *Point) ScaleBy(factor float64) {p.X *= factorp.Y *= factor
}
方法的名字是 (*Point).ScaleBy。括号是必须的
为了避免歧义,在声明方法时,如果一个类型名本身是一个指针的话,是不允许 其出现在接收器中的:
type P *int
func (P) f() { /* ... */ } // compile error: invalid receiver type
想要调用指针类型方法 (*Point).ScaleBy ,只要提供一个Point类型的指针即可:
r := &Point{1, 2}
r.ScaleBy(2)
fmt.Println(*r) // "{2, 4}"
p := Point{1, 2}
pptr := &p
pptr.ScaleBy(2)
fmt.Println(p) // "{2, 4}"
p := Point{1, 2}
(&p).ScaleBy(2)
fmt.Println(p) // "{2, 4}"
go:如果接收器p是一个Point类型的变量,并且其方法需要一个Point指针作为接收器,我们可以用下面这种简短的写法:
p.ScaleBy(2)
编译器会隐式地帮我们用&p去调用ScaleBy这个方法。这种简写方法只适用于“变量”,我们不能通过一个无法取到地址的接收器来调用指针方法,比如临时变量的内存地址就无法获取得到:
Point{1, 2}.ScaleBy(2) // compile error: can't take address of Point literal
我们可以用一个 *Point 这样的接收器来调用Point的方法,因为我们可以通过地址来找到这个变量,只要用解引用符号来取到该变量即可。
pptr.Distance(q)
(*pptr).Distance(q)
总结
- 不论是接收器的实际参数和其接收器的形式参数相同,比如两者都是类型T或者都是类 型 *T :
Point{1, 2}.Distance(q) // Point
pptr.ScaleBy(2) // *Point
- 接收器实参是类型T,但接收器形参是类型 *T ,这种情况下编译器会隐式地为我们取变量的地址:
p.ScaleBy(2) // implicit (&p)
- 接收器实参是类型 *T ,形参是类型T。编译器会隐式地为我们解引用,取到指针指向的实际变量:
pptr.Distance(q) // implicit (*pptr)
6.2.1 Nil也是一个合法的接收器类型
package urltype Values map[string][]stringfunc (v Values) Get(key string) string{if vs:=v[key];len(vs)>0{return vs[0]}return ""
}func (v Values) Add(key,value string){v[key]=append(v[key],value)
}
m := url.Values{"lang": {"en"}} // direct construction
m.Add("item", "1")
m.Add("item", "2")
fmt.Println(m.Get("lang")) // "en"
fmt.Println(m.Get("q")) // ""
fmt.Println(m.Get("item")) // "1" (first value)
fmt.Println(m["item"]) // "[1 2]" (direct map access)
m = nil
fmt.Println(m.Get("item")) // ""
m.Add("item", "3") // panic: assignment to entry in nil map
nil的字面量编译器无法判断其准备类型。最后的那行m.Add的调用就会产生一 个panic,因为他尝试更新一个空map。
6.3 通过嵌入结构体来扩展类型
import "image/color"
type Point struct{ X, Y float64 }
type ColoredPoint struct {PointColor color.RGBA
}
我们可以直接认为通过嵌入的字段就是 ColoredPoint自身的字段,而完全不需要在调用时指出Point
var cp ColoredPoint
cp.X = 1
fmt.Println(cp.Point.X) // "1"
cp.Point.Y = 2
fmt.Println(cp.Y) // "2"
可以把ColoredPoint类型当作接收器来调用 Point里的方法,即使ColoredPoint里没有声明这些方法:
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)) // "5"
p.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10"
Point类的方法也被引入了ColoredPoint
尽管q有着Point这个内嵌类型,我们也必须要显式地选择它。尝试直接传q的话你会看到下面这样的错误::
p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point
一个ColoredPoint并不是一个Point,但他"has a"Point,并且它有从Point类里引入的Distance 和ScaleBy方法。如果你喜欢从实现的角度来考虑问题,内嵌字段会指导编译器去生成额外的包装方法来委托已经声明好的方法,和下面的形式是等价的:
func (p ColoredPoint) Distance(q Point) float64 {return p.Point.Distance(q)
}
func (p *ColoredPoint) ScaleBy(factor float64) {p.Point.ScaleBy(factor)
}
在类型中内嵌的匿名字段也可能是一个命名类型的指针,这种情况下字段和方法会被间接地引入到当前的类型中(译注:访问需要通过该指针指向的对象去取)。添加这一层间接关系让我 们可以共享通用的结构并动态地改变对象之间的关系。下面这个ColoredPoint的声明内嵌了一 个*Point的指针。
type ColoredPoint struct {*PointColor color.RGBA
}
p := ColoredPoint{&Point{1, 1}, red}
q := ColoredPoint{&Point{5, 4}, blue}
fmt.Println(p.Distance(*q.Point)) // "5"
q.Point = p.Point // p and q now share the same Point
p.ScaleBy(2)
fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"
一个struct类型也可能会有多个匿名字段。我们将ColoredPoint定义为下面这样:
type ColoredPoint struct {Pointcolor.RGBA
}
这种类型的值便会拥有Point和RGBA类型的所有方法,以及直接定义在ColoredPoint中的方法。
var (mu sync.Mutex // guards mappingmapping = make(map[string]string)
)
func Lookup(key string) string {mu.Lock()v := mapping[key]mu.Unlock()return v
}
var cache = struct {sync.Mutexmapping map[string]string
}{mapping: make(map[string]string),
}func Lookup(key string) string {cache.Lock()v := cache.mapping[key]cache.Unlock()return v
}
6.4 方法值和方法表达式
p := Point{1, 2}
q := Point{4, 6}
distanceFromP := p.Distance
fmt.Println(distanceFromP(q))
var origin Point
fmt.Println(distanceFromP(origin)) // "2.23606797749979", sqrt(5)
scaleP := p.ScaleBy // method value
scaleP(2)
scaleP(3)
scaleP(10)
// p becomes (2, 4)
// then (6, 12)
// then (60, 120)
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)
当调用一个方法时,我们必须要用选择器(p.Distance)语法来指定方法的接收器。
当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)"
// 译注:这个Distance实际上是指定了Point对象为接收器的一个方法func (p Point) Distance(), // 但通过Point.Distance得到的函数需要比实际的Distance方法多一个参数,
// 即其需要用第一个额外参数指定接收器,后面排列Distance方法的参数。
// 看起来本书中函数和方法的区别是指有没有接收器,而不像其他语言那样是指有没有返回值。
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 {// Call either path[i].Add(offset) or path[i].Sub(offset).path[i] = op(path[i], offset)}
}
6.5 封装
一个对象的变量或者方法如果对调用方是不可见的话,一般就被定义为“封装”。封装有时候也
被叫做信息隐藏,同时也是面向对象编程最关键的一个方面。
Go语言只有一种控制可见性的手段:大写首字母的标识符会从定义它们的包中被导出,小写字母的则不会。这种限制包内成员的方式同样适用于struct或者一个类型的方法。因而如果我们想要封装一个对象,我们必须将其定义为一个struct。
这也就是IntSet被定义为struct类型的原因,尽管它只有一个字段:
type IntSet struct {words []uint64
}
一个struct类型的字段对同一个包的所有代码都有可见性,无论你的代码是写在一个函数还是一个方法里。
封装的优点:
- 因为调用方不能直接修改对象的变量值,其只需要关注少量的语句并且只要弄懂少量变量的可能的值即可。
- 隐藏实现的细节,可以防止调用方依赖那些可能变化的具体实现,这样使设计包的程序员在不破坏对外的api情况下能得到更大的自由。
- 阻止了外部调用方对对象内部的值任意地进行修改。
下面的Counter类型允许调用方来增加counter变量的值,并且允许将这个值reset为0,但是不允许随便设置这个值:
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,例子如下,比如log包里的Logger 类型对应的一些函数。在命名一个getter方法时,我们通常会省略掉前面的Get前缀。这种简洁上的偏好也可以推广到各种类型的前缀比如Fetch,Find或者Lookup。
package log
type Logger struct {flags intprefix string// ...
}
func (l *Logger) Flags() int
func (l *Logger) SetFlags(flag int)
func (l *Logger) Prefix() string
func (l *Logger) SetPrefix(prefix string)
相关文章:
go学习 6、方法
6、方法 面向对象编程(OOP),封装、组合。 6.1 方法声明 在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。 …...

MySQL Windows版本下载及安装时默认路径的修改
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、MySQL 下载二、默认路径修改1、安装前准备【非常重要】2、启动安装程序总结1、MySQL下载2、MySQL默认路径修改前言 MySQL 被Oracle收购后,各种操作规范及约束也相应的跟着来了,这不,只…...

第3章 配置与服务
1 CoreCms.Net.Configuration.AppSettingsHelper using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json; namespace CoreCms.Net.Configuration { /// <summary> /// 【应用设置助手--类】 /// <remarks> /// 摘要&#x…...

Arcgis之 KML/KMZ文件转shp
一般我们在Goole Earth上勾画的区域导出后都为KML或者KMZ格式的,但无法在arcgis等软件上直接应用,故需进行一定的转换 1.打开ArcMap,选择ArcToolbox->Conversion Tools->From KML->KML To Layer 得到如下结果(由于本KML…...

python绘制3D条形图
文章目录 数据导入三维条形图bar3d 数据导入 尽管在matplotlib支持在一个坐标系中绘制多组条形图,效果如下 其中,蓝色表示中国,橘色表示美国,绿色表示欧盟。从这个图就可以非常直观地看出,三者自2018到2022年的GDP变化…...
计算从曲线的起点到param指定的点的曲线段的长度
以下方法只能用于继承于AcDbCurve的类型 主要使用两个接口 派生类中此函数的实现应返回, 并将endParam设置为曲线端点的参数。 如果成功则返回Acad::eOk。 默认情况下, 该函数返回Acad::eNotImplemented。 virtual Acad::ErrorStatus getEndParam(double&endParam) cons…...

POLARDB IMCI 白皮书 云原生HTAP 数据库系统 一 数据压缩和打包处理与数据更新
开头还是介绍一下群,如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题,有需求都可以加群群内有各大数据库行业大咖,CTO,可以解决你的问题。加群请联系 liuaustin3 ,在新加的朋友会分到2群(共…...
linux----源码安装如何加入到系统服务中(systemclt)
将自己源码安装的软件加入到系统服务中。例如nginx,mysql 就以nginx为例,源码安装,加入到系统服务中 使用yum安装nginx,自动会加入到系统服务 16-Linux系统服务 - 刘清政 - 博客园 (cnblogs.com) 第一步: 源码安装好nginx之后࿰…...
Unity 使用UnityWebRequest 读取存档 (IOS只能这样做)
打IOS包的时候发现的,不能使用正常的IO流读取,不然会读取不到数据,只能使用UnityWebRequest 读取 代码如下 public IEnumerator ReadArchive(Action<bool, string> ac, string filepath ""){UnityWebRequest request Unit…...
Caused by: org.springframework.beans.factory.
问题解决:Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name IUserRepository defined in app.test4.OpportunityMatching.IUserRepository defined in EnableJpaRepositories declared on JpaRepositoriesRegistrar.Enable…...
【docker 安装】 与【docker-compose 安装】
不同的操作系统需要不同的docker安装文件:具体下载位置: Docker: https://download.docker.com/linux/static/stable/ docekr-compose:https://github.com/docker/compose/releases 1. 验证客户机器是否有docker 和docker-compose docker -…...

意外:WPS编程新工具,不用编程,excel用户:可以不用VBA啦
来来来,拓宽一下视野! 别总以为excel和WPS只能用VBA编程,也别总是想着ACCESS这些老生常谈的工具。其实对于电子表格高级用户来讲,不会VBA,不用ACCESS,也一样可以解决复杂问题或者高级应用。 尤其是WPS用户…...

GAMES101 笔记 Lecture12 Geometry3
目录 Mesh Operations: Geometry ProcessingMesh Subdivision (曲面细分)Mesh Simplification(曲面简化)Mesh Regularization(曲面正则化) Subdivision(细分)Loop Subdivision(Loop细分)如何来调整顶点位置呢?Loop Subdivision Result (Loop细分的结果) Catmull-Cla…...
Java的内部类
内部类的概念 在 Java 中,内部类是定义在另一个类或者方法的内部的类。内部类可以访问外部类的所有成员和方法,同时可以被外部类和其他类所访问。内部类可以分为四种类型:静态内部类、成员内部类、局部内部类和匿名内部类。 静态内部类 静…...

电赛培训(高频电路类赛题)学习总结
此篇文章基于全国电子设计大赛培训网的官网的高频电路类赛题总结的知识点。 高频电路赛题的相关理论知识点 (1)高频电路的单位 a.1kHz1000Hz不等于1KHz(大写的K是错误的) b.S是西门子,电导的单位,s是秒&…...

Rust ESP32C3开发
Rust ESP32C3开发 系统开发逐步使用Rust语言,在嵌入式领域Rust也逐步完善,本着学习Rust和ESP32的目的,搭建了ESP32C3的环境,过程中遇到了不少问题,予以记录。 ESP-IDF开发ESP32 这一部分可跳过,是使用C开…...

【Spring Cloud Gateway 新一代网关】—— 每天一点小知识
💧 S p r i n g C l o u d G a t e w a y 新一代网关 \color{#FF1493}{Spring Cloud Gateway 新一代网关} SpringCloudGateway新一代网关💧 🌷 仰望天空,妳我亦是行人.✨ 🦄 个人主页——微风撞见云的博客&a…...
Java 中的关键字 final 和 static
一、关键字 final final 修饰符可以用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。 1.1 final 变量 final 有"最后的、最终的"的含义…...
Spring Cloud OpenFeign 全教程
1. 声明式 REST 客户端: Feign Feign 是一个声明式的 Web Service 客户端。它使编写 Web Service 客户端更容易。要使用 Feign,需要创建一个接口并对其进行注解。它有可插拔的注解支持,包括 Feign 注解和 JAX-RS 注解。Feign 还支持可插拔的…...

LLaMA模型论文《LLaMA: Open and Efficient Foundation Language Models》阅读笔记
文章目录 1. 简介2.方法2.1 预训练数据2.2 网络架构2.3 优化器2.4 高效的实现 3.论文其余部分4. 参考资料 1. 简介 LLaMA是meta在2023年2月开源的大模型,在这之后,很多开源模型都是基于LLaMA的,比如斯坦福大学的羊驼模型。 LLaMA的重点是比…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...

均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...