结构体

 

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 \n",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 \n",p2)          // *main.person ,*代表指针,所以p2是 结构体指针
    fmt.Printf("p2=%#v \n",p2)      // p2=&main.person{name:"", city:"", age:0}
    fmt.Println(p2)                 // &{  0}

    var p3 person
    fmt.Printf("%T \n",p3)          // main.person
    fmt.Printf("p2=%#v \n",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\n", p3)          // *main.person
    fmt.Printf("p3=%#v\n", p3)      // p3=&main.person{name:"", city:"", age:0}

    p3.name = "博客"
    fmt.Printf("p3=%#v\n", 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 \n ",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 \n",&t.a)
    fmt.Printf("%p \n",&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 \n",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 \n",m1)       // 100
    fmt.Printf("%T \n",m1)        // main.Myint

}
代码示例

 

 

 

结构体的匿名字段

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

package main

import "fmt"

type Myint int

type person struct {
    string
    int8
}

func main() {

    p1 := person{
        "张三",
        18,
    }

    fmt.Printf("%#v \n",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 \n",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 \n",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\n", 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 会动 \n",a.name)
}

type Dog struct {
    Feet int8
    *Animal         // 通过嵌套匿名结构体,实现继承
}

func (d *Dog) wang() {
    fmt.Printf("%s 会叫 \n",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\n", 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\n", 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\n", 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]])
    }
}
代码示例

 

posted on 2021-04-22 11:37  二十四岁半  阅读(84)  评论(0编辑  收藏  举报

导航