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()

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容