讲完了结构体,如果要为这个结构体对象设计一些实用的方法,比如对于单链表,有单链表的遍历和反转等,这些方法是如何来定义的呢?
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。
再来看下指针接收器
//定义一个单链表的节点 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中的值给改变了,这就是值接收器与指针接收器的区别,关键在于可不可以改变原有变量的值。
总结一下,值接收器和指针接收器都可以接受值和指针传递。
那么两者何时使用呢?
当值接收器代价昂贵时,比如这个结构体类型很大,这时可以采用指针接收器当方法需要改变原对象的值时使用指针接收器其余情况都可以使用值接收器为了一致性,如果有指针接收器,那么最好都是指针接收器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所以,通过这种方式,可以对普通的类型实现方法。