zoukankan      html  css  js  c++  java
  • GO语言--第6章 结构体

     

    第6章 结构体(struct)

    Go语言通过自定义的方式形成新的类型

    结构体是类型中带有成员的复合类型,使用结构体和结构体成员描述真实世界的实体和实体对应的各种属性

    Go 语言中类型可以被实例化,使用new&构造的类型实例的类型是类型的指针

    结构体成员:一系列成员变量构成,成员变量也称“字段”

    字段特性:

    • 字段拥有自己的类型和值

    • 字段名必须唯一

    • 字段的类型也可以是结构体,甚至是字段所在结构体的类型

    注意:

    1. Go语言没有“类”的概念,也不支持“类”的继承等面向对象的概念

    2. Go 语言结构体与“类“都是符合结构体,但是结构体的内嵌配合接口比面向对象具有更高的扩展性和灵活性

    3. Go 语言不仅结构体能拥有方法,且每种自定义类型也可以拥有自己的方法

    6.1 定义结构体

    结构体定义只是一种内存布局的描述

    使用关键字type定义各种基本类型,整型、字符串、布尔等

    结构体定义格式:

    type 类型名 struct {
    字段1  字段1类型
    字段2 字段2类型
    ...
    }
    • 类型名:结构体名称标示名称,同一个包内不能重复

    • struct{}: 表示结构体类型

    • 字段1、字段2: 结构体内字段名称必须唯一

    例子:

    type Point struct{
    x int
     y int
    }

    同类型变量可以同一行

    type color struct{
     R, G, B byte
    }

    6.2 实例结构体-分配内存并初始化

    只有当结构体实例化时,才会真实分配内存

    结构体实例与实例之间的内存是完成独立的

    实例化方式

    1. 基本实例化方式 -- var ins T

    2. 创建指针类型的结构体 --new 关键字

    3. 取结构体的地址实例化 -- &

    6.2.1 基本实例化方式

    基本实例化格式:

    var ins T

    例子:

    type point struct {
     x int
     y int
    }

    var p Point
    p.x = 10

    使用“.”访问结构体体成员如p.x

    6.2.2 创建指针类型的结构体

    使用new 关键字进行实例化

    基本格式:

    ins := new(T)

    T:类型,如结构体、整型、字符串等

    ins:保存T类型被实例化的实例到该变量中国呢,如果T类型是*T,是属于指针类型

    例子:

      type Player struct {
    Name string
    HealthPoint int
    MagicPoint int
    }

    tank := new(Player)
    tank.Name = "Cannon"
    tank.HealthPoint = 300

    fmt.Println(tank)

    执行结果:

    &{Cannon 300 0}

    可以看出tank 是new(Player)的实例, 那么我们直接通知&方式取址也是可以进行结构体的创建的

    6.2.3 取结构体的地址实例化

    取址格式:

    ins := &T{}

    Ins : 结构体实例,类型是*T,是指针类型

    package main

    import (
    "fmt"
    )
    // 3. 取址实例化
    type Command struct {
    Name string
    Var *int
    Comment string
    }

    // 取址实例化函数
    func newCommand(name string, varref *int, comment string) *Command {
    return &Command{
    Name: name,
    Var: varref,
    Comment: comment,
    }
    }

    func main() {

    // 三种实例化方式
    // 第一种: 声明变量方式
    type Point struct {
    x int
    y int
    }
    var p Point
    p.x = 10
    p.y = 10

    fmt.Println(p);

    // 2. new关键字 实例化
    type Player struct {
    Name string
    HealthPoint int
    MagicPoint int
    }

    tank := new(Player)
    tank.Name = "Cannon"
    tank.HealthPoint = 300

    fmt.Println(tank)

    // 3. 取址实例化
    var version int = 1

    cmd := &Command{}
    cmd.Name = "version"
    cmd.Var = &version
    cmd.Comment = "shwo version"

    fmt.Println(cmd)
    //&{version 0xc000090000 shwo version}

    cmd2 := newCommand(
    "version",
    &version,
    "show version",
    )
    fmt.Println(cmd2)
    //&{version 0xc000096018 show version}
    }

    6.3 初始化结构体成员变量

    初始化两种方式:

    1. 一种“键值对”形式

    2. 多个值对列表形式

     

    6.3.1 使用“键值对”初始化

    1. 键值对初始化结构体的书写形式

      ins := 结构体类型名 {
      字段1: 字段1的值,
      字段2: 字段2的值,
      ...
      }

      键值之间以":"分隔,键值之间以“,”分隔,最后一个字段后面也要家逗号

    2. 使用键值对填充结构体的例子

      type People struct {
       name string
       child *People
      }

      relation := &People {
       name: "jihan",
       child: &People{
         name: "small yi"
         child: &People{
           name: "self"
        }
      }
      }

    6.3.2 使用多个值的列表初始化

    1. 多个值列表形式:

      ins := 结构体类型名{
       字段1的值,
       字段2的值,
       ...
      }

      注意:

      • 必须初始化所以字段

      • 每个初始值填充顺序与字段在结构体中声明顺序一致

      • 键值对与值列表的初始化形式不能混用

    2. 多个值列表初始化例子

    package main

    import (
    "fmt"
    )
    // 多个列表实例化
    type Address struct {
    Province string
    City string
    ZipCode int
    PhoneNuber string
    }

    func main() {
    addr := Address {
    "广东",
    "广州",
    610000,
    "0",
    }
    fmt.Println(addr)
    //{广东 广州 610000 0}
    }

    6.3.3 匿名结构体初始化

    匿名结构体:没有类型名称,无须通过type 关键字定义

    1. 定义及初始化形式:

      ins := struct {
       //匿名结构体字段定义
        字段1 字段1类型
        字段2 字段2类型
       ...
      }{
       //字段初始化
       初始化字段1: 字段1的值,
       初始化字段2: 字段2的值,
       ...
      }

      // 也可以不初始化成员
      ins := struct {
       //匿名结构体字段定义
        字段1 字段1类型
        字段2 字段2类型
       ...
      }{ }

      注意:

      • 必须初始化所以字段

      • 每个初始值填充顺序与字段在结构体中声明顺序一致

      • 键值对与值列表的初始化形式不能混用

    1. 初始化例子

    package main

    import (
    "fmt"
    )
    // 打印消息类型,传入匿名结构体
    func printMsgType (msg *struct {
    id int
    data string
    }) {
    fmt.Printf("%T ", msg)
    }

    func main() {
    // 实例化结构体
    msg := &struct {
    id int
    data string
    } {
    1024,
    "hello",
    }

    printMsgType(msg)
    // *struct { id int; data string }
    }

    6.4 构造函数 -- 结构体和类型的一系列初始化操作的函数封装

    go语言结构体没有构造函数功能 结构体初始化使用函数封装实现

    6.4.1 多种方式创建与初始化结构体 -- 模拟函数重载

    package main

    import (
    "fmt"
    )
    // 定义猫结构体
    type Cat struct {
    Color string
    Name string
    }
     
    // 声明以名字初始化猫的函数
    func NewCatByName(name string) *Cat {
    return &Cat {
    Name: name,
    }
    }
    //   声明以颜色初始化猫的函数
    func NewCatByColor(color string) *Cat{
    return &Cat {
    Color: color,
    }
    }

    func main() {
    // 实例化结构体

    fmt.Println(NewCatByColor("blue"))
    // &{blue }
    fmt.Println(NewCatByName("jihan"))
    // &{ jihan}
    // *struct { id int; data string }
    }

    6.4.1 带父子关系的创建与初始化结构体 -- 模拟父级构造调用

    package main

    import (
    "fmt"
    )
    // 定义猫结构体
    type Cat struct {
    Color string
    Name string
    }

    // 定义黑猫结构体
    type BlockCat  struct {
    Cat //嵌入Cat ,类似派生
    }
     
    // 构造基类
    func NewCat(name string) *Cat {
    return &Cat {
    Name: name,
    }
    }
    // 构造子类
    func NewBlockCat(color string) *BlockCat{
    // 无法实例化,获取不到Color属性
    // cat := &BlockCat {
    // Color: color,
    // }

    cat := &BlockCat{}
    cat.Color = color
    return cat
    }

    func main() {
    // 实例化结构体

    fmt.Println(NewBlockCat("blue"))
    // &{blue }
    }

    6.5 方法

    Go语言中方法(method): 是一种特定类型变量的函数,也称为接收器(Revicer)

    接收器的类型:任何类型

    6.5.1 结构体添加方法

    两种方式:

    1. 面向过程方法

    2. GO语言的结构体方法

      • 每个方法只能有一个接收器

    例子:

    1. 面向过程例子

    package main
    import(
    "fmt"
    )
    // 定义猫结构体
    type Bag struct {
    items []int
    }
    // 将一个物品繁缛到背包中到过程
    func Insert(b *Bag, itemid int) {
    b.items = append(b.items, itemid)
    }

    func main() {
    // 实例化结构体
    bag := new(Bag)

    Insert(bag, 1001)
    fmt.Println(bag.items)
    // [1001]
    }
    1. 结构体方法

    package main
    import(
    "fmt"
    )
    // 定义猫结构体
    type Bag struct {
    items []int
    }
    // (b *Bag) 表示接收器,即Insert作用的对象实例
    func (b *Bag) Insert(itemid int) {
    b.items = append(b.items, itemid)
    }

    func main() {
    // 实例化结构体
    bag := new(Bag)
    bag.Insert(1001)

    fmt.Println(bag.items)
    // [1001]  
    }

    6.5.2 接收器-- 方法作用的目标

    格式:

    func (接收器变量 接收器类型) 方法名(参数列表)(返回参数){
     函数体
    }
    • 接收器变量: 接收器命名,官方推荐使用接收器类型的第一个小写字母

    • 接收器类型:可以指针类型和非指针类型

    • 方法名、参数列表、返回参数:格式与函数的一致

    1. 理解指针类型接收器

      指针类型的接收器:由一个结构体的指针组成,接近与面向对象中this或者self

    package main
    import(
    "fmt"
    )
    // 定义属性结构
    type Property struct {
    value int
    }
    // 设置属性值
    func (p *Property) setValue(v int) {
    p.value = v
    }

    // 取属性值
    func (p *Property) Value() int {
    return p.value
    }

    func main() {
    // 实例化结构体
    p := new(Property)
    p.setValue(1)

    fmt.Println(p.Value())
    // 1  
    }
    1. 理解非指针类型非接收器

      方法作用与非指针类型时,go语言会在代码运行时将接收器值复制一份

    package main
    import(
    "fmt"
    )
    // 定义点结构
    type Point struct {
    X int
    Y int
    }
    // 非指针类型接收器方法
    func (p Point) Add(other Point) Point {
    return Point{p.X + other.X, p.Y + other.Y}
    }

    func main() {
    // 实例化结构体
    p1 := Point{1, 1}
    p2 := Point{2, 2}
    res := p1.Add(p2)

    fmt.Println(res)
    // {3 3}
    }

    小结:

    小对象复制比较快,使用非指针类型方法

    大对象适合使用指针接收器方法

    6.5.3 示例:二维矢量模拟游戏玩家移动

    playemove/vec.go

    package main
    import(
    "math"
    )
    // 定义属性结构
    type Vec2 struct {
    X,Y float32
    }
    // 矢量相加
    func (v Vec2) Add(other Vec2) Vec2 {
    return Vec2{v.X + other.X, v.Y + other.Y}
    }
    // 矢量减
    func (v Vec2) Sub(other Vec2) Vec2 {
    return Vec2{v.X - other.X, v.Y - other.Y}
    }
    // 矢量乘
    func (v Vec2) Scale(s float32) Vec2 {
    return Vec2{v.X * s , v.Y * s}
    }
    // 计算矢量之间距离
    func (v Vec2) DistanTo(other Vec2) float32 {
    dx := v.X - other.X
    dy := v.Y - other.Y
    return float32(math.Sqrt(float64(dx*dx + dy*dy)))
    }
    // 返回当前矢量标准化矢量
    func (v Vec2) Normalize() Vec2 {
    mag := v.X*v.X + v.Y*v.Y
    if mag > 0 {
    oneOverMag := 1 / float32(math.Sqrt(float64(mag)))
    return Vec2{v.X * oneOverMag, v.Y * oneOverMag}
    }
    return Vec2{0, 0}
    }

    playmove/player.go

    package main

    // 定义玩家结构体
    type Player struct {
    currPos Vec2 //当前位置
    targetPos Vec2 //目标位置
    speed float32 //移动速度
    }
    // 设置玩家移动目标位置
    func (p *Player) MoveTo(v Vec2) {
    p.targetPos = v
    }
    // 获取当前位置
    func (p *Player) Pos() Vec2 {
    return p.currPos
    }
    // 判断是否到达目的地
    func (p *Player) IsArrived() bool {
    //计算当前玩家位置与目标位置地距离不超过移动步长,判断已到达目标
    return p.currPos.DistanTo(p.targetPos) < p.speed
    }
    // 更新玩家位置
    func (p *Player) Update() {
    if !p.IsArrived() {
    // 计算当前位置指向目标地朝向
    dir := p.targetPos.Sub(p.currPos).Normalize()
    //添加速度矢量生成新地位置
    newPos := p.currPos.Add(dir.Scale(p.speed))
    //移动完成后,更新当前位置
    p.currPos = newPos
    }
    }
    // 创建新玩家
    func NewPlayer(speed float32) *Player {
    return &Player{
    speed: speed,
    }
    }

    playermove/main.go

    package main
    import "fmt"
    func main() {
    // 实例化玩家对象,并设速度为0.5
    p := NewPlayer(0.5)
    // 玩家移动到3,1点
    p.MoveTo(Vec2{12,1})
    //没有达到就一致循环
    if !p.IsArrived() {
    //更新玩家位置
    p.Update()
    //打印每次移动后玩家点位置
    fmt.Println(p.Pos())
    }
    }

    6.5.4 为类型添加方法

    go语言可以对任何类型添加方法

    1. 为基本类型添加方法

      package main
      import(
      "fmt"
      )
      //int定义为MyInt类型
      type MyInt int

      //添加IsZero()方法
      func (m MyInt) IsZero() bool {
      return m == 0
      }
      //MyInt添加Add()方法
      func (m MyInt) Add(other int) int {
      return other + int(m)
      }
      func main() {
      var b MyInt
      fmt.Println(b.IsZero())
       //true
      b = 1
      fmt.Println(b.Add(2))
      //3
      }
    2. http包中类型方法

    package main
    import (
    "fmt"
    "net/http"
    "io/ioutil"
    "os"
    "strings"
    )
    func main() {
    client := &http.Client{}
    //创建请求
    req, err := http.NewRequest("POST", "http://www.163.com/", strings.NewReader("key=vlue"))
    //错误处理
    if err != nil {
    fmt.Println(err)
    os.Exit(1)
    return
    }

     //为标头添加信息
     req.Header.Add("User-Agent", "myClient")
     //开始请求
     resp, err := client.Do(req)

     if err != nil {
    fmt.Println(err)
    os.Exit(1)
    return
    }

    //读取服务器返回对内容
    data, err := ioutil.ReadAll(resp.Body)
    fmt.Println(string(data))
    defer resp.Body.Close()
    }

    //结果:
    //<html>
    //<head><title>405 Not Allowed</title></head>
    //<body bgcolor="white">
    //<center><h1>405 Not Allowed</h1></center>
    //<hr><center>nginx</center>
    //</body>
    //</html>

    6.5.5 示例:事件系统响应和处理

    1. 方法和函数的统一调用

    package main

    import "fmt"

    //声明结构体
    type class struct {

    }
    //添加Do()方法
    func (c *class) Do(v int) {
    fmt.Println("call method do:", v)
    }
    //funcDo()
    func funcDo(v int) {
    fmt.Println("call function do:", v)
    }
    func main() {
    //声明函数回调
    var delegate func(int)
    //创建结构体实例
    c := new(class)
    //将回调设为c的Do方法
    delegate = c.Do
    // call method do: 100
    //调用
    delegate(100)
    //将回调设为普通函数
    delegate = funcDo
    //调用
    delegate(100)
    // call function do: 100
    }
    1. 事件系统基本原理

    事件系统将事件派发者与事件处理者解耦

    事件调用方:事发现场,负责将事件和事件发生的参数通过事件系统派发出去

    事件注册方:通过事件系统注册应该响应的事件及如何回调函数处理这些事情

    特性:

    • 能实现事件的一方,可根据事件ID或名字组册对应的事件

    • 事件发起者根据组册信息通知这些注册者

    • 一个事件可以有多个实现方响应

    步骤:

    1. 事件注册

    1. 事件调用

    2. 使用事件系统

    eventsys/reg.go

    package main

    // 实例化一个通过字符串映射函数切片的map
    var eventByName = make(map[string][]func(interface{}))

    // 1.事件注册
    func RegisterEvent(name string, callback func(interface{})) {
    //通过名字查找事件列表
    list := eventByName[name]
    //在列表切片中添加函数
    list = append(list, callback)
    //保存修改事件列表切片
    eventByName[name] = list
    }

    // 2.调用事件
    func CallEvent(name string, param interface{}) {
    //通过名字找事件
    list := eventByName[name]
    //遍历该事件所有回调
    for _, callback := range list {
    //传入参数回调
    callback(param)
    }
    }

    eventsys/main.go

    package main
    import "fmt"
    // 声明角色结构体
    type Actor struct {}

    // 为角色添加一个事件处理函数
    func (a *Actor) OnEvent(param interface{}) {
    fmt.Println("actor event:", param)
    }

    //全局事件
    func GlobalEvent(param interface{}){
    fmt.Println("global event:", param)
    }

    func main() {
    // 实例化角色
    a := new(Actor)
    //注册名为OnSkill
    RegisterEvent("OnSkill", a.OnEvent)
    // 注册全局事件
    RegisterEvent("OnSkill", GlobalEvent)

    // 打印事件列表
    fmt.Println(eventByName)

    //调用事件
    CallEvent("OnSkill", 100)
    // map[OnSkill:[0x1093ae0 0x10933b0]]
    // actor event: 100
    // global event: 100
    }

    6.6 类型内嵌和结构体内嵌

    结构体允许成员字段在声明时没有字段名且只有类型,称为类型内嵌或匿名字段

    类型内嵌格式:

    type Data struct {
     int
     float32
     bool
    }

    int := &Data {
     int: 10,
     float32: 3.14,
     bool: true,
    }

    6.6.1 声明结构体内嵌

    package main

    import "fmt"

    //基础颜色
    type BasicColor struct {
    //红、绿、蓝
    R,G,B float32
    }
    // 完整颜色定义
    type Color struct {
    //基本颜色作为成员
    Basic BasicColor
    //透明度
    Alpha float32
    }

    func main() {
    var c Color
    // 设置基本颜色
    c.Basic.R = 1
    c.Basic.G = 1
    c.Basic.B = 0

    //设置透明度
    c.Alpha = 1
    //显示整个结构体内容
    fmt.Printf("%+v", c)
    //{Basic:{R:1 G:1 B:0} Alpha:1}

    }

    结构体内嵌写法:

    package main

    import "fmt"

    //基础颜色
    type BasicColor struct {
    //红、绿、蓝
    R,G,B float32
    }
    // 完整颜色定义
    type Color struct {
    //内嵌结构体
    BasicColor
    //透明度
    Alpha float32
    }

    func main() {

    var c Color
    // 直接设置基本颜色
    c.R = 1
    c.G = 1
    c.B = 0

    //设置透明度
    c.Alpha = 1
    //显示整个结构体内容
    fmt.Printf("%+v", c)
    //{BasicColor:{R:1 G:1 B:0} Alpha:1}

    }

    6.6.2 结构体内嵌特性

    特性:

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

    2. 内嵌结构体的字段名是它的类型名

      var c Color
    // 也可以一层一层访问
    c.BasicColor.R = 1
    c.BasicColor.G = 1
    c.BasicColor.B = 0

    6.6.3 组合思想描述对象特性

    结构体内嵌特性就是一种组合特性

    package main
    import "fmt"

    //可飞行
    type Flying struct {}

    func (f *Flying) Fly(){
    fmt.Println("can fly")
    }

    // 可行走
    type Walkable struct{}

    func (f *Walkable) Walk() {
    fmt.Println("can walk")
    }
    //人类
    type Human struct{
    Walkable
    }
    //鸟类
    type Bird struct {
    Flying
    Walkable
    }
    func main() {
    //实例化鸟类
    b := new(Bird)
    fmt.Println("Bird: ")
    b.Fly()
    b.Walk()
    // Bird:
    // can fly
    // can walk

    //实例化人类
    h := new(Human)
    fmt.Println("human: ")
    h.Walk()
    // human:
    // can walk
    }

    6.6.4 初始化结构体内嵌

    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{
    Type: "1.4T",
    Power: 188,
    },
    }
    fmt.Printf("%+v", c)
    //{Wheel:{Size:18} Engine:{Power:188 Type:1.4T}}
    }

    6.6.5 初始化匿名结构内嵌

    package main
    import "fmt"

    //车轮
    type Wheel struct{
    Size int
    }


    //车
    type Car struct {
    Wheel
    // 直接内嵌结构体
    Engine struct {
    Type string
    Power int
    }
    }

    func main() {
    c := Car{
    Wheel: Wheel{
    Size: 18,
    },
    // 初始化引擎 , 需要再次声明结构体才能赋值数据
    Engine: struct {
    Type string
    Power int
    } {
    Type: "1.4T",
    Power: 188,
    },
    }
    fmt.Printf("%+v", c)
    //{Wheel:{Size:18} Engine:{Power:188 Type:1.4T}}

    }

    6.6.6 成员名称冲突

    package main
    import "fmt"

    type A struct{
    a int
    }

    type B struct{
    a int
    }

    type C struct{
    A
    B
    }
    func main() {
    c := &C{}
    c.A.a = 1 //需要直接赋值哪个结构体属性,否则报错
    fmt.Println(c)
    //&{{1} {0}}

    }

    6.7示例:匿名结构体分离JSON数据

    1. 定义数据结构

    2. 准备JSON数据

     

    package main

    import (
    "fmt"
    "encoding/json"
    )
    // 1 定义数据结构
    // 定义手机屏幕
    type Screen struct {
    Size float32
    ResX, ResY int
    }

    // 定义电池
    type Battery struct {
    Capacity int //容量
    }

    // 2 生成json数据
    func getJsonData() []byte {
    //完整数据结构
    raw := &struct{
    Screen
    Battery
    HasTouchID bool
    }{
    //屏幕参数
    Screen: Screen{
    Size: 5.5,
    ResX: 1920,
    ResY: 1080,
    },
    //电池参数
    Battery: Battery{
    2910,
    },
    //是否指纹识别
    HasTouchID: true,

    }
    //将数据转为json
    jsonData, _ := json.Marshal(raw)

    return jsonData
    }

    func main() {
    // 生成一段json数据
    jsonData := getJsonData()

    fmt.Println(string(jsonData))
    // {"Size":5.5,"ResX":1920,"ResY":1080,"Capacity":2910,"HasTouchID":true}

    //只需要屏幕和指纹识别信息结构和实例
    screenAndTouch := struct{
    Screen
    HasTouchID bool
    }{}

    // 反序列化到screeAndTouch中
    json.Unmarshal(jsonData, &screenAndTouch)

    // 输出screenAndTouch结构
    fmt.Printf("%+v ", screenAndTouch)
    // {Screen:{Size:5.5 ResX:1920 ResY:1080} HasTouchID:true}

    //电池和指纹信息
    batteryAndTouch := struct {
    Battery
    HasTouchID bool
    }{}

    // 反序列化到screeAndTouch中
    json.Unmarshal(jsonData, &batteryAndTouch)

    // 输出batteryAndTouch结构
    fmt.Printf("%+v ", batteryAndTouch)
    // {Battery:{Capacity:2910} HasTouchID:true}

    }

     

  • 相关阅读:
    Delegate(委托与事件)
    eclipse2020-06创建属于自己的JSP模板(图文)
    eclipse没有新建web项目的解决问题
    my97datepicker实现日期改变立刻触发函数
    jetty启动项目后js修改后无法保存
    js连续的日期判断,判断相差几天
    同步和异步
    面试题
    MYSQL 数据库名、表名、字段名查询
    Spring-MVC
  • 原文地址:https://www.cnblogs.com/smallyi/p/12445875.html
Copyright © 2011-2022 走看看