「Golang成长之路」面向“对象”

Golang
301
0
0
2022-04-28

一、结构体和方法

1.Go面向对象的特性

  • Go语言不是纯粹的面向对象的语言,准确是描述是,Go语言支持面向对象编程的特性.
  • Go语言中没有传统的面向对象编程语言的 class ,而Go语言中的 struct 和 其他编程语言中的 class 具有同等地位,也就是说Go语言是 基于 struct 来实现 面向对象 的特性的
  • 面向对象编程在Go语言中的支持设计得很具有特点,Go语言放弃了很多面向对象编程的概念,如 继承,重载, 构造函数, 析构函数 ,隐藏this指针等
  • Go语言可以实现面向对象编程的特性 封装 , 继承, 多态
  • Go语言仅支持封装,不支持继承和多态(在go语言中使用接口实现)
  • Go语言面向对象编程的支持是语言类型系统 中天然的组成部分,整个类型系统通过接口串联,灵活度高

2.结构体(struct)

在上面说过:“Go语言中的 struct 和 其他编程语言中的 class 具有同等地位”

对结构体的定义使用如下关键字:

type 标识符 struct{
       field1 type
       field2 type}

例如:

// 构建一个二叉树的结构体
type Node struct{
     Value int
     Left, right *Node
}

那么怎么来创建Node呢?

package main

import "fmt"
// 构建一个二叉树的结构体
type Node struct{
     Value int
     Left, Right *Node
}

func mian(){var root Node //创建一个根节点//root := Node{} 
    root = Node{Value: 3}//root.left = &Node{}  没有给值时,则默认值为0//left和right在结构体中都是指针需要加&
    root.Left = &Node{0, nil, nil}
    root.Right = &Node{5, nil, nil}
    root.Left.Right = &Node{2, nil, nil}
    root.Rigth.Left = new(Node) //先new再赋值
    root.Right.Left = &Node{0, nil, nil}

}
问题一:为什么这里指针可以表示为root.Right.Left

答:我们在c/c++中对于指针其实应该这样写:root.Right->Left

但是在Go语言中是没有->这种写法,它确实就是可以这样:root.Right.Left

总而言之:无论是地址还是结构体本身一律使用.来访问成员

在上面代码中我们就实现了这样的一棵树:

「Golang成长之路」面向对象篇

3.工厂函数:

Go语言中是没有构造函数的,但是Go语言本身就可以提供构造方法:

如:

root = Node{Value:  0}
root.Left = &Node{}
root.Rigth.Left =  new(Node) 
root.Right.Left =  &Node{4,  nil,  nil}

但是有时候我们想要自己来构造,构造的创建就可以使用工厂函数

//工厂函数(在golang中可以把取地址局部变量给全局变量使用)
func greatNode(value int) *Node{return &Node{Value:value}
}

其实工厂函数和普通函数没有本质上的差别

4.为结构定义方法

直接看例子:我们为Node定义一个打印的方法

//(node Node)中node为接收者,print()是为node接收的
func (node Node) print(){
      fmt.Println(node.value)
}

那么如何调用呢?

func mian(){var root Node //创建一个根节点//root := Node{} 
    root = Node{Value: 3}//root.left = &Node{}  没有给值时,则默认值为0//left和right在结构体中都是指针需要加&
    root.Left = &Node{0, nil, nil}
    root.Right = &Node{5, nil, nil}
    root.Left.Right = &Node{2, nil, nil}
    root.Rigth.Left = new(Node) //先new再赋值
    root.Right.Left = &Node{0, nil, nil}//调用如下:
    root.print()
}

打印值为:3

其实这里它和普通函数差不多

普通的定义如下:

func print(node Node){
    fmt.Println(noe.value)
}
//调用 print(root)

值接收者 VS 指针接收者:

  1. 改变内容必须用指针接收者
  2. 结构过大也可以考虑指针接收者
  3. 一致性:如有指针接收者,最好使用指针接收者
  • 看下面两个函数
//值接收者
func (node Node) setvalue(value int){
    node.Value = value
}
//指针接收者
func (node *Node) setvalue(value int){
    node.Value = value
}

第一个函数是值传递不会真正的修改node.Value的值

第二个函数是指针传参会真正的改变node.Value的值

  • nil指针也可以调用方法

介绍了这些方法,现在来实现一个树的中序遍历

func (node *Node) traverse(){if node == nil{return 
    }
    node.left.traverse()
    fmt.Println(node)
    node.right.traverse()
}

下面来调用:

func  mian(){var root Node //创建一个根节点  //root := Node{} 
    root = Node{Value:  3}  
    //root.left = &Node{} 没有给值时,则默认值为0 //left和right在结构体中都是指针需要加&
    root.Left =  &Node{0,  nil,  nil}
    root.Right =  &Node{5,  nil,  nil}
    root.Left.Right =  &Node{2,  nil,  nil} 
    root.Rigth.Left =  new(Node)  //先new再赋值
    root.Right.Left =  &Node{0,  nil,  nil}

    root.traverse()

}

输出为:

0 2 3 0 5

二、包和封装

1.封装

  • 名字一般使用CamelCase
  • 首字母大写:public
  • 首字母小写:private

2.包

包就是每一个程序中的package

  • 每一个目录只有一个包
  • main包包含了可执行入口
  • 为机构定义的方法必须放在同一个包内
  • 可以是不同的文件

下面是一个包:

对于这个包而言它我们要做可执行程序main中使用,就应该将构造体及其方法的首字母写成大写

package tree

import "fmt"
//结构体
type Node struct{
   Value int
   Right, Left *Node
}

//工厂函数(在golang中可以把取地址局部变量给全局变量使用)
func GreatNode(value int) *Node{return &Node{Value:value}
}

func (node *Node)Setvalue(value int){
    node.Value = value
}

//遍历树
func (node *Node) Traveser(){if node == nil {return}
   node.Left.Traveser()
   fmt.Println(node.Value)
   node.RightTraveser()
}

在可执行的main中:

我们引入的包名叫:tree

所以需要在构造方法中都添加tree

package mian
import ("awesomeProject/tree""fmt")

func  mian(){var root tree.Node //创建一个根节点  //root := tree.Node{} 
    root = tree.Node{Value:  3}  
    //root.left = &Node{} 没有给值时,则默认值为0 //left和right在结构体中都是指针需要加&
    root.Left =  &tree.Node{0,  nil,  nil}
    root.Right =  &tree.Node{5,  nil,  nil}
    root.Left.Right =  &tree.Node{2,  nil,  nil} 
    root.Rigth.Left =  new(tree.Node)  //先new再赋值
    root.Right.Left =  &tree.Node{0,  nil,  nil}

    //调用tree中中序遍历方法
    root.Traveser()
    root.Right.Right = new(tree.Node)

    //调用tree中Setvalue()方法
    root.Right.Right.Stevalue(100)
    fmt.Println(root.Right.Right.Value)

三、扩展已有类型

如果一个包是别人写的,那么我们想要扩展它怎么办?

在c++及Java里面我们会使用继承来扩展,相对较麻烦,所以在Go中干脆就取消了继承,在Go中扩展系统类型及别人的类型有两种方法:

  • 使用组合
  • 定义别名
  • 使用内嵌

1.使用组合:

tree包:

package tree

import "fmt"
//结构体
type Node struct{
   Value int
   Right, Left *Node
}

//工厂函数(在golang中可以把取地址局部变量给全局变量使用)
func GreatNode(value int) *Node{return &Node{Value:value}
}

func (node *Node)Setvalue(value int){
    node.Value = value
}

//遍历树
func (node *Node) Traveser(){if node == nil {return}
   node.Left.Traveser()
   fmt.Println(node.Value)
   node.RightTraveser()
}

我们在别人写tree包中使用中序遍历,那么我们现在还需要使用后续遍历,而现在tree包中没有后序遍历,此时就需要我们自己来实现

现在我们在main中写一个结构体:

type mytreeNode struct{
   node *tree.Node  //node为tree中的Node类型
}

程序main:

package main

import ("awesomeProject/tree""fmt")

type mytreeNode struct{
   node *tree.Node
}

//有接收者的后序遍历
func (myNode *mytreeNode)postOrder() {if myNode == nil || myNode.node == nil {return}
//使用组合,我们构建了mytreeNode的结构体,必须使用mytreeNode{}
   left := mytreeNode{myNode.node.Left}
   right := mytreeNode{myNode.node.Right}

   left.postOrder()
   right.postOrder()
   fmt.Print(myNode.node)
}

//普通后序遍历
func postOrder1(myNode *mytreeNode){if myNode == nil || myNode.node == nil{return}
   left := mytreeNode{myNode.node.Left}
   right := mytreeNode{myNode.node.Right}

   postOrder1(&left)postOrder1(&right)
   fmt.Print(myNode.node)

}

func main() {var root tree.Node
   root = tree.Node{Value: 0}
   root.Left = &tree.Node{1, nil, nil}
   root.Right = &tree.Node{2, nil, nil}
   root.Left.Left = &tree.Node{3, nil, nil}
   root.Left.Right = &tree.Node{4, nil, nil}
   root.Right.Left = new(tree.Node)
   root.Right.Left = &tree.Node{5, nil, nil}

   fmt.Println()//接收者函数
   myRoot := mytreeNode{&root}
   myRoot.postOrder()

   //普通函数postOrder1(&mytreeNode{&root})//postOrder1(myRoot)
}

最后输出:

341520
341520

2.使用别名

例如:

现在我们另一个目录下使用别名的方式来编写一个队列(Queue)

同样,我们将队列写在包里

//定义别名
type Queue []int

完整的queue包:

package queue

type Queue []int

//加入元素
func (q *Queue) Push(v int){*q = append(*q, v)
}

//移出首元素
func (q *Queue) Pop() int {
   head := (*q)[0]*q = (*q)[1:]return head
}

//判断队列是否为空
func (q *Queue)IsImpty() bool{return len((*q)) == 0
}

在main中调用包:

package main

import ("awesomeProject/tree/queue""fmt")

func main() {
   q := queue.Queue{1}
   q.Push(2)
   q.Push(3)
   q.Pop()
   fmt.Println(q)
   fmt.Println(q.IsImpty())
   fmt.Println(q.Pop())
   fmt.Println(q.Pop())
   fmt.Println(q.IsImpty())

最后输出为:

[2 3]
false
2
3
true

3.内嵌(emdedding)

内嵌其实也就是语法糖,它能使我们的代码量减少,什么是内嵌呢?看下面:

type mytreeNode struct{
   node *tree.Node
}
type mytreeNode struct{*tree.Node   //Embedding 内嵌
}

很容易看出:内嵌把node给省略了

使用内嵌后:

//后序遍历
func (myNode *mytreeNode)postOrder() {if myNode == nil || myNode.node == nil {return}

   left := mytreeNode{myNode.node.Left}
   right := mytreeNode{myNode.node.Right}

   left.postOrder()
   right.postOrder()
   fmt.Print(myNode.node.Value)
}

上面这段代码就会变成这样:

「Golang成长之路」面向对象篇

所以我们需要将.node改成.Node变成tree.Node后面的.Node; 也可以将.node直接扔掉变成:

/后序遍历
func (myNode *mytreeNode)postOrder() {if myNode == nil {return}

   left := mytreeNode{myNode.Left}
   right := mytreeNode{myNode.Right}

   left.postOrder()
   right.postOrder()
   fmt.Print(myNode.Value)
}

可选择:

「Golang成长之路」面向对象篇

还可以将main中的

func main() {var root tree.Node
   root = tree.Node{Value: 0}
   root.Left = &tree.Node{1, nil, nil}
   root.Right = &tree.Node{2, nil, nil}
   root.Left.Left = &tree.Node{3, nil, nil}
   root.Left.Right = &tree.Node{4, nil, nil}
   root.Right.Left = new(tree.Node)
   root.Right.Left = &tree.Node{5, nil, nil}

改成:

func main() {//var root tree.Node// root = tree.Node{Value: 0}
   root := mytreeNode{&tree.Node{Value: 0}}
   root.Left = &tree.Node{1, nil, nil}
   root.Right = &tree.Node{2, nil, nil}
   root.Left.Left = &tree.Node{3, nil, nil}
   root.Left.Right = &tree.Node{4, nil, nil}
   root.Right.Left = new(tree.Node)
   root.Right.Left = &tree.Node{5, nil, nil}
//这样就可直接将root值为参数了
   root.postOrder()

   postOrder1(&root)

「Golang成长之路」面向对象篇