4

你有对象类,我有结构体,Go lang1.18入门精炼教程,由白丁入鸿儒,go lang结构体(struc...

 2 years ago
source link: https://www.cnblogs.com/v3ucn/p/16570672.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK