0%

go语言入门学习之结构体(struct)

girl-2366438_1920

写在前面

在上一文《Golang入门学习之函数》当中,我们学习了Golang当中函数的用法。接下来,我们将一起来学习Golang的结构体。

结构体的定义

结构体(struct)类似于其他面向对象当中类(class)的概念,但它和类有着明显的不同。结构体是复合类型(composite types),它由一系列属性组成,这些属性对应着不同的值与类型。组成结构体类型的那些数据称为字段(fields).

Golang通过结构体的形式支持用户自定义类型,或者叫定制类型。

定义方式:

1
2
3
4
5
type StructName struct {
field1 type1
field2 type2
...
}

结构体的实例化

使用new()

我们可以使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针。

1
2
var t *StructName
t = new(StructName)

请看下面这个例子:

首先,我们在go_code/struct/model这个包当中,定义了一个结构体Immortal表示修仙者,并在main当中引入这个包,并使用Immortal

go_code/struct/model/immortal.go:

1
2
3
4
5
6
7
8
package model

// 修仙者
type Immortal struct {
Name string
Age int
Gender string
}

main中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"go_code/struct/model"
"strconv"
)

func main() {
// i是指向model.Immortal结构体变量的指针
var i *model.Immortal
i= new(model.Immortal)
i.Age = 500
i.Name = "韩立"
i.Gender = "男"

println(i.Name, strconv.Itoa(i.Age)+"岁",i.Gender)

}

输出:

1
韩立 500岁 男

我们可以通过点符号获取结构体的值或给其赋值,点符号在Golang中称之为选择器符(selector-notation)。

使用混合字面量语法

初始化一个结构体实例的更简短和惯用的方式如下:

1
2
3
4
5
6
// i的类型是 *model.Immortal
var i = &model.Immortal{"南宫婉",18,"女"}

// 或者
var i model.Immortal
i := model.Immortal{"南宫婉",18,"女"}

上面这两个例子当中的写法(&model.Immortal{"南宫婉",18,"女"} )称之为混合字面量语法 (composite literal sysntax),这是一种简写,底层仍然会调用new函数。在这种写法当中,需要注意的一点是值的顺序必须按照字段顺序来写。表达式new(Type)&Type()是等价的。

Golang中结构体的特点

内存布局

Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势。

递归结构体

结构体类型可以通过引用自身来定义。这在定义链表或二叉树的元素(通常叫节点)时特别有用,此时节点包含指向临近节点的链接(地址)。

1
2
3
4
5
6
package model

type Node struct {
data string
next *Node
}

其中,data字段当中存储的是当前节点的数据,next是指向下一个节点的指针

结构体的转换

Go 中的类型转换遵循严格的规则。当为结构体定义了一个 alias 类型时,此结构体类型和它的 alias 类型都有相同的底层类型。

请看下面的这个例子:

number.go

1
2
3
4
5
package model

type Number struct {
Value float32
}

conversion.go:

1
2
3
4
5
6
7
8
9
10
11
package main

import "go_code/struct/model"

func main() {
var a = myNumber{Value: 18.0}
b := model.Number(a)
println(b.Value)
}

type myNumber model.Number

使用工厂方法创建结构体实例

Golang不支持其他面向对象编程语言(例如Java)那样的构造方法。我们通常通过定义一个构造工厂方法用于创建结构体实例。

请看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package model

// 修仙者
type Immortal struct {
Name string
Age int
Gender string
}

func NewImmortal(age int, name, gender string) *Immortal {
if age<0 {
return nil
}
return &Immortal{Name: name,Gender: name,Age: age}
}

对于工厂方法的命名,我们一般以Newnew开头,它返回一个指向结构体实例的指针

如何强制使用工厂方法而不是直接new?

在讲这个问题之前,需要给大家补充一个知识点,那就是Golang当中包的可见性规则:

在Golang中,当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。

因此,为了达到我们的目的,我们需要这样去定义我们的结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package v2

type immortal struct {
name string
age int
gender string
}

func NewImmortal(age int, name, gender string) *immortal {
if age < 0 {
return nil
}

return &immortal{
name: name,
age: age,
gender: gender,
}
}

带标签的结构体

结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有包 reflect 能获取它。在gorm(一个Golng的orm框架,类似于Java当中的mybatis)当中使用tag标记字段,从而起到映射数据库表字段的作用。

请看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package tag

import (
"fmt"
"reflect"
)

type Immortal struct {
Name string "休仙者的名字"
Age int "修仙者的年龄"
Gender string "修仙者的性别"
}


func PrintTag(im Immortal, i int) {
imm :=reflect.TypeOf(im)
value :=imm.Field(i)
fmt.Println(value.Tag)
}
1
2
3
4
5
6
7
8
9
10
11
package main

import "go_code/struct/model/tag"

func main() {
var immortal = tag.Immortal{Name: "南宫婉", Age: 18, Gender: "女"}
for i:=0; i<3; i++ {
tag.PrintTag(immortal,i)
}
}

输出:

1
2
3
休仙者的名字
修仙者的年龄
修仙者的性别

匿名字段和内嵌结构体

结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体。在Golang当中,通过组合来实现其他面向对象编程语言当中的继承。

请看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import "fmt"

type immortal struct {
// 姓名
string
// 年龄
int
// 修仙境界
level

}

type level struct {
// 境界名称
string
// 灵气值
float32
}

func main() {
var im = immortal{
string: "韩立",
int: 500,
level: level{
"练气七层",
7800.0,
},
}
fmt.Println("======修仙者资料卡======")
fmt.Println("姓名:",im.string)
fmt.Println("年龄:",im.int)
fmt.Println("------境界信息---------")
fmt.Println("境界名称:",im.level.string)
fmt.Println("境界灵气值:",im.level.float32)
fmt.Println("------境界信息---------")
fmt.Println("======修仙者资料卡======")
}

输出:

1
2
3
4
5
6
7
8
======修仙者资料卡======
姓名: 韩立
年龄: 500
------境界信息---------
境界名称: 练气七层
境界灵气值: 7800
------境界信息---------
======修仙者资料卡======

写在最后

关于Golang结构体的学习就写到这里,本文当中涉及到的例子可以点击此处下载。如果我的学习笔记能够给你带来帮助,还请多多点赞鼓励。文章如有错漏之处还请各位小伙伴帮忙斧正。

欢迎关注我的其它发布渠道