您现在的位置是:自如初>Go基础Go基础

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

温新 2023-08-30 00:12:40 Go基础 96人已围观

简介本篇文章学习结构体的相关使用

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、相同的名字在同层次结构体中出现了重复,并且这个字段被程序调用了,这将导致程序错误。

很赞哦!(2)

文章评论

登录 注册

自如初--时间轴

站名:自如初

独白:向前走!向前走!

邮箱:ziruchu@qq.com

RSS: RSS

站点信息