结构体重要概念
在之前学过的数据类型中,数组与切片,只能存储同一类型的变量。若要存储多个类型的变量,就需要用到结构体,它是将多个任意类型的变量组合在一起的聚合数据类型。每个变量都成为该结构体的成员变量。
可以理解为 Go语言 的结构体struct和其他语言的class有相等的地位,但是Go语言放弃大量面向对象的特性,所有的Go语言类型除了指针类型外,都可以有自己的方法,提高了可扩展性。
在 Go 语言中没有没有 class 类的概念,只有 struct 结构体的概念,因此也没有继承
定义结构体
type 结构体名 struct { 属性名 属性类型 属性名 属性类型 ... }比如我要定义一个可以存储个人资料名为 Profile 的结构体,可以这么写
func main() {
type Profile struct {
name string
age int
gender string
mother *Profile // 指针
father *Profile // 指针
}
}
若相邻的属性(字段)是相同类型,可以合并写在一起
func main() {
type Profile struct {
name,gender string
age int
mother *Profile // 指针
father *Profile // 指针
}
}
通过结构体可以定义一个组合字面量
不过结构体有几个细节需要注意:
规则一:当最后一个字段和结果不在同一行时,,
不可省略。
xm := Profile{ name: "小明", age: 18, gender: "male", }
反之,不在同一行,就可以省略。
xm := Profile{ name: "小明", age: 18, gender: "male"}
规则二:字段名要不全写,要不全不写,不能有的写,有的不写。
例如下面这种写法是会报 mixture of field:value and value initializers
错误的
xm := Profile{ name: "小明", 18, "male", }
规则三:初始化结构体,并不一定要所有字段都赋值,未被赋值的字段,会自动赋值为其类型的零值。
xm := Profile{name: "小明"} fmt.Println(xm.age) // output: 0
但要注意的是,只有通过指定字段名才可以赋值部分字段。
若你没有指定字段名,像这样
xm := Profile{"小明"}
在编译的时候,是会直接报错的
$ go run demo.go
# command-line-arguments
./demo.go:12:16: too few values in Profile literal
绑定方法
在 Go 语言中,我们无法在结构体内定义方法,那如何给一个结构体定义方法呢,答案是可以使用组合函数的方式来定义结构体方法。它和普通函数的定义方式有些不一样,比如下面这个方法func (profile Profile) FmtProfile(addAge int) {
profile.age = profile.age + addAge
fmt.Printf("name:%v,age:%d\n", profile.name, profile.age)
}
其中
FmtProfile
是方法名,而
(person Profile)
:表示将 FmtProfile 方法与 Profile 的实例绑定。我们把 Profile 称为方法的接收者,而 person 表示实例本身,它相当于 Python 中的 self或PHP中的$this,在方法内可以使用
person.属性名
的方法来访问实例属性。
完整代码:
import (
"example.com/m/pagkage/linux"
)
func main() {
linux.ProcessStructDemo1()
}
type Profile struct {
name string
age int
mother *Profile
father *Profile
}
func (profile Profile) FmtProfile(addAge int) {
profile.age = profile.age + addAge
fmt.Printf("name:%v,age:%d\n", profile.name, profile.age)
}
func ProcessStructDemo1() {
myself := Profile{
name: "Linrux",
age: 10,
}
myself.FmtProfile(5)
}
输出:
方法的参数传递方式
当你想要在方法内改变实例的属性的时候,必须使用指针做为方法的接收者。代码:
type Profile struct {
name string
age int
mother *Profile
father *Profile
}
// FmtProfile 重点在这个* 使用地址传递
func (profile *Profile) FmtProfile(addAge int) {
profile.age = profile.age + addAge
fmt.Printf("name:%v,age:%d\n", profile.name, profile.age)
}
func ProcessStructDemo1() {
myself := Profile{
name: "Linrux",
age: 10,
}
myself.FmtProfile(5)
fmt.Println(myself.age)
}
可以看到修改已生效:
至此,我们知道了两种定义方法的方式:
- 以值做为方法接收者
- 以指针做为方法接收者
- 你需要在方法内部改变结构体内容的时候
- 出于性能的问题,当结构体过大的时候
结构体实现 “继承”
为什么标题的继承,加了双引号,因为 Go 语言本身并不支持继承。但我们可以使用组合的方法,实现类似继承的效果。
在生活中,组合的例子非常多,比如一台电脑,是由机身外壳,主板,CPU,内存等零部件组合在一起,最后才有了我们用的电脑。
同样的,在 Go 语言中,把一个结构体嵌入到另一个结构体的方法,称之为组合。
现在这里有一个表示公司(addRes)的结构体,还有一个表示用户(Profile)的结构体。
type Profile struct {
name string
age int
mother *Profile
}
type addRes struct {
companyName string
companyAddr string
}
若要将公司信息与用户关联起来,一般都会想到将 addRes 结构体的内容照抄到 Profile里。
type mergePro struct {
name string
age int
mother *Profile
companyName string
companyAddr string
}
虽然在实现上并没有什么问题,但在你对同一公司的多个用户初始化的时候,都得重复初始化相同的公司信息,这做得并不好,借鉴继承的思想,我们可以将公司的属性都“继承”过来。
但是在 Go 中没有类的概念,只有组合,你可以将 addRes 这个 结构体嵌入到 Profile 中,做为 Profile 的一个匿名字段,Profile 就直接拥有了 addRes 的所有属性了。
type Profile struct {
name string
age int
mother *Profile
addRes //嵌入 匿名字段
}
完整代码:
type Profile struct {
name string
age int
mother *Profile
addRes
}
type addRes struct {
companyName string
companyAddr string
}
func (profile *Profile) FmtProfile(addAge int) {
profile.age = profile.age + addAge
}
func ProcessStructDemo1() {
addres := addRes{
companyName: "Tencent",
companyAddr: "深圳市南山区",
}
myself := Profile{
name: "Linrux",
age: 10,
addRes: addres,
}
myself.FmtProfile(0)
//使用addRes去访问属性
fmt.Printf("name:%s age:%d 公司:%s 公司地址:%s", myself.name, myself.age, myself.addRes.companyName, myself.addRes.companyAddr)
//跳过addRes去访问属性
fmt.Printf("name:%s age:%d 公司:%s 公司地址:%s", myself.name, myself.age, myself.companyName, myself.companyAddr)
}
输出结果如下:
可以看出
三种实例化方法
第一种:正常实例化
x := Profile{
name: "linrux",
age: 20,
}
第二种:使用 new
x := new(Profile)
x.name = "linrux"
fmt.Println(x)
(*x).name = "linrux1"
fmt.Println(x)
第三种:使用 &
var x *Profile = &Profile{}
x.name = "linrux"
fmt.Println(x)
(*x).name = "linrux1"
fmt.Println(x)