乐趣区

关于go:你有对象类我有结构体Go-lang118入门精炼教程由白丁入鸿儒go-lang结构体struct的使用EP06

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

退出移动版