结构体struct
一 结构体介绍
什么是struct?
struct是go语言为我们提供的可以自定义的一种类型,该类型可以封装多个基本数据类型,可以用来存放一个事物的不同属性
为何要有struct?
原因有二:
1、Go语言内置的基础数据类型只能用来记录一个值,复合类型数组、切片、map等也虽然能存多个值,但多个值都必须是同一属性的,如果我们想把一个事物的多个不同属性都记录下来,我们之前学过的内置类型都无法满足需求,此时就可以定义一个结构体struct
类型来实现。
2、Go语言中通过struct
来实现面向对象。Go语言中没有“类”的概念(或者说结构体struct充当的就是类的作用),也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口以达到比面向对象更高的扩展性和灵活性。
二 知识储备
2.1 自定义类型
Go语言中可以使用type
关键字来定义自定义类型。我们可以基于go语言内置string
、整型
、浮点型
、布尔
等基本类型来自定义我们自己的类型,也可以基于struct来定义,但无论是基于什么,我们都是创造了一全新的类型,新类型具备原类型的一些特性,但它们是截然不同的两种类型
type NewInt int // NewInt具备int类型的特性,但它们是两种类型
var x NewInt = 10
var y int = 10
fmt.Println(x+y) // panic: (mismatched types NewInt and int)
2.2 类型别名
类型别名是Go1.9
版本添加的新功能。类型别名只是给某个类型起了一个别名,并没有创建新类型,事实上,类型的别名也只是在代码里的概念,在编译完成后所有的别名都会被转换成其真正的类型
type MyInt = int
var a MyInt = 10
fmt.Printf("%T",a) // 打印a的类型就是int
三 自定义结构体类型
使用type
和struct
关键字来定义结构体,语法如下:
type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}
// 注意:
类型名:或称结构体名,在同一个包内不能重复。
字段名:或称属性名,结构体中的字段名必须唯一。
字段类型:结构体字段的具体类型。
示例
type Person struct {
name string
age int
sex string
city string
hobbbies []string
sanwei map[string]float64
}
// 类型相同的字段也可以写在同一行
type Person struct {
name, sex, city string // 写在一行
age int
hobbbies []string
sanwei map[string]float64
}
四 结构体“实例化”
须知我们自定义的结构体本质就是一种数据类型,我们可以将声明一个结构体类型的变量称之为结构体的实例化,如下
var p1 Person // 零值填充结构体并把结构体的值赋值给p1
fmt.Printf("%T\n",p1) // main.Person
只有当结构体实例化时,才会真正地分配内存,分配内存之后才能使用结构体的字段,但是上述实例化的过程都是用零值填充结构体,此时访问到的字段值都是初始的零值
// 使用.操作符访问字段/属性
fmt.Println(p1.name) // 空串
fmt.Println(p1.sex) // 空串
fmt.Println(p1.age) // 0
fmt.Println(p1.hobbbies == nil) // true
fmt.Println(p1.sanwei == nil) // true
我们可以在实例化后,再为实例赋值属性,如下
// p1、p2、p3除了类型上不同之外,其他的用法完全一致
p1.name = "egon"
p1.age = 18
p1.sex = "male"
p1.city = "Shanghai"
p1.hobbbies = []string{"play", "music", "read"}
p1.sanwei = map[string]float64{
"xw": 200.3,
"yw": 30.3,
"tw": 200.5,
}
也完全可以在实例化的同时为其属性赋值,赋值方式有两种
// 方式一:键值方式
// 特点:
// (1) 指定key和value,指名道姓地赋值,不依赖声明的字段顺序
// (2) 不必为所有字段/属性赋值,没有指定初始值的字段的值就是该字段类型的零值。
var p1 Person = Person{
name: "egon",
hobbbies: []string{"read", "music"},
sanwei: map[string]float64{
"xw": 200.3,
"yw": 30.3,
"tw": 200.5,
},
}
fmt.Println(p1.sanwei["xw"])
fmt.Println(p1) // {egon 0 [read music] map[tw:200.5 xw:200.3 yw:30.3]}
// 方式二:列表方式
// 特点:
// (1) 直接写值,按照位置赋值,严格依赖声明的字段顺序
// (2) 必须为所有字段/属性赋值
// (3) 该方式不能与方式一混用
var p1 Person = Person{
"egon",
18,
"male",
"Shanghai",
[]string{"read", "music"},
map[string]float64{
"xw": 200.3,
"yw": 30.3,
"tw": 200.5,
},
}
针对临时使用的场景,我们可以定义匿名结构体,即无需使用type定义结构体类型,而直接在var中使用
var info struct {
name string
age int
sex string
}
info.name = "egon"
info.age = 18
info.sex = "male"
练习:创造一个书籍的结构体,实例化出多本书,并打印书籍信息
package main
import "fmt"
type Books struct {
name string
author string
book_id int
}
func main() {
var Book1 Books /* 声明 Book1 为 Books 类型 */
var Book2 Books /* 声明 Book2 为 Books 类型 */
/* book 1 描述 */
Book1.name = "Go 语言从入门到成仙"
Book1.author = "钟馗"
Book1.book_id = 4578
/* book 2 描述 */
Book2.name = "Python 语言从入门到入坟"
Book2.author = "张三丰"
Book2.book_id = 4579
/* 打印 Book1 信息 */
fmt.Printf( "Book 1 name : %s\n", Book1.name)
fmt.Printf( "Book 1 author : %s\n", Book1.author)
fmt.Printf( "Book 1 book_id : %d\n", Book1.book_id)
/* 打印 Book2 信息 */
fmt.Printf( "Book 2 name : %s\n", Book2.name)
fmt.Printf( "Book 2 author : %s\n", Book2.author)
fmt.Printf( "Book 2 book_id : %d\n", Book2.book_id)
}
五 结构体指针
结构体为值类型,来回传递结构体的开销会比较大,此时,我们可以声明结构体指针
var p2 *Person
fmt.Println(p2 == nil) // true
p2为nil,未开辟空间,无法直接使用,需要为其开辟好内存空间,有以下两种方式
// 1、声明结构体指针:基于new开辟好内存空间,p3不为nil
p3:=new(Person)
fmt.Println(p3 == nil) // false
// 2、声明结构体指针:
p4:=&Person{} // 与new等同
fmt.Println(p4 == nil) // false
该方式可以在声明的同时为字段赋值,如下
p4 = &Person{
name: "egon",
age: 18,
}
通过结构体指针为字段赋值,以p3为例,p4也一样
(*p3).name = "egon"
(*p3).age = 18
(*p3).sex = "male"
// 注意!!!
类似(*p3).name = "egon"的操作,可以简写成p3.name = "egon",这是Go语言帮我们实现的语法糖。
fmt.Printf("%T\n",p3) // *main.Person
fmt.Println(p3.name,p3.age) // egon 18
结构体指针会降低传参的开销
package main
import "fmt"
type Person struct {
name string
age int
sex string
city string
hobbbies []string
sanwei map[string]float64
}
func main() {
p3:=new(Person)
fmt.Println(p3 == nil) // false
(*p3).name = "egon"
(*p3).age = 18
(*p3).sex = "male"
tell(p3)
fmt.Println(p3.name) // EGON
}
func tell(p *Person){
fmt.Printf("%v:%v:%v\n",p.name,p.age,p.sex)
p.name="EGON"
}
ps:内建函数make(T, args)与new(T)
//1、相同点:
都是用来为某一类型开辟内存空间,并用零值填充到内存空间中
//2、不同点
make通常只用于slice、map和channel开辟内存空间,并且用零值填充,然后返回一个T类型的值,而不是指针*T。
new(T)用于为各种类型开辟内存空间,并用零值填充,然后返回其地址,即一个指针*T