结构体和方法使用

package main

import "fmt"

// 示例1。
// AnimalCategory 代表动物分类学中的基本分类法。
type AnimalCategory struct {
    kingdom string // 界。
    phylum  string // 门。
    class   string // 纲。
    order   string // 目。
    family  string // 科。
    genus   string // 属。
    species string // 种。
}

//这里绑定,这个String方法不需要任何参数声明,但需要有一个string类型的结果声明。我在调用fmt.Printf函数时,使用占位符%s和category值本身就可以打印出后者的字符串表示形式,而无需显式地调用它的String方法。
func (ac AnimalCategory) String() string {
    return fmt.Sprintf("%s%s%s%s%s%s%s",
        ac.kingdom, ac.phylum, ac.class, ac.order,
        ac.family, ac.genus, ac.species)
}

// 示例2。
type Animal struct {
    scientificName string // 学名。
    AnimalCategory        // 动物基本分类。这是一个嵌入字段
}

// 该方法会"屏蔽"掉嵌入字段中的同名方法。
// func (a Animal) String() string {
//  return fmt.Sprintf("%s (category: %s)", //Sprintf输出格式化的字符串
//      a.scientificName, a.AnimalCategory)
// }

// 示例3。
type Cat struct {
    name string
    Animal
}

// 该方法会"屏蔽"掉嵌入字段中的同名方法。
// func (cat Cat) String() string {
//  return fmt.Sprintf("%s (category: %s, name: %q)",
//      cat.scientificName, cat.Animal.AnimalCategory, cat.name)
// }

func main() {
    // 示例1。species字段指定了字符串值"cat",fmt.Printf函数会自己去寻找它。此时的打印内容会是The animal category: cat。
    category := AnimalCategory{species: "cat"}
    fmt.Printf("The animal category: %s\n", category)

    // 示例2。

    animal := Animal{
        scientificName: "American Shorthair",
        AnimalCategory: category,
    }
    fmt.Printf("The animal: %s\n", animal)

    // 示例3。
    cat := Cat{
        name:   "little pig",
        Animal: animal,
    }
    fmt.Printf("The cat: %s\n", cat)
}
go run demo29.go 
The animal category: cat
The animal: cat
The cat: cat

这里为什么打印三个cat呢?

因为: cat的string()方法: func (cat Cat) String() string 和Animal的string方法: func (a Animal) String() string 都被注释掉了,执行脚本的时候,当示例2和示例3调用fmt.Printf()方法,就只能使用func (ac AnimalCategory) String() string 方法打印7个字段了。如果不注释,后边的string方法会覆盖前面的string方法,打印结果发生变化。

package main

import "fmt"

// 示例1。
// AnimalCategory 代表动物分类学中的基本分类法。
type AnimalCategory struct {
    kingdom string // 界。
    phylum  string // 门。
    class   string // 纲。
    order   string // 目。
    family  string // 科。
    genus   string // 属。
    species string // 种。
}

//这里绑定,这个String方法不需要任何参数声明,但需要有一个string类型的结果声明。我在调用fmt.Printf函数时,使用占位符%s和category值本身就可以打印出后者的字符串表示形式,而无需显式地调用它的String方法。
func (ac AnimalCategory) String() string {
    return fmt.Sprintf("%s%s%s%s%s%s%s",
        ac.kingdom, ac.phylum, ac.class, ac.order,
        ac.family, ac.genus, ac.species)
}

// 示例2。
type Animal struct {
    scientificName string // 学名。
    AnimalCategory        // 动物基本分类。这是一个嵌入字段
}

// 该方法会"屏蔽"掉嵌入字段中的同名方法。
func (a Animal) String() string {
    return fmt.Sprintf("%s (category: %s)", //Sprintf输出格式化的字符串
        a.scientificName, a.AnimalCategory)
}

// 示例3。
type Cat struct {
    name string
    Animal
}

// 该方法会"屏蔽"掉嵌入字段中的同名方法。
func (cat Cat) String() string {
    return fmt.Sprintf("%s (category: %s, name: %q)",
        cat.scientificName, cat.Animal.AnimalCategory, cat.name)
}

func main() {
    // 示例1。species字段指定了字符串值"cat",fmt.Printf函数会自己去寻找它。此时的打印内容会是The animal category: cat。
    category := AnimalCategory{species: "cat"}
    fmt.Printf("The animal category: %s\n", category)

    // 示例2。
    //使用fmt.Printf函数和%s占位符试图打印animal的字符串表示形式,相当于调用animal的String方法。
    //虽然我们还没有为Animal类型编写String方法,但这样做是没问题的。
    //因为在这里,嵌入字段AnimalCategory的String方法会被当做animal的方法调用。
    //那如果我也为Animal类型编写一个String方法呢?这里会调用哪一个呢?
    //animal的String方法会被调用。这时,我们说,嵌入字段AnimalCategory的String方法被“屏蔽”了。注意,只要名称相同,无论这两个方法的签名是否一致,被嵌入类型的方法都会“屏蔽”掉嵌入字段的同名方法。
    animal := Animal{
        scientificName: "American Shorthair",
        AnimalCategory: category,
    }
    fmt.Printf("The animal: %s\n", animal)

    // 示例3。
    cat := Cat{
        name:   "little pig",
        Animal: animal,
    }
    fmt.Printf("The cat: %s\n", cat)
}
daixuandeMacBook-Pro:q0 daixuan$ go run demo29.go 
The animal category: cat
The animal: American Shorthair (category: cat)
The cat: American Shorthair (category: cat, name: "little pig")

一个数据类型关联的所有方法,共同组成了该类型的方法集合。同一个方法集合中的方法不能出现重名。并且,如果它们所属的是一个结构体类型,那么它们的名称与该类型中任何字段的名称也不能重复。

我们可以把结构体类型中的一个字段看作是它的一个属性或者一项数据,再把隶属于它的一个方法看作是附加在其中数据之上的一个能力或者一项操作。将属性及其能力(或者说数据及其操作)封装在一起,是面向对象编程

我声明了一个结构体类型,名叫Animal。它有两个字段。一个是string类型的字段scientificName,代表了动物的学名。而另一个字段声明中只有AnimalCategory,它正是我在前面编写的那个结构体类型的名字。这是什么意思呢?

问题是:Animal类型中的字段声明AnimalCategory代表了什么?

字段声明AnimalCategory代表了Animal类型的一个嵌入字段。Go 语言规范规定,如果一个字段的声明中只有字段的类型名而没有字段的名称,那么它就是一个嵌入字段,也可以被称为匿名字段。我们可以通过此类型变量的名称后跟“.”,再后跟嵌入字段类型的方式引用到该字段。也就是说,嵌入字段的类型既是类型也是名称。

说到引用结构体的嵌入字段,Animal类型有个方法叫Category,它是这么写的:

func (a Animal) Category() string {
return a.AnimalCategory.String()}

Category方法的接收者类型是Animal,接收者名称是a。在该方法中,我通过表达式a.AnimalCategory选择到了a的这个嵌入字段,然后又选择了该字段的String方法并调用了它。

顺便提一下,在某个代表变量的标识符的右边加“.”,再加上字段名或方法名的表达式被称为选择表达式,它用来表示选择了该变量的某个字段或者方法。

猜你喜欢

转载自blog.51cto.com/daixuan/2458688