• Title


    Go的结构体

    结构体

    Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct

    结构体的定义

    type 类型名 struct {
        字段名 字段类型
        字段名 字段类型
        …
    }
    
    • 类型名:自定义结构体的名称,在同一个包内不能重复
    • 字段名:结构体中的字段名必须惟一
    • 字段类型:表示结构体字段的具体类型

    例子一

    type persion struct {
      name string
      city string
      age int8
    }
    

    结构体实例化

    只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段

    结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型

    type persion struct {
      name string
      city string
      age int8
    }
    
    func main() {
      var p1 persion
      p1.name = "james"
      p1.city = "深圳"
      p1.age = 25
      fmt.Printf("p1=%v
    ", p1)
      fmt.Printf("p1=%#v
    ", p1)
    }
    // p1={james 深圳 25}
    // p1=main.persion{name:"james", city:"深圳", age:25}
    

    匿名结构体

    func main() {
      var user struct{Name string; Age int}
      user.Name = "james"
      user.Age = 25
      fmt.Printf("p1=%#v
    ", user)
    }
    // user=struct { Name string; Age int }{Name:"james", Age:25}
    

    创建指针类型的结构体

    使用new关键字对结构体进行实例化,得到的是结构体的地址

    type persion struct {
      name string
      city string
      age int8
    }
    func main() {
      var p2 =  new(persion)
      fmt.Printf("%T
    ", p2)
      fmt.Printf("%#v
    ", p2)
    }
    // *main.persion
    // &main.persion{name:"", city:"", age:0}
    

    p2是一个结构体指针

    结构体指针可以使用.来访问结构体的成员

    type persion struct {
      name string
      city string
      age int8
    }
    func main() {
      var p2 =  new(persion)
      p2.name = "james"
      p2.city = "深圳"
      p2.age = 25
      fmt.Println(p2.city)
      fmt.Printf("%#v
    ", p2)
    }
    // 深圳
    // &main.persion{name:"james", city:"深圳", age:25}
    

    取结构体的地址实例化

    使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作

    type persion struct {
      name string
      city string
      age int8
    }
    func main() {
      p3 := &persion{}         // 等于var p3 =  new(persion)
      fmt.Printf("%T
    ", p3)
      fmt.Printf("%#v
    ", p3)
      p3.name = "james"
      p3.city = "深圳"
      p3.age = 25
      fmt.Printf("%#v
    ", p3)
    }
    // *main.persion
    // &main.persion{name:"", city:"", age:0}
    // &main.persion{name:"james", city:"深圳", age:25}
    

    p3.name = "james"其实在底层是(*p3).name = "james",这是Go帮我们实现的语法糖

    结构体初始化

    没有初始化的结构体,其成员变量都是其类型对应的零值

    type persion struct {
      name string
      city string
      age int8
    }
    func main() {
      var p4 persion
      fmt.Printf("p4=%#v
    ", p4)
    }
    // p4=main.persion{name:"", city:"", age:0}
    

    使用键值对初始化

    type persion struct {
      name string
      city string
      age int8
    }
    func main() {
      p5 := persion {
        name : "james",
        city : "深圳",
        age : 25,
      }
      fmt.Printf("p5=%#v
    ", p5)
    }
    // p5=main.persion{name:"james", city:"深圳", age:25}
    

    对结构体指针进行初始化

    type persion struct {
      name string
      city string
      age int8
    }
    func main() {
      p6 := &persion {
        name : "james",
        city : "深圳",
        age : 25,
      }
      fmt.Printf("p6=%#v
    ", p6)
    }
    // p6=&main.persion{name:"james", city:"深圳", age:25}
    

    也可以只初始化某些字段

    type persion struct {
      name string
      city string
      age int8
    }
    func main() {
      p7 := &persion {
        name : "james",
      }
      fmt.Printf("p7=%#v
    ", p7)
    }
    // p7=&main.persion{name:"james", city:"", age:0}
    

    结构体内存布局

    结构体占用一块连续的内存

    type test struct {
      a int8
      b int8
      c int8
      d int
    }
    func main() {
      n := test{
        1, 2, 3, 4,
      }
      fmt.Printf("n.a: %p
    ", &n.a)
      fmt.Printf("n.b: %p
    ", &n.b)
      fmt.Printf("n.c: %p
    ", &n.c)
      fmt.Printf("n.d: %p
    ", &n.d)
    }
    // n.a: 0xc00006a004
    // n.b: 0xc00006a005
    // n.c: 0xc00006a006
    // n.d: 0xc00006a007
    

    猜结果

    type student struct {
      name string
      age int
    }
    func main() {
      m := make(map[string]*student)
      stus := []student{
        {name: "stu1", age: 6},
        {name: "stu2", age: 7},
        {name: "stu3", age: 6},
      }
      for _, stu := range stus {
        m[stu.name] = &stu
      }
      for k, v := range m {
        fmt.Println(k, "=>", v.name)
      }
    }
    // stu1 => stu3
    // stu2 => stu3
    // stu3 => stu3
    // 为什么v.name都是stu3,我也搞不懂
    

    go的构造函数

    Go语言的结构体没有构造函数,可以自己通过结构体实现

    struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型

    type persion struct {
      name string
      city string
      age int8
    }
    func newPersion(name, city string, age int8) *persion {
      return &persion{
        name : name,
        city : city,
        age : age,
      }
    }
    func main() {
      p9 := newPersion("james", "深圳", 26)
      fmt.Printf("%#v
    ", p9)
    }
    // &main.persion{name:"james", city:"深圳", age:26}
    

    go的方法和接收者

    Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self

    func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
      函数体
    }
    
    • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母
    • 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型
    • 方法名、参数列表、返回参数:具体格式与函数定义相同
    // Persion结构体
    type Persion struct {
      name string
      age int8
    }
    // NewPersion构造函数
    func NewPersion(name string, age int8) *Persion {
      return &Persion{
        name : name,
        age : age,
      }
    }
    // Dream Persion的做梦的方法
    func (p Persion) Dream() {
      fmt.Printf("%s的梦想是转行厨师
    ", p.name)
    }
    
    func main() {
      p1 := NewPersion("james", 26)
      p1.Dream()
    }
    // james的梦想是转行厨师
    

    方法与函数的区别是: 函数不属于任何类型,方法属于特定的类型

    指针类型接收者

    指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄

    // Persion 结构体
    type Persion struct {
      name string
      age int8
    }
    // NewPersion 构造函数
    func NewPersion(name string, age int8) *Persion {
      return &Persion{
        name : name,
        age : age,
      }
    }
    // SetAge 设置p的年龄
    // 使用指针类型接收者
    func (p *Persion) SetAge(newAge int8) {
      p.age = newAge
    }
    func main() {
      p1 := NewPersion("james", 26)
      fmt.Println(p1.age)  // 26
      p1.SetAge(22)
      fmt.Println(p1.age)  // 22
    }
    

    值类型接收者

    当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身

    // Persion 结构体
    type Persion struct {
      name string
      age int8
    }
    // NewPersion 构造函数
    func NewPersion(name string, age int8) *Persion {
      return &Persion{
        name : name,
        age : age,
      }
    }
    // Dream Persion的做梦的方法
    func (p Persion) Dream() {
      fmt.Printf("%s的梦想是转行厨师
    ", p.name)
    }
    func (p Persion) SetAge2(newAge int8) {
      p.age = newAge
    }
    func main() {
      p1 := NewPersion("james", 27)
      p1.Dream()
      fmt.Println(p1.age)
      p1.SetAge2(30)
      fmt.Println(p1.age)
    }
    // james的梦想是转行厨师
    // 27
    // 27
    

    什么时候应该使用指针类型接收者

    1. 需要修改接收者中的值
    2. 接收者是拷贝代价比较大的对象
    3. 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应使用指针接收者

    任意类型添加方法

    在Go语言中,接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法

    例子一

    type MyInt int
    
    func (m MyInt) SayHello() {
      fmt.Println("Hello World I am int")
    }
    func main() {
      var m1 MyInt
      m1.SayHello()
      m1 = 100
      fmt.Printf("%#v %T
    ", m1, m1)
    }
    // Hello World I am int
    // 100 main.MyInt
    

    注意: 不能给别的包的类型定义方法

    结构体的匿名字段

    结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段

    匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个

    // Persion 结构体Persion类型
    type Persion struct {
      string
      int
    }
    
    func main() {
      p1 := Persion {
        "james",
        27,
      }
      fmt.Printf("%#v
    ", p1)
      fmt.Println(p1.string, p1.int)
    }
    // main.Persion{string:"james", int:27}
    // james 27
    

    嵌套结构体

    // Address 地址结构体
    type Address struct {
    	Province string
    	City     string
    }
    
    // User 用户结构体
    type User struct {
    	Name    string
    	Gender  string
    	Address Address
    }
    
    func main() {
    	user1 := User{
    		Name:   "james",
    		Gender: "boy",
    		Address: Address{
    			Province: "广东",
    			City:    "深圳",
    		},
    	}
    	fmt.Printf("user1=%#v
    ", user1)
    }
    // user1=main.User{Name:"james", Gender:"boy", Address:main.Address{Province:"广东", City:"深圳"}}
    

    嵌套匿名结构体

    // Address 地址结构体
    type Address struct {
    	Province string
    	City     string
    }
    
    // User 用户结构体
    type User struct {
    	Name    string
    	Gender  string
    	Address // 匿名结构体
    }
    func main() {
      var user2 User
      user2.Name = "james"
      user2.Gender = "boy"
      user2.Address.Province = "广东"    // 通过匿名结构体.字段名访问
      user2.City = "深圳"                // 直接访问匿名结构体的字段名
      fmt.Printf("user2=%#v
    ", user2)
    }
    // user2=main.User{Name:"james", Gender:"boy", Address:main.Address{Province:"广东", City:"深圳"}}
    

    当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找

    注意: 嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段

    结构体的"继承"

    // Animal 动物
    type Animal struct {
      name string
    }
    // Animal都有的move方法
    func (a *Animal) move() {
      fmt.Printf("%v正在移动!
    ", a.name)
    }
    // Dog 动物狗
    type Dog struct {
      Feet int
      *Animal //通过嵌套匿名结构体实现继承
    }
    // Dog 都有的wang方法
    func (d *Dog) wang() {
      fmt.Printf("%v会汪汪汪~
    ", d.name)
    }
    func main() {
      d1 := &Dog{
        Feet: 5,
        Animal: &Animal{  // 注意嵌套的是结构体指针
          name: "大黄",
        },
      }
      d1.wang()
      d1.move()
    }
    // 大黄会汪汪汪~
    // 大黄正在移动!
    

    结构体字段的可见性

    结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)

    结构体和JSON序列化

    JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔

    // Student 学生
    type Student struct {
      ID int
      Gender string
      Name string
    }
    // Class 班级
    type Class struct {
      Title string
      Students []*Student
    }
    func main() {
      c := &Class{
        Title: "102",
        Students: make([]*Student, 0, 200),
      }
      for i := 0; i < 10; i++ {
        stu := &Student{
          ID: i,
          Name: fmt.Sprintf("stu%02d", i),
          Gender: "boy",
        }
        c.Students = append(c.Students, stu)
      }
      // JSON序列化:结构体==>JSON字符串
      data, err := json.Marshal(c)
      if err != nil {
        fmt.Println("json marshal failed")
        return
      }
      fmt.Printf("json: %s
    ", data)
      
      // JSON反序列化: JSON格式字符串==>结构体
      str := data
      c1 := &Class{}
      err = json.Unmarshal([]byte(str), c1)
      if err != nil {
        fmt.Println("json unmarshal failed")
        return
      }
      fmt.Printf("%#v
    ", c1)
    }
    

    结构体的标签(Tag)

    Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来

    Tag在结构体字段的后方定义,由一对反引号包裹起来

    结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔

    重点: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格

    // Student 学生
    type Student struct {
      ID int   `json:"id"`  //通过指定tag实现json序列化该字段时的key
      Gender string         //json序列化是默认使用字段名作为key
      name string           //私有不能被json包访问
    }
    func main() {
      s1 := Student{
        ID: 1,
        Gender: "boy",
        name: "james",
      }
      data, err := json.Marshal(s1)
      if err != nil {
        fmt.Println("json marshal failed!")
        return
      }
      fmt.Printf("%s
    ", data)
    }
    // {"id":1,"Gender":"boy"}
    
  • 相关阅读:
    java中JSON转换
    使用Admin监控
    linux安装tomcat
    SpringBoot整合Redis
    linux6.8安装docker
    使用Actuator监控
    SpringBoot集成阿里巴巴Druid监控
    使用Log4j日志处理
    SpringBoot多数据源
    SpringBoot文件上传下载
  • 原文地址:https://www.cnblogs.com/guotianbao/p/12165691.html
Copyright © 2020-2023  润新知