【Go学习笔记】结构体相关
Go语言基础之结构体
Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
结构体
Go语言中的基础数据类型可以表示一些事物的基本属性,但是当想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct
。 也就是我们可以通过struct
来定义自己的类型了。
Go语言中通过struct
来实现面向对象。
结构体的定义
使用type
和struct
关键字来定义结构体,具体代码格式如下:
1 | type 类型名 struct { |
其中:
- 类型名:标识自定义结构体的名称,在同一个包内不能重复。
- 字段名:表示结构体字段名。结构体中的字段名必须唯一。
- 字段类型:表示结构体字段的具体类型。
举个例子,我们定义一个Person
(人)结构体,代码如下:
1 | type person struct { |
同样类型的字段也可以写在一行
1 | type person1 struct { |
这样我们就拥有了一个person
的自定义类型,它有name
、city
、age
三个字段,分别表示姓名、城市和年龄。这样我们使用这个person
结构体就能够很方便的在程序中表示和存储人信息了。
语言内置的基础数据类型是用来描述一个值的,而结构体是用来描述一组值的。比如一个人有名字、年龄和居住城市等,本质上是一种聚合型的数据类型
1 | var 结构体实例 结构体类型 |
实例化示例:
1 | type info struct { |
我们通过.
来访问结构体的字段(成员变量),例如Info.name
和 Info.age
等。
取结构体的地址实例化 &{}
使用&
对结构体进行取地址操作相当于对该结构体类型进行了一次new
实例化操作。
1 | // 定义一个名为 info的结构体 |
Info.name = "zhangsan"
其实在底层是(*Info).name = "zhangsan"
,这是Go语言帮我们实现的语法糖
匿名结构体
定义匿名结构体时没有 type
关键字,与其他定义类型的变量一样,如果在函数外部需在结构体变量前加上 var
关键字,在函数内部可省略 var
关键字。
1 | func main() { |
结构体初始化
没有初始化的结构体,其成员变量都是对应其类型的零值。
1 | type info struct { |
通过位置参数初始化
初始化时对应结构体里面的位置传递元素,这种方式必须和声明里面的位置和数据类型一致。
1 | type info struct { |
通过键值对初始化
使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。
1 | type info struct { |
也可以对结构体指针进行键值对初始化,例如:
1 | type info struct { |
初始化字段类型是切片或map时
需要注意的是:当定义结构体的字段类型是 []切片
或者是map[string]string
时,结构体初始化时的字段需要make后再进行初始化
1 | package main |
值传递和指针传递
在开始前你需要先掌握结构体的定义、声明以及使用方法;
值传递
我们先来看一段代码:
1 | package main |
程序的输出是 18
因为这种方法传递的是值一个副本,在change() 函数中,你实际上修改的是副本的值;
指针传递
修改程序,让它使用指针进行传递:
1 | package main |
这段程序使用了&取地址操作符来获取结构体的地址,而change()函数期望一个Info
结构体的地址类型 *Info
,这里*Info
的意思是指向类型info
值得指针;
程序运行输出 999
但我们需要注意的是,实际上这里传递的依然是一个副本,只不过这个副本是一个地址,它指向原来的值;
所以,你可以修改 info.Age
的值,但你无法修改 Info
;
1 | package main |
这段代码输出18
那么,应该如何选择传递方式呢?
很明显,复制一个指针比复制一个结构的消耗要小的多;如果我们的结构非常复杂和庞大,那么复制结构会是一个很消耗性能的操作,在进行大量这样的操作时你的感觉会非常明显;
而使用指针则可避免这个问题;
当你的函数本意是改变原始数据时,那么肯定用指针转递;
当你的结构非常大时,比如包含庞大的切片、map时,也需要用指针转递;
但是如果你的结构体非常小,且不打算修改结构体内容,那么应该考虑使用值传递;
因为你不能保证程序没有bug导致误修改;且在某些多任务环境下,你需要为指针操作添加额外的锁操作,这样有些得不偿失。
结构体方法
结构体方法
是一种特殊的函数
,它规定只有指定的接收者
才能调用该方法
,有点像面向对象编程语言中与类绑定
的方法,接收者声明在func关键字
之后,函数名
之前。方法
的接收者
分为值接收者
与指针接收者
,若方法内部有需要改变接收者的值,需要使用指针接收者
。值接收者
是实参
的值拷贝
,会额外消耗
内存空间,当接收者
占用内存空间很大
时,使用值接收者显然有点浪费空间
,因此应尽量使用指针接收者
。指针对象
调用值接收者
的方法时,会自动解引用
。
1 | //声明一个Person结构体 |
方法和函数的区别
- 调用方式不一样:
函数
的调用方式为函数名(实参列表)
。方法
的调用方式:变量.方法名(实参列表)
- 对于
普通函数
,接收者为值类型
时,不能
将指针类型的数据直接传递
,反之亦然。 - 对于
方法
,如(struct的方法),接收者为值类型
时,可以直接用指针类型
的变量
调用方法,反过来同样也可以。