写点什么

你有对象类, 我有结构体,Go lang1.18 入门精炼教程,由白丁入鸿儒,go lang 结构体 (struct) 的使用 EP06

  • 2022 年 8 月 09 日
    北京
  • 本文字数:5149 字

    阅读完需:约 17 分钟

你有对象类,我有结构体,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang结构体(struct)的使用EP06

再续前文,在面向对象层面,Python 做到了超神:万物皆为对象,而 Ruby,则干脆就是神:飞花摘叶皆可对象。二者都提供对象类操作以及继承的方式为面向对象张目,但 Go lang 显然有一些特立独行,因为它没有传统的类,也没有继承,取而代之的是结构和组合的方式,也就是结构体(struct)的方式来组织代码,达到类似类的效果。

结构体 struct 的声明

在 Go lang 中使用下面的语法是对结构体的声明:


type struct_name struct {      attribute_name1   attribute_type      attribute_name2   attribute_type      ...  }
复制代码


假设定义一个名为 Lesson(课程) 的结构体:


type Lesson struct {    name   string //名称    target string //学习目标    spend  int    //学习花费时间  }
复制代码


这里声明了一个结构体类型 Lesson ,它有 name 、 target 和 spend 三个属性,相当于 Python 中类的私有属性。


也可以把相同类型的属性声明在同一行,这样可以使结构体变得更加紧凑:


type Lesson2 struct {      name, target    string      spend             int  }
复制代码


Lesson 称为命名的结构体(Named Structure) ,这里 Lesson 作为一种新的数据类型而存在,而它可以用于创建 Lesson 类型的结构体变量。


此外,声明结构体时也可以不用声明一个新类型,这样的结构体类型称为匿名结构体(Anonymous Structure) ,可以理解为结构体变量:


var MyLesson struct {      name, target    string      spend             int  }
复制代码

结构体 struct 的创建

声明了结构体之后,我们可以根据声明好的结构体类型来创建结构体,这个过程有点像 Python 语言中类的实例化:


import "fmt"    type Lesson struct {    name, target string    spend        int  }    func main() {    // 使用字段名创建结构体    lesson1 := Lesson{      name:   "go lang 1.18",      target: "学习Go lang,并完成web开发任务",      spend:  1,    }    // 不使用字段名创建结构体    lesson2 := Lesson{"go lang 1.18", "学习Go lang,并完成web开发任务", 1}      fmt.Println("lesson1 ", lesson1)    fmt.Println("lesson2 ", lesson2)  }
复制代码


程序返回:


lesson1  {go lang 1.18 学习Go lang,并完成web开发任务 1}  lesson2  {go lang 1.18 学习Go lang,并完成web开发任务 1}
复制代码


这里字段名可以做省略操作,但要注意传递顺序。


此外,也可以创建匿名结构体:


package main    import "fmt"    func main() {    // 创建匿名结构体变量    mylesson := struct {      name, target string      spend        int    }{      name:   "Go lang 1.18",      target: "学习go lang,完成web需求",      spend:  1,    }      fmt.Println("mylesson ", mylesson)  }
复制代码


当定义好的结构体没有被显式赋值时,结构体的字段将会默认赋为相应类型的零值:


package main    import "fmt"    type Lesson struct {    name, target string    spend        int  }    func main() {    // 不初始化结构体    var lesson = Lesson{}      fmt.Println("lesson ", lesson)  }
复制代码


程序返回:


lesson  {  0}
复制代码


假设某个或者某几个字段没有赋值,也会默认赋值为对应基本数据类型的零值:


package main    import "fmt"    type Lesson struct {    name, target    string    spend             int  }    func main() {    // 为结构体指定字段赋初值    var lesson = Lesson{      name: "go lang 1.18",    }        // 上面的结构体变量 lesson 只初始化了 name 字段, 其他字段没有初始化,所以会被初始化为零值    fmt.Println("lesson ", lesson)  }
复制代码


程序返回:


lesson  {go lang 1.18  0}
复制代码

结构体 struct 的属性与指针

通过点操作符 . 可以访问结构体的属性:


package main    import "fmt"    type Lesson struct {    name, target string    spend        int  }    func main() {      var lesson = Lesson{      name: "go lang 1.18",    }      fmt.Println("lesson name ", lesson.name)  }
复制代码


程序返回:


lesson name  go lang 1.18
复制代码


也可以使用点操作符 . 对结构体的字段进行赋值操作:


package main    import "fmt"    type Lesson struct {    name, target string    spend        int  }    func main() {      var lesson = Lesson{      name: "go lang 1.18",    }      fmt.Println("lesson name ", lesson.name)      lesson.name = "Python 3.11"      fmt.Println("lesson name ", lesson.name)    }
复制代码


程序返回:


lesson name  go lang 1.18  lesson name  Python 3.11
复制代码


需要注意的是,赋值变量和结构体属性的基本数据类型要一致。


在前一篇:借问变量何处存,牧童笑称用指针,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang类型指针(Pointer)的使用EP05我们使用了指针来操作变量,指针也可以指向结构体:


package main    import "fmt"    type Lesson struct {    name, target string    spend        int  }    func main() {    lesson := &Lesson{"go lang 1.18", "完成对应web需求", 1}    fmt.Println("lesson name: ", (*lesson).name)    fmt.Println("lesson name: ", lesson.name)  }
复制代码


程序返回:


lesson name:  go lang 1.18  lesson name:  go lang 1.18
复制代码


lesson 是一个指向结构体 Lesson 的指针,上面用 (*lesson).name 访问 lesson 的 name 字段,lesson.name 是代替 (*lesson).name 的解引用访问。


在创建结构体时,属性可以只有类型没有属性名,这种属性称为匿名字段(Anonymous Field) :


package main    import "fmt"    type Lesson struct {    string    int  }    func main() {    lesson := Lesson{"go lang 1.18", 1}    fmt.Println("lesson ", lesson)    fmt.Println("lesson string: ", lesson.string)    fmt.Println("lesson int: ", lesson.int)  }
复制代码


程序返回:


lesson  {go lang 1.18 1}  lesson string:  go lang 1.18  lesson int:  1
复制代码


这里程序结构体定义了两个匿名字段,虽然这两个字段没有字段名,但匿名字段的名称默认就是它的类型。所以上面的结构体 Lesoon 有两个名为 string 和 int 的字段,同样需要注意顺序和字段数据类型的匹配问题。

嵌套结构体

结构体本身也支持复合的嵌套结构:


package main    import "fmt"    type Author struct {    name string  }    type Lesson struct {    name, target string    spend        int    author       Author  }    func main() {    lesson := Lesson{      name:  "go lang 1.18",      spend: 1,    }    lesson.author = Author{      name: "佚名",    }    fmt.Println("lesson name:", lesson.name)    fmt.Println("lesson spend:", lesson.spend)    fmt.Println("lesson author name:", lesson.author.name)  }
复制代码


程序返回:


lesson name: go lang 1.18  lesson spend: 1  lesson author name: 佚名
复制代码


这里结构体 Author 本身作为结构体 Lesson 的一个属性而存在,赋值时,通过父结构体直接调用子结构体名称即可。


如果结构体中有匿名的结构体类型字段,则该匿名结构体里的字段就称为提升字段(Promoted Fields) 。这是因为提升字段就像是属于外部结构体一样,可以用外部结构体直接访问:


package main    import (    "fmt"  )    type Address struct {    city, state string  }  type Person struct {    name string    age  int    Address  }    func main() {    var p Person    p.name = "Naveen"    p.age = 50    p.Address = Address{      city:  "Chicago",      state: "Illinois",    }    fmt.Println("Name:", p.name)    fmt.Println("Age:", p.age)    fmt.Println("City:", p.city)   //city is promoted field    fmt.Println("State:", p.state) //state is promoted field  }
复制代码


系统返回:


Name: Naveen  Age: 50  City: Chicago  State: Illinois
复制代码


如果我们把 Person 结构体中的字段 address 直接用匿名字段 Address 代替, Address 结构体的字段例如 city 就不用像 p.address.city 这样访问,而是使用 p.address 就能访问 Address 结构体中的 address 字段。现在结构体 Address 有 city 字段,访问字段就像在 Person 里直接声明的一样,因此我们称之为提升字段,说白了就是把子结构体的字段提升为父结构体的字段,但是定义还是在子结构体之中。


假设结构体的全部属性都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用 == 或 != 运算符进行比较。可以通过==运算符或 DeeplyEqual()函数比较两个结构相同的类型并包含相同的字段值:


package main    import (        "fmt"  )    type name struct {        firstName string      lastName string  }      func main() {        name1 := name{"Steve", "Jobs"}      name2 := name{"Steve", "Jobs"}      if name1 == name2 {          fmt.Println("name1 and name2 are equal")      } else {          fmt.Println("name1 and name2 are not equal")      }        name3 := name{firstName:"Steve", lastName:"Jobs"}      name4 := name{}      name4.firstName = "Steve"      if name3 == name4 {          fmt.Println("name3 and name4 are equal")      } else {          fmt.Println("name3 and name4 are not equal")      }  }
复制代码


程序返回:


name1 and name2 are equal  name3 and name4 are not equal
复制代码


如果结构变量包含的字段是不可比较的,那么结构变量是不可比较的:


package main    import (    "fmt"  )    type image struct {    data map[int]int  }    func main() {    image1 := image{data: map[int]int{      0: 155,    }}    image2 := image{data: map[int]int{      0: 155,    }}    if image1 == image2 {      fmt.Println("image1 and image2 are equal")    }  }
复制代码


程序报错:


# command-line-arguments  .\test.go:18:5: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)
复制代码


由此可知,结构体的比较可以理解为其属性的批量比较。

结构体绑定方法

在 Go lang 中无法在结构体内部定义方法,这一点与 C 语言类似:


package main    import "fmt"    // Lesson 定义一个名为 Lesson 的结构体  type Lesson struct {    name, target string    spend        int  }    // ShowLessonInfo 定义一个与 Lesson 的绑定的方法  func (l Lesson) ShowLessonInfo() {    fmt.Println("name:", l.name)    fmt.Println("target:", l.target)  }    func main() {    l := Lesson{      name: "go lang 1.1 8",    }    l.ShowLessonInfo()  }
复制代码


程序返回:


name: go lang 1.1 8  target:
复制代码


这里定义了一个与结构体 Lesson 绑定的方法 ShowLessonInfo() ,其中 ShowLessonInfo 是方法名, (l Lesson) 表示将此方法与 Lesson 的实例绑定,这在 Go lang 中称为接收者,而 l 表示实例本身,相当于 Python 中的 self ,在方法内可以使用实例本身.属性名称来访问实例属性。


如果绑定结构体的方法中要改变实例的属性时,必须使用指针作为方法的接收者:




package main import "fmt" // Lesson 定义一个名为 Lesson 的结构体 type Lesson struct { name, target string spend int } // ShowLessonInfo 定义一个与 Lesson 的绑定的方法 func (l Lesson) ShowLessonInfo() { fmt.Println("spend:", l.spend) } // AddTime 定义一个与 Lesson 的绑定的方法,使 spend 值加 n func (l *Lesson) AddTime(n int) { l.spend = l.spend + n } func main() { lesson13 := Lesson{ spend: 1, } fmt.Println("添加add方法前") lesson13.ShowLessonInfo() lesson13.AddTime(5) fmt.Println("添加add方法后") lesson13.ShowLessonInfo() }
复制代码


程序返回:


添加add方法前  spend: 1  添加add方法后  spend: 6
复制代码

结语

大抵上,Go lang 的结构体就是对象类的变种,虽然并没有显性的继承操作,但是通过嵌套结构体和提升字段两种方式,也能达到“继承”的效果,结构体的最终目的和效果与对象类并无二致,类比的话,有点像电脑散热的两种方式:风冷和水冷,我们不能说哪一种方式更好或者不好,只要这种方式可以完成项目中的需求即可,不管黑猫白猫,只要能抓到耗子,就是好猫。

发布于: 刚刚阅读数: 3
用户头像

专注技术,凝聚意志,解决问题 v3u.cn 2020.12.21 加入

还未添加个人简介

评论

发布
暂无评论
你有对象类,我有结构体,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang结构体(struct)的使用EP06_Go_刘悦的技术博客_InfoQ写作社区