从零开发区块链应用--结构体初识

逆水行舟用力撑,一篙松劲退千寻。这篇文章主要讲述从零开发区块链应用--结构体初识相关的知识,希望能为你提供帮助。




文章目录

  • ??一、结构体定义??
  • ??二、初始化结构体??
  • ??三、结构体的访问??
  • ??四、结构体指针??
  • ??五、结构体可见性??
  • ??六、结构体标签??
  • ??七、结构体嵌套??
  • ??八、结构体方法??
  • ??九、结构体特性??


Go语言中提供了对struct的支持,struct,中文翻译称为结构体,与数组一样,属于复合类型,并非引用类型。


Go语言的struct,与C语言中的struct或其他面向对象编程语言中的类(class)类似,可以定义字段(属性)和方法,但也有很不同的地方,需要深入学习,才能区分他们之间的区别。
一、结构体定义Go语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合
结构体成员是由一系列的成员变量构成,这些成员变量也被称为“字段”
type Member struct
idint
name, email string
gender, age int

上面的代码中,我们定义了一个包含5个字段的结构体,可以看到,相同类型name和email、gender和age在同一行中定义,但比较好的编程习惯是每一行只定义一个字段,如:
type Member struct
idint
namestring
emailstring
gender int
ageint

当然,结构体也可以不包含任何字段,称为空结构体,struct表示一个空的结构体,注意,直接定义一个空的结构体并没有意义,但在并发编程中,channel之间的通讯,可以使用一个struct作为信号量。
ch := make(chan struct)
ch < - struct

二、初始化结构体上面的例子中,我们定义了Member结构体类型,接下就可以这个自定义的类型创建变量了。
  • 直接定义变量
直接定义变量,这个使用方式并没有为字段赋初始值,因此所有字段都会被自动赋予自已类型的零值,比如name的值为空字符串"",age的值为0。
var m1 Member//所有字段均为空值

另外也有使用字面量创建变量,这种使用方式,可以在大括号中为结构体的成员赋初始值,有两种赋初始值的方式:
  • 按照顺序提供初始化值:
var m2 = Member
1,
"杰哥的技术杂货铺",
"jiege@163.com",
1,
18,


或者
// 简短变量声明方式:
m2 := Member
1,
"杰哥的技术杂货铺",
"jiege@163.com",
1,
18

【从零开发区块链应用--结构体初识】这种方式要求所有的字段都必须赋值,因此如果字段太多,每个字段都要赋值,会很繁琐,另一种则使用字段名为指定字段赋值,如下面代码中变量m3的创建,使用这种方式,对于其他没有指定的字段,则使用该字段类型的零值作为初始化值。
  • 通过field:value的方式初始化,这样可以任意顺序
var m3 = Member
id:2,
"name":"杰哥的技术杂货铺"


或者
// 简短变量声明方式:
m3 := Member
id:2,
"name":"杰哥的技术杂货铺"

三、结构体的访问通过变量名,使用符号点(.),可以访问结构体类型中的字段,或为字段赋值,也可以对字段进行取址(& )操作。
package main

import "fmt"

//定义结构体
type Persion struct
namestring
ageint
sexstring
address string


func main()
//1.方法一
var p1 Persion
p1.name = "杰哥的技术杂货铺"
p1.age = 18
p1.sex = "男"
p1.address = "技术博客"
fmt.Printf("姓名:%s,年龄:%d,性别:%s,地址:%s\\n",p1.name,p1.age,p1.sex,p1.address)

//2.方法二
p2 := Persion
p2.name = "黄风怪"
p2.age = 3000
p2.sex = "男"
p2.address = "黄风洞"
fmt.Printf("姓名:%s,年龄:%d,性别:%s,地址:%s\\n",p2.name,p2.age,p2.sex,p2.address)


//3.方法三:创建结构体对象时,直接进行赋值
p3 := Persionname: "蜘蛛精",age: 500,sex: "女",address: "盘丝洞"
fmt.Printf("姓名:%s,年龄:%d,性别:%s,地址:%s\\n",p3.name,p3.age,p3.sex,p3.address)

p4 := Persion
name:"黄眉大王",
age: 1000,
sex: "男",
address: "小雷音寺",

fmt.Println(p4)

//4.方法四:创建结构体对象时,不写字段名,直接赋予数值
//此种方式需要注意顺序
p5 := Persion"白骨精",300,"女","白骨洞"
fmt.Println(p5)

四、结构体指针结构体与数组一样,都是值传递,比如当把数组或结构体作为实参传给函数的形参时,会复制一个副本,所以为了提高性能,一般不会把数组直接传递给函数,而是使用切片(引用类型)代替,而把结构体传给函数时,可以使用指针结构体。
指针结构体,即一个指向结构体的指针,声明结构体变量时,在结构体类型前加*号,便声明一个指向结构体的指针,如:


注意,指针类型为引用类型,声明结构体指针时,如果未初始化,则初始值为nil,只有初始化后,才能访问字段或为字段赋值。


var m1 *Member
m1.name = "杰哥的技术杂货铺"//错误用法,未初始化,m1为nil

m1 = & Member
m1.name = "杰哥的技术杂货铺"//初始化后,结构体指针指向某个结构体地址,才能访问字段,为字段赋值。

另外,使用Go内置new()函数,可以分配内存来初始化结构休,并返回分配的内存指针,因为已经初始化了,所以可以直接访问字段。
var m2 = new(Member)
m2.name = "杰哥的技术杂货铺"

五、结构体可见性上面的例子中,我们定义结构体字段名首字母是小写的,这意味着这些字段在包外不可见,因而无法在其他包中被访问,只允许包内访问。
下面的例子中,我们将Member声明在member包中,而后在main包中创建一个变量,但由于结构体的字段包外不可见,因此无法为字段赋初始值,无法按字段还是按索引赋值,都会引发panic错误。
package member
type Member struct
idint
namestring
emailstring
gender int
ageint

package main

fun main()
var m = member.Member
1,
"杰哥的技术杂货铺",
"jiege@163.com",
1,
18
//会引发panic错误

因此,如果想在一个包中访问另一个包中结构体的字段,则必须是大写字母开头的变量,即可导出的变量,如:
type Member struct
Idint
Namestring
Emailstring
Gender int
Ageint

六、结构体标签在定义结构体字段时,除字段名称和数据类型外,还可以使用反引号为结构体字段声明元信息,这种元信息称为Tag,用于编译阶段关联到字段当中,如我们将上面例子中的结构体修改为:
type Member struct
Idint`json:"id"`
Namestring `json:"name"`
Emailstring `json:"email"`
Gender int`json:"gender,"`
Ageint`json:"age"`

上面例子演示的是使用encoding/json包编码或解码结构体时使用的Tag信息。
Tag由反引号括起来的一系列用空格分隔的key:"value"键值对组成,如:
Id int ??json:"id" gorm:"AUTO_INCREMENT"??
七、结构体嵌套结构体嵌套,可以理解为定义一个结构体中,其字段可以是其他的结构体,这样,不同的结构体就可以共用相同的字段。


注意, 结构体不能包含自身,但可能包含指向自身的结构体指针。


package main

import (
"fmt"
)

//1.定义一个书的结构体
type Book struct
bookname string
price float64


//2.定义学生的结构体
type Student struct
name string
age int
book Book

type Student2 struct
name string
age int
book *Book//book结构体的地址

func main()
b1 := Book
b1.bookname = "西游记"
b1.price = 66.6

s1 := Student
s1.name = "红孩儿"
s1.age = 18
s1.book = b1//值传递
fmt.Println(b1)
fmt.Println(s1)
fmt.Printf("学生姓名:%s,学生年龄:%d,看的书是:《%s》,书的价格是:%.2f\\n",s1.name,s1.age,s1.book.bookname,s1.book.price)

s2 := Studentname: "武松",age: 28,book: Bookbookname: "《Go语言从入门到放弃》",price: 88.8
fmt.Println(s2.name,s2.age)
fmt.Println("\\t",s2.book.bookname,s2.book.price)

s3 := Student
name: "jack",
age: 17,
book: Book
bookname: "十万个为啥",
price: 35.5,
,

fmt.Println(s3.name,s3.age)
fmt.Println("\\t",s3.book.bookname,s3.book.price)


b4 := Bookbookname: "射雕英雄传",price: 76.0
s4 := Student2name:"张三",age: 20,book: & b4
fmt.Println(b4)
fmt.Println(s4)
fmt.Println("\\t",s4.book)

s4.book.bookname = "挪威的森林"
fmt.Println(b4)
fmt.Println(s4)
fmt.Println("\\t",s4.book)

可以看到,我们定义Student结构体时,可以把Book结构体作为Student的字段。
八、结构体方法在Go语言中,将函数绑定到具体的类型中,则称该函数是该类型的方法,其定义的方式是在func与函数名称之间加上具体类型变量,这个类型变量称为方法接收器,如:


注意,并不是只有结构体才能绑定方法,任何类型都可以绑定方法,只是我们这里介绍将方法绑定到结构体中。


func setName(m Member,name string)//普通函数
m.Name = name


func (m Member)setName(name string)//绑定到Member结构体的方法
m.Name = name

从上面的例子中,我们可以看出,通过方法接收器可以访问结构体的字段,我们可以任意命名方法接收器。
调用结构体的方法,与调用字段一样:
m := Member
m.setName("小明")
fmt.Println(m.Name)//输出为空

上面的代码中,我们会很奇怪,不是调用setName()方法设置了字段Name的值了吗?为什么还是输出为空呢?
这是因为,结构体是值传递,当我们调用setName时,方法接收器接收到是只是结构体变量的一个副本,通过副本对值进行修复,并不会影响调用者,因此,我们可以将方法接收器定义为指针变量,就可达到修改结构体的目的了。
func (m *Member)setName(name string)//将Member改为*Member
m.Name = name


m := Member
m.setName("小明")
fmt.Println(m.Name)//小明

方法和字段一样,如果首字母为小写,则只允许在包内可见,在其他包中是无法访问的,因此,如果要在其他包中访问??setName???,则应该将方法名改为??SetName??


由此我们可以看出,要想改变结构体内容时就需要使用指针接收者。


那什么时候该使用值接收者,什么时候使用指针接收者呢,可归纳为以下几点:
  • 要更改内容的时候必须使用指针接收者
  • 值接收者是go语言特有,因为它函数传参过程是通过值的拷贝,因此需要考虑性能问题,结构体过大也需要考虑使用指针接收者
  • 一致性:如果有指针接收者,最好都使用指针接收者
  • 值/指针接收者均可接受值/指针,定义方法的人可以随意改动接收者的类型,这并不会改变调用方式
九、结构体特性下面总结几点结构体的相关特性:
  • 值传递
结构体与数组一样,是复合类型,无论是作为实参传递给函数时,还是赋值给其他变量,都是值传递,即复一个副本。
  • 没有继承
Go语言是支持面向对象编程的,但却没有继承的概念,在结构体中,可以通过组合其他结构体来构建更复杂的结构体。
  • 结构体不能包含自己
一个结构体,并没有包含自身,比如Member中的字段不能是Member类型,但却可能是*Member。





    推荐阅读