原创做者,公众号【程序员读书】,欢迎关注公众号,转载文章请注明出处哦。程序员
Go语言中提供了对struct的支持,struct
,中文翻译称为结构体
,与数组同样,属于复合类型,并不是引用类型。编程
Go语言的struct,与C语言中的struct或其余面向对象编程语言中的类(class)相似,能够定义字段(属性)和方法,但也有很不一样的地方,须要深刻学习,才能区分他们之间的区别。json
注意复合类型与引用类型之间的区别,这应该也是值传递和引用传递的区别吧。数组
使用struct关键字能够定义一个结构体,结构体中的成员,称为结构体的字段或属性。bash
type Member struct {
id int
name, email string
gender, age int
}
复制代码
上面的代码中,咱们定义了一个包含5个字段的结构体,能够看到,相同类型name
和email
、gender
和age
在同一行中定义,但比较好的编程习惯是每一行只定义一个字段,如:并发
type Member struct {
id int
name string
email string
gender int
age int
}
复制代码
固然,结构体也能够不包含任何字段,称为空结构体
,struct{}表示一个空的结构体,注意,直接定义一个空的结构体并无意义,但在并发编程中,channel之间的通信,可使用一个struct{}做为信号量。编程语言
ch := make(chan struct{})
ch <- struct{}{}
复制代码
上面的例子中,咱们定义了Member结构体类型,接下就能够这个自定义的类型建立变量了。函数
直接定义变量,这个使用方式并无为字段赋初始值,所以全部字段都会被自动赋予自已类型的零值,好比
name
的值为空字符串"",age的值为0。性能
var m1 Member//全部字段均为空值
复制代码
使用字面量建立变量,这种使用方式,能够在大括号中为结构体的成员赋初始值,有两种赋初始值的方式,一种是按字段在结构体中的顺序赋值,下面代码中
m2
就是使用这种方式,这种方式要求全部的字段都必须赋值,所以若是字段太多,每一个字段都要赋值,会很繁琐,另外一种则使用字段名为指定字段赋值,以下面代码中变量m3
的建立,使用这种方式,对于其余没有指定的字段,则使用该字段类型的零值做为初始化值。学习
var m2 = Member{1,"小明","xiaoming@163.com",1,18} // 简短变量声明方式:m2 := Member{1,"小明","xiaoming@163.com",1,18}
var m3 = Member{id:2,"name":"小红"}// 简短变量声明方式:m3 := Member{id:2,"name":"小红"}
复制代码
经过变量名,使用逗号(.)
,能够访问结构体类型中的字段,或为字段赋值,也能够对字段进行取址(&)操做。
fmt.Println(m2.name)//输出:小明
m3.name = "小花"
fmt.Println(m3.name)//输出:小花
age := &m3.age
*age = 20
fmt.Println(m3.age)//20
复制代码
结构体与数组同样,都是值传递,好比当把数组或结构体做为实参传给函数的形参时,会复制一个副本,因此为了提升性能,通常不会把数组直接传递给函数,而是使用切片(引用类型)代替,而把结构体传给函数时,可使用指针结构体
。
指针结构体,即一个指向结构体的指针,声明结构体变量时,在结构体类型前加*号,便声明一个指向结构体的指针,如:
注意,指针类型为引用类型,声明结构体指针时,若是未初始化,则初始值为nil,只有初始化后,才能访问字段或为字段赋值。
var m1 *Member
m1.name = "小明"//错误用法,未初始化,m1为nil
m1 = &Member{}
m1.name = "小明"//初始化后,结构体指针指向某个结构体地址,才能访问字段,为字段赋值。
复制代码
另外,使用Go内置new()函数,能够分配内存来初始化结构休,并返回分配的内存指针,由于已经初始化了,因此能够直接访问字段。
var m2 = new(Member)
m2.name = "小红"
复制代码
咱们知道,若是将结构体转给函数,只是复制结构体的副本,若是在函数内修改结构体字段值,外面的结构体并不会受影响,而若是将结构体指针传给函数,则在函数中使用指针对结构体所作的修改,都会影响到指针指向的结构体。
func main() {
m1 := Member{}
m2 := new(Member)
Change(m1,m2)
fmt.Println(m1,m2)
}
func Change(m1 Member,m2 *Member){
m1.Name = "小明"
m2.Name = "小红"
}
复制代码
上面的例子中,咱们定义结构体字段名首字母是小写的,这意味着这些字段在包外不可见
,于是没法在其余包中被访问,只容许包内访问。
下面的例子中,咱们将Member声明在member包中,然后在main包中建立一个变量,但因为结构体的字段包外不可见,所以没法为字段赋初始值,没法按字段仍是按索引赋值,都会引起panic错误。
package member
type Member struct {
id int
name string
email string
gender int
age int
}
package main
fun main(){
var m = member.Member{1,"小明","xiaoming@163.com",1,18}//会引起panic错误
}
复制代码
所以,若是想在一个包中访问另外一个包中结构体的字段,则必须是大写字母开头的变量,便可导出的变量,如:
type Member struct {
Id int
Name string
Email string
Gender int
Age int
}
复制代码
在定义结构体字段时,除字段名称和数据类型外,还可使用反引号为结构体字段声明元信息,这种元信息称为Tag,用于编译阶段关联到字段当中,如咱们将上面例子中的结构体修改成:
type Member struct {
Id int `json:"id,-"`
Name string `json:"name"`
Email string `json:"email"`
Gender int `json:"gender,"`
Age int `json:"age"`
}
复制代码
上面例子演示的是使用encoding/json包编码或解码结构体时使用的Tag信息。
Tag由反引号括起来的一系列用空格分隔的key:"value"键值对组成,如:
Id int `json:"id" gorm:"AUTO_INCREMENT"`
复制代码
下面总结几点结构体的相关特性:
结构体与数组同样,是复合类型,不管是做为实参传递给函数时,仍是赋值给其余变量,都是值传递,即复一个副本。
Go语言是支持面向对象编程的,但却没有继承的概念,在结构体中,能够经过组合其余结构体来构建更复杂的结构体。
一个结构体,并无包含自身,好比Member中的字段不能是Member类型,但却多是*Member。
在Go语言中,将函数绑定到具体的类型中,则称该函数是该类型的方法,其定义的方式是在func与函数名称之间加上具体类型变量,这个类型变量称为方法接收器
,如:
注意,并非只有结构体才能绑定方法,任何类型均可以绑定方法,只是咱们这里介绍将方法绑定到结构体中。
func setName(m Member,name string){//普通函数
m.Name = name
}
func (m Member)setName(name string){//绑定到Member结构体的方法
m.Name = name
}
复制代码
从上面的例子中,咱们能够看出,经过方法接收器
能够访问结构体的字段,这相似其余编程语言中的this关键词,但在Go语言中,只是一个变量名而已,咱们能够任意命名方法接收器
。
调用结构体的方法,与调用字段同样:
m := Member{}
m.setName("小明")
fmt.Println(m.Name)//输出为空
复制代码
上面的代码中,咱们会很奇怪,不是调用setName()方法设置了字段Name的值了吗?为何仍是输出为空呢?
这是由于,结构体是值传递,当咱们调用setName时,方法接收器接收到是只是结构体变量的一个副本,经过副本对值进行修复,并不会影响调用者,所以,咱们能够将方法接收器定义为指针变量,就可达到修改结构体的目的了。
func (m *Member)setName(name string){/将Member改成*Member
m.Name = name
}
m := Member{}
m.setName("小明")
fmt.Println(m.Name)//小明
复制代码
方法和字段同样,若是首字母为小写,则只容许在包内可见,在其余包中是没法访问的,所以,若是要在其余包中访问setName
,则应该将方法名改成SetName
。
咱们知道,结构体中并无继承的概念,其实,在Go语言中也没有继承的概念,Go语言的编程哲学里,推荐使用组合
的方式来达到代码复用效果。
组合,能够理解为定义一个结构体中,其字段能够是其余的结构体,这样,不一样的结构体就能够共用相同的字段。
注意,在记得咱们前面提过的,结构体不能包含自身,但可能包含指向自身的结构体指针。
例如,咱们定义了一个名为Animal表示动物,若是咱们想定义一个结构体表示猫,如:
type Animal struct {
Name string //名称
Color string //颜色
Height float32 //身高
Weight float32 //体重
Age int //年龄
}
//奔跑
func (a Animal)Run() {
fmt.Println(a.Name + "is running")
}
//吃东西
func (a Animal)Eat() {
fmt.Println(a.Name + "is eating")
}
type Cat struct {
a Animal
}
func main() {
var c = Cat{
a: Animal{
Name: "猫猫",
Color: "橙色",
Weight: 10,
Height: 30,
Age: 5,
},
}
fmt.Println(c.a.Name)
c.a.Run()
}
复制代码
能够看到,咱们定义Cat结构体时,能够把Animal结构体做为Cat的字段。
上面的例子,咱们看到,把Animal结构体做为Cat的字段时,其变量名为a,因此咱们访问Animal的方法时,语法为c.a.Run()
,这种经过叶子属性访问某个字段类型所带的方法和字段用法很是繁琐。
Go语言支持直接将类型做为结构体的字段,而不须要取变量名,这种字段叫匿名字段
,如:
type Lion struct {
Animal //匿名字段
}
func main(){
var lion = Lion{
Animal{
Name: "小狮子",
Color: "灰色",
},
}
lion.Run()
fmt.Println(lion.Name)
}
复制代码
经过上面例子,能够看到,经过匿名字段组合其余类型,然后访问匿名字段类型所带的方法和字段时,不须要使用叶子属性,很是方便。
在Go语言编程中,结构体大概算是使用得最多的数据类型了,经过定义不一样字段和方法的结构体,抽象组合不一样的结构体,这大概即是Go语言中对面向对象编程了。
你的关注,是我写做路上最大的鼓励!