go中的结构体

结构体的定义

以学生结构体为例,如下:

复制代码
type Student struct {
    name string
    age  int
}

同类型的可以写在一行:

复制代码
type Student struct {
    name,course string
    age  int
}

结构体初始化

方法1:使用var关键字

复制代码
package main

import "fmt"

type Student struct {
    name string
    age  int
}

func main() {
    var s Student
    fmt.Printf("s=%#v\n", s) 
}

方法2:使用值或键值对初始化

方法2.1:使用值初始化写法

复制代码
s:= Student{
    "cc",
    18,
}

如果{}在一行,逗号可以省略

s:=Student{"cc",18}

方法2.2:使用键值对初始化写法

复制代码
s:=Student{
    name:"cc",
    age:18,
}

方法2.3:对结构体指针进行键值对初始化

复制代码
s:=&Student{
    name:"cc",
    age:18,
}

demo:

复制代码
package main

import "fmt"

type Student struct {
    name string
    age  int
}

func main() {
    //方法1
    var s=Student{
        name:"cc",
        age:18,
    }
    //方法2.1
    s:=Student{
        "cc",
        18,
    }
    
    //方法2.2
    s:=Student{
        name:"cc",
        age:18,
    }
    fmt.Printf("s=%#v\n", s) 
    //方法2.3:结构体指针
    s:=&Student{
        name:"cc",
        age:18,
    }
    fmt.Printf("s=%#v\n", s) 
    
}

方法3:给结构体成员赋值的方式进行初始化

demo

复制代码
package main

import (
    "fmt"
)

type Student struct {
    name string
    age  int
}

func main() {
    var s Student
    fmt.Printf("s=%v\n", s)

    s.name = "cc"
    s.age = 18
    fmt.Printf("p1=%v\n", s)
    fmt.Printf("p1=%#v\n", s)
}

匿名结构体与结构体的匿名字段

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

复制代码
import (
    "fmt"
)

func main() {
    var user struct {
        Name string
        Age  int
    }
    user.Name = "pprof.cn"
    user.Age = 18
    fmt.Printf("%#v\n", user)
}

输出结果:

struct { Name string; Age int }{Name:"pprof.cn", Age:18}

结构体的字段定义时可以没有字段名,这种字段被称为匿名字段

复制代码
package main

import (
    "fmt"
)

//Person 结构体
type Person struct {
    string
    int
}

func main() {
    p1 := Person{
        "pprof.cn",
        18,
    }
    fmt.Printf("%#v\n", p1)        //main.Person{string:"pprof.cn", int:18}
    fmt.Println(p1.string, p1.int) //pprof.cn 18
}

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

结构体指针

除了上面几种实例化结构体方式,还可以通过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)     
    fmt.Printf("p2=%#v\n", p2) 
}

输出结果:

复制代码
*main.person
p2=&main.person{name:"", city:"", age:0}

Go语言中支持对结构体指针直接使用.来访问结构体的成员

复制代码
package main

import (
    "fmt"
)

type person struct {
    name string
    city string
    age  int8
}

func main() {
    var p2 = new(person)  //p2 是一个结构体指针
    p2.name = "测试"      //对结构体指针直接使用`.`来访问结构体的成员
    p2.age = 18
    p2.city = "北京"
    fmt.Printf("p2=%#v\n", p2)
}

输出结果:

复制代码
p2=&main.person{name:"测试", city:"北京", age:18}

p2.name = "测试"其实在底层是(*p2).name = "测试",这是Go语言帮我们实现的语法糖。

取结构体的地址实例化

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

复制代码
package main

import (
    "fmt"
)

type person struct {
    name string
    city string
    age  int8
}

func main() {
    p3 := &person{}
    fmt.Printf("%T\n", p3)     //*main.person
    fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}
    p3.name = "博客"
    p3.age = 30
    p3.city = "成都"
    fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"博客", city:"成都", age:30}
}

输出结果:

复制代码
*main.person
p3=&main.person{name:"", city:"", age:0}
p3=&main.person{name:"博客", city:"成都", age:30}

结构体作为函数参数

  1. 实例1:将结构体作为函数参数

    package main

    import "fmt"

    type Books struct {
    title string
    author string
    subject string
    book_id int
    }

    func main() {
    var Book1 Books /* 声明 Book1 为 Books 类型 /
    var Book2 Books /
    声明 Book2 为 Books 类型 */

    复制代码
     /* book 1 描述 */
     Book1.title = "Go 语言"
     Book1.author = "www.runoob.com"
     Book1.subject = "Go 语言教程"
     Book1.book_id = 6495407
    
     /* book 2 描述 */
     Book2.title = "Python 教程"
     Book2.author = "www.runoob.com"
     Book2.subject = "Python 语言教程"
     Book2.book_id = 6495700
    
     /* 打印 Book1 信息 */
     printBook(Book1)
    
     /* 打印 Book2 信息 */
     printBook(Book2)

    }

    func printBook(book Books) {
    fmt.Printf("Book title : %s\n", book.title)
    fmt.Printf("Book author : %s\n", book.author)
    fmt.Printf("Book subject : %s\n", book.subject)
    fmt.Printf("Book book_id : %d\n", book.book_id)
    }

输出结果:

复制代码
Book title : Go 语言
Book author : www.runoob.com
Book subject : Go 语言教程
Book book_id : 6495407
Book title : Python 教程
Book author : www.runoob.com
Book subject : Python 语言教程
Book book_id : 6495700

实例2:将结构体指针作为函数参数

复制代码
package main

import "fmt"

type Books struct {
    title   string
    author  string
    subject string
    book_id int
}

func main() {
    var Book1 Books /* 声明 Book1 为 Books 类型 */
    var Book2 Books /* 声明 Book2 为 Books 类型 */

    /* book 1 描述 */
    Book1.title = "Go 语言"
    Book1.author = "www.runoob.com"
    Book1.subject = "Go 语言教程"
    Book1.book_id = 6495407

    /* book 2 描述 */
    Book2.title = "Python 教程"
    Book2.author = "www.runoob.com"
    Book2.subject = "Python 语言教程"
    Book2.book_id = 6495700

    /* 打印 Book1 信息 */
    //地址就是一个指针,指针的实质就是一个地址
    printBook(&Book1)

    /* 打印 Book2 信息 */
    printBook(&Book2)
}
func printBook(book *Books) {
    fmt.Printf("Book title : %s\n", book.title)
    fmt.Printf("Book author : %s\n", book.author)
    fmt.Printf("Book subject : %s\n", book.subject)
    fmt.Printf("Book book_id : %d\n", book.book_id)
}

结构体内存布局

结构体内存布局是连续的

复制代码
package main

import "fmt"

type test struct {
    a int8
    b int8
    c int8
    d int8
}

func main() {
    n := test{
        1, 2, 3, 4,
    }
    fmt.Printf("n.a %p\n", &n.a)
    fmt.Printf("n.b %p\n", &n.b)
    fmt.Printf("n.c %p\n", &n.c)
    fmt.Printf("n.d %p\n", &n.d)
}

输出结果:

复制代码
n.a 0xc00000a198
n.b 0xc00000a199
n.c 0xc00000a19a
n.d 0xc00000a19b

构造函数

构造函数是一种特殊的函数,用来在对象实例化的时候初始化对象的成员变量。

复制代码
package main

import "fmt"

type person struct {
    name string
    city string
    age  int8
}

func main() {
    p9 := newPerson("pprof.cn", "测试", 90)
    fmt.Printf("%#v\n", p9)

}

//构造函数,用于初始化结构体person
func newPerson(name, city string, age int8) *person {
    return &person{
        name: name,
        city: city,
        age:  age,
    }
}

输出结果:

复制代码
&main.person{name:"pprof.cn", city:"测试", age:90}

方法和接收器

1、为结构体添加方法

复制代码
package main

type Bag struct {
    items []int
}

//定义在背包结构体上的名为Insert的方法
func (b *Bag) Insert(itemid int) {
    b.items = append(b.items, itemid)
}
func main() {
    b := new(Bag)
    //在结构体b上调用方法
    b.Insert(1001)
}

2、接收器------方法作用的对象

2、1指针类型的接收器

指针类型的接收器由一个结构体的指针组成,更接近于面向对象中的 this 或者 self。

由于指针的特性,调用方法时,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的。

在下面的例子,使用定义一个 Property 结构体,为 Property 添加 SetValue() 方法以封装设置属性的过程,通过属性的 Value() 方法可以重新获得属性的数值,使用属性时,通过 SetValue() 方法的调用,可以达成修改属性值的效果。

复制代码
package main
import "fmt"
// 定义结构体 Property 
type Property struct {
    value int  // 属性值
}
// 设置 Property 值
func (p *Property) SetValue(v int) {
    // 修改p的成员变量
    p.value = v
}
// 取 Property 值
func (p *Property) Value() int {
    return p.value
}
func main() {
    // 实例化 Property 
    p := new(Property)
    // 设置值
    p.SetValue(100)
    // 打印值
    fmt.Println(p.Value())
}

2、2 非指针类型的接收器

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

复制代码
package main
import (
    "fmt"
)
// 定义点结构
type Point struct {
    X int
    Y int
}
// 非指针接收器的加方法
func (p Point) Add(other Point) Point {
    // 成员值与参数相加后返回新的结构体
    return Point{p.X + other.X, p.Y + other.Y}
}
func main() {
    // 初始化点
    p1 := Point{1, 1}
    p2 := Point{2, 2}
    // 与另外一个点相加
    result := p1.Add(p2)
    // 输出结果
    fmt.Println(result)
}

输出结果:

复制代码
{3 3}

由于例子中使用了非指针接收器,Add() 方法变得类似于只读的方法,Add() 方法内部不会对成员进行任何修改。

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

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

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

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

嵌套结构体

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

复制代码
package main

import (
    "fmt"
)

//Address 地址结构体
type Address struct {
    Province string
    City     string
}

//User 用户结构体
type User struct {
    Name    string
    Gender  string
    Address Address
}

func main() {
    user1 := User{
        Name:   "pprof",
        Gender: "女",
        Address: Address{
            Province: "黑龙江",
            City:     "哈尔滨",
        },
    }
    fmt.Printf("user1=%#v\n", user1) //user1=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
}

输出结果:

复制代码
user1=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}

嵌套匿名结构体

复制代码
package main

import (
    "fmt"
)

//Address 地址结构体
type Address struct {
    Province string
    City     string
}

//User 用户结构体
type User struct {
    Name    string
    Gender  string
    Address //匿名结构体
}

func main() {
    var user2 User
    user2.Name = "pprof"
    user2.Gender = "女"
    user2.Address.Province = "黑龙江"   //通过匿名结构体.字段名访问
    user2.City = "哈尔滨"               //可以直接访问匿名结构体的字段名
    fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
}

输出结果:

复制代码
user2=main.User{Name:"pprof", 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 = "2000"   //指定Email结构体中的CreateTime
    fmt.Printf("user3=%#v\n", user3)
}

输出结果:

复制代码
user3=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"", City:"", CreateTime:"2000"}, Email:main.Email{Account:"", CreateTime:"2000"}}

如果直接使用user3.CreateTime = "2000"会报错:ambiguous selector user3.CreateTime

结构体的继承

复制代码
package main

import (
    "fmt"
)

//动物结构体
type Animal struct {
    name string
}


//动物结构体的move方法
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序列化

什么是对象序列化呢?

序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。

通俗讲呢:对象序列化就是指将对象的状态转换为字符串。

Json语法规则:

  • 数据在键值对中
  • 数据由逗号分隔
  • 花括号保存对象
  • 方括号保存数组

json格式1:

{ "firstName":"John" , "lastName":"Doe" }

json格式2:

复制代码
{
    "employees": [
        { "firstName":"John" , "lastName":"Doe" },
        { "firstName":"Anna" , "lastName":"Smith" },
        { "firstName":"Peter" , "lastName":"Jones" }
    ]
}

json格式3:

复制代码
var employees = [
    { "firstName":"Bill" , "lastName":"Gates" },
    { "firstName":"George" , "lastName":"Bush" },
    { "firstName":"Thomas" , "lastName": "Carter" }
];

demo:

复制代码
package main

import (
    "encoding/json"
    "fmt"
)

//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)
}

输出结果:

复制代码
json:{"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"}]}
&main.Class{Title:"101", Students:[]*main.Student{(*main.Student)(0xc0000749c0), (*main.Student)(0xc0000749f0), (*main.Student)(0xc000074a20), (*main.Student)(0xc000074a50), (*main.Student)(0xc000074ab0), (*main.Student)(0xc000074ae0), (*main.Student)(0xc000074b10), (*main.Student)(0xc000074b40), (*main.Student)(0xc000074b70), (*main.Student)(0xc000074ba0)}}

结构体标签(Tag)

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

Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

`key1:"value1" key2:"value2"`

结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。 注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

例如我们为Student结构体的每个字段定义json序列化时使用的Tag:

复制代码
package main

import (
    "encoding/json"
    "fmt"
)

//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":"女"}
相关推荐
码界筑梦坊17 分钟前
98-基于Python的网上厨房美食推荐系统
开发语言·python·美食
光爷不秃27 分钟前
Go语言中安全停止Goroutine的三种方法及设计哲学
开发语言·安全·golang
bobz96533 分钟前
恶补 vhost,vDPA
后端
lpfasd12338 分钟前
非中文语音视频自动生成中文字幕的完整实现方案
开发语言·python
泉城老铁1 小时前
在高并发场景下,如何优化线程池参数配置
spring boot·后端·架构
泉城老铁1 小时前
Spring Boot中实现多线程6种方式,提高架构性能
spring boot·后端·spring cloud
昵称为空C1 小时前
SpringBoot 实现DataSource接口实现多租户数据源切换方案
后端·mybatis
hqwest1 小时前
C#WPF实战出真汁05--左侧导航
开发语言·c#·wpf·主界面·窗体设计·视图viewmodel
NEUMaple2 小时前
python爬虫(四)----requests
开发语言·爬虫·python
sufu10652 小时前
说说内存泄漏的常见场景和排查方案?
java·开发语言·面试