再续前文,在面向对象层面,Python 做到了超神:万物皆为对象,而 Ruby,则罗唆就是神:飞花摘叶皆可对象。二者都提供对象类操作以及继承的形式为面向对象张目,但 Go lang 显然有一些特立独行,因为它没有传统的类,也没有继承,取而代之的是构造和组合的形式,也就是构造体(struct)的形式来组织代码,达到相似类的成果。
构造体 struct 的申明
在 Go lang 中应用上面的语法是对构造体的申明:
type struct_name struct {
attribute_name1 attribute_type
attribute_name2 attribute_type
...
}
假如定义一个名为 Lesson(课程) 的构造体:
type Lesson struct {
name string // 名称
target string // 学习指标
spend int // 学习破费工夫
}
这里申明了一个构造体类型 Lesson,它有 name、target 和 spend 三个属性,相当于 Python 中类的公有属性。
也能够把雷同类型的属性申明在同一行,这样能够使构造体变得更加紧凑:
type Lesson2 struct {
name, target string
spend int
}
Lesson 称为命名的构造体(Named Structure),这里 Lesson 作为一种新的数据类型而存在,而它能够用于创立 Lesson 类型的构造体变量。
此外,申明构造体时也能够不必申明一个新类型,这样的构造体类型称为匿名构造体(Anonymous Structure),能够了解为构造体变量:
var MyLesson struct {
name, target string
spend int
}
构造体 struct 的创立
申明了构造体之后,咱们能够依据申明好的构造体类型来创立构造体,这个过程有点像 Python 语言中类的实例化:
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
// 应用字段名创立构造体
lesson1 := Lesson{
name: "go lang 1.18",
target: "学习 Go lang,并实现 web 开发工作",
spend: 1,
}
// 不应用字段名创立构造体
lesson2 := Lesson{"go lang 1.18", "学习 Go lang,并实现 web 开发工作", 1}
fmt.Println("lesson1", lesson1)
fmt.Println("lesson2", lesson2)
}
程序返回:
lesson1 {go lang 1.18 学习 Go lang,并实现 web 开发工作 1}
lesson2 {go lang 1.18 学习 Go lang,并实现 web 开发工作 1}
这里字段名能够做省略操作,但要留神传递程序。
此外,也能够创立匿名构造体:
package main
import "fmt"
func main() {
// 创立匿名构造体变量
mylesson := struct {
name, target string
spend int
}{
name: "Go lang 1.18",
target: "学习 go lang,实现 web 需要",
spend: 1,
}
fmt.Println("mylesson", mylesson)
}
当定义好的构造体没有被显式赋值时,构造体的字段将会默认赋为相应类型的零值:
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
// 不初始化构造体
var lesson = Lesson{}
fmt.Println("lesson", lesson)
}
程序返回:
lesson {0}
假如某个或者某几个字段没有赋值,也会默认赋值为对应根本数据类型的零值:
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
// 为构造体指定字段赋初值
var lesson = Lesson{name: "go lang 1.18",}
// 下面的构造体变量 lesson 只初始化了 name 字段, 其余字段没有初始化, 所以会被初始化为零值
fmt.Println("lesson", lesson)
}
程序返回:
lesson {go lang 1.18 0}
构造体 struct 的属性与指针
通过点操作符 . 能够拜访构造体的属性:
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
var lesson = Lesson{name: "go lang 1.18",}
fmt.Println("lesson name", lesson.name)
}
程序返回:
lesson name go lang 1.18
也能够应用点操作符 . 对构造体的字段进行赋值操作:
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
var lesson = Lesson{name: "go lang 1.18",}
fmt.Println("lesson name", lesson.name)
lesson.name = "Python 3.11"
fmt.Println("lesson name", lesson.name)
}
程序返回:
lesson name go lang 1.18
lesson name Python 3.11
须要留神的是,赋值变量和构造体属性的根本数据类型要统一。
在前一篇:借问变量何处存, 牧童笑称用指针,Go lang1.18 入门精炼教程,由白丁入鸿儒,go lang 类型指针 (Pointer) 的应用 EP05 咱们应用了指针来操作变量,指针也能够指向构造体:
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {lesson := &Lesson{"go lang 1.18", "实现对应 web 需要", 1}
fmt.Println("lesson name:", (*lesson).name)
fmt.Println("lesson name:", lesson.name)
}
程序返回:
lesson name: go lang 1.18
lesson name: go lang 1.18
lesson 是一个指向构造体 Lesson 的指针,下面用 (*lesson).name 拜访 lesson 的 name 字段,lesson.name 是代替 (*lesson).name 的解援用拜访。
在创立构造体时,属性能够只有类型没有属性名,这种属性称为匿名字段(Anonymous Field):
package main
import "fmt"
type Lesson struct {
string
int
}
func main() {lesson := Lesson{"go lang 1.18", 1}
fmt.Println("lesson", lesson)
fmt.Println("lesson string:", lesson.string)
fmt.Println("lesson int:", lesson.int)
}
程序返回:
lesson {go lang 1.18 1}
lesson string: go lang 1.18
lesson int: 1
这里程序结构体定义了两个匿名字段,尽管这两个字段没有字段名,但匿名字段的名称默认就是它的类型。所以下面的构造体 Lesoon 有两个名为 string 和 int 的字段,同样须要留神程序和字段数据类型的匹配问题。
嵌套构造体
构造体自身也反对复合的嵌套构造:
package main
import "fmt"
type Author struct {name string}
type Lesson struct {
name, target string
spend int
author Author
}
func main() {
lesson := Lesson{
name: "go lang 1.18",
spend: 1,
}
lesson.author = Author{name: "佚名",}
fmt.Println("lesson name:", lesson.name)
fmt.Println("lesson spend:", lesson.spend)
fmt.Println("lesson author name:", lesson.author.name)
}
程序返回:
lesson name: go lang 1.18
lesson spend: 1
lesson author name: 佚名
这里构造体 Author 自身作为构造体 Lesson 的一个属性而存在,赋值时,通过父构造体间接调用子结构体名称即可。
如果构造体中有匿名的构造体类型字段,则该匿名构造体里的字段就称为晋升字段(Promoted Fields)。这是因为晋升字段就像是属于内部构造体一样,能够用内部构造体间接拜访:
package main
import ("fmt")
type Address struct {city, state string}
type Person struct {
name string
age int
Address
}
func main() {
var p Person
p.name = "Naveen"
p.age = 50
p.Address = Address{
city: "Chicago",
state: "Illinois",
}
fmt.Println("Name:", p.name)
fmt.Println("Age:", p.age)
fmt.Println("City:", p.city) //city is promoted field
fmt.Println("State:", p.state) //state is promoted field
}
零碎返回:
Name: Naveen
Age: 50
City: Chicago
State: Illinois
如果咱们把 Person 构造体中的字段 address 间接用匿名字段 Address 代替,Address 构造体的字段例如 city 就不必像 p.address.city 这样拜访,而是应用 p.address 就能拜访 Address 构造体中的 address 字段。当初构造体 Address 有 city 字段,拜访字段就像在 Person 里间接申明的一样,因而咱们称之为晋升字段,说白了就是把子结构体的字段晋升为父构造体的字段,然而定义还是在子结构体之中。
假如构造体的全副属性都是能够比拟的,那么构造体也是能够比拟的,那样的话两个构造体将能够应用 == 或 != 运算符进行比拟。能够通过 == 运算符或 DeeplyEqual()函数比拟两个构造雷同的类型并蕴含雷同的字段值:
package main
import ("fmt")
type name struct {
firstName string
lastName string
}
func main() {name1 := name{"Steve", "Jobs"}
name2 := name{"Steve", "Jobs"}
if name1 == name2 {fmt.Println("name1 and name2 are equal")
} else {fmt.Println("name1 and name2 are not equal")
}
name3 := name{firstName:"Steve", lastName:"Jobs"}
name4 := name{}
name4.firstName = "Steve"
if name3 == name4 {fmt.Println("name3 and name4 are equal")
} else {fmt.Println("name3 and name4 are not equal")
}
}
程序返回:
name1 and name2 are equal
name3 and name4 are not equal
如果构造变量蕴含的字段是不可比拟的,那么构造变量是不可比拟的:
package main
import ("fmt")
type image struct {data map[int]int
}
func main() {image1 := image{data: map[int]int{0: 155,}}
image2 := image{data: map[int]int{0: 155,}}
if image1 == image2 {fmt.Println("image1 and image2 are equal")
}
}
程序报错:
# command-line-arguments
.\test.go:18:5: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)
由此可知,构造体的比拟能够了解为其属性的批量比拟。
构造体绑定办法
在 Go lang 中无奈在构造体外部定义方法,这一点与 C 语言相似:
package main
import "fmt"
// Lesson 定义一个名为 Lesson 的构造体
type Lesson struct {
name, target string
spend int
}
// ShowLessonInfo 定义一个与 Lesson 的绑定的办法
func (l Lesson) ShowLessonInfo() {fmt.Println("name:", l.name)
fmt.Println("target:", l.target)
}
func main() {
l := Lesson{name: "go lang 1.1 8",}
l.ShowLessonInfo()}
程序返回:
name: go lang 1.1 8
target:
这里定义了一个与构造体 Lesson 绑定的办法 ShowLessonInfo(),其中 ShowLessonInfo 是办法名,(l Lesson) 示意将此办法与 Lesson 的实例绑定,这在 Go lang 中称为接收者,而 l 示意实例自身,相当于 Python 中的 self,在办法内能够应用实例自身. 属性名称来拜访实例属性。
如果绑定构造体的办法中要扭转实例的属性时,必须应用指针作为办法的接收者:
package main
import "fmt"
// Lesson 定义一个名为 Lesson 的构造体
type Lesson struct {
name, target string
spend int
}
// ShowLessonInfo 定义一个与 Lesson 的绑定的办法
func (l Lesson) ShowLessonInfo() {fmt.Println("spend:", l.spend)
}
// AddTime 定义一个与 Lesson 的绑定的办法,使 spend 值加 n
func (l *Lesson) AddTime(n int) {l.spend = l.spend + n}
func main() {
lesson13 := Lesson{spend: 1,}
fmt.Println("增加 add 办法前")
lesson13.ShowLessonInfo()
lesson13.AddTime(5)
fmt.Println("增加 add 办法后")
lesson13.ShowLessonInfo()}
程序返回:
增加 add 办法前
spend: 1
增加 add 办法后
spend: 6
结语
大抵上,Go lang 的构造体就是对象类的变种,尽管并没有显性的继承操作,然而通过嵌套构造体和晋升字段两种形式,也能达到“继承”的成果,构造体的最终目标和成果与对象类并无二致,类比的话,有点像电脑散热的两种形式:风冷和水冷,咱们不能说哪一种形式更好或者不好,只有这种形式能够实现我的项目中的需要即可,不论黑猫白猫,只有能抓到耗子,就是好猫。