跟着老猫来搞GO,"面向对象"

前言

之前和大家分享了容器以及相关的基础语法,以及函数,相信如果大家有接触过C++或者java的朋友都晓得面向对象,其实在GO语言中也存在面向对象,但是还是比较简单的,下面我们来看一下GO语言的“面向对象”。

面向对象

结构体的定义

其实在GO语言中并不能准确得说是面向对象,go语言其实是面向接口函数编程的语言。为什么要说成GO语言的面向对象,其实也是部分特性类似于面向对象。GO语言中的面向对象还是比较简单的,GO语言仅支持封装,不支持多态和继承。语言没有class,只有struct。

结构体本质上也是一种数据类型,它是将0个或者多个任意类型的命名变量组合在一起的聚合数据类型,其中每个变量都叫做结构体的成员。看下结构体的定义,具体如下

type Employee struct {
	ID int //id编号
	Name string //名称
	Address string //地址
	Position string //职位
}

func main() {
	var jack Employee
	jack.Name = "jack"
	jack.Address = "shanghai"
	fmt.Println(jack.Name)
}

以上我们就定义一个员工的结构体,这个结构体包含了名称、地址、职位等一些属性。在main函数中,我们创建了一个Employee类型的jack,并且给予其初始化了值。上述比较简单地,咱们可以直接用“.”的方式进行对结构体变量进行赋值以及取值,当然咱们也可以获取成员变量的地址,然后通过指针来访问它。如下:

func main() {
	var jack Employee
	jack.Name = "jack"
	jack.Address = "shanghai"
	jack.Position = "Engineer"
	//fmt.Println(jack.Name)

	position := &jack.Position
	*position = "Senior" + *position
	fmt.Println(*position)
}

显然这里的jack被升值了,变成了高级工程师。

咱们的点号其实也可以直接用在结构体的指针上,如下

func main() {
	var jack Employee
	jack.Name = "jack"
	jack.Address = "shanghai"
	jack.Position = "Engineer"
	//fmt.Println(jack.Name)

	position := &jack.Position
	*position = "Senior" + *position
	//fmt.Println(*position)

	var employeeA *Employee = &jack
	employeeA.Position = "Super" + employeeA.Position
	fmt.Println(employeeA.Position)
}

还没理解透彻指针的小伙伴可以会有点懵,后面老猫还是专门把指针说一下。上面的那个步骤,我们只是获取了jack的职位并通过指针将其重新赋值升级,那么下面,其实咱们就定义了一个Employee的指针,并且这个指针指向的是jack这个结构体,那么针对我们的employeeA这个员工指针就能获取其结构体中所有的属性,并且将其重新赋值。

当然我们甚至可以定义指针类型的结构体函数,当然,其返回值必须是某个结构体的地址,具体定义如下:

func EmployeeByID(id int) *Employee {
	var json Employee
	json.ID = id
	json.Name = "json"
	json.Address = "beijing"
	json.Position = "Engineer"
	return &json
}

上述,咱们主要介绍了结构体以及指针的相关的用法,那么关于结构体的话还有哪些注意点呢?

  • 成员变量的顺序对于结构体同一性很重要,如果我们将上面的Employee结构体的属性进行顺序颠倒调换,那么我们就说定义了另外一个不同类型的结构体。
  • 关于GO结构中定义变量的大小写,大家可以看到,老猫上述定义的都是以大写字母开头的,因为只有以大写字母开头定义的属性,才能够被外围访问。大家可以手动敲一下代码体验一下。这个也是GO最主要的访问控制机制。
  • 结构体类型不可以定义一个拥有相同结构体类型s的成员变量,也就是一个聚合类型不可以包含它自己。

这个是什么意思呢?咱们来看一下例子!
编译出错

上图我们可以看到,如果结构体中套有自身是会报编译错误的。但是Employee中可以定义个S的指针类型。例如下面则是OK的

type Employee struct {
	ID int
	Name string
	Address string
	Position *Employee
}

所以咱们就可以利用这种形式来做递归结构的定义,例如链表或者树的定义咱们就可以这么来定义

type tree struct {
	value int
	left,right *tree
}

结构体的字面量

这里说的字面量就是结构体中的值,我们结构体类型中的值可以通过结构体字面量来进行设置。如下,有两种结构体字面量。

第一种

type Point struct {X,Y int}
p:=Point{1,2}

这种方式要求按照正常顺序为每个成员变量进行赋值,很显然,如果结构体比较简单的时候无所谓,但是一旦结构体之后随着业务的演化变得相当复杂的时候,代码的可维护性就变得相当差了。

第二种

type Point struct {X,Y int}
p:=Point{X:1,Y:2}

这种方式显然会比较清晰一些,但是需要注意的是如果其中某个成员变量没有指定的值的话,那么其值默认就为零值。由于指定了成员变量的名字,在这种方式中相当于第一种而言,这里的顺序就无所谓了。

结构体的比较

如果结构体的所有成员变量都可以比较,那么这个结构体就是可以比较的,两个结构体的比较直接使用==或者!=即可。

p:=Point{1,2}
q:=Point{2,1}
e:=Point{1,2}
fmt.Println(q.x == p.x) //成员函数比较 false
fmt.Println(p == q) //整体比较 false
fmt.Println(p == e) // true

在面向对象语言中,例如java,在我们比较两个对象值的时候需要去比较两个对象的hash值,甚至需要重写equals方法,我们在这里看到的go语言的结构体对象的比较就很简单明了了。这里不多赘述,还是希望大家能够多写写。

结构体的嵌套机制

结构体的嵌套机制可以让我们将一个命名结构体当做另一个结构体类型的匿名成员来使用。这句话可能有点不好理解,我们还是来直接看一下例子。

首先咱们来定义一个圆,圆的话包含了圆心的坐标以及相关的半径,由此,咱们可以抽象出如下代码

type Circle struct {
	X,Y,Radius int
}

在我们的日常生活中,轮子也是圆形的,轮子可能多一些条幅数,由此,咱们轮子也可以抽象一下

type Wheel struct {
    X,Y,Radius,Spokes int
}

看到上述两个,咱们其实可以发现这两个结构体中有挺多相同的成员变量,那咱们是不是可以再度抽象一下,于是咱们就抽象成了如下:

type Circle struct {
	Center Point
	Radius int
}

type Wheel struct {
	Circle Circle
	Spokes int
} 

这样,咱们就会发现整个程序看起来变得更加清晰。其实这也是更好地说明了结构体说白了也是一种特殊的数据类型而已。

写在最后

本篇中其实和大家粗略分享了结构体的相关知识,有java相关面向对象语言经验的小伙伴会发现,结构体和面向对象语言中的类比较相像,但是GO语言中的结构体的用法相比之下会简单得多。

我是老猫,更多内容,欢迎大家搜索关注老猫的公众号“程序员老猫”。

posted @ 2021-11-30 23:19  程序员老猫  阅读(468)  评论(0编辑  收藏  举报