适用于有面向对象语言基础
go的基本结构
Go语言的基本结构包括以下几个部分:
- 包(Package):Go语言的代码是以包的形式组织的,每个Go文件都属于一个包。包可以是自己定义的,也可以是标准库或第三方库提供的。
- 导入(Import):通过导入其他包,可以使用其他包中的函数、变量和类型。使用关键字import来导入包。
- 函数(Function):Go语言的程序执行入口是main函数,每个可执行程序必须包含一个main函数。除了main函数外,还可以定义其他函数来实现具体的功能。
- 变量(Variable):在Go语言中,需要先声明变量,然后才能使用。可以使用关键字var来声明变量,也可以使用短变量声明方式:=来声明并初始化变量。
- 控制语句(Control Statements):Go语言提供了常见的控制语句,如条件语句(if-else)、循环语句(for、while)、选择语句(switch)等,用于控制程序的执行流程。
- 数据类型(Data Types):Go语言支持多种基本数据类型,包括整型、浮点型、布尔型、字符串等。还可以使用结构体、数组、切片、映射等复合数据类型。
- 指针(Pointer):Go语言支持指针类型,可以通过指针来间接访问和修改变量的值。
- 结构体(Struct):结构体是一种自定义的复合数据类型,可以包含多个字段,每个字段可以是不同的数据类型。
- 方法(Method):Go语言中的方法是一种特殊的函数,与某个类型关联,可以在该类型的实例上调用。
- 接口(Interfa型。类ce):接口定义了一组方法的集合,实现了这些方法的类型就是该接口的实现
package main
//源文件中非注释的第一行指明这个文件属于哪个包
import "fmt"
//导包
/* 多个包可以使用如下语法
import{
fmt
XXX
}
*/
func main() {
/* 简单的程序 万能的hello world */
fmt.Println("Hello Go")
}
变量的声明
局部变量的声明:
⽅法⼀:声明⼀个变量 默认的值是0 var a int
⽅法⼆:声明⼀个变量,初始化⼀个值 var b int = 100
⽅法三:在初始化的时候,可以省去数据类型,通过值⾃动匹配当前的变量的数据类型 var c = 100
⽅法四:(常⽤的⽅法) 省去var关键字,直接⾃动匹配 e := 10
全局变量的声明:
除上述方法四之外都可以
多变量的声明:
var xx, yy int = 1, 2
var aa, bb = 10086, "judy"
var ( //一般用于全局变量的声明
a int = 1
b string = "judy"
c bool = true
)
常量的声明:
加入关键字const
const a int = 1
const (
a = 1
b = 2
)
iota标示符
作用:可以用于增长数字的定义,可以用于表达式,在常量中的存储结果值。
函数
基本语法
函数的基本组成为:关键字 func、函数名、参数列表、返回值、函数体和返回语句
注意:go语言的函数支持返回多个值
func 函数名(形参) 返回值{
内容
}
func swap(x, y string) (string, string) {
return y, x
}
执行顺序
golang里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。
虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。
go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。
程序的初始化和执行都起始于main包。
如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。
当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。
等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:
值传递与引用传递
go语言支持指针
*指针
&取地址符
defer
类似于java中的finally
常用于
- 释放占用的资源
- 捕捉处理异常
- 输出日志
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。
func Demo(){
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
defer fmt.Println("4")
}
// 4 3 2 1
数组 & slice & map
与java的比较:
- 切片(Slice&集合):
- 在Go语言中,slice是一种动态大小的数据结构,类似于动态数组。它可以通过索引访问元素,并且可以自动扩展以容纳更多的元素。在Java中,ArrayList是一个类似于slice的数据结构,它可以动态地添加和删除元素。
- 映射(Map):
- Go语言的映射是一个键值对集合,类似于哈希表或字典。映射是无序的,键必须是唯一的。
- Java中的映射接口是java.util.Map,实现该接口的常见类有java.util.HashMap和java.util.TreeMap。它们也提供了键值对的存储,但Java的映射是有序的,且键必须是唯一的。
数组
数组的声明
//固定长度的数组
var myArray1 [10]int
myArray2 := [10]int{1,2,3,4}
myArray3 := [4]int{11,22,33,44}
注意:固定⻓度的数组在传参的时候, 是严格匹配数组类型的
func printArray(myArray [4]int) { //这点和java不同
//值拷贝
}
slice切片
类似于动态数组, 切⽚的扩容机制,append的时候,如果⻓度增加后超过容量,则将容量增加2倍
切片可以通过使用make函数来创建,例如slice := make([]int, length, capacity),其中length是切片当前的长度,capacity是切片底层数组的容量。也可以通过类型转换将一个数组转换为切片,例如slice := []int(array)。
切片与底层数组是分离的,切片操作不会修改底层数组。切片的长度是当前元素在底层数组中的索引,而容量则是底层数组从切片的起始索引到数组末尾的长度。
切片的定义:
var slice0 []type 一个切片在未初始化之前默认为 nil,长度为 0
var slice1 []type = make([]type, len)
slice1 := make([]type, len)
slice2 :=make([]int,len,cap)//cap指定容量 len 是数组的长度并且也是切片的初始长度
切片的初始化:
声明slice1是一个切片,并且初始化,默认值是1,2,3。 长度len是3 slice1 := []int{1, 2, 3}
声明slice1是一个切片,但是并没有给slice分配空间 var slice1 []int slice1 = make([]int, 3) //开辟3个空间 ,默认值是0
声明slice1是一个切片,同时给slice分配空间,3个空间,初始化值是0 var slice1 []int = make([]int, 3)
声明slice1是一个切片,同时给slice分配空间,3个空间,初始化值是0, 通过:=推导出slice是一个切片 slice1 := make([]int, 3)
切片相关函数
- len() 函数:获取长度。
- cap() 函数:测量切片最长可以达到多少。
切⽚的⻓度和容量不同,⻓度表示左指针⾄右指针之间的距离,容量表示左指针⾄底层数组末尾的距离。
- append()函数:追加新元素。如果当前容量不足,底层数组会自动扩容。切片的容量可以多次扩容,每次扩容容量会翻倍。
- copy()函数:拷贝切片。将原切片的内容复制到新的切片中,不会改变切片的长度和容量。
/* 添加多个元素 */
numbers = append(numbers, 2,3,4)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
可以通过delete函数删除切片中的元素,例如slice = append(slice[:index-1], slice[index+1:]...)。
切片可以使用==运算符进行比较,比较的是切片的长度、容量和底层数组的内容是否相同。
map
类似于java中的map<key,value>
声明方式
映射可以通过make函数来创建,例如myMap := make(map[keyType]valueType),其中keyType是键的类型,valueType是值的类型。也可以通过直接赋值来创建一个空的映射,例如myMap := map[keyType]valueType{}
使用方式
- 映射的访问:
可以通过键来访问映射中的值,例如value := myMap[key]。如果键不在映射中,将返回该类型的零值。 - 映射的修改:
可以通过键来修改映射中的值,例如myMap[key] = newValue。如果键不存在,会将该键添加到映射中,并将其值设置为指定值。 - 映射的删除:
可以使用delete函数删除映射中的键及其对应的值,例如delete(myMap, key)。 - 映射的遍历:
可以使用range关键字遍历映射的键值对,例如for key, value := range myMap { ... }。
//增删改查
val, key := language["php"] //查找是否有php这个子元素
if key {
fmt.Printf("%v", val)
} else {
fmt.Printf("no");
}
language["php"]["id"] = "3" //修改了php子元素的id值
language["php"]["nickname"] = "hello" //增加php元素里的nickname值
delete(language, "php") //删除了php子元素
面向对象特征
继承
结构体
在面向对象编程中,我们定义类(class)来描述具有相似属性和行为的对象。然后创建该类的实例,即对象。在Go语言中,我们使用结构体来定义类,并通过结构体的方法来实现对象的行为。
类似于java中的类class
如果说类的属性首字母大写, 表示该属性是对外能够访问的,否则的话只能够类的内部访问
//如果类名首字母大写,表示其他包也能够访问
type Hello struct {
Name string
age int
}
this关键字
与java中的基本相同
父类
子类可以重写父类的方法,也可以调用父类的方法和自己的方法
java中是extend 父类;go中在结构体里声明就行了
type Teacher struct {
Human//父类
major string
}
子类
定义子类对象
//s := Teacher{Human{"judy", "female"}, "语文"}
var s Teacher
s.name = "judy"
s.sex = "male"
s.major = “数学”
多态
接口
接口的定义
接口的本质是指针
type AnimalIF interface {
Sleep() //睡觉方法
GetNumber() string //获取动物的数量
}
实现接口
重写全部方法即可实现此接口
var animal AnimalIF //接口的数据类型
animal = &Cat{"3"}
animal.Sleep() //调用Cat的Sleep()方法
空接口interface{}
类似于java的object, 可以⽤interface{}类型 引⽤ 任意的数据类型
所有的基本类型都实现了此接口
类型断言
如果断言失败,那么ok的值将会是false,但是如果断言成功ok的值将会是true,同时value将会得到所期待的正确的值。
str := "hello"
value, ok := str.(string)
反射
变量结构
Golang关于类型设计的一些原则
- 变量包括(type, value)两部分
- type 包括
static type和concrete type. 简单来说static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型 - 类型断言能否成功,取决于变量的
concrete type,而不是static type. 因此,一个reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer.
在Golang的实现中,每个interface变量都有一个对应pair,pair中记录了实际变量的值和类型:pair(value,type)
value是实际变量值,type是实际变量的类型。一个interface{}类型的变量包含了2个指针,一个指针指向值的类型【对应concrete type】,另外一个指针指向实际的值【对应value】。
-reflect包-
TypeOf & ValueOf
ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0
TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil
- reflect.TypeOf: 直接给到了我们想要的type类型,如float64、int、各种pointer、struct 等等真实的类型
- reflect.ValueOf:直接给到了我们想要的具体的值,如1.2345这个具体数值,或者类似&{1 "Allen.Wu" 25} 这样的结构体struct的值
- 也就是说明反射可以将“接口类型变量”转换为“反射类型对象”,反射类型指的是reflect.Type和reflect.Value这两种
结构体标签
type Movie struct {
Title string `json:"title"`
Year int `json:"year"`
Price int `json:"rmb"`
Actors []string `json:"actors"`
}
可以通过反射通过key获取value
主要可以用来json格式编码与解码
//编码的过程 结构体---> json
jsonStr, err := json.Marshal(movie)
if err != nil {
fmt.Println("json marshal error", err)
return
}
//解码的过程 jsonstr ---> 结构体
myMovie := Movie{}
err = json.Unmarshal(jsonStr, &myMovie)
if err != nil {
fmt.Println("json unmarshal error ", err)
return
}
goroutine
goroutine是Go语言并行设计的核心,有人称之为go程。 Goroutine从量级上看很像协程,它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。
只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。开发⼈员无需了解任何执⾏细节,调度器会自动将其安排到合适的系统线程上执行。
go func() {
// 执行任务
}()
使用defer语句来注册一个函数,该函数会在goroutine退出时自动执行。
func main() {
go func() {
defer func() {
// 执行任务2
}()
// 执行任务1
}()
}
调用 runtime.Goexit() 将立即终止当前 goroutine 执⾏,调度器确保所有已注册 defer 延迟调用被执行。
特点:
- Goroutine可以与普通的函数和变量一起使用,可以在任何地方启动和调度。
- Goroutine比操作系统线程更轻量,可以被更高效地创建和销毁。
- Goroutine可以在同一个线程中并发执行,无需使用昂贵的线程切换开销。
- Goroutine之间可以通过通道(channel)进行通信和同步。
- Goroutine可以在任何时候被调度程序调度执行,因此它们的执行顺序是不确定的。
- 主goroutine退出后,其它的工作goroutine也会自动退出
channel
channel是Go语言中的一个核心类型,可以把它看成管道。并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上又进一步降低了编程的难度。
channel是一个数据类型,主要用来解决go程的同步问题以及go程之间数据共享(数据传递)的问题。
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。goroutine 奉行通过通信来共享内存,而不是共享内存来通信。
引⽤类型 channel可用于多个 goroutine 通讯。其内部实现了同步,确保并发安全。
make(chan Type) //等价于make(chan Type, 0)
make(chan Type, capacity)
当 参数capacity= 0 时,channel 是无缓冲阻塞读写的;当capacity > 0 时,channel 有缓冲、是非阻塞的,直到写满 capacity个元素才阻塞写入。
语法格式:
channel <- value //发送value到channel
<-channel //接收并将其丢弃
x := <-channel //从channel中接收数据,并赋值给x
x, ok := <-channel //功能同上,同时检查通道是否已关闭或者是否为空
无缓冲
要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。否则,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。
有缓冲
并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也不同。
只有通道中没有要接收的值时,接收动作才会阻塞。
只有通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
如果给定了一个缓冲区容量,通道就是异步的。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行。
借助函数 len(ch) 求取缓冲区中剩余元素个数, cap(ch) 求取缓冲区元素容量大小。
注意:
- channel不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的,才去关闭channel;
- 关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
- 关闭channel后,可以继续从channel接收数据;
- 对于nil channel,无论收发都会被阻塞。
- 可以使用 range 来迭代不断操作channel
有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道没有这种保证。
Select
Go里面提供了一个关键字select,通过select可以监听channel上的数据流动。
有时候我们希望能够借助channel发送或接收数据,并避免因为发送或者接收导致的阻塞,尤其是当channel没有准备好写或者读时。select语句就可以实现这样的功能。
select的用法与switch语言非常类似,由select开始一个新的选择块,每个选择条件由case语句来描述。
与switch语句相比,select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作,大致的结构如下:
select {
case <- chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
在一个select语句中,Go语言会按顺序从头至尾评估每一个发送和接收的语句。
如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。
如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况:
l 如果给出了default语句,那么就会执行default语句,同时程序的执行会从select语句后的语句中恢复。
l 如果没有default语句,那么select语句将被阻塞,直到至少有一个通信可以进行下去。
示例代码:
GO Modules
Go modules 是 Go 语言的依赖解决方案,目的是淘汰现有的 GOPATH 的使用模式。
GOPATH
GOPATH目录下一共包含了三个子目录,分别是:
- bin:存储所编译生成的二进制文件。
- pkg:存储预编译的目标文件,以加快程序的后续编译速度。
- src:存储所有
.go文件或源代码。在编写 Go 应用程序,程序包和库时,一般会以$GOPATH/src/github.com/foo/bar的路径进行存放。
因此在使用 GOPATH 模式下,我们需要将应用代码存放在固定的$GOPATH/src目录下,并且如果执行go get来拉取外部依赖会自动下载并安装到$GOPATH目录下。
弊端:
- A. 无版本控制概念. 在执行
go get的时候,你无法传达任何的版本信息的期望,也就是说你也无法知道自己当前更新的是哪一个版本,也无法通过指定来拉取自己所期望的具体版本。
- B.无法同步一致第三方版本号. 在运行 Go 应用程序的时候,你无法保证其它人与你所期望依赖的第三方库是相同的版本,也就是说在项目依赖库的管理上,你无法保证所有人的依赖版本都一致。
- C.无法指定当前项目引用的第三方版本号. 你没办法处理 v1、v2、v3 等等不同版本的引用问题,因为 GOPATH 模式下的导入路径都是一样的,都是
github.com/foo/bar。
go mod命令
| 命令 | 作用 |
|---|---|
| go mod init | 生成 go.mod 文件 |
| go mod download | 下载 go.mod 文件中指明的所有依赖 |
| go mod tidy | 整理现有的依赖 |
| go mod graph | 查看现有的依赖结构 |
| go mod edit | 编辑 go.mod 文件 |
| go mod vendor | 导出项目所有的依赖到vendor目录 |
| go mod verify | 校验一个模块是否被篡改过 |
| go mod why | 查看为什么需要依赖某模块 |
go mod 配置
Go语言提供了 GO111MODULE这个环境变量来作为 Go modules 的开关,其允许设置以下参数:
- auto:只要项目包含了 go.mod 文件的话启用 Go modules,目前在 Go1.11 至 Go1.14 中仍然是默认值。
- on:启用 Go modules,推荐设置,将会是未来版本中的默认值。
- off:禁用 Go modules,不推荐设置。
GOPROXY——这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其作用是用于使 Go 在后续拉取模块版本时直接通过镜像站点来快速拉取。
GOPROXY 的默认值是:https://proxy.golang.org,direct
proxy.golang.org国内访问不了,需要设置国内的代理.
“direct” 是一个特殊指示符,用于指示 Go 回源到模块版本的源地址去抓取