定义结构体与实例化
单一的数据类型已经满足不了现实开发需求,于是Go语言提供了结构体来定义复杂的数据类型。结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
结构体的定义格式,具体示例如下。
type 类型名 struct {
成员属性1 类型1
成员属性2 类型2
成员属性3, 成员属性4 类型3
}
在使用结构体的过程中注意以下三点。
类型名:标识结构体的名称,在同一个包内不能重复。
结构体中属性,也叫字段,必须唯一。
同类型的成员属性可以写在一行。
定义结构体与实例化
结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正分配内存。因此必须在定义结构体并实例化后才能使用结构体;
实例化就是根据结构体定义的格式创建一份与格式一致的内存区域。结构体实例之间的内存是完全独立的。实例化的语法如例所示。
package main
import "fmt"
//定义Teacher结构体
type Teacher struct {
name string
age int8
sex byte
}
func main() {
//1、var声明方式实例化结构体,初始化方式为:对象.属性=值
var t1 Teacher
fmt.Println(t1)
fmt.Printf("t1:%T , %v , %q \n", t1, t1, t1)
//if t1 == nil {
// fmt.Println()
//}
t1.name = "Steven"
t1.age = 35
t1.sex = 1
fmt.Println(t1)
fmt.Println("-------------------")
//2、变量简短声明格式实例化结构体,初始化方式为:对象.属性=值
t2 := Teacher{}
t2.name = "David"
t2.age = 30
t2.sex = 1
fmt.Println(t2)
fmt.Println("-------------------")
//3、变量简短声明格式实例化结构体,声明时初始化。初始化方式为:属性:值 。属性:值可以同行,也可以换行。(类似map的用法)
t3 := Teacher{
name: "Josh",
age: 28,
sex: 1,
}
t3 = Teacher{name: "Josh2", age: 27, sex: 1}
fmt.Println(t3)
fmt.Println("-------------------")
//4、变量简短声明格式实例化结构体,声明时初始化,不写属性名,按属性顺序只写属性值
t4 := Teacher{"Ruby", 30, 0}
fmt.Println(t4)
fmt.Println("-------------------")
}
运行
{ 0 0}
t1:main.Teacher , { 0 0} , {"" '\x00' '\x00'}
{Steven 35 1}
-------------------
{David 30 1}
-------------------
{Josh2 27 1}
-------------------
{Ruby 30 0}
-------------------
结构体的语法糖
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。
通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。结构体和数组中都含有语法糖。
使用内置函数new()对结构体进行实例化,结构体实例化后形成指针类型的结构体。new内置函数会分配内存。第一个参数是类型,而不是值,返回的值是指向该类型新分配的零值的指针。该函数用于创建某个类型的指针。
package main
import "fmt"
//定义结构体 Emp
type Emp struct {
name string
age int8
sex byte
}
func main() {
//使用new()内置函数实例化struct
emp1 := new(Emp)
fmt.Printf("emp1: %T , %v , %p \n", emp1, emp1, emp1)
(*emp1).name = "David"
(*emp1).age = 30
(*emp1).sex = 1
//语法糖写法
emp1.name = "David2"
emp1.age = 31
emp1.sex = 1
fmt.Println(emp1)
fmt.Println("-----------------")
SyntacticSugar()
}
func SyntacticSugar() {
// 数组中的语法糖
arr := [4]int{10, 20, 30, 40}
arr2 := &arr
fmt.Println((*arr2)[len(arr)-1])
fmt.Println(arr2[0])
// 切片中的语法糖?
arr3 := []int{100, 200, 300, 400}
arr4 := &arr3
fmt.Println((*arr4)[len(arr)-1])
//fmt.Println(arr4[0])
}
运行
emp1: *main.Emp , &{ 0 0} , 0xc0000044a0
&{David2 31 1}
-----------------
40
10
400
结构体是值类型
结构体作为函数参数,若复制一份传递到函数中,在函数中对参数进行修改,不会影响到实际参数。证明结构体是值类型。如例所示。
package main
import "fmt"
type Human struct {
name string
age int8
sex byte
}
func main() {
//1、初始化Human
h1 := Human{"Steven", 35, 1}
fmt.Printf("h1: %T , %v, %p \n", h1, h1, &h1)
fmt.Println("--------------------")
//将结构体对象拷贝
h2 := h1
h2.name = "David"
h2.age = 30
fmt.Printf("h2修改后: %T , %v, %p \n", h2, h2, &h2)
fmt.Printf("h1: %T , %v, %p \n", h1, h1, &h1)
fmt.Println("--------------------")
//将结构体对象作为参数传递
changeName(h1)
fmt.Printf("h1: %T , %v, %p \n", h1, h1, &h1)
}
func changeName(h Human) {
h.name = "Daniel"
h.age = 13
fmt.Printf("函数体内修改后:%T, %v , %p \n" , h , h , &h)
}
运行
h1: main.Human , {Steven 35 1}, 0xc0000044a0
--------------------
h2修改后: main.Human , {David 30 1}, 0xc000004520
h1: main.Human , {Steven 35 1}, 0xc0000044a0
--------------------
函数体内修改后:main.Human, {Daniel 13 1} , 0xc0000045c0
h1: main.Human , {Steven 35 1}, 0xc0000044a0
结构体的深拷贝和浅拷贝
值类型是深拷贝,深拷贝就是为新的对象分配了内存。引用类型是浅拷贝,浅拷贝只是复制了对象的指针。结构体的拷贝实例,如例所示。
package main
import (
"fmt"
)
type Dog struct {
name string
color string
age int8
kind string
}
func main() {
//1、实现结构体的深拷贝
//struct是值类型,默认的复制就是深拷贝
d1 := Dog{"豆豆", "黑色", 2, "二哈"}
fmt.Printf("d1: %T , %v , %p \n", d1, d1, &d1)
d2 := d1 //深拷贝
fmt.Printf("d2: %T , %v , %p \n", d2, d2, &d2)
d2.name = "毛毛"
fmt.Println("d2修改后:", d2)
fmt.Println("d1:", d1)//d1没有因为d2的改变而改变,所以是深拷贝
fmt.Println("------------------")
//2、实现结构体浅拷贝:直接赋值指针地址
d3 := &d1
fmt.Printf("d3: %T , %v , %p \n", d3, d3, d3)
d3.name = "球球"
d3.color = "白色"
d3.kind = "萨摩耶"
fmt.Println("d3修改后:", d3)
fmt.Println("d1:", d1)//d1因为d3的改变而改变,所以是浅拷贝
fmt.Println("------------------")
//3、实现结构体浅拷贝:通过new()函数来实例化对象
d4 := new(Dog)
d4.name = "多多"
d4.color = "棕色"
d4.age = 1
d4.kind = "巴哥犬"
d5 := d4
fmt.Printf("d4: %T , %v , %p \n", d4, d4, d4)
fmt.Printf("d5: %T , %v , %p \n", d5, d5, d5)
fmt.Println("------------------")
d5.color = "金色"
d5.kind = "金毛"
fmt.Println("d5修改后:", d5)
fmt.Println("d4:", d4)
fmt.Println("------------------")
}
运行
h1: main.Human , {Steven 35 1}, 0xc0000044a0
--------------------
h2修改后: main.Human , {David 30 1}, 0xc000004520
h1: main.Human , {Steven 35 1}, 0xc0000044a0
--------------------
函数体内修改后:main.Human, {Daniel 13 1} , 0xc0000045c0
h1: main.Human , {Steven 35 1}, 0xc0000044a0
D:\www\go>go run test.go
d1: main.Dog , {豆豆 黑色 2 二哈} , 0xc000044040
d2: main.Dog , {豆豆 黑色 2 二哈} , 0xc000044100
d2修改后:{毛毛 黑色 2 二哈}
d1:{豆豆 黑色 2 二哈}
------------------
d3: *main.Dog , &{豆豆 黑色 2 二哈} , 0xc000044040
d3修改后:&{球球 白色 2 萨摩耶}
d1:{球球 白色 2 萨摩耶}
------------------
d4: *main.Dog , &{多多 棕色 1 巴哥犬} , 0xc000044300
d5: *main.Dog , &{多多 棕色 1 巴哥犬} , 0xc000044300
------------------
d5修改后:&{多多 金色 1 金毛}
d4:&{多多 金色 1 金毛}
------------------
结构体作为函数的参数及返回值
结构体作为函数的参数及返回值有两种形式,值传递和引用传递,接下来通过案例了解两者的区别
package main
import "fmt"
type Flower struct {
name, color string
}
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)
fmt.Println("----------------------")
// 将结构体指针作为参数
changeInfo2(&f1)
fmt.Printf("f1: %T , %v , %p \n" , f1 , f1 , &f1)
fmt.Println("----------------------")
//2、结构体作为返回值的用法
//结构体对象作为返回值
f2 := getFlower1()
f3 := getFlower1()
fmt.Println("更改前" , f2 , f3)
f2.name = "杏花"
fmt.Println("更改后" , f2 , f3)
fmt.Printf("f2地址为 %p,f3地址为%p\n ", &f2, &f3)
//结构体指针作为返回值
f4 := getFlower2()
f5 := getFlower2()
fmt.Println("更改前" , f4 , f5)
f4.name = "桃花"
fmt.Println("更改后" , f4 , f5)
}
//返回结构体对象
func getFlower1() (f Flower){
f = Flower{"牡丹", "白"}
fmt.Printf("函数getFlower1内f: %T , %v , %p \n" , f , f , &f)
return
}
//返回结构体指针
func getFlower2() (f *Flower){
//f = &Flower{"芙蓉", "红"}
temp := Flower{"芙蓉", "红"}
fmt.Printf("函数getFlower2内temp: %T , %v , %p \n" , temp , temp , &temp)
f = &temp
fmt.Printf("函数getFlower2内f: %T , %v , %p , %p \n" , f , 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 , {玫瑰 红} , 0xc0000044a0
----------------------
函数changeInfo1内f: main.Flower , {月季 粉} , 0xc000004520
f1: main.Flower , {玫瑰 红} , 0xc0000044a0
----------------------
函数changeInfo2内f: *main.Flower , &{蔷薇 紫} , 0xc0000044a0 , 0xc000006030
f1: main.Flower , {蔷薇 紫} , 0xc0000044a0
----------------------
函数getFlower1内f: main.Flower , {牡丹 白} , 0xc000004640
函数getFlower1内f: main.Flower , {牡丹 白} , 0xc0000046c0
更改前 {牡丹 白} {牡丹 白}
更改后 {杏花 白} {牡丹 白}
f2地址为 0xc000004620,f3地址为0xc0000046a0
函数getFlower2内temp: main.Flower , {芙蓉 红} , 0xc0000047a0
函数getFlower2内f: *main.Flower , &{芙蓉 红} , 0xc0000047a0 , 0xc000006038
函数getFlower2内temp: main.Flower , {芙蓉 红} , 0xc000004820
函数getFlower2内f: *main.Flower , &{芙蓉 红} , 0xc000004820 , 0xc000006040
更改前 &{芙蓉 红} &{芙蓉 红}
更改后 &{桃花 红} &{芙蓉 红}