对于数组与切片,只能存储同一类型的变量。若要存储多个类型的变量,就需要用到可将多种类型的变量组合在一起的聚合数据类型——结构体。每个变量是该结构体的成员变量。
Go语言 的结构体 struct 和其他面向对象语言的class(Go语言中没有,也就没有继承),但是Go语言放弃大量面向对象的特性。但同时Go语言满足:除指针类型外,所有的类型都可以有自己的方法。
定义结构体
结构体的声明
type 结构体名 struct {
属性名 属性类型
属性名 属性类型
...
}
比如要定义一个存储个人资料的名为 User 的结构体,可以这么写
type User struct {
name string
age int
gender string
mother *User // 指针
father *User // 指针
}
定义方法(值传递)
在 Go 语言中,无法在结构体内定义方法,但是可以使用组合函数的方式来定义结构体方法。
它和普通函数的定义方式有些不一样,比如:
func (person User) FmtUser() {
fmt.Printf("名字:%s\n", person.name)
fmt.Printf("年龄:%d\n", person.age)
fmt.Printf("性别:%s\n", person.gender)
}
其中 FmtUser 是方法名,而(person User) :表示将 FmtUser 方法与 User 的实例对象 person 绑定。
FmtUser 称为方法的接收者
person 表示实例对象本身,
在方法内可以使用 person.属性名 的方法来访问实例属性。完整代码如下:
package main
import "fmt"
// 定义一个名为User 的结构体
type User struct {
name string
age int
gender string
mother *User // 指针
father *User // 指针
}
// 定义一个与 User 的绑定的方法
func (person User) FmtUser() {
fmt.Printf("名字:%s\n", person.name)
fmt.Printf("年龄:%d\n", person.age)
fmt.Printf("性别:%s\n", person.gender)
}
func main() {
// 实例化
myself := User{name: "小明", age: 24, gender: "male"}
// 调用函数
myself.FmtUser()
}
输出如下:
名字:小明
年龄:24
性别:male
定义方法(指针传递)
当你想要在以上面方式定义的方法内改变实例的属性的时候,必须使用指针做为方法的接收者:
package main
import "fmt"
// 声明一个 User 的结构体
type User struct {
name string
age int
gender string
mother *User // 指针
father *User // 指针
}
// 重点在于这个星号: *
func (person *User) increase_age() {
person.age += 1
}
func main() {
myself := User{name: "小明", age: 24, gender: "male"}
fmt.Printf("当前年龄:%d\n", myself.age)
myself.increase_age()
fmt.Printf("当前年龄:%d", myself.age)
}
输出如下:
当前年龄:24
当前年龄:25
可以看到在方法内部对 age 的修改已经生效。尝试去掉 *,使用值做为方法接收者,age则不会发生改变。
至此,两种定义方法的方式就有了:
- 以值做为方法接收者
- 以指针做为方法接收者
不管你使用哪种方法定义方法,以值或指针做为接收者都可以,都是可以调用的,Go语言中没有什么约束。
但是在以下的情况中,最好使用指针做为方法的接收者:
- 需要在方法内部改变结构体内容的时候,
- 出于性能的问题,当结构体过大的时候,使用指针做为方法的接收者
同时考虑代码一致性,建议全部情况都使用指针作为方法的接受者。
结构体实现 “继承”
Go 语言本身并不支持继承(所以加了引号)。但可以使用组合的方法,实现类似继承的效果。
在生活中,比如一台电脑,是由机身外壳,主板,CPU,内存等零部件组合在一起。
而在 Go 语言中,把一个结构体嵌入到另一个结构体的方法,称之为组合。
举个栗子:有一个表示公司(company)的结构体和一个表示公司职员(staff)的结构体,将公司信息与公司职员关联起来。
如果将 company 结构体的内容照抄到 staff 里。在实现上并没问题,但对同一公司的多个 staff 初始化时,会重复初始化相同的公司信息。
因此,借鉴继承的思想,可以将公司的属性都“继承”过来。而在 Go 中没有类的概念,只有组合。
type company struct {
companyName string
companyAddr string
}
type staff struct {
name string
age int
gender string
position string
}
组合允许将 company 这个结构体嵌入到 staff 中,做为 staff 的一个匿名字段,staff 就直接拥有了 company 的所有属性了:
type staff struct {
name string
age int
gender string
position string
company // 匿名字段
}
完整代码如下 :
package main
import "fmt"
type company struct {
companyName string
companyAddr string
}
type staff struct {
name string
age int
gender string
position string
company
}
func main() {
myCom := company{
companyName: "Tencent",
companyAddr: "深圳市南山区",
}
staffInfo := staff{
name: "小明",
age: 28,
gender: "男",
position: "算法工程师",
company: myCom,
}
fmt.Printf("%s 在 %s 工作\n", staffInfo.name, staffInfo.companyName)
fmt.Printf("%s 在 %s 工作\n", staffInfo.name, staffInfo.company.companyName)
}
输出如下:
小明 在 Tencent 工作
小明 在 Tencent 工作
可见staffInfo.companyName
和 staffInfo.company.companyName
的效果是一样的。
内部方法与外部方法
在 Go 语言中,函数名的首字母大小写非常重要,它被来实现控制对方法的访问权限。
-
当方法的首字母为大写时,这个方法对于所有包都是Public,其他包可以随意调用
-
当方法的首字母为小写时,这个方法是Private,其他包是无法访问的。