再续前文,在面向对象层面,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的构造体就是对象类的变种,尽管并没有显性的继承操作,然而通过嵌套构造体和晋升字段两种形式,也能达到“继承”的成果,构造体的最终目标和成果与对象类并无二致,类比的话,有点像电脑散热的两种形式:风冷和水冷,咱们不能说哪一种形式更好或者不好,只有这种形式能够实现我的项目中的需要即可,不论黑猫白猫,只有能抓到耗子,就是好猫。