go语言方法之方法声明

从我们的理解来讲,一个对象其实也就是一个简单的赋值或者一个变量,在这个对象中会包含一些方法,而一个方法则是一个一个和特殊类型关联的函数。一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接去操作对象,而是借助方法来做这些事情。

在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。

package main

import (
	"fmt"
	"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)
}

func main() {
	p := Point{1, 2}
	q := Point{4, 6}
	fmt.Println(Distance(p, q)) 
	fmt.Println(p.Distance(q))  
}

5
5

Process finished with the exit code 0

上面的代码里面有个附加参数p,叫做方法的接收器(receiver),早期的面向对象语言留下的遗产将调用一个方法称为"向一个对象发送消息"。

在Go语言中,我们并不会像其他语言那样用this或者self作为接收器,我们可以任意的选择接收器的名字。由于接收器的名字经常会被使用到,所以保持其在方法间传递的一致性和简短性是不错的主意。这里的建议是可以使用其类型的第一个字母,比如这里使用了Point的首字母p。

在方法调用过程中,接收器参数一般会在方法名之前出现。这种方法声明是一样的,都是接收器参数在方法名字之前。

上图中的两个函数都是调用的distance,但是却没有发生冲突。第一个distance的调用实际上用的是包级别的函数main.Distance,而第二个则是使用刚刚声明的Point,调用的是Point类下声明的Point.Distance方法。

这种p.Distance的表达式叫做选择器,因为他会选择合适的对应p这个对象的Distance方法来执行。选择器也会被用来选择一个struct类型的字段,比如p.X。由于方法和字段都是在同一命名空间,所以如果我们在这里声明一个X方法的话,编译器会报错,因为在调用p.X时会有歧义。

因为每种类型都有其他方法的命名空间,我们在用Distance这个名字的时候,不同的Distance调用指向了不同类型的Distance方法。让我们来定义一个Path类型,这个Path代表一个线段 的集合,并且也给这个Path定义一个叫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.0
    for i := range path {
        if i > 0 {
            sum += path[i-1].Distance(path[i])
        }
    }
    return sum
}

Path是一个命名的slice类型,而不是Point那样的struct类型,然而我们依然可以为它定义方法。在能够给任意类型定义方法这一点上,Go和很多其它的面向对象的语言不太一样。因此 在Go语言里,我们为一些简单的数值、字符串、slice、map来定义一些附加行为很方便。方法可以被声明到任意类型,只要不是一个指针或者一个interface。

两个Distance方法有不同的类型。他们两个方法之间没有任何关系,尽管Path的Distance方法 会在内部调用Point.Distance方法来计算每个连接邻接点的线段的长度。 让我们来调用一个新方法,计算三角形的周长:

perim := Path{
    {1, 1},
    {5, 1},
    {5, 4},
    {1, 1},
}
fmt.Println(perim.Distance()) // "12"

在上面两个对Distance名字的方法的调用中,编译器会根据方法的名字以及接收器来决定具体 调用的是哪一个函数。第一个例子中path[i-1]数组中的类型是Point,因此Point.Distance这个 方法被调用;在第二个例子中perim的类型是Path,因此Distance调用的是Path.Distance。 Go语言圣经 方法声明 211 对于一个给定的类型,其内部的方法都必须有唯一的方法名,但是不同的类型却可以有同样 的方法名,比如我们这里Point和Path就都有Distance这个名字的方法;所以我们没有必要非 在方法名之前加类型名来消除歧义,比如PathDistance。这里我们已经看到了方法比之函数 的一些好处:方法名可以简短。当我们在包外调用的时候这种好处就会被放大,因为我们可 以使用这个短名字,而可以省略掉包的名字,下面是例子:

import "gopl.io/ch6/geometry"

perim := geometry.Path{{1, 1}, {5, 1}, {5, 4}, {1, 1}}
fmt.Println(geometry.Path.Distance(perim)) // "12", standalone function
fmt.Println(perim.Distance()) // "12", method of geometry.Path
相关推荐
C-SDN花园GGbond40 分钟前
【探索数据结构与算法】插入排序:原理、实现与分析(图文详解)
c语言·开发语言·数据结构·排序算法
罗政2 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
迷迭所归处2 小时前
C++ —— 关于vector
开发语言·c++·算法
架构文摘JGWZ2 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
leon6252 小时前
优化算法(一)—遗传算法(Genetic Algorithm)附MATLAB程序
开发语言·算法·matlab
拾光师3 小时前
spring获取当前request
java·后端·spring
锦亦之22333 小时前
QT+OSG+OSG-earth如何在窗口显示一个地球
开发语言·qt
我是苏苏3 小时前
Web开发:ABP框架2——入门级别的增删改查Demo
java·开发语言
姜太公钓鲸2333 小时前
c++ static(详解)
开发语言·c++
菜菜想进步3 小时前
内存管理(C++版)
c语言·开发语言·c++