go面向对象(接口)

wxvirus2022年3月20日
大约 4 分钟

面向对象

  • Go语言仅支持封装,不支持继承和多态
  • Go语言没有class,只有struct

结构体的定义

type point struct {i, j int}

示例:

package main

import "fmt"

type treeNode struct {
	value       int
	left, right *treeNode // 指针
}

func main() {
	// 建立了一个根节点
	var root treeNode

	root = treeNode{value: 3}
	root.left = &treeNode{}
	root.right = &treeNode{5, nil, nil}
	root.right.left = new(treeNode) // new 返回的是一个地址

	nodes := []treeNode{
		{value: 3},
		{},
		{6, nil, &root},
	}

	fmt.Println(nodes)
}

Go语言使用.的方式进行调用变量或者调用函数或者调用指针。

如果想要使用类似类中的构造函数,我们需要自己定义工厂函数

func createNode(value int) *treeNode {
	return &treeNode{value: value}
}
root.left.right = createNode(2)

注意:这里是返回的是函数体内部的局部变量的地址!

那么这个局部变量是分配在栈上还是在堆上呢?

  • 不需要知道
  • 根据编译器和运行环境来决定的

如果这里的返回值没有取地址且返回出去,编译器很可能认为这个变量不需要外面去访问,就在栈上分配;

当编译器看到你这个加了取地址返回给别人使用时,它就会去堆上去分配,然后treeNode会参与垃圾回收机制,外面拿着指针的人去做事,等到做完了,扔掉不用了,就会被回收掉。

Go语言中可以这样使用,但是在C++里就会发生错误!


通过上述的代码,我们构造了一个如下图的树结构:

树结构

我们对其进行遍历操作:

我们首先要为这个结构体定义方法,这个方法不是写在结构体里的,而是要写在结构体外部的。

func (node treeNode) print()  {
	fmt.Print(node.value)
}

它的func有一个特点,有一个接收者,表示这个print方法是给这个node来进行使用的。

root.print()

当我们调用了root.print()就是代表rootprint函数的接收者,这里为什么会放在前面呢,但其实放在函数括号内也可以,无非是调用的方式变了:

func print(node treeNode)  {
	fmt.Print(node.value)
}

调用方式就变为:

print(root)

其实和上面的没啥两样。

这两种方式都相当于参数传递,但是这两个是引用传参呢还是值传递呢?

Go当然是值传递了

func (node treeNode) setValue(value int) {
	node.value = value
}

然后我们来修改某一个节点的值

func main() {
	// 建立了一个根节点
	var root treeNode

	root = treeNode{value: 3}
	root.left = &treeNode{}
	root.right = &treeNode{5, nil, nil}
	root.right.left = new(treeNode)
	root.left.right = createNode(2)

    // 这里设置vlaue = 4
	root.right.left.setValue(4)
    // 打印其实,它没有变成4,还是0,所以Go是值传递
	root.right.left.print()
	fmt.Println()
}

我们如何更改为可以修改它的值呢?

func (node *treeNode) setValue(value int) {
	node.value = value
}

我们只要将参数传递变为指针类型。我们调用的时候,语法上会有一些人性化的事情,如果接收者变成了指针接收者,它实际调用的会把root.right.left的地址传给它,再说了,这个root.right.left其实也是指针类型;当我们调用root.print()的时候,其实也会解析出当前的地址,来获取到对应的value,拷贝一份值给它,进行打印。

我们再次进行调用上述代码,最终值变成了4

  • 只有使用指针才可以改变结构内容
  • nil指针也可以调用方法!

使用nil调用方法

func (node *treeNode) setValue(value int) {
	if node == nil {
		fmt.Println("setting a value to nil node. Ignored.")
		return
	}
	node.value = value
}

中序遍历

func (node *treeNode) traverse() {
	if node == nil {
		return
	}
	// 中序遍历 左中右
	node.left.traverse()
	node.print()
	node.right.traverse()
}
0 2 3 4 5

结合上述的图来看,因为 4 是后面设置的值,所以此时的图应该是 5 左下角的应该是 4.

值接收者和指针接收者

  • 要改变内容必须使用指针接收者
  • 结构过大也考虑使用指针接收者
  • 一致性:如有指针接收者,最好都是指针接收者(建议
  • 值接收者Go语言特有的
  • 值/指针接收者均可接收值/指针
Loading...