31、Go 结构体 - 结构体的相关使用

作者: 温新

分类: 【Go基础】

阅读: 713

时间: 2023-08-29 16:12:40

hi,我是温新

结构体是值类型

结构体若作为参数传递到函数中,在函数中对参数进行修改不会影响到实际参数。

package main

import "fmt"

func main() {
    h1 := Human{"王美丽", 20, 1}
    fmt.Printf("%T, %v, %p\n", h1, h1, &h1)

    // 赋值结构体
    h2 := h1
    h2.name = "二哈"
    fmt.Printf("h2修改后:%T, %v, %p\n", h2, h2, &h2)

    // 结构体作为参数传递
    changeName(h1)
    fmt.Printf("%T, %v, %p\n", h1, h1, &h1)
}

type Human struct {
    name string
    age  int8
    sex  byte
}

func changeName(h Human) {
    h.name = "王小丽"
    fmt.Printf("在函数中修改后:%T, %v, %p\n", h, h, &h)
}

输出结果

main.Human, {王美丽 20 1}, 0xc000008078
h2修改后:main.Human, {二哈 20 1}, 0xc0000080c0
在函数中修改后:main.Human, {王小丽 20 1}, 0xc000008108
main.Human, {王美丽 20 1}, 0xc000008078

从结果中可以看到,修改 h2 对 h1 没有影响,把 h1 作为参数传递给函数然后进行修改,依旧没有对 h1 造成影响。

结构体的深拷贝与浅拷贝

值类型是深拷贝,深拷贝就是为新的对象分配了内存。引用类型是浅拷贝,浅拷贝只是复制了对象的指针。

package main

import "fmt"

func main() {
    // 结构体深拷贝
    dog1 := Dog{"二哈", "棕色", 2}
    fmt.Printf("dog1:%T, %v, %p\n", dog1, dog1, &dog1)
    // 1、深拷贝
    dog2 := dog1
    fmt.Printf("dog2:%T, %v, %p\n", dog2, dog2, &dog2)
    dog2.name = "中华田园犬-小花"
    fmt.Println("dog2 修改后:", dog2)
    fmt.Println("dog1:", dog1)
    fmt.Println("------------------")

    // 2、结构体浅拷贝,复制指针地址
    dog3 := &dog1
    fmt.Printf("dog3:%T, %v, %p\n", dog3, dog3, dog3)
    dog3.name = "边牧"
    dog3.color = "黑色"
    fmt.Println("dog3修改后:", dog3)
    fmt.Println("dog1:", dog1)
    fmt.Println("------------------")

    // 3、结构体浅拷贝,new 函数
    dog4 := new(Dog)
    dog4.name = "拉布拉多"
    dog5 := dog4
    fmt.Printf("dog4:%T, %v, %p\n", dog4, dog4, dog4)
    fmt.Printf("dog5:%T, %v, %p\n", dog5, dog5, dog5)
    fmt.Println("------------------")
    dog5.color = "黄色"
    fmt.Println("dog5 修改后:", dog5)
    fmt.Println("dog4:", dog4)
}

type Dog struct {
    name, color string
    age         int8
}

输出结果

dog1:main.Dog, {二哈 棕色 2}, 0xc000114510
dog2:main.Dog, {二哈 棕色 2}, 0xc0001145a0
dog2 修改后: {中华田园犬-小花 棕色 2}
dog1: {二哈 棕色 2}
------------------
dog3:*main.Dog, &{二哈 棕色 2}, 0xc000114510
dog3修改后: &{边牧 黑色 2}
dog1: {边牧 黑色 2}
------------------
dog4:*main.Dog, &{拉布拉多  0}, 0xc000114720
dog5:*main.Dog, &{拉布拉多  0}, 0xc000114720
------------------
dog5 修改后: &{拉布拉多 黄色 0}
dog4: &{拉布拉多 黄色 0}

结构体作为函数的参数及返回值

结构体作为函数的参数及返回值有两种形式:值传递和引用传递。

package main

import "fmt"

func main() {
    /**
     * 1、结构体作为参数的用法
     */
    f1 := Flower{"水仙花", "白色"}
    fmt.Printf("f1:%T,%v,%p\n", f1, f1, &f1)
    fmt.Println("-----------------------")
    // 结构体对象作为函数参数
    changeInfo1(f1)
    fmt.Printf("f1:%T,%v,%p\n", f1, f1, &f1)
    // 结构体指针作为函数参数
    changeInfo2(&f1)
    fmt.Printf("f1:%T,%v,%p\n", f1, f1, &f1)
    fmt.Println("-----------------------")

    /**
     * 结构体作为返回值的用法
     */
    // 结构体对象作为返回值
    f2 := getFlower1()
    f3 := getFlower1()
    fmt.Println("修改前:", f2, f3)
    fmt.Printf("f2地址为 %p,f3 地址为%p\n", &f2, &f3) // 地址发生变化,对象发生了复制
    f2.name = "杏花"
    fmt.Println("修改后:", f2, f3)
    fmt.Println("-----------------------")

    // 结构体作为指针返回值
    f4 := getFlower2()
    f5 := getFlower2()
    fmt.Println("修改前:", f4, f5)
    f4.name = "樱花"
    fmt.Println("修改后:", f4, f5)
}

type Flower struct {
    name, color string
}

// 返回结构体对象
func getFlower1() (f Flower) {
    f = Flower{"菊花", "黄色"}
    fmt.Printf("函数 getFlower1 内 f:%T, %v, %p\n", f, f, &f)

    return
}

// 返回结构体指针
func getFlower2() (f *Flower) {
    temp := Flower{"玫瑰", "红色"}
    fmt.Printf("函数 getFlower2 内 temp:%T, %v, %p\n", temp, temp, &temp)
    f = &temp
    fmt.Printf("函数 getFlower2 内 f:%T, %v, %p\n", f, f, &f)

    return
}

// 传递结构对对象
func changeInfo1(f Flower) {
    f.name = "月季"
    f.color = "粉色"
    fmt.Printf("函数 changeInfo1 内 f:%T, %v, %p\n", f, f, &f)
}

// 传递结构体指针
func changeInfo2(f *Flower) {
    f.name = "蔷薇"
    f.color = "紫色"
    fmt.Printf("函数 changeInfo2 内 f:%T, %v, %p, %p\n", f, f, f, &f)
}

输出结果

f1:main.Flower,{水仙花 白色},0xc0000603c0
-----------------------
函数 changeInfo1 内 f:main.Flower, {月季 粉色}, 0xc000060440
f1:main.Flower,{水仙花 白色},0xc0000603c0
函数 changeInfo2 内 f:*main.Flower, &{蔷薇 紫色}, 0xc0000603c0, 0xc00000a030
f1:main.Flower,{蔷薇 紫色},0xc0000603c0
-----------------------
函数 getFlower1 内 f:main.Flower, {菊花 黄色}, 0xc000060560
函数 getFlower1 内 f:main.Flower, {菊花 黄色}, 0xc0000605e0
修改前: {菊花 黄色} {菊花 黄色}
f2地址为 0xc000060540,f3 地址为0xc0000605c0
修改后: {杏花 黄色} {菊花 黄色}
-----------------------
函数 getFlower2 内 temp:main.Flower, {玫瑰 红色}, 0xc0000606c0
函数 getFlower2 内 f:*main.Flower, &{玫瑰 红色}, 0xc00000a038
函数 getFlower2 内 temp:main.Flower, {玫瑰 红色}, 0xc000060740
函数 getFlower2 内 f:*main.Flower, &{玫瑰 红色}, 0xc00000a040
修改前: &{玫瑰 红色} &{玫瑰 红色}
修改后: &{樱花 红色} &{玫瑰 红色}

一定要动手写一边,一边写一边执行,观察变化,哪里不同。

匿名结构体和匿名字段

匿名结构体

匿名结构体就是没有名字的结构体,不用通过type 关键字定义就可以直接使用。

创建匿名结构体时,同时要创建对象。匿名结构体由结构体定义和键值对初始化两部分组成,语法如下:

variableName := struct {
	// 成员属性
}{
	// 初始化成员属性
}

匿名结构体案例

package main

import (
	"fmt"
	"math"
)

func main() {
    // 匿名函数
    res := func(m, n float64) float64 {
        return math.Pow(m, n)
    }(2, 3)
    fmt.Println(res)

    // 匿名结构体
    add := struct {
        province, city string
    }{"湖北省", "武汉市"}
    fmt.Println(add)
}

结构体的匿名字段

匿名字段就是在结构体中的字段没有名字,只包含一个没有字段名的类型,这些字段就是匿名字段。

若字段没有名字,则默认使用类型作为字段名,同一个类型只能有一个匿名字段。

package main

import "fmt"

func main() {
    // 实例化结构体
    user := User{"王美丽", 19, 168.7}
    fmt.Println(user)
    fmt.Printf("名字:%s\n", user.string)
    fmt.Printf("年龄:%d\n", user.int8)
    fmt.Printf("身高:%.2f\n", user.float64)
}

type User struct {
    string
    int8
    float64
}

看到案例应该发现一个问题了,匿名字段很不好你,完全不知道它代表啥子鬼东西。

嵌套结构体

将结构体作为另一个结构体的属性,这种结构就是结构体嵌套。

结构体嵌套可以模拟面向对象编程中的两种关系:

1、聚合关系:一个类作为另一个类的属性;

2、继承关系:一个类作为另一个类的子类。

嵌套结构体-聚合关系

package main

import "fmt"

func main() {
    // 模拟对象之间的聚合关系
    p := Person{}
    p.name = "王美丽"
    p.age = 19
    // 地址赋值
    addr := Address{}
    addr.province = "湖北"
    addr.city = "武汉"
    p.address = &addr

    fmt.Println(p)
    fmt.Println("姓名:", p.name, " 年龄:", p.age, "省:", p.address.province, "市:", p.address.city)
    fmt.Println("-------------")

    // 修改 Person 对象嵌套对象的数据会影响原数据
    p.address.city = "宜昌"
    fmt.Println("姓名:", p.name, " 年龄:", p.age, "省:", p.address.province, "市:", p.address.city, "addr.cidy", addr.city)
    fmt.Println("-------------")

    // 修改 Address 中的数据,会影响到 Person 使用的嵌套数据
    addr.city = "襄阳"
    fmt.Println("姓名:", p.name, " 年龄:", p.age, "省:", p.address.province, "市:", p.address.city, "addr.cidy", addr.city)
}

type Address struct {
    province, city string
}

type Person struct {
    name    string
    age     int8
    address *Address
}

输出结果

{王美丽 19 0xc0000603c0}
姓名: 王美丽  年龄: 19 省: 湖北 市: 武汉
-------------
姓名: 王美丽  年龄: 19 省: 湖北 市: 宜昌 addr.cidy 宜昌
-------------
姓名: 王美丽  年龄: 19 省: 湖北 市: 襄阳 addr.cidy 襄阳

传递嵌套关系时,若不使用引用方式传递时,不会互相影响。这个案例使用了 &addr 进行传递指针,因此彼此受到影响。

嵌套结构体-继承关系

在结构体中,属于匿名结构体的字段称为提升字段,它们可以被访问,匿名结构体就像是该结构体的父类。

采用匿名字段的形式就是模拟继承关系。而模拟聚合关系时一定要采用有名字的结构体作为字段。

package main

import "fmt"

func main() {
    // 1、实例化并初始化 Person2
    p2 := Person2{"王美丽", 32, "女"}
    fmt.Println(p2)
    fmt.Println("--------------------")

    // 2、实例化并初始化 Student
    // 写法一
    s1 := Student{p2, "中国人民大学"}
    printInfo(s1)
    // 写法二
    s2 := Student{Person2{"郝帅", 40, "男"}, "上海交通大学"}
    printInfo(s2)
    // 写法三
    s3 := Student{Person2: Person2{
        name: "王大美",
        age:  42,
        sex:  "女",
    },
        schoolName: "武汉大学",
    }
    printInfo(s3)
    // 写法四
    s4 := Student{}
    s4.name = "Lcuy"
    s4.sex = "女"
    s4.age = 30
    s4.schoolName = "华中师范大学"
    printInfo(s4)
}

type Person2 struct {
    name string
    age  int8
    sex  string
}

type Student struct {
    Person2
    schoolName string
}

func printInfo(s Student) {
    fmt.Println(s)
    fmt.Printf("%+v\n", s)
    fmt.Printf("姓名:%s, 年龄:%d, 性别:%s, 学校:%s\n", s.name, s.age, s.sex, s.schoolName)
    fmt.Println("--------------------")
}

输出结果

{王美丽 32 女}
--------------------
{{王美丽 32 女} 中国人民大学}
{Person2:{name:王美丽 age:32 sex:女} schoolName:中国人民大学}
姓名:王美丽, 年龄:32, 性别:女, 学校:中国人民大学
--------------------
{{郝帅 40 男} 上海交通大学}
{Person2:{name:郝帅 age:40 sex:男} schoolName:上海交通大学}
姓名:郝帅, 年龄:40, 性别:男, 学校:上海交通大学
--------------------
{{王大美 42 女} 武汉大学}
{Person2:{name:王大美 age:42 sex:女} schoolName:武汉大学}
姓名:王大美, 年龄:42, 性别:女, 学校:武汉大学
--------------------
{{Lcuy 30 女} 华中师范大学}
{Person2:{name:Lcuy age:30 sex:女} schoolName:华中师范大学}
姓名:Lcuy, 年龄:30, 性别:女, 学校:华中师范大学
--------------------

在这个案例中,Person2 相当是一个父类,而 Student 就是一个子类,该子类继承了Person2,因此可以直接使用父类中的 schoolName 属性。

命名冲突

当两个字段拥有相关的名字时会产生冲突,一般有两种情况:

1、外层字段的名字覆盖内层字段的名字,但两者的内存空间都会保留,这提供了一种重载字段 或方法的方式;

2、相同的名字在同层次结构体中出现了重复,并且这个字段被程序调用了,这将导致程序错误。

请登录后再评论