Go学习笔记——复合数据结构之结构体
这一篇,捎带复习了一下之前的内容,因为我的笔记不是从最开始记录的,所以会有一些穿插。。。因为是在一边敢项目,一边学go所以笔记有点乱,进度也有点慢,现在还是在熟悉基础知识的阶段。
先补个漏
环境配置
近期我的机器硬件出问题了,经常蓝屏,就换了台电脑。随之而来的就是各种开发环境需要重新搭建,在搭建go开发环境的时候,遇到了一点小问题,这里记录一下。
正常来说,如果个人电脑是Windows系统,我还是比较推荐结合使用wsl2+vscode来使用的,等于是在linux内核的系统里安装go环境,然后通过vscode的远程开发插件,直接进行开发,非常好用,对于以后移植生产也会方便不少。但我们单位的机子非常辣鸡,开了hyper-v之后简直没法看,所以我就还是直接安装的Windows的msi包,
我安装的是最新的稳定版,1.17.8,装好之后,用vscode装好go的扩展插件,然后,就开始突突冒问题了
什么“the gopls command is not available”之类的。然后vscode的左下标这里会出现一个叹号

我这里因为已经处理好了,所以变成了闪电⚡。
然后按照编辑器的提示,一致没装好,提示超时之类的问题,开始我以为是我的go版本太新了,导致有的插件没有安装成功,后来我试了低版本的稳定版,依然不行,所以考虑还是网络问题。这里,就其实处理起来也简单,就全局把go环境的下载包源改一下就好了,建议直接使用模块代理,这个地址里也有具体用法,我这里在简单引用一下;
Windows环境
在powershell里输入
$env:GO111MODULE = "on"
$env:GOPROXY = "https://goproxy.cn"
linux&Mac
$ export GO111MODULE=on
$ export GOPROXY=https://goproxy.cn
或者
$ echo "export GO111MODULE=on" >> ~/.profile
$ echo "export GOPROXY=https://goproxy.cn" >> ~/.profile
$ source ~/.profile
设置好以后,再安装go的开发模块,就会顺畅许多了(可能要重开一下vscode,或者直接在windows Terminal里安装)

gomodule模式
在go 1.15以后,go默认的构建模式就是module模式了,而早期的gopah模式将会被移除
我这里再练习结构体的时候,发现自己定义的结构体要引入main文件时,一直出错,而我一直是在用的gopath模式,如果用这种模式的话,自己定义的包,需要放到gopath目录下的src下才能引入。改用go module模式后,只需要通过把包放到项目下即可。这里就以一个例子来看

比如我要引入自定义的book里的内容,则可以这样
package store
type Person struct {
Name string
Phone string
Addr string
_ int
}
type Book struct {
Title string
Person
Indexes map[string]int
Pages int
}
然后引入的时候,就这样
package main
import (
"fmt"
"go17/internal/store"
)
...
然后引入第三方的包时,使用go mod tidy命令来进行构建。好了具体的内容不再多说,可以参见
正题开始
概念
go语言中的结构体,是通过整合多种基本数据类型和符合数据类型,来构建对真实世界的抽象,而提供这种聚合抽象能力的类型,就是结构体类型(struct)。
再回头看一下怎么定义新类型
方法一、类型定义
type T S //定义一个新类型T
注意,上面的伪代码里,S表示一个已定义的类型或者基础类型,比如以下两种情况
type T1 int
type T2 T1
//比如这段代码
type T1 int
type T2 T1
type T3 string
func main() {
var n1 T1
var n2 T2 = 5
n1 = T1(n2) // ok
var s T3 = "hello"
n1 = T1(s) // 错误:cannot convert s (type T3) to type T1
}
类型定义和变量声明相似,也可以放入块中进行
type {
T1 int
T2 T1
T3 string
}
类型定义也支持通过类型字面值来定义新类型,
type M map[int]string
type S []string
方法二、类型别名
别名定义的特点就是通过“=”链接两个类型,等号两端的类型是完全一致的,只是名字不同。
type T = S // type alias
这个比较容易理解,直接看个具体的例子
func main() {
type T = string
s := "hello"
var t T = s
fmt.Printf("t的类型是【%T】\ns的类型是【%T】\n", t, s)
}

如何定义一个结构体类型?
类型字面值
复合类型的定义一般都是通过类型字面值的方式来进行的,作为复合类型之一的结构体类型也不例外
type T struct {
Field1 T1
Field2 T2
... ...
FieldN Tn
}
struct关键字后面的大括号所包裹的内容,就是一个类型字面值。(
type Person struct {
Name string
Phone string
Addr string
_ int //隐藏字段
}
type Book struct {
Title string
Person
Indexes map[string]int
Pages int
}
这里要再提一下,结构体类型中的字段首字母都是用的大写,目的是标识该模块各个字段都是导出标识符,也就是只要其他包引入了book包,就可以在这些包中直接引用类型名Book,也可以通过Book变量来直接引用Name,Pages等字段(和C#里定义模型类类似)
package main
import (
"fmt"
"go17/internal/store"
)
func main() {
var book store.Book
fmt.Println(book.Person.Phone)//nil
book.Person.Phone = "110"
fmt.Println(book.Person.Phone)//110
}
而如果结构体类型旨在定义的包内使用,就可以将类型名的首字母小写,或者只是不想结构体中某个字段爆露出来,则只需要把对应的字段首字母小写


像这样,把Phone字段首字母小写,编辑器就会报错了,改成小写也不行
空结构体
空结构体也是定义结构体的一种方法,它没有包含任何字段,通过unsafe包获取它的大小也会得到一个0,也就是无内存占用,
type Empty struct{} // Empty是一个不包含任何字段的空结构体类型
var s Empty
println(unsafe.Sizeof(s)) // 0
而这里的实际意义,课上老师说的是作为“事件”信息进行Goroutine之间通信,具体的内容我就不罗列了,对我来说现阶段再往深里挖会增加心智负担,所以我决定先跳过这里,先知道有空结构体这个概念就好。
使用其他结构体作为自定义结构体中字段的类型
我在上面的例子其实就是这种情况,Book里的Author字段,其实是另外引用的一个Person的结构体,这个和C#语言里的自定义模型类也很类似,所以不难理解,为了语法的简洁,甚至可以取消变量名,比如
type Book struct {
Title string
Author Person
}
//可以直接改写成-----
type Book struct {
Title string
Person
}
而在实际使用时,直接调用book.Person.xx就可以了,这种方式叫做“嵌入字段”或者匿名字段,也就是类型名本身也可以当作变量名
但是注意,以下这些情况是不被允许的
type T struct {
t T
... ...
}
type T1 struct {
t2 T2
}
type T2 struct {
t1 T1
}
Go 语言不支持这种在结构体类型定义中,递归地放入其自身类型字段的定义方式。但我们却可以拥有自身类型的指针类型、以自身类型为元素类型的切片类型,以及以自身类型作为 value 类型的 map 类型的字段
,比如这样
type T struct {
t *T // ok
st []T // ok
m map[string]T // ok
}
结构体变量的声明与初始化
和其他所有变量的声明一样,我们也可以使用标准变量声明语句,或者是短变量声明语句声明一个结构体类型的变量

不过,这里要注意,我们在前面说过,结构体类型通常是对真实世界复杂事物的抽象,这和简单的数值、字符串、数组 / 切片等类型有所不同,结构体类型的变量通常都要被赋予适当的初始值后,才会有合理的意义
变量初始化的方式大致分为三种
零值初始化
结构体类型本身是“零值可用”的,也就是我们定义好结构体后,其内部各个字段的值都是对应的零值状态。
使用复合字面值
最简单的对结构体变量进行显式初始化的方式,就是按顺序依次给每个结构体字段进行赋值,比如下面的代码
//注意,这么写虽然正确,但这是反例奥~
//这种情况当遇到结构体内字段较多的情况后,在进行初始化,就很麻烦了,go也不推荐我们这么做
type Book struct {
Title string // 书名
Pages int // 书的页数
Indexes map[string]int // 书的索引
}
var book = Book{"The Go Programming Language", 700, make(map[string]int)}
Go 推荐我们用“field:value”形式的复合字面值,对结构体类型变量进行显式初始化(形式上有点像map或者哈希结构)
var t = T{
F2: "hello",
F1: 11,
F4: 14,
}
使用特定的构造函数
略
好了,关于这篇的笔记就记录到这里,还有结构体类型的内存布局的部分我没有记录,但这块我是看了的,也基本明白了,但我条过记录还是觉得这部分的记录会增加心智负担,但注意,这并不是不想学习的的原因,只是暂时不做记录,为的是后续我再返回来看的时候,会很容易消化掉比较容易消化的部分,而偏原理性的内容,我会先认真看一遍,然后先明白大概意思就达成目标了,后续真正用go做项目的时候,肯定还是会重新深入这部分。先这样啦