GO学习笔记——面向对象编程之方法(16)

讲完了结构体,如果要为这个结构体对象设计一些实用的方法,比如对于单链表,有单链表的遍历和反转等,这些方法是如何来定义的呢?

C++中,这些方法也一同放在class中定义了,但是GO中不可以,必须在结构体外定义。因此,为了能够对结构体进行上述实用的操作,GO中有了方法这个概念。

方法本质其实就是一个函数,下面来看看实现细节。


方法的定义

//定义一个单链表的节点
type ListNode struct {
	next *ListNode
	val int
}

//定义一个方法来遍历这个单链表
func (node *ListNode) printList(){
	for node !=  nil{
		fmt.Print(node.val)
		if node.next != nil {
			fmt.Print("->")
		}
		node = node.next
	}
}

func main() {
	root := &ListNode{val:1}
	root.next = &ListNode{val:2}
	root.next.next = &ListNode{val:3}
	root.next.next.next = &ListNode{val:4}
	root.printList()    //使用该方法
}

输出结果

1->2->3->4

我们可以看到,方法的定义和普通函数的定义差不多,也就多了一个(node *ListNode),这其实就是一个接收器,表示我们这个方法的接收器类型是 *ListNode,这样,这个方法就可以在这个接收器类型内部被访问了(使用.访问)


有了函数为什么还要方法?

其实函数和方法差不了很多,可以看下面

func (node *ListNode) printList(){
	for node !=  nil{
		fmt.Print(node.val)
		if node.next != nil {
			fmt.Print("->")
		}
		node = node.next
	}
}

func printList(node *ListNode){
	for node !=  nil{
		fmt.Print(node.val)
		if node.next != nil {
			fmt.Print("->")
		}
		node = node.next
	}
}

上面是方法,下面是函数,两者只是调用的方式不同罢了。那么为什么还要方法?

  • 相同名字的函数只能有一个(GO不支持函数重载),但是相同名字的方法就可以有多个了,只要它们的接收器不同
  • 通过方法这种实现形式,可以达到和其他编程语言的面向对象编程的意思(C++中调用类型中的函数也是用.来调用的)

指针接收器与值接收器

先来看一下值接收器

//定义一个单链表的节点
type ListNode struct {
	next *ListNode
	val int
}

//定义一个方法改变node的val,这里是值接收器
func (node ListNode) Setvalue(val int){
	node.val = val
}

func (node ListNode) Print(){
	fmt.Print(node.val)
}

func main() {
	root := &ListNode{val:1}    //这里root是一个指针对象
	root.Setvalue(2)
	root.Print()
}

上面的Setvalue的接收器类型是一个ListNode,因为GO中的函数都是值传递,所以在这里也是值传递。但是在外面,方法的调用者root确是一个指针,也就是说调用者其实是一个*ListNode,而不是ListNode。但是在这里还是调用成功的,编译器会进行自动转化,在接收的时候它其实拿到的是root指向的内容,而不是root这个地址。

1

但是因为是值接收器,所以这边对这个值进行改变,不会改变原来的root。

扫描二维码关注公众号,回复: 11388451 查看本文章

再来看下指针接收器

//定义一个单链表的节点
type ListNode struct {
	next *ListNode
	val int
}

//定义一个方法改变node的val,这里是指针接收器
func (node *ListNode) Setvalue(val int){
	node.val = val
}

func (node ListNode) Print(){
	fmt.Print(node.val)
}

func main() {
	root := ListNode{val:1}		//这里root是一个ListNode对象
	root.Setvalue(2)
	root.Print()
}

可以看到,我们将 Setvalue 方法的接收器改为了指针接收器,但是我们将root改为了一个ListNode的对象,而不是它的指针。但是其实这里还是会调用成功的,编译器会自动将其转化为指针接收。

2

这里就将原来root中的值给改变了,这就是值接收器与指针接收器的区别,关键在于可不可以改变原有变量的值。

总结一下,值接收器和指针接收器都可以接受值和指针传递。

那么两者何时使用呢?

  • 当值接收器代价昂贵时,比如这个结构体类型很大,这时可以采用指针接收器
  • 当方法需要改变原对象的值时使用指针接收器
  • 其余情况都可以使用值接收器
  • 为了一致性,如果有指针接收器,那么最好都是指针接收器

nil指针也可以调用方法

GO中的nil不像C++中的NULL,NULL是一个雷区,一个指针一旦是NULL,那么对它进行操作都将变为非法的。但是GO不一样,这一点在之后的学习中应该还会感觉到。

//定义一个单链表的节点
type ListNode struct {
	next *ListNode
	val int
}

//定义一个方法改变node的val,这里是指针接收器
func (node *ListNode) Setvalue(val int){
	if node == nil {
		fmt.Println("ignore")
		return
	}
	node.val = val
}

func main() {
	var root *ListNode
	root.Setvalue(1)
}

输出结果

ignore

所以对于一个nil指针,我们还是可以对它调用方法。


接收器不是结构体类型的方法

接收器还可以不是结构体类型的,还可以是别的类型。但是有一个前提,为了在一个类型上定义一个方法,方法的接收器类型定义和方法的定义应该在同一个包中。

比如,我们想将int设为接收器类型

func (num1 int) add(num2 int) int{
	return num1 + num2
}

func main() {
	num := 1
	fmt.Print(num.add(2))
}

程序会报错,因为add方法的定义和int方法的定义不在一个包中。

cannot define new methods on non-local type int

我们可以使用类型别名来解决下面的问题。

//定义一个新类型myInt,其实也就是int
type myInt int

func (num1 myInt) add(num2 myInt) myInt{
	return num1 + num2
}

func main() {
	num1 := myInt(1)
	num2 := myInt(2)
	fmt.Print(num1.add(num2))
}

输出结果

3

所以,通过这种方式,可以对普通的类型实现方法。

猜你喜欢

转载自blog.csdn.net/lvyibin890/article/details/83504473