您现在的位置是:自如初>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)