go中的面向对象基础

目前接触到的语言层面上提供面向对象的实现,主要有三个基础:

type、method、interface

type:

go中没有java或c#的类(class),但有一个同等地位的type。用户可通过type关键字自定义需要的数据结构类型。

//自定义基本类型
type MyFloat float64
//自定义复合类型
type Vertex struct{
    X,Y int
    M,N string
}
...
var cInt = MyInt(64)
fmt.Println(cInt)
-->64
var cVertex = Vertex{1,2,"x","y"}
fmt.Println(cVertex)
-->{1 2 x y}

method:

当然,仅仅提供组织数据结构的能力,谈不上面向对象。go中所有通过type关键字定义的类型,都可以定义方法。

//自定义复合类型
type Vertex struct{
    X,Y int
    M,N string
}
//定义getX方法,返回实例的X,此时形参接收的是一个值
func (v Vertex) getX() int{
    return v.X
}
//定义getX方法,返回实例的X,此时形参接收的是一个指针
func (v *Vertex) getX() int{
    return v.X
}
...
fmt.Println(cVertex.getX())
-->1

其语法和一般的函数定义很类似,只是在func方法名之间多了(v Vertex),即表示type主体的形参名及其类型,文档称之为receiver。如上,第一个getX方法的receiver称为value receiver,第二个称为getX方法的receiver称为pointer receiver。

这里要注意的是,调用一般函数时,传入的参数必须和形参一致:

//函数需要传入值
func getX_v(v Vertex){
    return v.X
}
//函数需要传入指针
func getX_p(v *Vertex){
    return v.X
} 
...
var myVertex = Vertex{1,2,"x","y"}
fmt.Println(getX_v(myVertex))  //ok
fmt.Println(getX_p(myVertex))  //报错,因为传入的参数必须是指针

但是在类型的methods中,go会自动做一些处理,

//receiver 是个value
func (v Vertex) getX_v() int{
   return v.X
}
//receiver 是个pointer
func (v *Vertex) getX_p() int{
   return v.X   
}
...
fmt.Println(myVertex.getX_v())  //ok
fmt.Println(myVertex.getX_p())  //ok
fmt.Println((&myVertex).getX_v())  //ok
fmt.Println((&myVertex).getX_p())  //ok

1.在调用方法时,以Vertex直接调用method(第一二种情况),以Vertex的指针调用method(第三四种情况),是等效的。

2.当方法被调用时,如果不涉及修改Vertex自身的值,无论receiver是value还是pointer,执行结果也是等效的。

  上面代码中,执行myVertex.getX_p时,实际执行的是return (&v).X

 上面代码中,执行(&myVertex).getX_v时,实际执行的是return (*v).X

3.在更多时候,给type添加方法时,我们往往使用的是pointer receiver,有两个原因:

    一是pointer receiver是个指针,我们可以在方法中借之修改结构体自身的值。

    二是value receiver得到的是结构体的一份拷贝,不能修改结构体自身的值,而且当结构体比较大而我们又不需要修改自身值时时,拷贝操作就有点低效了。

组合:

面向对象的一个有力特性是可以通过继承来复用一些函数和属性。go中的继承是通过类型的组合来实现的:

package main
import "fmt"
//定义了Base类型
type Base struct{
  X,Y int
}
//Base类型添加方法
func (v Base) showMessage(){
	fmt.Println("hello world")
}
//定义Super类,内部组合有Base类型
type Super struct{
  Base
}

func main() {
	var mySuper = Super{}
	mySuper.showMessage() //ok
-->hello world
}

interface:

go中有一种特殊类型interface,是一系列方法的签名。interface变量自身并不实现这些方法,当一个类型提供了interface中所有方法的实现时,我们就说它满足这个interface。

//定义接口
type People interface{
  call()
}
//定义某个类型
type Vertex struct{
  X,Y int
}
//给Vertex添加call方法
func (v Vertex) call(){
    fmt.Println("my name is xxx")
}

func main(){
//定义一个People变量,被赋予一个Vertex实例
  var a People = Vertex{1,2}
  a.call()
-->my name is xxx
}

和java、c#中的接口不太一样。java、c#中的接口,需要被类继承,并在类中实现这些方法,然后通过调用类的方法来使用。而go中的接口,虽然也是只提供方法签名,也是在类型中实现这些方法,但是需要把满足接口的类型的实例赋予interface变量,然后通过interface.method来调用。

而接口的底层,实际上可看作一个值及其类型(value  ,  type)。value是赋予接口的具体实例,type是这个示例的类型。上边代码中接口a就是一个 ({1,2} , main.People)

一个值的注意的地方是,调用interface变量的方法时,是区分value receiver和pointer receiver的。

//定义接口
type Abser interface {
	Abs() float64
}
//定义Vertex类型
type Vertex struct {
	X, Y float64
}
//实现Abs方法,此时receiver是pointer receiver
func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    var a Abser
    var v = Vertex{3, 4}

    a = &v 
    fmt.Println(a.Abs())  //不会报错,因为v *Vertex并实现了Abs()
    a = v
    fmt.Println(a.Abs())  //会报错,因为v Vertex并没有实现Abs()
}

go中interface的好处是:

1.方法的签名和实现解耦,interface甚至不关心方法的receiver是value还是pointer。如何实现由不同的类型自己决定,。

2.interface变量可以被复用,只要是实现了接口的类型,都可以被赋予该interface变量,这就很容易实现多态。

接口赋值:

接口赋值由两种情况,

第一种是将满足interface的类型的实例直接赋予interface变量。

第二种是将接口赋予另一个接口。

第一种情况有个需要注意的地方是,如果接口中的某个方法在实现时用的是pointer receiver,赋值的时候要用指针赋值:

package main
import "fmt"
//自定义类型和接口
type Integer int
type LessAdd interface{
	Less(b Integer) bool
	Add(b Integer)
}
//给Integer类型添加实现
func (i Integer) Less(b Integer) bool{
	if i<b {
		return true
	}else{
	    return false
	}
}
func (i *Integer) Add(b Integer){
    *i += b
}

func main() {
    var i Integer = 10
    var b Integer = 11
	var inf LessAdd
	fmt.Println(i.Less(b))
	i.Add(b)
	fmt.Println(i)
	inf = i
}

上面代码中,将类型Integer的实例赋予interface变量inf时,go编译报错:

./compile387.go:29:6: cannot use i (type Integer) as type LessAdd in assignment:
	Integer does not implement LessAdd (Add method has pointer receiver)

这是因为Integer在实现接口方法时,Add方法用了pointer receiver,而此时interface变量接收的是一个值i而不是指针&i。结果就是interface中的两个方法,Less满足了,但Add不满足。

那为什么改为:

	inf = &i

就不报错了呢?这是因为传入&i这个类型指针作为接口方法的receiver时,go可以根据函数

func (i Integer) Less(b Integer) bool{

生成函数

func (i *Integer) Less(b Integer) bool{

此时,Less函数和Add函数都有了,而且这两个函数的receiver都是指针。

但是反过来,go没办法根据

func (i *Integer) Add(b Integer){...}

生成

func (i Integer) Add(b Integer){...}

更何况,如果Add方法的内部实现需要修改指针所指内存的值,receiver由pointer改为value的话,就无法实现值的修改,这也不符合逻辑。

接口赋值的第二种情况,是接口给另一个接口赋值。比如:

interfaceA = interfaceB

此时要求,interfaceA的接口和interfaceB的接口有相同的方法列表。

或者interfaceB的方法列表是interfaceA的超集。

猜你喜欢

转载自blog.csdn.net/weixin_36094484/article/details/82152222
今日推荐