Golang简易入门教程——面向对象篇

今天是 golang专题
的第9篇文章,我们一起来看看golang当中的面向对象的部分。

在现在高级语言当中,面向对象几乎是不可或缺也是一门语言最重要的部分之一。golang作为一门刚刚诞生十年的新兴语言自然是支持面向对象的,但是golang当中面向对象的概念和特性与我们之前熟悉的大部分语言都不尽相同。比如Java、Python等,相比之下, golang这个部分的设计 非常得简洁和优雅
(仁者见仁),所以即使你之前没有系统地了解过面向对象,也没有关系,也一定能够看懂。

常见的面向对象的部分,比如继承、构造函数、析构函数,这些内容在golang当中 统统没有
,因此整体的学习成本和其他的语言比起来会更低一些。

struct

在golang当中没有类的概念,代替的是 结构体
(struct)这个概念。我们可以给结构体类型定义方法,为了表明该方法的适用对象是当前结构体,我们需要在方法当中定义接收者,位于func关键字和方法名之间。
我们一起来看一个例子:

type Point struct {
 x int
 y int
}

func (p Point) Dis() float64 {
 return math.Sqrt(float64(p.x*p.x + p.y*p.y))
}
复制代码

在上面这段代码当中我们定义了一个叫做Point的结构体,以及一个面向这个结构体的方法Dis。我们一个一个来看它们的语法。

对于结构体来说,我们 通过type关键字定义
。在golang当中type关键字的含义是定义一个新的类型。比如我们也可以这样使用type:

type Integer int
复制代码

它的含义是 从int类型定义了一个新的类型Integer
,从此之后我们可以在后序的代码当中使用Integer来代替int。它有些类似于C++当中的typedef,结合这个含义,我们再来看结构体的定义就很好理解了。其实是我们通过struct关键字构造了一个结构体,然后使用type关键字定义成了一个类型。

之后我们创建了一个面向结构体Point的函数Dis,这个函数和我们之前使用的函数看起来并没有太多的不同,唯一的区别在于我们在func和函数名之间多了一个(p Point)的定义。这其实是 定义这个函数的接收者
,也就是说它接受一个结构体的调用。
不仅如此,我们可以给golang当中的任何类型添加方法,比如:

type Integer int

func (a Integer) Less(b Integer) bool {
 return a < b
}
复制代码

在这个例子当中,我们给原生的int类型添加了Less这个方法,用来比较大小。我们在添加方法之前使用type给int起了一个别名,这是因为 golang不允许给简单的内置类型添加方法,并且接收者的类型定义和方法声明必须在同一个包里
,我们必须要使用type关键字临时定义一个新的类型。这里要注意的是,虽然我们定义出来的Integer和int的功能完全一样,但是它们属于不同的类型,不能互相赋值。
和别的语言比较起来,这样的定义的一个好处就是清晰。举个例子,比如在Java当中,同样的功能会写成不同的样子:

class Integer {
    private int val;
    public boolean less(Integer b) {
        return this.val < b.val;
    }
}
复制代码

对于初学者而言,可能会觉得困惑,less函数当中的这个this究竟是哪里来的?其实这是因为Java的成员方法当中隐藏了this这个参数,这一点在Python当中要稍稍清晰一些,因为它将self参数明确地写了出来:

class Integer:
    def __init__(self, val):
        self.val = val
    def less(self, val):
        return self.val < val.val
复制代码

而golang明确了结构体函数的接收者以及参数,显得更加清晰。

指针接收者

golang当中,我们也可以将函数的接收者 定义成指针类型

比如我们可以将刚才的函数写成这样:

type Point struct {
 x int
 y int
}

func (p *Point) Dis() float64 {
 return math.Sqrt(float64(p.x*p.x + p.y*p.y))
}
复制代码

指针接收者和类型接收者在使用上是一样的,我们并不需要将结构体转化成指针类型,可以直接进行调用。golang内部会自己完成这个转化:

func main() {
 p := Point{3, 4}
 fmt.Print(p.Dis())
}
复制代码

那么这两者的区别是什么呢?我们既然可以定义成普通的结构体对象,为什么还要有一个指针对象的接收者呢?

其实很好理解, 两者的区别 有些类似于C++当中的值传递和引用传递
。在值传递当中,我们传递的是值的一个拷贝,我们在函数当中修改参数并不会影响函数外的结果。而引用传递则不然,传递的是参数的引用,我们在函数内部修改它的话,会影响函数外的值。

也就是说在golang当中,如果我们函数接收的是一个指针类型,我们 可以在函数内部修改这个结构体的值
。否则的话,传入的是一个拷贝,我们在其中修改值并不会影响它本身。我们来看个例子:

func (p *Point) Modify() {
 p.x += 5
 p.y -= 3
}

func main() {
 p := Point{3, 4}
 p.Modify()
 fmt.Print(p)
}
复制代码

上面这段代码当中函数的接收者是一个指针,所以我们得到的结果会是{8, 1},如果我们把指针去掉,改成普通的值接收的话,那么最后的结果仍然是{3, 4}。

总结

我们今天学的内容有些多,我们来简单梳理一下。首先,我们了解了通过type和struct关键字来定义一个结构体, 结构体是golang当中面向对象的载体
,golang抛弃了传统的面向对象的实现方式和特性,拥有自己的面向对象的理念。
对于结构体来说,我们可以把它当做是接受者传递给一个函数,使得我们可以以类似调用类当中方法的形式来调用一个函数。并且对于函数而言,接受者除了值以外还可以是一个指针。如果是指针的话,当我们对结构体值进行修改的时候,会影响到原值。即使我们定义的接收者类型是指针,我们在调用的时候也不必显示将它转化成结构体指针,golang当中会自动替我们完成这样的转化。

面向对象部分可以说是golang这一门语言当中最大的创新之一,也正是因为抛弃了传统的类以及继承、派生的概念,使得golang当中的面向对象语法糖相对简洁。也因此有人将golang称为 升级版的C语言
。虽然我们啰啰嗦嗦写了很多,但是实际谈到的内容并不多,我想理解起来也不会特别困难。

今天的文章到这里就结束了,如果喜欢本文,可以的话,请 点个关注
,给我一点鼓励,也方便获取更多文章。

本文使用 mdnice
排版