zoukankan      html  css  js  c++  java
  • 结构体

    Go中没有"类"的概念,也不支持"类"的继承等面向对象的概念。

    Go中通过结构体的内嵌,再配合接口,比面向对象具有更高的扩展性和灵活性。

    Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或者部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,即struct。也就是我们可以通过struct来定义自己的类型。GO语言中通过struct来实现面向对象。

    定义:

    使用 type 和 struct 关键字来定义结构体。

        type 类型名 struct {    
            字段名 字段类型
            字段名 字段类型
            ...
        }
        类型名:标识自定义结构体的名称,在同一个包内不能重复
        字段名:表示结构体字段名,结构体中的字段名必须唯一
        字段类型:表示结构体字段的具体类型
    
        type person struct {
            name string
            city string
            // name,city string         // 相同类型的字段,可以写在一行
            age  int 
        } 

     这样就拥有了一个person的自定义类型,它有name,city,age三个字段,表示三种属性。这样使用person结构体就能够很方便的在程序中表示和存储人信息了。

    语言内置的基础数据类型是用来描述一个值的,而结构体是用来描述一组值的。比如一个人有名字,年龄和居住城市等,本质上是一种聚合型的数据类型。

    结构体实例化

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

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

    var  结构体实例  结构体类型
    

      

    基本实例化

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        
        type person struct {
            name string
            city string
            age int
        }
    
        var p person
        p.name = "张三"
        p.city = "北京"
        p.age = 20
    
        fmt.Println(p)              // {张三 北京 20}
        fmt.Println(p.name)         // 张三
    
    }

    可以通过 . 来访问结构体的字段,例如:p.name,p.age 等 

    匿名结构体:

    在定义一些临时数据结构等场景下,还可以使用匿名结构体。

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        
        var user struct{Name string;Age int}
        user.Name = "张三"
        user.Age = 22
        fmt.Println(user)                   // {张三 22}
        fmt.Printf("%#v 
    ",user)           // struct { Name string; Age int }{Name:"张三", Age:22}
    
    }

    创建指针类型结构体

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

    package main
    
    import "fmt"
    
    type person struct {
        name string
        city string
        age int8
    }
    
    func main() {
        
        var p2 = new(person)
        fmt.Printf("%T 
    ",p2)          // *main.person ,*代表指针,所以p2是 结构体指针
        fmt.Printf("p2=%#v 
    ",p2)      // p2=&main.person{name:"", city:"", age:0}
        fmt.Println(p2)                 // &{  0}
    
        var p3 person
        fmt.Printf("%T 
    ",p3)          // main.person
        fmt.Printf("p2=%#v 
    ",p3)      // p2=main.person{name:"", city:"", age:0}
        fmt.Println(p3)                 // {  0}
    
    }
    代码示例

     取结构体的地址实例化

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

    package main
    
    import "fmt"
    
    type person struct {
        name string
        city string
        age int8
    }
    
    func main() {
        
        var p3 = &person{}
    
        fmt.Printf("%T
    ", p3)          // *main.person
        fmt.Printf("p3=%#v
    ", p3)      // p3=&main.person{name:"", city:"", age:0}
    
        p3.name = "博客"
        fmt.Printf("p3=%#v
    ", p3)      // p3=&main.person{name:"博客", city:"", age:0}
        fmt.Println(p3.name)            // 博客
    
    }
    代码示例

    结构体初始化

    package main
    
    import "fmt"
    
    type person struct {
        name string
        city string
        age int8
    }
    
    func main() {
        
        var p4 person
    
        fmt.Printf(" %#v 
     ",p4)       // main.person{name:"", city:"", age:0}
        fmt.Println(p4)                 // {  0}
    
    }
    代码示例

    使用键值对初始化

    package main
    
    import "fmt"
    
    type person struct {
        name string
        city string
        age int8
    }
    
    func main() {
        
        var p5  = person{
            city : "北京",
            name : "张三",
        }
        fmt.Println(p5)
    
        // 对结构体指针,进行 键值对初始化
        var p6 = &person{
            name : "李四",
            city : "上海",
        }
        fmt.Println(p6)
    
    }
    代码示例

    初始化时,键对应结构体字段,值对应字段的初始值。

    当某个字段没有指定初始值时,会使用该字段默认的零值。

    赋值时,指定字段即可,不必按结构体字段照顺序进行赋值。

    使用值的列表初始化

    初始化结构体的时候可以简写,也就是初始化的时候,不写键,直接写值。

    package main
    
    import "fmt"
    
    type person struct {
        name string
        city string
        age int8
    }
    
    func main() {
        
        var p8 = person{
            "张三",
            "北京",
            18,
        }
    
        fmt.Println(p8)         // {张三 北京 18}
    
    }
    代码示例

    简写时,必须初始化结构体的所有字段。

    初始化值得顺序必须与结构体中字段的顺序一致。

    该方式不能和键值初始化方式混用。

    结构体内存布局

    每个字段的内存地址独立,不一致

    package main
    
    import "fmt"
    
    type test struct {
        a int8
        b int
    }
    
    func main() {
        
        var t = test{
            1,1,
        }
    
        fmt.Printf("%p 
    ",&t.a)
        fmt.Printf("%p 
    ",&t.b)
    
    }
    代码示例

    构造函数:用于新创建对象的初始化工作。

    析构函数:用于在撤销对象前,完成一些清理工作。比如:释放内存

    每当创建对象时,需要添加初始化代码时,则需要定义自己的构造函数;而对象撤销时,需要自己添加清理工作的代码时,则需要定义自己的析构函数。

    Go语言的结构体没有构造函数,但是可以自己实现。下面的代码就实现了一个 person的构造函数。

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

    package main
    
    import "fmt"
    
    type person struct {
        name string
        city string
        age int8
    }
    
    func newPerson(name,city string,age int8) *person{
        return &person{
            name : name,
            city : city,
            age : age,
        }
    }
    
    func main() {
        
        p9 := newPerson("张三","北京",18)
        fmt.Printf("%#v 
    ",p9)                 // &main.person{name:"张三", city:"北京", age:18}
    
    }
    代码示例

    方法和接收者

    GO语言中的方法,是一种作用于 特定类型变量  的函数,这种  特定类型变量  叫做  接收者,接收者的概念类似于其他语言的 this/self

    func (接受者变量  接收者类型) 方法名(参数列表) (返回参数) {
        函数体
    }

    接收者变量:在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self/this,

    接收者类型:接收者类型与参数类似,可以是 指针类型和非指针类型。

    方法名,参数列表,返回参数:具体格式与函数定义相同。

    package main
    
    import "fmt"
    
    type Person struct {
        name string
        age int8
    }
    
    // 构造函数
    func NewPerson(name string,age int8) *Person {
        return &Person{
            name : name,
            age  : age,
        }
    }
    
    // 方法
    func (p Person) Dream() {
        fmt.Printf("%s的梦想是世界和平",p.name)
    }
    
    func main() {
    
        p1 := NewPerson("张三",18)
        p1.Dream()
    
    }
    代码示例

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

    指针类型的接收者

    指针类型的接收者,由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者的任意成员变量,在方法结束后,修改都是有效的。

    package main
    
    import "fmt"
    
    type Person struct {
        name string
        age int8
    }
    
    // 构造函数
    func NewPerson(name string,age int8) *Person {
        return &Person{
            name : name,
            age  : age,
        }
    }
    
    // 方法
    func (p *Person) SetAge(newAge int8) {
        p.age = newAge                          // 修改 年龄
    }
    
    func main() {
    
        p1 := NewPerson("张三",18)
        fmt.Println(p1.age)
        p1.SetAge(30)
        fmt.Println(p1.age)
    
    }
    代码示例

    值类型的接收者

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

    package main
    
    import "fmt"
    
    type Person struct {
        name string
        age int8
    }
    
    // 构造函数
    func NewPerson(name string,age int8) *Person {
        return &Person{
            name : name,
            age  : age,
        }
    }
    
    // 方法
    func (p Person) SetAge2(newAge int8) {
        p.age = newAge                          // 修改 年龄
    }
    
    func main() {
    
        p1 := NewPerson("张三",18)
        fmt.Println(p1.age)         // 18
        p1.SetAge2(30)
        fmt.Println(p1.age)         // 18
    
    }
    代码示例

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

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

    任意类型,添加方法

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

    比如:我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法。

    package main
    
    import "fmt"
    
    type Myint int
    
    func (m Myint) SayHello() {
        fmt.Println("hello world!!!")
    }
    
    func main() {
    
        var m1 Myint
        m1.SayHello()                  // hello world!!!
        m1 = 100
        fmt.Printf("%#v 
    ",m1)       // 100
        fmt.Printf("%T 
    ",m1)        // main.Myint
    
    }
    代码示例

    结构体的匿名字段

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

    package main
    
    import "fmt"
    
    type Myint int
    
    type person struct {
        string
        int8
    }
    
    func main() {
    
        p1 := person{
            "张三",
            18,
        }
    
        fmt.Printf("%#v 
    ",p1)         // main.person{string:"张三", int8:18}
        fmt.Printf(p1.string)           // 张三
    
    }
    代码示例

    嵌套结构体

    一个结构体中可以嵌套包含另一个结构体或结构体指针。

    package main
    
    import "fmt"
    
    type Address struct {
        Province string
        City     string
    }
    
    type User struct {
        Name    string
        Gender  string
        Address Address
    }
    
    func main() {
    
        user1 := User{
            Name : "张三",
            Gender : "",
            Address : Address{
                Province : "黑龙江",
                City : "哈尔滨",
            },
        }
    
        fmt.Printf("%#v 
    ",user1)
    
    }
    
    // main.User{Name:"张三", Gender:"男", Address:main.Address{Province:"黑龙江", City:"哈 尔滨"}}
    代码示例

    嵌套匿名结构体

    package main
    
    import "fmt"
    
    type Address struct {
        Province string
        City     string
    }
    
    type User struct {
        Name    string
        Gender  string
        Address             // 匿名结构体
    }
    
    func main() {
    
        var user2 User
        user2.Name = "张三"
        user2.Gender = ""
        user2.Address.Province = "黑龙江"       // 通过 匿名结构体.字段 访问
        user2.City = "哈尔滨"                   // 直接访问匿名结构体的字段名
    
        fmt.Printf("%#v 
    ",user2)
        // main.User{Name:"张三", Gender:"男", Address:main.Address{Province:"黑龙江", City:"哈 尔滨"}}
    }
    代码示例

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

    嵌套结构体的字段名冲突

    嵌套结构体内部可能存在相同的字段名,这时 需要指定具体的内嵌结构体的字段。

    package main
    
    import "fmt"
    
    //Address 地址结构体
    type Address struct {
        Province   string
        City       string
        CreateTime string
    }
    
    //Email 邮箱结构体
    type Email struct {
        Account    string
        CreateTime string
    }
    
    //User 用户结构体
    type User struct {
        Name   string
        Gender string
        Address
        Email
    }
    
    func main() {
        var user3 User
        user3.Name = "pprof"
        user3.Gender = ""
        // user3.CreateTime = "2019" //ambiguous selector user3.CreateTime
        user3.Address.CreateTime = "2000" //指定Address结构体中的CreateTime
        user3.Email.CreateTime = "5000"   //指定Email结构体中的CreateTime
        fmt.Printf("%#v
    ", user3)
    }
    
    // main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"", City:"", CreateTime:"2000"}, Email:main.Email{Account:"", CreateTime:"5000"}}
    代码示例

    结构体的 继承

    Go语言使用的结构体也可以实现其他编程语言中  面向对象的继承

    package main
    
    import "fmt"
    
    type Animal struct {
        name string
    }
    
    func (a *Animal) move() {
        fmt.Printf("%s 会动 
    ",a.name)
    }
    
    type Dog struct {
        Feet int8
        *Animal         // 通过嵌套匿名结构体,实现继承
    }
    
    func (d *Dog) wang() {
        fmt.Printf("%s 会叫 
    ",d.name)
    }
    
    func main() {
    
        d1 := &Dog{
            Feet : 4,
            Animal : &Animal{       // 嵌套结构体的指针
                name : "乐乐",
            },
        }
        d1.wang()
        d1.move()
    
    }
    代码示例

    结构体字段的可见性

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

    结构体与JSON序列化

    package main
    
    import "fmt"
    import "encoding/json"
    
    //Student 学生
    type Student struct {
        ID     int
        Gender string
        Name   string
    }
    
    //Class 班级
    type Class struct {
        Title    string
        Students []*Student
    }
    
    func main() {
        c := &Class{
            Title:    "101",
            Students: make([]*Student, 0, 200),
        }
        for i := 0; i < 10; i++ {
            stu := &Student{
                Name:   fmt.Sprintf("stu%02d", i),
                Gender: "",
                ID:     i,
            }
            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 := `{"Title":"101","Students":[{"ID":0,"Gender":"","Name":"stu00"},{"ID":1,"Gender":"","Name":"stu01"},{"ID":2,"Gender":"","Name":"stu02"},{"ID":3,"Gender":"","Name":"stu03"},{"ID":4,"Gender":"","Name":"stu04"},{"ID":5,"Gender":"","Name":"stu05"},{"ID":6,"Gender":"","Name":"stu06"},{"ID":7,"Gender":"","Name":"stu07"},{"ID":8,"Gender":"","Name":"stu08"},{"ID":9,"Gender":"","Name":"stu09"}]}`
        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时,必须遵守键值对规则,结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。 

    package main
    
    import "fmt"
    import "encoding/json"
    
    //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: "",
            name:   "pprof",
        }
        data, err := json.Marshal(s1)
        if err != nil {
            fmt.Println("json marshal failed!")
            return
        }
        fmt.Printf("json str:%s
    ", data)       //json str:{"id":1,"Gender":"女"}
    }
    代码示例

    删除map类型的结构体

    package main
    
    import "fmt"
    
    type student struct {
        id   int
        name string
        age  int
    }
    
    func main() {
        ce := make(map[int]student)
        ce[1] = student{1, "xiaolizi", 22}
        ce[2] = student{2, "wang", 23}
        fmt.Println(ce)             // map[1:{1 xiaolizi 22} 2:{2 wang 23}]
        delete(ce, 2)
        fmt.Println(ce)             // map[1:{1 xiaolizi 22}]
    }
    代码示例

    实现map有序输出

    package main
    
    import (
        "fmt"
        "sort"
    )
    
    func main() {
        map1 := make(map[int]string, 5)
        map1[1] = "www.topgoer.com"
        map1[2] = "rpc.topgoer.com"
        map1[5] = "ceshi"
        map1[3] = "xiaohong"
        map1[4] = "xiaohuang"
        sli := []int{}
        for k, _ := range map1 {
            sli = append(sli, k)
        }
        sort.Ints(sli)
        for i := 0; i < len(map1); i++ {
            fmt.Println(map1[sli[i]])
        }
    }
    代码示例
  • 相关阅读:
    linux下创建一个指定大小的文件
    批量替换多个文件中的字符串
    redhat 搭建yum 源
    python ConfigParser 模块
    python yaml 模块
    python xml文件处理
    py2exe 和pyinstaller打包
    wxpython 学习之 --threading
    wxpython 学习之 --文本框与Boxsizer布局管理器
    wxpython 学习之 --窗口分割
  • 原文地址:https://www.cnblogs.com/yizhixiaowenzi/p/14688829.html
Copyright © 2011-2022 走看看