什么是struct?
struct是go语言为我们提供的可以自定义的一种类型,该类型可以封装多个基本数据类型,可以用来存放一个事物的不同属性
为何要有struct?
原因有二:
1、Go语言内置的基础数据类型只能用来记录一个值,复合类型数组、切片、map等也虽然能存多个值,但多个值都必须是同一属性的,如果我们想把一个事物的多个不同属性都记录下来,我们之前学过的内置类型都无法满足需求,此时就可以定义一个结构体struct
类型来实现。
2、Go语言中通过struct
来实现面向对象。Go语言中没有“类”的概念(或者说结构体struct充当的就是类的作用),也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口以达到比面向对象更高的扩展性和灵活性。
Go语言中可以使用type
关键字来定义自定义类型。我们可以基于go语言内置string
、整型
、浮点型
、布尔
等基本类型来自定义我们自己的类型,也可以基于struct来定义,但无论是基于什么,我们都是创造了一全新的类型,新类型具备原类型的一些特性,但它们是截然不同的两种类型
| type NewInt int |
| |
| var x NewInt = 10 |
| var y int = 10 |
| |
| fmt.Println(x+y) |
类型别名是Go1.9
版本添加的新功能。类型别名只是给某个类型起了一个别名,并没有创建新类型,事实上,类型的别名也只是在代码里的概念,在编译完成后所有的别名都会被转换成其真正的类型
| type MyInt = int |
| |
| var a MyInt = 10 |
| |
| fmt.Printf("%T",a) |
使用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 |
| |
| fmt.Printf("%T\n",p1) |
只有当结构体实例化时,才会真正地分配内存,分配内存之后才能使用结构体的字段,但是上述实例化的过程都是用零值填充结构体,此时访问到的字段值都是初始的零值
| |
| fmt.Println(p1.name) |
| fmt.Println(p1.sex) |
| fmt.Println(p1.age) |
| fmt.Println(p1.hobbbies == nil) |
| fmt.Println(p1.sanwei == nil) |
我们可以在实例化后,再为实例赋值属性,如下
| |
| 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, |
| } |
也完全可以在实例化的同时为其属性赋值,赋值方式有两种
| |
| |
| |
| |
| 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) |
| |
| |
| |
| |
| |
| |
| 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 |
| var Book2 Books |
| |
| |
| Book1.name = "Go 语言从入门到成仙" |
| Book1.author = "钟馗" |
| Book1.book_id = 4578 |
| |
| |
| Book2.name = "Python 语言从入门到入坟" |
| Book2.author = "张三丰" |
| Book2.book_id = 4579 |
| |
| |
| 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) |
| |
| |
| 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) |
p2为nil,未开辟空间,无法直接使用,需要为其开辟好内存空间,有以下两种方式
| |
| p3:=new(Person) |
| fmt.Println(p3 == nil) |
| |
| |
| p4:=&Person{} |
| fmt.Println(p4 == nil) |
| |
| 该方式可以在声明的同时为字段赋值,如下 |
| 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) |
| fmt.Println(p3.name,p3.age) |
结构体指针会降低传参的开销
| 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) |
| |
| (*p3).name = "egon" |
| (*p3).age = 18 |
| (*p3).sex = "male" |
| |
| tell(p3) |
| fmt.Println(p3.name) |
| } |
| |
| 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)
| |
| 都是用来为某一类型开辟内存空间,并用零值填充到内存空间中 |
| |
| |
| make通常只用于slice、map和channel开辟内存空间,并且用零值填充,然后返回一个T类型的值,而不是指针*T。 |
| new(T)用于为各种类型开辟内存空间,并用零值填充,然后返回其地址,即一个指针*T |