您的位置 首页 golang

大白话 golang 教程-11-详解结构体的应用

go 语言放弃了其他语言的 class 的概念,但是 struct 结构体有同等的地位,可以为结构体定义方法集,前面章节提到的 StringHeader 和 SliceHeader 都是结构体,为结构体添加方法的语法如下:

 func (instance Instance) methodName(args...) returnType {
  // 
}


func (instance *Instance) methodName(args...) returnType {
  // 
}  

上面两个都可以为结构体添加方法,第一种定义方法无法改变 instance 内部的属性,因为它调用的时候值传递它复制的是结构体对象,想要修改结构体实例的属性,需要使用第二种指针定义的方法。

 type cat struct {
  name  string
  color string
  age   int
}  

上面定义了一个猫的结构体,小写的 cat 表明不想把它暴露到包外面,给他定义一个年龄增长的方法如下:

 func (c cat) grow1() {
  fmt.Printf("%p\n", &c) // 0xc000092390
  c.age++
}

func (c *cat) grow2() {
  fmt.Printf("%p\n", c) // 0xc000092360
  c.age++
}  

在 grow1 方法中,c cat是调用者的拷贝,而 grow2 中 c *cat 指向调用者的内存地址,所以第一种方法修改的 age 字段无法反应到调用者本身上,一般没有特殊情况,都会使用第二种定义方法,毕竟它的拷贝大小只是一个内存地址。下面的是测试打印的结果:

 func Test_cat(t *testing.T) {
  myCat := cat{"虎斑猫", "黑黄", 1}
  fmt.Printf("%p\n", &myCat) // 0xc000092360

  myCat.run()
  myCat.jump()
  myCat.grow1()
  fmt.Println(myCat) // {虎斑猫 黑黄 1}

  ptrMyCat := &myCat
  ptrMyCat.run()
  ptrMyCat.jump()
  ptrMyCat.grow2()
  fmt.Println(*ptrMyCat) // {虎斑猫 黑黄 2}
}  

通过为结构体添加方法集,结构体的所有实例都具备了同样的行为,在设计上完成了抽象和复用。结构体没有继承,但是可以嵌套,假设猫和人都需要一个家庭住址信息,如果直接申明就会有重复:

 type cat struct {
  color   string
  address string
  phone   string
}

type person struct {
  sex     string
  address string
  phone   string
}  

这里 address 和 phone 都是重复信息,可以提取出来一个单独的结构,再嵌入到 cat 和 person 中。

 type addr struct {
  address string
  phone   string
}

type cat struct {
  color string
  addr  // 匿名字段
}

type person struct {
  sex  string
  addr // 匿名字段
}  

测试代码如下:

 func Test_cat(t *testing.T) {
  where := addr{address: "东大街", phone: "123"}
  c := cat{color: "黑红", addr: where}
  fmt.Println(c)
}

func Test_dog(t *testing.T) {
  p := person{sex: "女", addr: addr{address: "南大街", phone: "321"}}
  fmt.Println(p)
}  

如果把 struct 赋值给一个 interface{},如何转换回来呢? 可以使用断言,其实不仅仅是 struct,go 里面 int、string 等基础类型都可以使用断言,因为 interface{} 就像其他语言的 object 一样。

 func Test_person(t *testing.T) {
  var what interface{}
  what = &person{"zhangsan", 20}

  if p, ok := what.(*person); ok {
    p.eat()
  }
}  

断言的语法比较特殊 val, ok := target.(type) 意为尝试转换 target 到 type 类型,如果转换成功 ok 为真,val 就是转换后的类型了。断言适合于预知的目标类型,如果编码的时候面对 interface{} 有多种类型的情况或者无法预知类型的情况下,可以使用反射来获得更多的信息:

 func Test_person(t *testing.T) {
  var what interface{}
  what = person{"zhangsan", 20}

  // fmt.Println(reflect.TypeOf(what))
  // fmt.Println(reflect.ValueOf(what))

  val := reflect.TypeOf(what)
  switch val.Kind() {
  case reflect.Struct:
    for i := 0; i < val.NumField(); i++ {
      field := reflect.ValueOf(val.Field(i))
      fmt.Println(field)
    }
  default:
    fmt.Println("not struct")
  }
}  

下面介绍 go 的结构体的 tag 用法,用的非常的多,试想如果要把一张数据表的字段映射到结构体上怎么做? go 的方案是用 tag 标签绑定,它常用语 json、xml、bson 等转换的场景。下面的代码把结构体 dump 成 json 字符串:

 type record struct {
  Name  string
  Age   int
  Score float32
}

func Test_record(t *testing.T) {
  rec := record{"zhangsan", 20, 98.5}
  buf, err := json.Marshal(rec)
  if err != nil {
    t.Fatal(err)
  }

  fmt.Println(string(buf)) // {"Name":"zhangsan","Age":20,"Score":98.5}
}
  

string(buf)是把字节强转为字符串,如果想定义字段的名称可以加上 tag 字段定义,修改 record 结构体如下:

 type record struct {
  Name  string  `json:"nickname"` // 字段重命名
  Age   int     `json:"age"`
  Score float32 `json:"score"`
}  

再次运行测试函数,输出结果如下:

 {"nickname":"zhangsan","age":20,"score":98.5}  

类似的可以使用 Unmarshal 方法,从 json 字符串中创建出结构体对象。

 func Test_recordLoad(t *testing.T) {
  rec := record{}
  // str := `{"nickname":"zhangsan","age":20,"score":98.5}`
  str := `{
    "nickname":"zhangsan",
    "age":20,
    "score":98.5
  }`
  if err := json.Unmarshal([]byte(str), &rec); err != nil {
    t.Fatal(err)
  }

  fmt.Println(rec) // {zhangsan 20 98.5}
}  

[]byte(str)是把字符串强转为字节,如果需要支持 xml 添加 tag 即可,使用 encoding/xml 包可以完成转换,请你去试一试。

 func Test_xmlDump(t *testing.T) {
  rec := record{"zhangsan", 20, 98.5}
  buf, _ := xml.Marshal(rec)
  fmt.Println(string(buf)) // <record nickname="zhangsan" age="20" score="98.5"></record>

  newRec := record{}
  _ = xml.Unmarshal([]byte(buf), &newRec)
  if reflect.DeepEqual(rec, newRec) {
    fmt.Println("dump and load ok")
  }
}  

本章节的代码

文章来源:智云一二三科技

文章标题:大白话 golang 教程-11-详解结构体的应用

文章地址:https://www.zhihuclub.com/98625.shtml

关于作者: 智云科技

热门文章

网站地图