5.5. 类型内嵌和结构体内嵌

5.5.1. 1.匿名字段

通过匿名字段,可获得和继承类似的复用能力。依据编译器查找次序,只需在外层定义同名方法,就可以实现 “override”。

简单来说就是实现类似Java中的方法的重载。

package main

import "fmt"

type User struct {
    id   int
    name string
}
type Manager struct {
    User
    title string
}

func (self *User) ToString() string {
    return fmt.Sprintf("User: %p, %v", self, self)
}
func (self *Manager) ToString() string {
    return fmt.Sprintf("Manager: %p, %v", self, self)
}
func main() {
    m := Manager{User{1, "Tom"}, "Administrator"}
    fmt.Println(m.ToString())       //Manager: 0xc000056330, &{{1 Tom} Administrator}
    fmt.Println(m.User.ToString())  //User: 0xc000056330, &{1 Tom}
}

结构体允许其成员字段在声明时没有字段名而只有类型,这种形式的字段被称为类型内嵌或匿名字段。

package main

import "fmt"

// 创建结构体
type Data struct {
    int
    float32
    bool
}

// 实例化结构体并赋初值
func main() {
    ins :=&Data{
        int:     10,
        float32: 3.14,
        bool:    true,
    }
    fmt.Println(ins.int)
    fmt.Println(ins.float32)
    fmt.Println(ins.bool)
}

5.5.2. 2.声明结构体内嵌

package main

import "fmt"

// 基础颜色
type BasicColor struct {
    // 颜色
    R, G, B float32
}

// 完整颜色
type Color struct {
    // 将基本颜色作为成员
    Basic BasicColor
    // 透明度
    Alpha float32
}

func main() {
    // 实例化Color结构体
    var c Color
    // 设置基本颜色分量
    c.Basic.R = 1
    c.Basic.G = 2
    c.Basic.B = 0
    // 设置透明度
    c.Alpha = 1
    // 显示整个结构体的内容
    fmt.Printf("%+v", c)            //{Basic:{R:1 G:2 B:0} Alpha:1}

}

使用Go语言的结构体内嵌写法重新调整代码如下:

package main

import "fmt"

// 基础颜色
type BasicColor struct {
    // 颜色
    R, G, B float32
}

// 完整颜色
type Color struct {
    // 将基本颜色作为成员
    BasicColor
    // 透明度
    Alpha float32
}

func main() {
    // 实例化Color结构体
    var c Color
    // 设置基本颜色分量
    c.R = 1
    c.B = 2
    c.G = 0
    // 设置透明度
    c.Alpha = 1
    // 显示整个结构体的内容
    fmt.Printf("%+v", c)            //{Basic:{R:1 G:2 B:0} Alpha:1}

}

5.5.3. 3.结构内嵌特性:

Go语言的结构体内嵌有如下特性。

1.内嵌的结构体可以直接访问其成员变量

例如:ins.a.b.c 的访问可以简化为 ins.C。
  1. 内嵌结构体的字段名是它的类型名

内嵌结构体字段仍然可以使用详细的字段进行一层层访问,内嵌结构体的字段名就是它的类型名,代码如下:

var c Color
c.BasicColor,R =1
c.BasicColor,G =1
c.BasicColor,B =1

5.5.4. 4.使用组合思想描述对象特性

面对对象的设计原则中,建议对象最好不要使用多重继承。Java和C#就默认禁止了。 组合特性可以快速构建对象的不同特性。比如:人和鸟的特性中,都有行走。

package main

import "fmt"

// 可飞行的,声明飞行结构
type Flying struct{}

// 指针接收器,为飞行结构添加Fly方法
func (f *Flying) Fly() {
    fmt.Println("can fly")
}

// 可行走的,声明行走结构
type Walkable struct{}

// 指针接收器,为行走结构添加Walk方法
func (w *Walkable) Walk() {
    fmt.Println("can calk")
}

// 声明人类结构
type Human struct {
    Walkable // 人类能行走
}

// 声明鸟类结构
type Bird struct {
    Flying
    Walkable // 鸟类既能飞行也能行走
}

func main() {
    // 实例化鸟类
    b := new(Bird)
    fmt.Println("Bird: ")
    // 调用鸟类的Fly()方法
    b.Fly()
    b.Walk()

    // 实例化人类
    h :=new(Human)
    fmt.Println("Human:")
    //调用人类的walk方法
    h.Walk()

}

5.5.5. 5.初始化结构体内嵌

结构体内嵌初始化时,将结构体内嵌的类型作为字段名像普通结构体一样进行初始化。

package main

import "fmt"

// 车轮
type Wheel struct {
    Size int
}

// 引擎
type Engine struct {
    Power int    // 功率
    Type  string // 类型
}

// 车
type Car struct {
    Wheel
    Engine
}

func main() {
    c := Car{
        // 初始化轮子
        Wheel: Wheel{
            Size: 18,
        },
        // 初始化引擎
        Engine: Engine{
            Power: 100,
            Type:  "1.4T",
        },
    }
    fmt.Printf("%+v\n", c)
}

//{Wheel:{Size:18} Engine:{Power:100 Type:1.4T}}

5.5.6. 6.初始化内嵌匿名结构体

package main

import "fmt"

// 车轮
type Wheel struct {
    Size int
}

// 车
type Car struct {
    Wheel
    // 引擎
    Engine struct {
        Power int    // 功率
        Type  string // 类型
    }
}

func main() {
    c := Car{
        // 初始化轮子
        Wheel: Wheel{
            Size: 18,
        },
        // 初始化引擎
        Engine: struct {
            Power int
            Type  string
        }{Power: 143, Type: "1.4T"},
    }
    fmt.Printf("%+v\n", c)
}

//{Wheel:{Size:18} Engine:{Power:100 Type:1.4T}}

原来的Engine结构体被直接定义在Car的结构体中,这种嵌入的写法就是将原来的结构体类型转换为struct{…}

对Car的Engine开始初始化的时候,由于Engine字段的类型没有被单独定义,因此在初始化其字段时需要先填写struct{…}声明其类型。

填充匿名结构体的数据,按”“键:值”格式填充。

6.1成员名字冲突

package main

import "fmt"

type A struct {
    a int
}

type B struct {
    a int
}

type C struct {
    A
    B
}

func main() {
    // 实例化c结构体
    c :=&C{}
    //c.a = 1       //会报错,因为A结构和B结构中都有一个相同的a。编译器无法区分
    c.A.a = 1
    c.B.a = 2
    fmt.Println(c)  //&{{1} {2}}

}

5.5.7. 7. 用外层结构体引用内嵌接口的实例

package main

import "fmt"

type Printer interface {
    Print()
}

type CanonPrinter struct {
    Printname string
}

func (printer CanonPrinter) Print() {
    fmt.Println("this is cannoprinter printing now")
}

type PrintWorker struct {
    Printer
    name string
    age int
}

func main() {
    canon := CanonPrinter{"canoprint_num1"}
    printworker := PrintWorker{
        Printer: canon,
        name:    "hujiangli",
        age:     26,
    }
    printworker.Print()
}

5.5.8. 8.示例:使用匿名结构体分离JSON数据

package main

import (
    "encoding/json"
    "fmt"
)

// 定义手机屏幕
type Screen struct {
    Size       float32 // 屏幕尺寸
    ResX, RexY int     // 屏幕水平分辨率
}

type Battery struct {
    Capacity int // 容量
}

/*生成json数据*/
func genJsonData() []byte {
    // 完整数据结构
    /*
        定义一个匿名结构体,内嵌了Screen和Battery结构体,同时加入了HasTouchID字段
    */
    raw := &struct {
        Screen
        Battery
        HasTouchID bool
    }{
        //屏幕参数, 为声明的匿名结构体填充数据
        Screen: Screen{
            Size: 6.6,
            ResX: 3,
            RexY: 2,
        },
        // 电池参数
        Battery: Battery{
            2020,
        },
        // 是否有指纹识别
        HasTouchID: true,
    }
    // 将数据序列化为JSON
    jsonData, _ := json.Marshal(raw)
    return jsonData
}

func main() {
    // 生成一段json数据
    jsonDatat := genJsonData()
    fmt.Println(string(jsonDatat))      //{"Size":6.6,"ResX":3,"RexY":2,"Capacity":2020,"HasTouchID":true}

    // 只要屏幕和指纹识别信息的结构和实例
    screenAndTOuch := struct {
        Screen
        HasTouchID bool
    }{}
    // 反序列到screenAndTOuch中
    json.Unmarshal(jsonDatat, &screenAndTOuch)
    // 输出screenAndTOuch的详细结构
    fmt.Printf("%+v\n", screenAndTOuch) //{Screen:{Size:6.6 ResX:3 RexY:2} HasTouchID:true}

    // 只需要电池和指纹的结构和实例
    batteryAndTOuch := struct {
        Battery
        HasTouchID bool
    }{}
    // 反序列到batteryAndTOuch中
    json.Unmarshal(jsonDatat, &batteryAndTOuch)
    // 输出screenAndTOuch的详细结构
    fmt.Printf("%+v\n", batteryAndTOuch)        //{Battery:{Capacity:2020} HasTouchID:true}
}