Go入门21:面向对象

method

func (r ReceiverType) funcName(parameters) (results)

带有接收者的函数,称为method。

method是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在func后面增加了一个receiver(也就是method所依从的主体)。

type Rectangle struct {

    width, height float64

}

type Circle struct {

    radius float64

}

func (r Rectangle) area() float64 {

    return r.width * r.height

}

func (c Circle) area() float64 {

    return c.radius * c.radius * math.Pi

}

func main() {

    r1 := Rectangle{12, 2}

    r2 := Rectangle{9, 4}

    c1 := Circle{10}

    c2 := Circle{25}

    fmt.Println("Area of r1 is: ", r1.area()) // 24

    fmt.Println("Area of r2 is: ", r2.area()) // 36

    fmt.Println("Area of c1 is: ", c1.area()) // 314.1592653589793

    fmt.Println("Area of c2 is: ", c2.area()) // 1963.4954084936207

}

从上面的例子中可以看到:

1)虽然method的名字一模一样,但是如果接收者不一样,那么method就不一样;

2)method里面可以访问接收者的字段;

3)调用method通过"."访问,就像struct里面访问字段一样;

上例图示如下:

不同struct的method不同

在上例,method area() 分别属于Rectangle和Circle,于是他们的 Receiver 就变成了Rectangle 和 Circle, 或者说,这个area()方法 是由 Rectangle/Circle 发出的。

需要说明的是,图示中method用虚线标出,意思是此处方法的Receiver是以值传递,而非引用传递。

Receiver还可以是指针, 两者的差别在于, 指针作为Receiver会对实例对象的内容发生操作,而普通类型作为Receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。

method的接收者类型

method的接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:在任何你自定义的类型、内置类型、struct等任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型。但是接收者不能是一个接口类型

const (

    WHITE = iota

    BLACK

    BLUE

    RED

    YELLOW

)

type Color byte    // 作为byte的别名

type Box struct {

    width, height, depth float64

    color                Color

}

type BoxList []Box // a slice of boxes

// 返回容量

func (b Box) Volume() float64 {

    return b.width * b.height * b.depth

}

// 修改颜色为c

func (b *Box) SetColor(c Color) {

    b.color = c

}

// 返回容量最大的颜色

func (bl BoxList) BiggestColor() Color {

    v := 0.0

    k := Color(WHITE)

    for _, b := range bl {

        if bv := b.Volume(); bv > v {

            v = bv

            k = b.color

        }

    }

    return k

}

// 把颜色涂为黑色

func (bl BoxList) PaintItBlack() {

    for i := range bl {

        bl[i].SetColor(BLACK)

    }

}

// 返回Color的具体颜色(字符串格式)

func (c Color) String() string {

    strings := []string{"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}

    return strings[c]

}

func main() {

    boxes := BoxList{

        Box{4, 4, 4, RED},

        Box{10, 10, 1, YELLOW},

        Box{1, 1, 20, BLACK},

        Box{10, 10, 1, BLUE},

        Box{10, 30, 1, WHITE},

        Box{20, 20, 20, YELLOW},

    }

    fmt.Println("boxes length: ", len(boxes))                      // 6

    fmt.Println("first volume: ", boxes[0].Volume())                // 64

    fmt.Println("last color: ", boxes[len(boxes)-1].color.String()) // YELLOW

    fmt.Println("biggest color: ", boxes.BiggestColor().String())  // YELLOW

    boxes.PaintItBlack()

    fmt.Println("second color:", boxes[1].color.String())        // BLACK

    fmt.Println("biggest color:", boxes.BiggestColor().String()) // BLACK

}

从上例中,通过const定义了一些常量,定义了一些自定义类型

1)Color作为Byte的别名;

2)定义了一个struct:Box,含有三个长宽高字段和一个颜色属性;

3)定义了一个slice:BoxList,含有Box;

然后以上面的自定义类型为接收者定义了一些method。

注意: Go语言不允许为简单的内置类型添加方法,所以下面定义的方法是非法的。

func Add(a ,b int){        //函数合法

  fmt.Println(a+b)

}

func (a int) Add (b int){    //方法非法!不能是内置数据类型

  fmt.Println(a+b)

}

这个时候我们需要用Go语言的type,来临时定义一个和int具有同样功能的类型。这个类型不能看成是int类型的别名,它们属于不同的类型,不能直接相互赋值。

修改后如下:

type myInt int

func Add(a ,b int){            //函数

  fmt.Println(a+b)

}

func (a myInt) Add (b myInt){  //方法

  fmt.Println(a+b)

}

指针作为receiver

上例中SetColor这个method,它的receiver是一个指向Box的指针,你可以使用*Box。

想想为啥要使用指针而不是Box本身呢?

定义SetColor的真正目的是想改变这个Box的颜色,如果不传Box的指针,那么SetColor接受的其实是Box的一个copy,也就是说method内对于颜色值的修改,其实只作用于Box的copy,而不是真正的Box,所以我们需要传入指针。

结合上面的例子:

问题1:SetColor函数里面应该这样定义*b.Color=c,而不是b.Color=c,因为我们需要读取到指针相应的值?

回答1:其实Go里面这两种方式都是正确的,当你用指针去访问相应的字段时(虽然指针没有任何的字段),Go知道你要通过指针去获取这个值。

问题2:PaintItBlack函数里面调用SetColor的时候是不是应该写成(&bl[i]).SetColor(BLACK),因为SetColor的receiver是*Box,而不是Box?

回答2:这两种方式都可以,因为Go知道receiver是指针,他自动帮你转了。

总结来说:

1)如果一个method的receiver是*T,你可以在一个T类型的实例变量V上面调用这个method,而不需要&V去调用这个method;

2)如果一个method的receiver是T,你可以在一个*T类型的变量P上面调用这个method,而不需要 *P去调用这个method;

method继承

method也是可以继承的。如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method。

type Human struct {

    name  string

    age  int

    phone string

}

type Student struct {

    Human  // 匿名字段

    school string

}

type Employee struct {

    Human  // 匿名字段

    company string

}

// 在human上面定义了一个method

func (h *Human) SayHi() {

    fmt.Printf("name:%s, phone:%s\n", h.name, h.phone)

}

func main() {

    mark := Student{Human{"A", 23, "222-222"}, "MIT"}

    sam := Employee{Human{"B", 22, "333-333"}, "Inc"}

    mark.SayHi()

    sam.SayHi()

}

method重写

如果在上例中,Employee想要实现自己的SayHi,怎么办?

和匿名字段冲突一样的道理,我们可以在Employee上面定义一个method,重写了匿名字段的方法。

type Human struct {

    name  string

    age  int

    phone string

}

type Student struct {

    Human  // 匿名字段

    school string

}

type Employee struct {

    Human  // 匿名字段

    company string

}

// 在human上面定义了一个method

func (h *Human) SayHi() {

    fmt.Printf("human name:%s, phone:%s\n", h.name, h.phone)

}

// 在employee的method重写Human的method

func (e *Employee) SayHi() {

    fmt.Printf("employee name:%s, phone:%s, company:%s\n", e.name, e.phone, e.company)

}

func main() {

    mark := Student{Human{"A", 23, "222-222"}, "MIT"}

    sam := Employee{Human{"B", 22, "333-333"}, "Inc"}

    mark.SayHi()

    sam.SayHi()

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容