您现在的位置是:首页 >技术教程 >go破冰之旅·12·大行其道的结构体网站首页技术教程

go破冰之旅·12·大行其道的结构体

ProblemTerminator 2024-06-04 10:27:27
简介go破冰之旅·12·大行其道的结构体

只有干货,把各种玩法一次性带给你!

上文中,我们以大量篇幅分析了切片扩容:切片的扩容

本文我们来看看go中的结构体如何play。

目录

初识结构体

关于命名

定义、实例化、属性操作

结构体嵌套

关于属性首字母的大小写

结构体指针与参数传递


初识结构体

结构体就是一组属性及其类型的集合,你可以在一个结构体中定义想定义的任何类型的属性。

通过一个结构体,可以定义或描述一个事物及其属性集合。

关于命名

重点在于首字母的大小写。其余命名和go中其它如包的命名等规范一致。

首字母大写时,可公开调用;

首字母小写时,不可公开调用。公开调用是指可在不同包之间进行调用,不可公开就是只能同包内调用。

定义、实例化、属性操作

举个例子,对于现实中的“人”,具有姓名、年龄、性别、爱好等属性,这里我们用一个结构体来描述一下:

// 使用type关键字,People为结构体的名称
type People struct {
	// 括号里是一个个属性的定义
	Name   string
	Age    int8
	Gender int8     // 0:男,1:女
	Hobby  []string // 一个人可能有多个爱好
}

这样就“抽象”出来了,这个结构体就是一个“模板”,用这个“模板”我们可以制造出该模板的实例化对象。

接着我们创建它的对象并将多个“People”组装:

	// 实例化3个对象,设置每个对象的自有值
	p1 := People{Name: "name1", Age: 99, Gender: 0, Hobby: []string{"play games", "read"}}
	p2 := People{"name2", 99, 1, []string{"read"}}
	p3 := People{"name3", 97, 0, []string{"play ball", "do sports"}}

	// 使用对象的某个属性
	fmt.Println(p1.Name)

	// 将p2的年龄改为98
	p2.Age = 98

	// 定义一个People列表,把这三个People对象加进来
	var peoples = []People{
		p1, p2, p3,
	}

	for _, people := range peoples {
		fmt.Printf("My name is %s, i like %s
", people.Name, strings.Join(people.Hobby, "、"))
	}

	/*
		My name is name1, i like play games、read
		My name is name2, i like read
		My name is name3, i like play ball、do sports
	*/

结构体嵌套

结构体也可以多个嵌套哦!

下面有一个学生结构体,这需要使用People及其属性,怎样才能避免把People里的属性不用在学生里再次重复定义一遍呢?

	s1 := Student{
		People: People{"stu1", 8, 1, []string{"read"}},
		StuID:  1, Grade: 2, Class: 1,
	}
	s2 := Student{
		People: People{"stu2", 8, 0, []string{"do sports"}},
		StuID:  2, Grade: 2, Class: 2,
	}

	var students []Student
	students = append(students, s1, s2)
	for _, stu := range students {
		fmt.Printf("My name is %s, i like %s, id is %d, grade %d class %d
",
			stu.Name, strings.Join(stu.Hobby, "、"), stu.StuID, stu.Grade, stu.Class)
	}

后面再有教师、医生、律师等其他职业的结构体要抽象,只需把People这个基本结构嵌套进去即可,因为这个基本结构就包含了职业这一类对象的基本属性。

你可以称之为“结构体继承”,但建议不要以面向对象的相关思想来在go中套用,每个语言都有其特色,上述的嵌套是为了让共用的、基本的属性更便捷的在新结构体中进行表述。

关于属性首字母的大小写

基于结构体首字母大写的前提下,属性首字母小写时,即使另一个包可以调用到该结构体,但到了调用其中的首字母小写的属性时就无法调用了。

关于json的转换

另外,属性首字母大写时,转换json无问题,首字母小写时转换出来的json没有该字段。

我们将上述People结构体的Gender属性进行修改,首字母改为小写,然后进行json转换:

	// 定义一个待转换的对象
	p1 := People{Name: "name1", Age: 99, gender: 1, Hobby: []string{"play games", "read"}}
	bs, _ := json.Marshal(p1)
	fmt.Println(string(bs))  
    // {"Name":"name1","Age":99,"Hobby":["play games","read"]}

可以看到得到的json字符串中gender字段消失了。

如要在json中包含全部字段,首字母定义时是大写但想让转换出的json字段为小写时,还可通过结构体的tag(标签)来自定义,比如我们让gender字段转换后为小写:

	type People struct {
		// 括号里是一个个属性的定义
		Name   string
		Age    int8
		Gender int8     `json:"gender"`    // 0:男,1:女
		Hobby  []string // 一个人可能有多个爱好
	}

再次以同样的方式:

	p1 := People{Name: "name1", Age: 99, Gender: 1, Hobby: []string{"play games", "read"}}
	bs, _ := json.Marshal(p1)
	fmt.Println(string(bs)) 
	// {"Name":"name1","Age":99,"gender":1,"Hobby":["play games","read"]}

可以看到gender显示为小写。

结构体指针与参数传递

对于结构体指针,同样我们使用上面的People来阐述。

	// 定义结构体指针,表示p1是*People类型的对象
	var p1 *People
	// 实例化(&取址符放在结构体前面),此时p1指针变量存储的是该对象的内存地址
	p1 = &People{Name: "name1", Age: 99, Gender: 1, Hobby: []string{"play games", "read"}}

	// 假设有一个peoples切片,是People类型,而不是*People类型
	var peoples []People
	// 现在要把p1追加进来
	peoples = append(peoples, *p1) // 通过*来解引用,把这个内存地址对应的p1的值就可以取出来

	// 访问/调用属性,注意并不是*p1.Name
	fmt.Println(p1.Name)  // name1

结构体对象作为参数传递

我们以一个函数修改结构体对象的name属性为需求,分别有这两个函数:

func updateName0(p People){
	p.Name = "new"
}

func updateName1(p *People){
	p.Name = "new"
}

这两个区别是,前者传的参数是结构体对象,函数中修改的是副本的属性,外界对象并未变化,而后者传的是结构体指针,在通过访问属性来修改时可以正常修改,测试数据:

	p1 := People{Name: "name1", Age: 99, Gender: 1, Hobby: []string{"play games", "read"}}
	updateName0(p1)
	fmt.Println(p1)
	updateName1(&p1)  // 将p1的内存地址传进去
	fmt.Println(p1)

结果不出所料:

{name1 99 1 [play games read]}
{new 99 1 [play games read]}

这和前文数组与切片:数组作为参数传递 情形如出一辙。

感谢支持,再会!

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。