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

六、golang基础之面向对象特征

文章目录

  • 一、方法
  • 二、方法值和方法表达式
    • (一)方法值
    • (二)方法表达式

一、方法

假设有两个方法,一个方法的接收者是指针类型,一个方法的接收者是值类型,那么:

  • 对于值类型的变量和指针类型的变量,这两个方法有什么区别?
  • 如果这两个方法是为了实现一个接口,那么这两个方法都可以调用吗?
  • 如果方法是嵌入到其他结构体中的,那么上面两种情况又是怎样的?
package main
import "fmt"type T struct {name string
}
func (t T) method1() {t.name = "new name1";
}
func (t *T) method1() {t.name = "new name2";
}
func main() {fmt.Println("method1 调用前 ", t.name)t.method1()fmt.Println("method1 调用后 ", t.name)fmt.Println("method2 调用前 ", t.name)t.method2()fmt.Println("method2 调用后 ", t.name)
}
method1 调用前  old name
method1 调用后  old name
method2 调用前  old name
method2 调用后  new name2

当调用t.method1()时相当于method1(t),实参和行参都是类型 T,可以接受。此时在method1()中的t只是参数t的值拷贝,所以method1()的修改影响不到main中的t变量。

当调用t.method2()=>method2(t),这是将 T 类型传给了 *T 类型,go可能会取 t 的地址传进去:method2(&t)。所以 method1() 的修改可以影响 t。

T 类型的变量这两个方法都是拥有的

二、方法值和方法表达式

(一)方法值

我们经常选择一个方法,并且在同一个表达式里执行,比如常见的p.Distance()形式,实际上将其分成两步来执行也是可能的。p.Distance叫作“选择器”,选择器会返回一个方法"值"一个将方法(Point.Distance)绑定到特定接收器变量的函数。
这个函数可以不通过指定其接收器即可被调用;即调用时不需要指定接收器,只要传入函数的参数即可

package mainimport "fmt"
import "math"type Point struct{ X, Y float64 }//这是给struct Point类型定义一个方法
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, 6}distanceFormP := p.Distance   // 方法值(相当于C语言的函数地址,函数指针)fmt.Println(distanceFormP(q)) // "5"fmt.Println(p.Distance(q))    // "5"//实际上distanceFormP 就绑定了 p接收器的方法DistancedistanceFormQ := q.Distance   //fmt.Println(distanceFormQ(p)) // "5"fmt.Println(q.Distance(p))    // "5"//实际上distanceFormQ 就绑定了 q接收器的方法Distance
}

在一个包的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)

省掉了上面那个例子里的匿名函数。

(二)方法表达式

和方法"值"相关的还有方法表达式。当调用一个方法时,与调用一个普通的函数相比,我们必须要用选择器(p.Distance)语法来指定方法的接收器。

当T是一个类型时,方法表达式可能会写作T.f或者(*T).f,会返回一个函数"值",这种函数会将其第一个参数用作接收器,所以可以用通常(译注:不写选择器)的方式来对其进行调用:

package mainimport "fmt"
import "math"type Point struct{ X, Y float64 }//这是给struct Point类型定义一个方法
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, 6}distance1 := Point.Distance //方法表达式, 是一个函数值(相当于C语言的函数指针)fmt.Println(distance1(p, q))fmt.Printf("%T\n", distance1) //%T表示打出数据类型 ,这个必须放在Printf使用distance2 := (*Point).Distance //方法表达式,必须传递指针类型distance2(&p, q)fmt.Printf("%T\n", distance2)}

结果:

5
func(main.Point, main.Point) float64
func(*main.Point, main.Point) float64
// 这个Distance实际上是指定了Point对象为接收器的一个方法func (p Point) Distance(),
// 但通过Point.Distance得到的函数需要比实际的Distance方法多一个参数,
// 即其需要用第一个额外参数指定接收器,后面排列Distance方法的参数。
// 看起来本书中函数和方法的区别是指有没有接收器,而不像其他语言那样是指有没有返回值。

当你根据一个变量来决定调用同一个类型的哪个函数时,方法表达式就显得很有用了。你可以根据选择来调用接收器各不相同的方法。下面的例子,变量op代表Point类型的addition或者subtraction方法,Path.TranslateBy方法会为其Path数组中的每一个Point来调用对应的方法:

package mainimport "fmt"
import "math"type Point struct{ X, Y float64 }//这是给struct Point类型定义一个方法
func (p Point) Distance(q Point) float64 {return math.Hypot(q.X-p.X, q.Y-p.Y)
}func (p Point) Add(another Point) Point {return Point{p.X + another.X, p.Y + another.Y}
}func (p Point) Sub(another Point) Point {return Point{p.X - another.X, p.Y - another.Y}
}func (p Point) Print() {fmt.Printf("{%f, %f}\n", p.X, p.Y)
}//定义一个Point切片类型 Path
type Path []Point//方法的接收器 是Path类型数据, 方法的选择器是TranslateBy(Point, bool)
func (path Path) TranslateBy(another Point, add bool) {var op func(p, q Point) Point //定义一个 op变量 类型是方法表达式 能够接收Add,和 Sub方法if add == true {op = Point.Add //给op变量赋值为Add方法} else {op = Point.Sub //给op变量赋值为Sub方法}for i := range path {//调用 path[i].Add(another) 或者 path[i].Sub(another)path[i] = op(path[i], another)path[i].Print()}
}func main() {points := Path{{10, 10},{11, 11},}anotherPoint := Point{5, 5}points.TranslateBy(anotherPoint, false)fmt.Println("------------------")points.TranslateBy(anotherPoint, true)
}

运行结果:

{5.000000, 5.000000}
{6.000000, 6.000000}
------------------
{10.000000, 10.000000}
{11.000000, 11.000000}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • element的下拉框封装
  • Nacos服务注册总流程(源码分析)
  • Elasticsearch:结合稀疏、密集和地理字段
  • (6) 深入探索Python-Pandas库的核心数据结构:DataFrame全面解析
  • Java中的锁都有什么
  • WPF中逻辑树和视觉树
  • SQL 游标
  • CentOS7 安装 git 命令
  • 使用kali Linux启动盘轻松破解Windows电脑密码
  • 博斯克化学试剂与元宇宙的融合探索
  • Linux实战记录
  • Github 2024-07-07php开源项目日报 Top9
  • Vue项目使用mockjs模拟后端接口
  • 笔记:mysql双主,keepalived 配置
  • godis源码分析——Redis协议解析器
  • 77. Combinations
  • 78. Subsets
  • Django 博客开发教程 8 - 博客文章详情页
  • java中的hashCode
  • windows下使用nginx调试简介
  • 初识 webpack
  • 分类模型——Logistics Regression
  • 关于Flux,Vuex,Redux的思考
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 使用Maven插件构建SpringBoot项目,生成Docker镜像push到DockerHub上
  • 事件委托的小应用
  • 微服务入门【系列视频课程】
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • ​字​节​一​面​
  • (二)hibernate配置管理
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (欧拉)openEuler系统添加网卡文件配置流程、(欧拉)openEuler系统手动配置ipv6地址流程、(欧拉)openEuler系统网络管理说明
  • (算法)N皇后问题
  • 、写入Shellcode到注册表上线
  • .axf 转化 .bin文件 的方法
  • .htaccess 强制https 单独排除某个目录
  • .NET CLR Hosting 简介
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端
  • .Net Remoting常用部署结构
  • .Net Web项目创建比较不错的参考文章
  • .NET8 动态添加定时任务(CRON Expression, Whatever)
  • .NET处理HTTP请求
  • .NET是什么
  • .net用HTML开发怎么调试,如何使用ASP.NET MVC在调试中查看控制器生成的html?
  • @Query中countQuery的介绍
  • [ 渗透测试面试篇 ] 渗透测试面试题大集合(详解)(十)RCE (远程代码/命令执行漏洞)相关面试题
  • [ 隧道技术 ] 反弹shell的集中常见方式(四)python反弹shell
  • [04]Web前端进阶—JS伪数组
  • [20181219]script使用小技巧.txt
  • [20190401]关于semtimedop函数调用.txt
  • [ASP]青辰网络考试管理系统NES X3.5
  • [C#]OpenCvSharp 实现Bitmap和Mat的格式相互转换
  • [C#数据加密]——MD5、SHA、AES、RSA