Go Study Day04 - 结构体和包
# 结构体
Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct
。 也就是我们可以通过struct
来定义自己的类型了。
Go语言中通过struct
来实现面向对象。
# 结构体定义
type 结构体名 struct{
字段1 字段1的类型
字段2 字段2的类型
...
}
// 如
type person struct {
name string
city string
age int8
}
2
3
4
5
6
7
8
9
10
11
结构体是值类型,需要注意的是,由于结构体中可能包含指针类型的成员,因此在复制结构体时,仅会复制指针的值,而不会复制指针所指向的内存地址和数据。这意味着在复制结构体时,新的结构体和原始结构体可能会共享某些数据。
# 结构体初始化
# 先声明再赋值
var p person // 声明一个person类型的变量p
p.name = "元帅"
p.age = 18
fmt.Println(p)
2
3
4
# 声明同时初始化
- 键值对初始化
// 键值对初始化
var p2 = person{
name: "冠华",
age: 15,
}
fmt.Println(p2)
2
3
4
5
6
- 值列表初始化
// 值列表初始化
var p3 = person{
"理想",
100,
}
fmt.Println(p3)
2
3
4
5
6
注意事项:
- 两者不能混用
- 没有赋值的字段会使用对应类型的零值.
# 匿名结构体
在定义一些临时数据结构等场景下还可以使用匿名结构体。
package main
import (
"fmt"
)
func main() {
var user struct{Name string; Age int}
user.Name = "小王子"
user.Age = 18
fmt.Printf("%#v\n", user)
}
2
3
4
5
6
7
8
9
10
11
12
# 结构体指针
结构体是值类型,赋值的时候都是拷贝.
当结构体字段较多的时候,为了减少内存消耗可以传递结构体指针.
我们还可以通过使用new
关键字对结构体进行实例化,得到的是结构体的地址。 格式如下:
var p2 = new(person)
fmt.Printf("%T\n", p2) //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}
2
3
# 面向对象
# 构造函数
返回一个结构体变量的函数,为了实例化结构体的时候更省事儿.
func newPerson(name string, age int)person{
return person{
name: name,
age: age,
}
}
2
3
4
5
6
# 方法
方法是作用于特定类型的函数.
方法的定义:(万变不离其宗)
func (接收者变量 接收者类型)方法名(参数)返回值{
// 方法体
}
2
3
# 接收者
接收者通常使用类型首字母的小写,不建议使用诸如this
和self
这样的.
# 值接收者和指针接收者的区别
使用值接收者的方法不能修改结构体变量
// SetAge2 设置p的年龄
// 使用值接收者
func (p Person) SetAge2(newAge int8) {
p.age = newAge
}
func main() {
p1 := NewPerson("小王子", 25)
p1.Dream()
fmt.Println(p1.age) // 25
p1.SetAge2(30) // (*p1).SetAge2(30)
2
3
4
5
6
7
8
9
10
11
12
使用指针接收者的方法可以修改结构体的变量
// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int8) {
p.age = newAge
}
2
3
4
5
调用该方法:
func main() {
p1 := NewPerson("小王子", 25)
fmt.Println(p1.age) // 25
p1.SetAge(30)
fmt.Println(p1.age) // 30
}
2
3
4
5
6
- 需要修改接收者中的值
- 接收者是拷贝代价比较大的大对象
- 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
我们应该尽量使用指针接收者.
# 匿名字段
没有名字的字段.
//Person 结构体Person类型
type Person struct {
string
int
}
func main() {
p1 := Person{
"二狗",
18,
}
fmt.Printf("%#v\n", p1) //main.Person{string:"北京", int:18}
fmt.Println(p1.string, p1.int) //北京 18
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 嵌套结构体
type address struct {
province string
city string
}
type company struct {
name string
addr address // 嵌套
}
2
3
4
5
6
7
8
9
10
# 匿名嵌套结构体
type address struct {
province string
city string
}
type company struct {
name string
address // 嵌套匿名结构体
}
2
3
4
5
6
7
8
9
# 匿名嵌套结构体的字段冲突
先在自己结构体找这个字段,找不到就去匿名嵌套的结构体中查找该字段
type address struct {
province string
city string
}
type workPlace struct {
province string
city string
}
type person struct {
name string
age int
address // 匿名嵌套结构体
workPlace // 匿名嵌套结构体
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 结构体的“继承”
Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。
//Animal 动物
type Animal struct {
name string
}
func (a *Animal) move() {
fmt.Printf("%s会动!\n", a.name)
}
//Dog 狗
type Dog struct {
Feet int8
*Animal //通过嵌套匿名结构体实现继承
}
func (d *Dog) wang() {
fmt.Printf("%s会汪汪汪~\n", d.name)
}
func main() {
d1 := &Dog{
Feet: 4,
Animal: &Animal{ //注意嵌套的是结构体指针
name: "乐乐",
},
}
d1.wang() //乐乐会汪汪汪~
d1.move() //乐乐会动!
}
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
# 结构体与JSON
JSON:是一种跨语言的数据格式.多用于在不同语言间传递数据
// 1.序列化: 把Go语言中的结构体变量 --> json格式的字符串
// 2.反序列化: json格式的字符串 --> Go语言中能够识别的结构体变量
type person struct {
Name string `json:"name" db:"name" ini:"name"`
Age int `json:"age"`
}
func main() {
p1 := person{
Name: "周林",
Age: 9000,
}
// 序列化
b, err := json.Marshal(p1)
if err != nil {
fmt.Printf("marshal failed, err:%v", err)
return
}
fmt.Printf("%v\n", string(b))
// 反序列化
str := `{"name":"理想","age":18}`
var p2 person
json.Unmarshal([]byte(str), &p2) // 传指针是为了能在json.Unmarshal内部修改p2的值
fmt.Printf("%#v\n", p2)
}
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
# 包(package)
包的定义 -->package
关键字,包名通常是和目录名一致,不能包含-
- 一个文件夹就是一个包
- 文件夹里面放的都是
.go
文件
包的导入 --> import
- 包的路径从
GOPATH/src
后面的路径开始写起,路径分隔符用/
- 想被别的包调用的标识符都要首字母大写!
- 单行导入和多行导入
- 导入包的时候可以指定别名
- 导入包不想使用包内部的标识符,需要使用匿名导入
sql
包导入时会讲这个 - 每个包导入的时候会自动执行一个名为
init()
的函数,它没有参数也没有返回值也不能手动调用 - 包导入的时候会自动执行
- 一个包里面只有一个init()
- 多个包中都定义了
init()
函数,则它们的执行顺序见下图:
# 定义包
package packagename
# 包的引入
要在当前包中使用另外一个包的内容就需要使用import
关键字引入这个包,并且import语句通常放在文件的开头,package
声明语句的下方。完整的引入声明语句格式如下:
import importname "path/to/package"
importname:引入的包名,通常都省略。默认值为引入包的包名。
path/to/package:引入包的路径名称,必须使用双引号包裹起来。
Go语言中禁止循环导入包。
批量引入
import ( "fmt" "net/http" "os" )
1
2
3
4
5
# init函数
在每一个Go源文件中,都可以定义任意个如下格式的特殊函数:
func init(){
// ...
}
2
3
这种特殊的函数不接收任何参数也没有任何返回值,我们也不能在代码中主动调用它。当程序启动的时候,init函数会按照它们声明的顺序自动执行。
一个包的初始化过程是按照代码中引入的顺序来进行的,所有在该包中声明的init
函数都将被串行调用并且仅调用执行一次。每一个包初始化的时候都是先执行依赖的包中声明的init
函数再执行当前包中声明的init
函数。确保在程序的main
函数开始执行时所有的依赖包都已初始化完成。
# go module介绍
Go module 是 Go1.11 版本发布的依赖管理方案,从 Go1.14 版本开始推荐在生产环境使用,于Go1.16版本默认开启。Go module 提供了以下命令供我们使用:
命令 | 介绍 |
---|---|
go mod init | 初始化项目依赖,生成go.mod文件 |
go mod download | 根据go.mod文件下载依赖 |
go mod tidy | 比对项目文件中引入的依赖与go.mod进行比对 |
go mod graph | 输出依赖关系图 |
go mod edit | 编辑go.mod文件 |
go mod vendor | 将项目的所有依赖导出至vendor目录 |
go mod verify | 检验一个依赖包是否被篡改过 |
go mod why | 解释为什么需要某个依赖 |
# GOPROXY
GOPROXY 的默认值是:https://proxy.golang.org,direct
,由于某些原因国内无法正常访问该地址,所以我们通常需要配置一个可访问的地址。目前社区使用比较多的有两个https://goproxy.cn
和https://goproxy.io
,当然如果你的公司有提供GOPROXY地址那么就直接使用。设置GOPAROXY的命令如下:
go env -w GOPROXY=https://goproxy.cn,direct
# GOPRIVATE
设置了GOPROXY 之后,go 命令就会从配置的代理地址拉取和校验依赖包。当我们在项目中引入了非公开的包(公司内部git仓库或 github 私有仓库等),此时便无法正常从代理拉取到这些非公开的依赖包,这个时候就需要配置 GOPRIVATE 环境变量。GOPRIVATE用来告诉 go 命令哪些仓库属于私有仓库,不必通过代理服务器拉取和校验。
GOPRIVATE 的值也可以设置多个,多个地址之间使用英文逗号 “,” 分隔。我们通常会把自己公司内部的代码仓库设置到 GOPRIVATE 中,例如:
$ go env -w GOPRIVATE="git.mycompany.com"
# 使用
go mod init 项目名