结构体是类型中带有成员的复合类型。go语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性。
go语言中的类型可以被实例化,使用new和&构造类型实例的类型是类型的指针。
结构体成员是由一系列成员变量构成,成员(字段)有以下属性:

  • 字段名称唯一;
  • 拥有类型和值;
  • 成员的类型可以是结构体,甚至是字段所在结构体的类型。

    go不支持”类“的概念,也不支持”继承”面向对象的概念。 go 语言不仅认为结构体能拥有方法,且每种自定义类型也可以有自己的方法。

在函数外部定义结构体,作用域是全局的。

结构体基础

定义结构体

  1. type 结构体名称 struct {
  2. 字段名 字段类型
  3. ...
  4. }

结构体类型名在包内不能重复

示例,表示直角坐标系中的点的坐标

  1. type Point struct {
  2. X int
  3. Y int
  4. }

实例化结构体—为结构体分配内存并初始化

结构体定义只是一种内存布局的描述,只有结构化后才能真正分配内存。

基本的实例化形式

示例,表示直角坐标系中的点的坐标

  1. type Point struct {
  2. X int
  3. Y int
  4. }
  5. var p Point
  6. p.x = 10
  7. p.y = 20

创建指针类型的结构体

使用new关键字对类型进行实例化,会形成指针类型的结构体。

  1. type Player struct {
  2. Name string
  3. HealthPoint int
  4. MagicPoint int
  5. }
  6. tank := new(Player)
  7. tank.name = "Canon"
  8. tank.HealthPoint = 300

go 语言中访问成员变量可以继续使用”.”,go增加了语法糖,将ins.Name转化为(*ins).Name

取结构体地址实例化

对结构体进行&取地址,视为对该类型进行一次new实例化操作。

  1. ins := &T{}
  • T表示结构体类型
  • Ins 为结构体实例,类型是*T

示例,定义一个命令行指令,指令包含名称,关联变量和注释

  1. type Command struct {
  2. Name string
  3. Var *int // 此处使用指针,可以随时与绑定的值保持同步
  4. Comment string
  5. }
  6. var version int = 1
  7. cmd := &Command{}
  8. cmd.Name = "version"
  9. cmd.Var = &version
  10. cmd.Comment = "show version"

初始化结构体成员变量

使用基本形式初始化结构体

示例,颜色的RGB值

  1. type Color struct {
  2. R, G, B byte
  3. }

使用键值对初始化结构体

示例,描述家庭任务关联

  1. type People struct {
  2. name string
  3. child *People
  4. }
  5. relationship := &People{
  6. name: "grand father",
  7. child: &People{
  8. name: "Dad",
  9. child: &People{
  10. name: "I",
  11. },
  12. },
  13. }
  14. fmt.Println(relationship.child.child.name)

使用多个值列表初始化结构体

注意

  • 必须初始化结构体的所有字段
  • 初始值与字段顺序保持一致
  • 键值对与值列表的形式不能混用

示例,地址

  1. type Address struct {
  2. Privince string
  3. City string
  4. ZipCode int
  5. PhoneNumber string
  6. }
  7. addr := Address{
  8. "上海"
  9. "上海"
  10. "222300",
  11. "0"
  12. }

构造函数—结构体初始化的函数封装

go语言或者结构体没有构造函数的功能,结构体的初始化的过程可以用函数封装实现。

模拟构造函数重载

示例,根据颜色和名称可以构造不同猫的实例。

  1. type Cat struct {
  2. Name string
  3. COlor string
  4. }
  5. func NewCatByName(name string) *Cat {
  6. return &Cat{
  7. Name: name,
  8. }
  9. }
  10. func NewCatByColor(color string) *Cat {
  11. return &Cat{
  12. Color: color,
  13. }
  14. }

模拟父级构造调用

示例,黑猫是一种猫,猫是黑猫的泛称。

  1. package main
  2. import "fmt"
  3. type Cat struct {
  4. Name string
  5. Color string
  6. }
  7. type BlackCat struct {
  8. Cat // 嵌入Cat,类似于派生
  9. }
  10. func NewCat(name string) *Cat {
  11. return &Cat{
  12. Name: name,
  13. }
  14. }
  15. func NewBlackCat(name string) *BlackCat {
  16. cat := &BlackCat{}
  17. cat.Color = "black"
  18. cat.Name = name
  19. return cat
  20. }
  21. func main() {
  22. cat := NewCat("wow")
  23. fmt.Println(cat)
  24. blackcat := NewBlackCat("hello")
  25. fmt.Println(blackcat)
  26. }

方法

使用背包作为”对象”,将物品放入背包的过程称为”方法“。

面向过程的实现方法

面向过程中没有“方法”概念,只能通过结构体和函数,由使用者用函数参数和调用关系来形成接近”方法“的概念。

  1. package main
  2. import "fmt"
  3. type Bag struct {
  4. items []int
  5. }
  6. func Insert(b *Bag, itemid int) {
  7. b.items = append(b.items, itemid)
  8. }
  9. func main() {
  10. bag := &Bag{}
  11. Insert(bag, 100)
  12. fmt.Println(bag.items)
  13. }

Go 语言结构体方法

将背包及放入背包物品中使用go语言的结构体和方法方式编写,为 *Bag创建一个方法。

  1. package main
  2. import "fmt"
  3. type Bag struct {
  4. items []int
  5. }
  6. func (b *Bag) Insert(itemid int) {
  7. b.items = append(b.items, itemid)
  8. }
  9. func main() {
  10. bag := &Bag{}
  11. bag.Insert(100)
  12. fmt.Println(bag.items)
  13. }

Insert(itemid int)的写法与函数一致,(b *Bag)表示接收器,即Insert作用的对象实例。每个方法只能有一个接收器。

接收器—方法作用的目标

结构体-struct - 图1

  1. func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {}

指针和非指针接收器的使用

在计算机中,小对象由于值复制时的速度较快,适合使用非指针接收器。大对象因为复制性能较低,适合使用指针接收器,在接收器和参数间传递 不进行复制, 只是传指针。

指针类型接收器

指针类型接收器由一个结构体指针组成,更接近于面向对象中的thisself
由于指针的特性,调用方法时,修改接收器成员变量,在方法结束后是有效的。

  1. package main
  2. import "fmt"
  3. type Property struct {
  4. value int
  5. }
  6. func (p *Property) SetValue(value int) {
  7. p.value = value
  8. }
  9. func (p *Property) Value() int {
  10. return p.value
  11. }
  12. func main() {
  13. p := &Property{}
  14. p.SetValue(1)
  15. fmt.Println(p.Value())
  16. }

非指针类型接收器

当方法作用于非指针接收器时,go语言会在代码运行时将接收器的值复制一份。在非指针接收器的方法中可以获取接收器的成员值,但修改后无效

  1. package main
  2. import "fmt"
  3. type Point struct {
  4. X int
  5. Y int
  6. }
  7. func (p Point) Add(other Point) Point {
  8. return Point{p.X+other.X, p.Y+other.Y}
  9. }
  10. func main() {
  11. p1 := Point{1, 1}
  12. p2 := Point{3, 4}
  13. fmt.Println(p1.Add(p2))
  14. }

示例,time时间的中的Second属性是 Duration,Duration拥有String()方法。

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. fmt.Println(time.Second.String())
  8. }
  9. //const (
  10. // Nanosecond Duration = 1
  11. // Microsecond = 1000 * Nanosecond
  12. // Millisecond = 1000 * Microsecond
  13. // Second = 1000 * Millisecond
  14. // Minute = 60 * Second
  15. // Hour = 60 * Minute
  16. //)
  17. //
  18. //func (d Duration) String() string {
  19. // ...
  20. // return string(buf[w:])

结构体内嵌

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

  1. type Data struct {
  2. int
  3. float32
  4. bool
  5. }
  6. ins : = &Data{
  7. int: 1 0,
  8. float32 : 3 .14,
  9. bool: true
  10. }

结构体类型内嵌比普通类型内嵌的概念复杂 ,下面通过一个实例来理解。

  1. package main
  2. import "fmt"
  3. type BasicColor struct {
  4. R, G, B float32
  5. }
  6. type Color struct {
  7. Basic BasicColor
  8. Alpha float32
  9. }
  10. func main() {
  11. var color Color
  12. color.Basic.R = 1
  13. color.Basic.G = 1
  14. color.Basic.B = 0
  15. color.Alpha = 1
  16. fmt.Printf("%+v", color)
  17. }

将 BasicColor 结构体嵌入Color 结构体中, BasicColor 没有宇段名而只有类型,这种写法就叫做结构体内嵌。

  1. package main
  2. import "fmt"
  3. type BasicColor struct {
  4. R, G, B float32
  5. }
  6. type Color struct {
  7. BasicColor
  8. Alpha float32
  9. }
  10. func main() {
  11. var color Color
  12. color.R = 1
  13. color.G = 1
  14. color.B = 0
  15. color.Alpha = 1
  16. fmt.Printf("%+v", color)
  17. }

初始化结构体内嵌

  1. package main
  2. import "fmt"
  3. type Wheel struct {
  4. Size int
  5. }
  6. type Engine struct {
  7. Power int
  8. Type string
  9. }
  10. type Car struct {
  11. Wheel
  12. Engine
  13. }
  14. func main() {
  15. car := Car{
  16. Wheel: Wheel{
  17. Size: 10,
  18. },
  19. Engine: Engine{
  20. Power: 10,
  21. Type: "Dz",
  22. },
  23. }
  24. fmt.Printf("%+v", car)
  25. }

使用匿名结构体分离json数据

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. )
  6. // 定义屏幕
  7. type Screen struct {
  8. Size float32 // 屏幕尺寸
  9. ResX, ResY int //屏幕水平和垂直分辨率
  10. }
  11. // 定义电池
  12. type Battery struct {
  13. Capacity int // 容量
  14. }
  15. func genJsonData () []byte {
  16. raw := &struct {
  17. Screen
  18. Battery
  19. HasTouchID bool
  20. }{
  21. Screen: Screen{
  22. Size: 5.5,
  23. ResX: 1920,
  24. ResY: 1080,
  25. },
  26. Battery: Battery{
  27. Capacity: 100,
  28. },
  29. HasTouchID: true,
  30. }
  31. jsonData, err := json.Marshal(raw)
  32. if err != nil {
  33. return make([]byte, 0)
  34. } else {
  35. return jsonData
  36. }
  37. }
  38. func main() {
  39. // 生成一段 JSON 数据
  40. jsonData := genJsonData()
  41. fmt.Println(string(jsonData))
  42. // 只需要屏幕和指纹识别信息的结构和实例
  43. screenAndTouch := struct {
  44. Screen
  45. HasTouchID bool
  46. }{}
  47. // 反序列化到 screenAndTouch
  48. err := json.Unmarshal(jsonData, &screenAndTouch)
  49. if err == nil {
  50. fmt.Printf("%+v\n", screenAndTouch)
  51. }
  52. // 只需要电池和指纹识别信息的结构和实例
  53. batteryAndTouch := struct {
  54. Battery
  55. HasTouchID bool
  56. }{}
  57. err = json.Unmarshal(jsonData, &batteryAndTouch)
  58. if err == nil {
  59. fmt.Printf("%+v\n", &batteryAndTouch)
  60. }
  61. }