[TOC]
GO语言语法进阶指南
本笔记中没有基础语法介绍,适合学习完基础语法之后,想进一步了解go语言特性的。
代码中有很多注释说明,会参与一些额外的知识说明
内建方法
内建方法,就是go语言自带的一种方法,不需要额外的引用包。
内建方法一般都会有使用场景,这个需要注意,多使用之后就会熟悉了。
make:
功能:创建 slice、map、chan,完成内存的初始化。
说明:
- slice和数组的区别:数组初始化内存之后,大小固定,无法扩容,slice可以通过内建函数进行扩容
- chan是go语言中独有的一种类型,叫做管道,也可以定义大小。可以完成协程的数据交互
返回:返回类型引用,而不是指针
package main
import "fmt"
func main() {
// 提示,所有的内建函数和内建类型,可以参考源码:builtin/builtin.go 文件
makeSlice()
fmt.Println("-----------------------------")
makeMap()
fmt.Println("-----------------------------")
makeChan()
}
//以下为主要测试函数
// make test
func makeSlice() {
mSlice := make([]string, 2)
mSlice[0] = "a"
mSlice[1] = "b"
fmt.Println("len(mSlice):{}, cap(mSlice):{} ", len(mSlice), cap(mSlice))
// 内建函数 append 会进行扩容
mSlice = append(mSlice, "d")
mSlice = append(mSlice, "e")
mSlice = append(mSlice, "f")
fmt.Println(mSlice)
fmt.Println("len(mSlice):{}, cap(mSlice):{} ", len(mSlice), cap(mSlice))
}
// make map
func makeMap() {
// interface 代表的就是value可以为int,string,这些
mMap := make(map[string]interface{}, 3)
mMap["a"] = 1
mMap["b"] = "BBB"
mMap["c"] = "CCC"
mMap["d"] = 4
fmt.Println(mMap)
fmt.Println("len(mMap):{}", len(mMap))
// map 类型无法使用 append
}
// make chan
func makeChan() {
//管道内的容量为3 ,可以保存 3 个 int 数据
// 暂不讲解使用方式
mChan := make(chan int, 10)
close(mChan)
}
new:
New 方法和make方法是有区别的
1、new出来的,内存会置为0, make可以直接初始化指定内存大小的对象
2、返回穿入类型的指针地址,new的类型可以任意,但是返回的只是指向这一类型的指针地址,make返回的是类型引用
func main() {
returnType()
}
func returnType() {
nMap := new([]string)
mMap := make([]string, 5)
fmt.Println("nMap type:{}", reflect.TypeOf(nMap))
fmt.Println("mMpa type:{}", reflect.TypeOf(mMap))
// nMap因为没有初始化,无法使用len和cap获取长度和容量大小,会报错
// fmt.Println("nMap len:{}, nMap:{}", len(nMap), cap(nMap))
fmt.Println("mMpa len:{}, cap:{}", len(mMap), cap(mMap))
}
// 返回结果:
// nMap type:{} *[]string
// mMpa type:{} []string
// mMpa len:{}, cap:{} 5 5
append&delete:
append和delete是针对于上面两种方式创建变量的一些操作的。不同操作可能要区分类型,如下:
slice -> append & copy
Map -> delete
append©:
append追加元素,可以进行自动扩容。
copy并不会对 目标对象进行扩容,如果长度较小,则可能丢数据
// make test append & copy
func testAppend() {
mSlice := make([]string, 2)
mSlice[0] = "a"
mSlice[1] = "b"
fmt.Printf("len(mSlice):%d, cap(mSlice):%d ", len(mSlice), cap(mSlice))
// 内建函数 append 会进行扩容
mSlice = append(mSlice, "d")
mSlice = append(mSlice, "e")
mSlice = append(mSlice, "f")
fmt.Println(mSlice)
fmt.Printf("len(mSlice):%d, cap(mSlice):%d ", len(mSlice), cap(mSlice))
// 使用copy 复制
targetSlice := make([]string, 4)
i := copy(targetSlice, mSlice)
// targetSlice 长度就是4 ,所以就复制了4个,并没有完全复制
fmt.Printf("cSlice:%s, i:%d", targetSlice, i)
}
// 结果:
// len(mSlice):2, cap(mSlice):2 [a b d e f]
// len(mSlice):5, cap(mSlice):8 cSlice:[a b d e], i:4
Delete:
delete删除map类型的数据,格式为delete(map, key),
没有返回数据
func deleteMap() {
mMap := make(map[string]interface{}, 3)
mMap["a"] = 1
mMap["b"] = "BBB"
mMap["c"] = "CCC"
mMap["d"] = 4
fmt.Println(mMap)
fmt.Println("len(mMap):{}, mao", len(mMap))
// 删除存在的key
delete(mMap, "c")
fmt.Println(mMap)
delete(mMap, "d")
fmt.Println(mMap)
// 删除不存在的key, 没有任何操作,也不会报错
delete(mMap, "c")
fmt.Println(mMap)
}
// 结果:
// map[a:1 b:BBB c:CCC d:4]
// len(mMap):{}, mao 4
// map[a:1 b:BBB d:4]
// map[a:1 b:BBB]
// map[a:1 b:BBB]
Panic&recover:
go语言中一对异常处理组合,不同于 java或者python的 throw、try catch方式
Panic:抛出异常
Recover:捕获异常
// ====================================Panic&recover 异常抛出和捕获 测试===========================================
func testException() {
// 通过defer 声明匿名函数,捕获调用函数抛出来的异常
// defer的使用稍后详细补充
// 这个匿名函数也可以拆出来,比如,defer recoverException(),在任何需要捕获异常的地方都可以使用
//defer func() {
// errInfo := recover()
// fmt.Println("异常信息为:", errInfo)
//}()
defer recoverException()
// 会捕获到第一个异常, 可以更换函数的位置,体验不同的效果
throwException03()
throwException()
throwException02()
}
func recoverException() {
// Panic 抛出来的不一定是error类型,也可以是用户自定义的一种结构体
reInfo := recover()
// fmt.Println("异常信息为:", reInfo)
// 可以对返回结果进行处理,根据recover的结果,做出判断
switch reInfo.(type) {
case string:
fmt.Println("返回字符串为:", reInfo)
case error:
fmt.Println("异常信息为:", reInfo)
default:
fmt.Println("default 信息为:", reInfo)
}
}
// 直接抛出一个字符串
func throwException() {
panic("a ba a ba 0101")
}
// 直接抛出一个字符串
func throwException02() {
panic("a ba a ba 0202")
}
// 真实的抛出一个异常
func throwException03() {
panic(errors.New("panic a error"))
// 或者使用:panic(fmt.Errorf("panic a error")) 创建异常都可以
}
func main() {
testException()
}
// 返回结果:
// 异常信息为: panic a error
Len&cap&close:
Len:变量的长度,slice、chan,string,array,map
cap:变量的容量,slice、chan、array,map无法使用,
close:关闭chan,chan比较占用资源,是协程的一个通道,使用完的时候,尽量关闭。
总结:
内建方法 | 功能描述 | 其他说明 |
---|---|---|
Make() | 创建 slice、map、chan,完成内存的初始化。 | 返回类型引用,而不是指针 |
New() | 创建类型变量的另外一种方式 | 和new的区别:<br />1、new出来的,内存会置为0, make可以指定内存大小<br />2、new 返回的是类型指针,不是引用 |
Append() | append追加元素,并可以进行自动扩容。 | |
Copy() | 复制对象 | copy并不会对 目标对象进行扩容,如果长度较小,则可能丢数据 |
Delete() | 删除map类型指定key的数据 | 没有的key进行删除不会抛出异常 |
Panic() | 抛出异常 | |
Recover() | 捕获异常 | |
Len() | 变量的长度 | 作用在:slice、chan,string,array,map |
Cap() | 变量的容量 | 作用在:slice、chan、array,map无法使用, |
Close() | 关闭chan | chan比较占用资源,是协程的一个通道,使用完的时候,尽量关闭。 |
结构体:
若干属性的一个集合
是面向对象的一个体现
创建&初始化:
首先定义一个结构体,然后创建一个该类型的 对象
可以直接通过var去声明一个,或者通过new的方式创建该类型的一个指针
// Dog 定义dog结构体
type Dog struct {
// 品种
variety string
name string
color string
}
func main() {
// 创建方式 1
var dog01 Dog
dog01.name = "test"
dog01.variety = "小黄狗"
dog01.color = "yellow"
fmt.Println("创建方式 1:", dog01)
// 创建方式 2
// := 这是一种语法糖,在函数内,声明变量和赋值一起。函数外不可以使用该语法糖
dog02 := Dog{color: "red"}
dog02.name = "test02"
fmt.Println("创建方式 2:", dog02)
// 创建方式 3
dog03 := new(Dog)
dog03.name = "test03"
// 三种不同的打印方式,可以观察new出来的指针
// 打印的结果带有&,表示为一个指针
fmt.Println("创建方式 3:", dog03)
// 通过 * 获取地址内的对象,* 根据地址取值
fmt.Println("创建方式 3, *dog03: ", *dog03)
// 通过& 取地址符, 获取地址
fmt.Println("创建方式 3, &dog03:", &dog03)
}
// 打印结果:
// 创建方式 1: {小黄狗 test yellow}
// 创建方式 2: { test02 red}
// 创建方式 3: &{ test03 }
// 创建方式 3, *dog03: { test03 }
// 创建方式 3, &dog03: 0xc00000e030
属性及函数:
函数:必须初始化结构体之后,才可以调用函数
作用域:通过函数名称的大写和小写却分函数的作用域,go语言中只有private和public两种区别
备注:java中其实是有四种作用域,private、protect、public、default
结构体中的函数声明,并不是直接写到结构体中的,通过func (d *Dog)Run(){}这种形式
// Dog 定义dog结构体
type Dog struct {
// 品种
variety string
name string
color string
}
func (d Dog) Run() {
fmt.Println("the dog is running ..... ")
}
// 测试结构体中的函数
func testStructsMethod() {
d := Dog{}
d.name = "小黄"
fmt.Println(d)
d.Run()
}
func main() {
testStructsMethod()
}
组合:
面向对象的一个特性就是继承 ,go语言中是通过结构体和结构体的组合形式,实现继承关系的。
// animal类型
type animal struct {
id int8
animalType string
}
func (a animal) eat() {
fmt.Println("Animal is eating")
}
// Dog 定义dog结构体
type Dog struct {
// 继承 animal结构体
animal
// 品种
variety string
name string
color string
}
func (d Dog) Run() {
fmt.Println("the dog is running ..... ")
}
// 测试结构体中的函数
func testStructsMethod() {
d := Dog{}
d.name = "小黄"
d.animalType = "dog"
fmt.Println(d)
d.Run()
d.eat()
}
func main() {
testStructsMethod()
}
接口
go语言可以通过接口,可以抽象、封装和多态,这些也是面向对象的一些特征
概念及实现:
接口如何声明,如何实现已经声明的这些接口
接口通过interface进行声明的,接口的实现是直接针对结构体,实现写所有接口中定义的方法,即为实现接口,不用显示的声明实现这个接口
// Behavior
// 通过interface声明结构为一个接口
// 接口是一些方法的集合
type Behavior interface {
Run() string
Eat() string
}
type Animal struct {
id int8
animalType string
}
func (a Animal) Eat() {
fmt.Println("Animal is eating")
}
// Dog 定义dog结构体
type Dog struct {
// 继承 animal结构体
Animal
// 品种
variety string
name string
color string
}
// 直接实现接口中定义的方法,即为实现接口
// 在goland中可以看到已经有实现接口的标识
func (d Dog) Eat() string {
fmt.Println("the Animal is eating ..... ")
return "eating"
}
func (d Dog) Run() string {
fmt.Println("the dog is running ..... ")
return "running"
}
func testInterface() {
d := Dog{}
d.name = "小黄"
d.animalType = "dog"
fmt.Println(d)
fmt.Println(d.Run())
fmt.Println(d.Eat())
}
func main() {
testInterface()
}
多态:
多态其实就是同一个接口,具有不同的实现,便实现了多态
下面,Dog和Cat 的结构体都实现了Behavior接口,实现多态的效果
还稍微使用了一下,结构体中的标签,这个后面常用,这里可以做了解一下
结构体中的大小写,同样具有作用域的效果,如果为小写,json序列化的时候,可能会被忽略,无法序列化
// Behavior
// 通过interface声明结构为一个接口
// 接口是一些方法的集合
type Behavior interface {
Run() string
Eat() string
}
type Animal struct {
id int8
animalType string
}
// Dog 定义dog结构体
type Dog struct {
// 继承 animal结构体
Animal
// 品种
variety string
name string
color string
}
// Eat 直接实现接口中定义的方法,即为实现接口
func (d Dog) Eat() string {
fmt.Println("the Dog is eating ..... ")
return "eating"
}
func (d Dog) Run() string {
fmt.Println("the Dog is running ..... ")
return "running"
}
// Cat 定义Cat结构体
type Cat struct {
// 继承 animal结构体
Animal
// 品种
variety string `default:"good"`
// 扩展,go语言中接口体的默认值,和结构体序列化之后,json中的key名称
// 这里使用的是json,还有其他xml,ason,bson这些
// 这也是go语言中所说的打标签,参考:https://juejin.cn/post/7005465902804123679
Name string `json:"Name" default:"橘猫"`
color string
}
// Eat 直接实现接口中定义的方法,即为实现接口
func (c Cat) Eat() string {
fmt.Println("the Eat is eating ..... ")
return "eating"
}
func (c Cat) Run() string {
fmt.Println("the Eat is running ..... ")
return "running"
}
func testInterface() {
d := Dog{}
d.name = "小黄"
d.animalType = "dog"
fmt.Println(d)
fmt.Println(d.Run())
fmt.Println(d.Eat())
fmt.Println("=============================")
c := Cat{}
c.color = "black"
// 没有序列化
fmt.Println(c)
result, _ := json.Marshal(c)
// 序列化之后
// 即使序列化之后,仍然看不到默认值
fmt.Println(string(result))
fmt.Println(c.Run())
fmt.Println(c.Eat())
}
func main() {
testInterface()
}
总结:
结构体的声明:interface,
结构的实现:直接实现接口中所有的方法实现,出入参保持一致
多态:不同结构体,同一实现一个接口的所有方法,即实现了多态的效果
并发
go语言的并发,是通过协程实现的,而且go语言中的协程资源占用很小,一个协程初始化之后只有几KB。实际的可能需要看执行的函数内容和其他属性定义决定。
- 更轻量:资源更小,几KB,
- 更易用:直接一个go关键字就可以开启一个协程,go run()即可
- 更便捷:更容易嵌入其他程序中,没有java或者python中的进程或者协程那么繁琐
多协程通信:chan管道通信,chan可以实现信号和信号量的效果
多协程同步:协程A在处理某个数据,协程B不应该在A操作过程中,对数据做处理,否则会让A获取到脏数据。java 和python会使用锁机制,go语言中也可以使用多协程同步,实现锁的机制。特别是针对于大数据的处理。
协程:
默认go语言使用的协程,占用的是所有的CPU核心数。
一般会使用代码限制协程占用CPU的核心数,默认留出一个核心,给系统或者其他调度服务
- 协程在执行期间,主进程停止之后,会停止正在运行中的协程
- runtime.GOMAXPROCS( n int )进行设置使用CPU核心数
- runtime.NumCPU() 获取服务器CPU核心数
// 循环测试函数
func goLoopTest01() {
for i := 0; i < 20; i++ {
time.Sleep(1 * time.Second)
fmt.Println("goLoopTest01 goLoop : ", i)
}
}
// 循环测试函数 02
func goLoopTest02() {
for i := 0; i < 20; i++ {
time.Sleep(1 * time.Second)
fmt.Println("goLoopTest02 goLoop : ", i)
}
}
func main() {
// 获取服务器CPU核心数
cpu := runtime.NumCPU()
fmt.Println("服务器CPU核心数为: ", cpu)
// 限制协程运行的核心数,2个CPU核心
runtime.GOMAXPROCS(2)
// 这样运行过快,main结束,则协程也会被关闭,需要额外注意
go goLoopTest01()
go goLoopTest02()
// 这样等待10秒,两个协程打印,但是不可能等待所有打印出来
time.Sleep(10 * time.Second)
fmt.Println("main函数执行结束")
}
协程通信:
协程之间的通信和同步,都是通过管道chan接受和发送数据。
一般也会通过select配合使用,不停的获取数据。
// 声明一个管道,和声明管道内部数据类型
var channel chan int = make(chan int, 10)
var channel02 chan int = make(chan int, 10)
// Send 发送数据
func Send() {
channel <- 1
channel <- 2
channel <- 3
time.Sleep(2 * time.Second)
channel <- 4
channel <- 5
}
// Receive 接收管道里的数据
func Receive() {
num := <-channel
fmt.Println(num)
for {
// select 会不停的获取管道的数据
select {
// case 可以写多个,从不同的管道中获取
case num := <-channel:
fmt.Println("管道channel获取到的数据 : ", num)
// 写多个
case num02 := <-channel02:
fmt.Println("管道channel02获取到的数据 : ", num02)
}
}
}
func main() {
go Send()
go Receive()
time.Sleep(10 * time.Second)
协程同步:
系统工具:sync.WaitGroup{} ,可以实现多协程的同步,比如:读写的操作,要等写完之后,才可以读取
add(delta int):添加协程记录
Done():移除协程记录
Wait():同步等待所有记录的协程记录全部结束
说明:所有add和done的次数必须一致,否则可能导致一直等待
var WG = sync.WaitGroup{}
func Read() {
// 添加读者
for i := 0; i < 3; i++ {
fmt.Println("Read WG add 1")
WG.Add(1)
}
}
func Write() {
// 等待一个读者 完毕,WG减少一个
for i := 0; i < 3; i++ {
fmt.Println("Write WG done 1")
time.Sleep(1 * time.Second)
WG.Done()
}
}
func main() {
// 主进程内,读取
Read()
// 开启协程 写入数据
go Write()
// 等待所有协程执行完毕
WG.Wait()
fmt.Println("all gorotine done....")
}
总结:
知识点 | 描述 | |
---|---|---|
如何启动一个协程 | 关键字go直接启动 | |
CPU核心数使用的设置 | runtime.GOMAXPROCS( n int )进行设置使用CPU核心数 <br />runtime.NumCPU() 获取服务器CPU核心数 | |
协程之间的通信 | 类型chan进行通信,配合select进行循环获取 | |
如何控制协程之间的同步 | sync.WaitGroup{} 控制协程同步。具有add,done,waite方法 |
指针
指针的操作,主要包括:
- 基本使用
- 指针数组
- 指向指针的指针
- 值传递和指针传递
指针基本使用:
定义指针变量
为指针变量赋值
访问指针变量中指向地址的值
go语言不支持指针的运算
// & 取地址,获取某个变量的地址 // * 指针运算符,表示一个变量是指针类型,也可以表示一个指针变量所指向的存储单元, 也就是这个地址所存储的值.
func main() {
var count int = 20
var countPoint *int
var countPoint01 *int
// & 取地址,获取某个变量的地址
// * 指针运算符,表示一个变量是指针类型,也可以表示一个指针变量所指向的存储单元, 也就是这个地址所存储的值.
countPoint = &count
fmt.Println(countPoint)
// count 和 countPoint 地址一样
fmt.Printf("Count 地址:%x,CountPoint 地址: %x, CountPoint 内容:%d ", &count, countPoint, *countPoint)
// countPoint01 地址,是空的
fmt.Println("countPoint01 地址是:", countPoint01)
}
指针数组&数组指针:
func TestPointArray() {
// 指针数组:一个数组里面保存的都是对象的指针
// 数组指针:一个指向数组的指针
// 创建变量
a, b, c := 1, 2, 3
//指针数组
pointArr := [...]*int{&a, &b, &c}
fmt.Println("指针数组, point Arr: ", pointArr)
// 数组指针
arr := [...]int{1, 2, 3}
arrPoint := &arr
fmt.Println("数组指针地址:", arrPoint)
}
func main() {
TestPointArray()
}
JSON
go语言主要是通过encoding/json 进行序列化和反序列化的。
序列化:将对象和结构体,变为json格式,json.Marshal(v interface{}) ([]byte, error)
- 结构体的序列化
- Map的序列化
反序列化:将json格式变为对象,json.Unmarshal(data []byte, v interface{}) error
Tag:在go语言中,叫标签,有点类似java中的注解。写在结构体的后面,在序列化的时候,可以作为json中的key,反序列化的时候也作为一个映射。
json序列化:
type Server struct {
ServerName string
ServerIP string
ServerPort int
}
func Serialize() {
// 创建一个server
// 也可以这样创建 : server:= Server{ServerIP: "10.0.0.1", ServerName: "JsonTest", ServerPort: 8888}
// new出来的是一个指针类型
server := new(Server)
server.ServerIP = "10.0.0.1"
server.ServerName = "JsonTest"
server.ServerPort = 8080
// 打印原有的对象,指针的指针,才是指针原有地址的内容
fmt.Println("server : ", *server)
// 打印序列化之后的
serverJson, err := json.Marshal(server)
if err != nil {
fmt.Println("序列化出现异常,异常信息为: ", err.Error())
}
fmt.Println("ServerJson is ", string(serverJson))
// 序列化map类型
serverMap := make(map[string]interface{})
serverMap["ServerIP"] = "10.0.0.1"
serverMap["ServerName"] = "JsonTestMap"
serverMap["ServerPort"] = 8080
m := make(map[string]string)
m["test"] = "test"
serverMap["ServerDetail"] = m
resultMap, _ := json.Marshal(serverMap)
fmt.Println("map序列化之后为:", string(resultMap))
}
func main() {
Serialize()
}
json反序列化:
反序列化,就是将json转换为对应的结构体或者map,是上面的反向操作,这个就不在演示
json.Unmarshal(data []byte, v interface{}) error
语法糖
语法糖:增加语言的可读性和实用性,对语言原有逻辑并不造成任何影响
go语言中的语法糖:
- ... 可变参数
- := 声明、赋值、类型推断
func Candy(values ...string) {
for i, value := range values {
fmt.Printf("%d 个元素是%s \n", i, value)
}
}
func CandyTest() {
// := 声明变量,并推断变量类型,作用在方法内
values := make([]string, 5)
values = append(values, "test01", "test02", "test03", "test04")
// 打印类型
fmt.Println(reflect.TypeOf(values))
// Candy函数中可以追加任意个数的参数, 但是类型都应该是字符串
Candy("test01", "test02", "test03", "test04")
}
func main() {
CandyTest()
}
Module介绍
Module介绍:
Go module 是go语言的一个包管理,类似于java的maven,python的pip 对引用的包的版本等管理。
这些命令还是希望多动手去敲,去使用。
命令 | 功能说明 | 备注 |
---|---|---|
go mod init | 初始化项目,使用go module 进行包管理,会生成go.mod和go.sum文件 | |
go mod graph | 可视化go项目包依赖的关系 | graphviz, echart也可以实现 |
go mod download | 下载某个依赖包 | go get |
go mod tidy | 会检测该文件夹目录下所有引入的依赖,写入 go.mod 文件<br />下载需要的依赖,删除无用的依赖 | |
go mod verify | 校验某个安装包,在go.mod文件中会记录文件的MD5 | |
go mod why | 查看为什么会依赖某一个包 | |
go mod edit | 编辑go.mod,添加或者删除依赖 | |
go mod vendor | 将依赖下载到当前项目vendor包下,实现 | |