Go基础笔记
变量四种声明
var a int // var a int = 0
var b int = 10
var c = 100
d := 1000 // 只能用在函数体内来声明
注意:全局变量只能用前三种方式,第四种方式只能用在函数体内来声明。
const
const
标识只读属性,可用于定义枚举类型。
// 可以在const() 添加一个关键字 iota,每行iota会累加1,第一行默认值为0
const (
BEIJING = iota // iota= 0
SHANGHAI // iota = 1
GUANGZHOU // iota = 2
SHENZHEN // iota = 3
)
函数返回值
// 无返回值
func foo0(a string, b int) {
fmt.Println("-----foo0------")
fmt.Println("a = ", a)
fmt.Println("b = ", b)
}
// 一个返回值
func foo1(a string, b int) int {
fmt.Println("-----foo1------")
fmt.Println("a = ", a)
fmt.Println("b = ", b)
c := 10
return c
}
// 返回多个值,匿名
func foo2(a string, b int) (int, int) {
fmt.Println("-----foo2------")
fmt.Println("a = ", a)
fmt.Println("b = ", b)
c := 10
return c, 100
}
// 返回多个值,有形参名称
func foo3(a string, b int) (r1 int, r2 int) {
fmt.Println("-----foo3------")
fmt.Println("a = ", a)
fmt.Println("b = ", b)
// r1 r2 属于形参,初始化值为0,作用域是foo3整个函数体 {} 空间
fmt.Println("r1 = ", r1, ", r2 = ", r2)
// 给有名返回值变量赋值
r1 = 10
r2 = 100
return
}
// 返回值同类型
func foo4(a string, b int) (r1, r2 int) {
fmt.Println("-----foo4------")
fmt.Println("a = ", a)
fmt.Println("b = ", b)
// 给有名返回值变量赋值
r1 = 10
r2 = 100
return
}
init函数与import导包
注意:默认情况,导入包但是不使用会报错。
import (
_ "mylib/lib1" // 匿名,不再报错,会执行 init()
"mylib/lib2"
)
- import导包方式
import _ "fmt"
给 fmt 包请一个别名,匿名,无法使用当前包的方法,但是会执行当前包内部的 init() 方法impot aa "fmt"
给当前包起一个别名,aa,aa.Println() 形式来调用包的方法import . "fmt"
将 fmt 包中的全部方法导入当前本包的作用中,fmt 的全部方法可以直接API调用,不需要fmt.API
(应避免使用)
指针
package main
import "fmt"
func changeValue1(val int) {
val = 10
}
func changeValue2(pval *int) {
*pval = 10
}
func main() {
a := 1
changeValue1(a)
fmt.Println("changeValue1 a = ", a) // changeValue1 a = 1
changeValue2(&a)
fmt.Println("changeValue2 a = ", a) // changeValue2 a = 10
}
defer
当前函数生命周期结束执行。return 以后执行。
数组和切片
数组
-
声明方式
var myArray1 [10]int myArray2 := [10]int{1, 2, 3, 4} // 长度为10 myArray3 := [4]int{1, 2, 3, 4}
-
数组是固定长度的
-
固定长度的数组在传参的时候,是严格匹配数组类型的
func printArray(myArray [4]int)
-
数组作为函数参数采用值传递
切片(动态数组)
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。
声明方式
// 1、声明切片,并初始化,长度为4
mySlice1 := []int{1, 2, 3, 4}
// 2、声明切片,并没有分配空间
var mySlice2 []int
mySlice2 = make([]int, 4) // 分配4个空间,初始化值为0
// 3、声明切片,分配空间,初始化为0
mySlice3 := make([]int, 4)
引用传递
动态数组在传参上是引用传递,而且不同元素长度的动态数组的形参是一致的
func printSlice(mySlice []int)
切片扩容
切片中添加元素,容量2倍增长
func main() {
// 创建切片,len = 3, cap = 5
var numbers = make([]int, 3, 5)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
// 向切片追加一个元素
numbers = append(numbers, 4)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
// 向切片追加两个元素
numbers = append(numbers, 5, 6)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
}
len = 3, cap = 5, slice = [0 0 0]
len = 4, cap = 5, slice = [0 0 0 4]
len = 6, cap = 10, slice = [0 0 0 4 5 6]
【扩容源码】$GOROOT/src/runtime/slice.go
源码,其中扩容相关代码如下:
newcap := old.cap
doublecap := newcap + newcap
// 1、新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)
if cap > doublecap {
newcap = cap
} else {
// 2、旧切片的长度小于1024,则最终容量 (newcap) 就是旧容量 (old.cap) 的两倍
if old.cap < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
// 3、最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,直到最终容量(newcap)大于等于新申请的容量
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
// 4、如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
if newcap <= 0 {
newcap = cap
}
}
}
- 新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)
- 否则 (cap <= 2 * old.cap) :
- 旧切片的长度小于1024,则最终容量 (newcap) 就是旧容量 (old.cap) 的两倍
- 旧切片长度大于等于1024:
- 最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,直到最终容量(newcap)大于等于新申请的容量
- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)
切片截取
左闭右开区间 [startIndex:endIndex]
mySlice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
fmt.Println("origin\t", mySlice)
fmt.Println("[:3]\t", mySlice[:3]) // [0, 3)
fmt.Println("[1:4]\t", mySlice[1:4]) // [1, 4)
fmt.Println("[2:]\t", mySlice[2:]) // [2, len(mySlice))
fmt.Println("[:]\t\t", mySlice[:]) // [0, len(mySlice))
注意:截取的切片底层是一致的,看起来就像浅拷贝一样
切片复制
copy(destSlice, srcSlice []T)
func main() {
// copy()复制切片
a := []int{1, 2, 3, 4, 5}
b := make([]int, 5, 5)
copy(b, a) //使用copy()函数将切片a中的元素复制到切片b
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(b) //[1 2 3 4 5]
b[0] = 1000
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(b) //[1000 2 3 4 5]
}
切片元素删除
要从切片a中删除索引为index
的元素,操作方法是a = append(a[:index], a[index+1:]...)
func main() {
// 从切片中删除元素
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// 要删除索引为2的元素
a = append(a[:2], a[3:]...)
fmt.Println(a) //[30 31 33 34 35 36 37]
}
映射--map
map声明方式
-
方式一:声明空map,使用前需要make分配数据空间
//声明myMap1是一种map类型 key是string, value是string var myMap1 map[string]string if myMap1 == nil { fmt.Println("myMap1 是一个空map") } //在使用map前, 需要先用make给map分配数据空间 myMap1 = make(map[string]string, 2) myMap1["one"] = "java" myMap1["two"] = "c++" myMap1["three"] = "python" fmt.Println(myMap1)
-
方式二:声明并使用make
myMap2 := make(map[int]string) myMap2[1] = "java" myMap2[2] = "c++" myMap2[3] = "python" fmt.Println(myMap2)
-
方式三:不用make,直接创建并初始化
myMap3 := map[string]string{ "one": "php", "two": "c++", "three": "python", } fmt.Println(myMap3)
map使用
-
map 底层使用哈希,Go 采用的是数组 + 链地址法解决哈希冲突
-
删除元素
delete(myMap, key)
-
map做为形参,使用引用传递
OOP
struct
结构体作为参数,使用值传递
//声明一种行的数据类型 myint,是int的一个别名
type myint int
//定义一个结构体
type Book struct {
title string
auth string
}
func changeBook(book Book) {
//传递一个book的副本
book.auth = "Jake"
}
func changeBook2(book *Book) {
//指针传递
book.auth = "Jake"
}
class
封装
- 如果类名首字母大写,表示其他包也能够访问
- 如果说类的属性首字母大写, 表示该属性是对外能够访问的,否则的话只能够类的内部访问
- 方法首字母大写,其他包也能访问
//如果类名首字母大写,表示其他包也能够访问
type Hero struct {
//如果说类的属性首字母大写, 表示该属性是对外能够访问的,否则的话只能够类的内部访问
Name string
Ad int
level int
}
func (obj *Hero) GetName() string {
return obj.Name
}
func (obj *Hero) SetName(newName string) {
obj.Name = newName
}
继承
package main
import "fmt"
type Human struct {
name string
sex string
}
func (obj *Human) Eat() {
fmt.Println("Human.Eat()...")
}
func (obj *Human) Walk() {
fmt.Println("Human.Walk()...")
}
//=================
type SuperMan struct {
Human // SuperMan 类继承了 Human 类的方法
level int
}
//重定义父类的方法Eat()
func (obj *SuperMan) Eat() {
fmt.Println("SuperMan.Eat()...")
}
//子类的新方法
func (obj *SuperMan) Fly() {
fmt.Println("SuperMan.Fly()...")
}
func main() {
h := Human{"Jake", "male"}
h.Eat()
h.Walk()
fmt.Println("----------------")
//定义一个子类对象
//s := SuperMan{Human{"Jake", "male"}, 666}
var s SuperMan
s.name = "Jake"
s.sex = "male"
s.level = 666
s.Walk() //父类的方法
s.Eat() //子类的方法
s.Fly() //子类的方法
}
多态
通过接口实现,接口本质是一个指针。
package main
import "fmt"
// 本质是一个指针
type AnimalIF interface {
Sleep()
GetColor() string // 获取动物的颜色
GetType() string // 获取动物的种类
}
// 具体的类
type Cat struct {
color string // 猫的颜色
}
func (obj *Cat) Sleep() {
fmt.Println("Cat is Sleep")
}
func (obj *Cat) GetColor() string {
return obj.color
}
func (obj *Cat) GetType() string {
return "Cat"
}
// 具体的类
type Dog struct {
color string
}
func (obj *Dog) Sleep() {
fmt.Println("Dog is Sleep")
}
func (obj *Dog) GetColor() string {
return obj.color
}
func (obj *Dog) GetType() string {
return "Dog"
}
func showAnimal(animal AnimalIF) {
animal.Sleep() // 多态
fmt.Printf("color = %s, kind = %s\n", animal.GetColor(), animal.GetType())
}
func main() {
var animal AnimalIF // 接口的数据类型, 父类指针
animal = &Cat{"Green"}
animal.Sleep() // 调用的就是Cat的Sleep()方法, 多态的现象
animal = &Dog{"Yellow"}
animal.Sleep() // 调用Dog的Sleep方法,多态的现象
cat := Cat{"Green"}
dog := Dog{"Yellow"}
showAnimal(&cat)
showAnimal(&dog)
}
空接口 interface{}
是万能数据类型。interface{} 改如何区分此时引用的底层数据类型到底是什么?“类型断言” 的机制
package main
import "fmt"
// interface{} 是万能数据类型
func myFunc(arg interface{}) {
// interface{} 改如何区分 此时引用的底层数据类型到底是什么?
// 给 interface{} 提供 “类型断言” 的机制
value, ok := arg.(string) // 判断是否是 string 类型
if !ok {
fmt.Printf("arg is not string type, type is %T\n", arg)
} else {
fmt.Println("arg is string type, value = ", value)
}
}
func main() {
myFunc(3.14)
myFunc("abc")
}
反射
变量结构
var a string
// pair<statictype:string, value:"abcd">
a = "abcd"
// pair<type: ,value:>
var allType interface{}
//pair<type:string, value:a地址>
allType = a
reflect包
reflect包文档:http://docscn.studygolang.com/pkg/reflect/
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
func (v Value) NumField() int
func (v Value) Field(i int) Value
func DoFiledAndMethod(input interface{}) {
//获取input的type
inputType := reflect.TypeOf(input)
fmt.Println("inputType is :", inputType.Name())
//获取input的value
inputValue := reflect.ValueOf(input)
fmt.Println("inputValue is:", inputValue)
//通过type 获取里面的字段
//1. 获取interface的reflect.Type,通过Type得到NumField ,进行遍历
//2. 得到每个field,数据类型
//3. 通过filed有一个Interface()方法等到 对应的value
for i := 0; i < inputType.NumField(); i++ {
field := inputType.Field(i)
value := inputValue.Field(i).Interface()
fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
}
//通过type 获取里面的方法,调用
for i := 0; i < inputType.NumMethod(); i++ {
m := inputType.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type)
}
}
结构体标签
type resume struct {
Name string `info:"name" doc:"名字"`
Sex string `info:"sex"`
}
func findTag(str interface{}) {
t := reflect.TypeOf(str).Elem()
for i := 0; i < t.NumField(); i++ {
taginfo := t.Field(i).Tag.Get("info")
tagdoc := t.Field(i).Tag.Get("doc")
fmt.Println("info: ", taginfo, " doc: ", tagdoc)
}
}
info: name doc: 名字
info: sex doc:
结构体标签应用:
- json 编码
- orm 映射关系
Goroutine
- Goroutine 就是协程
- go func() 形式创建 Goroutine
- main 退出所有协程都终止
runtime.Goexit()
退出当前 Goroutine- 无法通过返回获取结果
channel
-
channel 定义
make(chan Type) // 等价于 make(chan Type, 0) make(chan Type, capacity)
-
channel 使用
channel <- value // 发送value到channel <-channel // 接收并丢弃数据 x := <-channel // 从channel中接收数据,斌赋值给x x, ok := <-channel // 从channel中接收数据,斌赋值给x,同时检查通道是否已关闭或者是否为空
-
无缓冲channel
用于读写的两个 Goroutine 都会阻塞等待对端操作(写Goroutine写入数据并等待读端读取,读Goroutine等待写端写入数据)
-
有缓冲channel
- 当channel已满,再向里面写数据,会阻塞
- 当channel为空,从里面取数据,会阻塞
-
关闭channel
close(channel)
- 关闭channel后,无法向channel再发送数据(向关闭后的channel发数据,会引发 panic 错误,导致接收立即返回零值)
- 关闭channel后,可以继续从channel接收数据
- 对于 nil channel,无论收发都会被阻塞
-
channel与range
for { //ok如果为true表示channel没有关闭,如果为false表示channel已经关闭 if data, ok := <-c; ok { fmt.Println(data) } else { break } } // ========= 等价 ============== //可以使用range来迭代不断操作channel for data := range c { fmt.Println(data) }
-
channel与select
单流程下,一个go同时只能监控一个channel的状态,select可以完成监控多个channel状态的任务
select { case <- chan1: // 如果chan1成功读到数据,则进行该处理流程 case chan2 <- 1: // 如果成功向chan2写入数据,则进行该处理流程 default: // 如果上面都没成功,则进行该处理流程 }
Go modules
GOPATH弊端
- 无版本控制概念
- 无法同步一致第三方版本号
- 无法指定当前项目引用的第三方版本号
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 环境变量
环境变量名 | 作用 | 补充 |
---|---|---|
GO111MODULE | 是否开启go modules模式 | 建议go V1.11之后,都设置为on |
GOPROXY | 1. 项目的第三方依赖库的下载源地址; 2. 建议设置国内的地址; 3. direct,用于指示 Go 回源到模块版本的源地址去抓取(比如 GitHub 等) |
阿里云:https://mirrors.aliyun.com/goproxy/ 七牛云:https://goproxy.cn,direct |
GOSUMDB | 用来校验拉取的第三方库是否是完整的;默认也是国外的网站,如果设置了GOPROXY,这个就不用设置了 | |
GONOPROXY | 通过设置GOPRIVATE即可 | |
GONOSUMDB | 通过设置GOPRIVATE即可 | |
GOPRIVATE | 表示是私有仓库,不会进行GOPROXY下载和校验 | go evn -w GOPRIVATE="*.example.com";表示所有模块路路径为example.com的子域名都不会进行GOPROXY下载和校验 |
Go Modules初始化项目
-
开启Go Modules模块
GO111MODULE=on
-
初始化项目
-
任意文件夹创建一个项目(不要求在$GOPATH/src)
-
创建 go.mod 文件,同时起当前项目的模块名称
go mod init github.com/xxx/module_test
go.mod
module github.com/xxx/module_test go 1.16
-
在该项目中编写源代码,源代码依赖某个库(假定为 github.com/gin-gonic/gin )
-
获取到依赖库,依赖库会下载到 $GOPATH/pkg/ 下
-
手动 down
go get github.com/gin-gonic/gin
-
自动 down
go.mod
module github.com/xxx/module_test go 1.16 require github.com/gin-gonic/gin v1.7.1 // indirect
go.sum,罗列当前项目直接或间接的依赖所有模块版本,保证今后项目依赖的版本不会被篡改。
- h1:hash :表示整体项目的zip文件打开之后的全部文件的校验和来生成的hash。如果不存在,可能表示依赖的库可能用不上。
- xxx/go.mod h1:hash :表示go.mod文件做的hash
-
-
修改项目模块的版本依赖关系
go mod edit -replace=被替换的版本=替换版本
-