介绍
Golang也称Go语言。是Google于2007年开发,于2009开源的一门编程语言。
Go语言的特别是天生支持多核,可以实现高并发的编程。
Go是一门编译型语言,因此打包后可以直接运行,不像java运行需要虚拟机。
另外Go的效率比较高,但是还是不如Rust,C++等,因为Go语言需要进行垃圾回收。
Go一般理解为有python的开发效率和C++的执行效率。因为知乎分享过他们使用Python,然后使用Go重构后,节省了80%的计算资源。
安装
MacOS
官方网站 下载安装包,其中区分apple芯片和intel芯片。
直接双击进行安装。go version
如果运行后显示当前版本则安装成功。
go配置
使用go env
可以查看go语言对应的环境变量。go env -w GOPROXY=https://goproxy.cn,direct
进行插件加速。
跨平台编译
交叉编译
交叉编译是指在一个平台上生成另外一个平台的可执行程序。
macOS编译Linux和windows64可执行程序
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build hello.go
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build hello.go
linux编译macOS和windows64可执行程序
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build hello.go
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build hello.go
Windows编译mac和linux64
set CGO_ENABLED=0
set GOOS=darwin
set GOARCH=amd64
go build hello.go
set CGO_ENABLED=0
set GOOS=linux
set GOARCH=amd64
go build hello.go
GOOS: 目标平台的操作系统(darwin,freebsd,linux,windows)
GOARCH:目标平台的架构(386,amd64,arm)
交叉编译不支持CGO所以要禁用它。CGO_ENABLED=1
启用CGO,表示在编译的时候,会将引用的libc的库(比如常用的net包)。以动态链接的方法生成目标文件。CGO_ENABLED=0
则将目标文件中未定义的符号一起链接到可执行文件。
开发工具
可以使用VSCode进行开发,然后安装golang的插件。
运行go的插件可能需要翻墙。可以设置代理。
go mod
hello world
新建一个文件夹mkdir gocode
, 然后执行go mod init learncode
初始化模块。初始化完成后,会在当前目录下生成一个go.mod
的文件,内容如下。更多关于go mod的内容会在后面提及。
module learncode
go 1.18
接着,在当前文件中新建一个main.go
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
上面的代码中,package
表示声明一个模块,其中main是一个特殊的模块,在编译的时候会直接编译成一个可执行文件,其他名称的模块则会编译成一个依赖包。
import "fmt"
表示引入系统提供的一个包,该包定义了一些输出的函数。
func main()
其中func
表示定义一个函数,main
是函数名称。如果package是main,则必须有一个main函数作为可执行文件的入口函数。
fmt.Println()
表示调用fmt包里面的Println函数。其中,由于该函数不是在当前文件声明的,因此如果想使用其它文件的函数,则该函数的首字母必须大写。
运行程序
运行程序有两种方式。
方式1:go build main.go
编译成可执行文件,会在当前目录下生成一个文件为main的可执行文件。go build -o hello main.go
也可以通过-o
指定编译后的文件名称。然后执行./hello
运行该程序。
方式2:go run main.go
执行运行。其实本质还是先编译之后,再运行。VSCode直接右键运行,可以实现一样的效果。
注释
注释是为了提高代码的可读性,不会参与程序的编译。
// 单行注释
/*
多行注释
*/
变量和常量
- 变量是可以多次被修改,变量使用var关键字
- 全局变量,在函数外部定义变量。
- 局部变量,在函数内部定义的变量,局部变量定义之后必须使用,否则编译失败。
- 常量不可以被修改,常量使用let关键字
// 语法: var表示变量,name是变量的名称 string是数据类型
var name string
// 也可以一次定义多个变量
var name, name1, name2 string
// 定义变量的时候直接初始化
var name string = "libai"
// 定义多个变量并初始化
var name, name1, name2 string = "libai", "wukong", "ake"
类型推断:省略数据类型。
// 省略数据类型
var name = "鲁班七号"
// 省略var,此时不能全局变量,只能在函数内部
// := 这种语法也叫简短声明
name := "后羿"
age1, age2 := 19, 20
常量,不能被修改,且必须在编译期间确认
// 定义常量
const PI = 3.14
// 定义多个常量,
const PI1, PI2 = 3.14, 3.1415
对于常量和变量,如果同时定义多个值,可以使用括号进行一一对应。
var (
a = 10
b = 20
)
const (
addr1 = "中国"
addr2 = "China"
)
常量和iota
// iota每一行常量加1
const (
a = iota //0
b //1
c //2
)
// 遇到const重置为0
const (
d = iota // 0
e // 1
f = iota // 2
g // 3
h = 100 // 100
i // 100
j = iota // 6 因为gh跨越了4和5
a1, a2 = iota + 1, iota + 2 //8 9 // 虽然这里使用了2次,但是只有一行代码,因此当前的iota还是7
a3, a4 = iota + 3, iota + 4 // 11 12
)
func main() {
fmt.Println("", a, b, c, d, e, f, g, h, i, j, a1, a2, a3, a4)
}
常量结合数量级的用法
package main
import "fmt"
const (
_ = iota
KB = 1 << (10 * iota) // iota是2,1左移10位就是1024
MB = 1 << (10 * iota) // iota是3,左移30位就是1048576
GB = 1 << (10 * iota) // 1073741824
TB = 1 << (10 * iota) // 1099511627776
)
func main() {
fmt.Println("", KB, MB, GB, TB)
}
命名规范
大写开头的变量或者函数可以被导出,小写开头的变量或者函数不可以被导出。后面会继续有模块的内容有深入的讲解导入和导出。
数据类型
- Boolean 布尔类型,默认是false,只能有true和false两个值。
- 整数类型,支持int和unit。目前32位和64位都是32位来表示int,unit,其它类型有rune,int8,int16,int32,int64,byte,uint8,uint16,uint32,uint64。虽然int是32位,但是不能和int32进行运算。
- 浮点类型 float32和float64。默认是float64类型。
- 复数类型 complex128(64位的实部和64位的虚部),complex64(32位的实部和32位的虚部)
进制
// ob开头的数字表示二进制
a := 0b101011
// b := 0o10 //八进制用0或者0o开头
// c := 0x11 // 十六进制用0x开头
// 十进制输出
// a is 43
fmt.Printf("a is %d\n", a)
// 二进制输出
// a is 101011
fmt.Printf("a is %b\n", a)
// 八进制输出
// a is 53
fmt.Printf("a is %o\n", a)
// 十六进制输出
// a is 2b
fmt.Printf("a is %x\n", a)
获取变量的类型
a := 10
b := "Hello"
// %v可以对任意类型进行格式化输出
fmt.Printf(" a is %v b is %v\n", a, b)
// %#v可以更详细的一点
fmt.Printf(" a is %#v b is %#v\n", a, b)
// %T可以获取数据类型
fmt.Printf(" a is %T b is %T\n", a, b)
// 输出
a is 10 b is Hello
a is 10 b is "Hello"
a is int b is string
字符串类型和字符类型
字符串使用双引号,字符使用单引号。
a := "hello world"
b := 'h'
//a is hello world b is 104
fmt.Println("a is ", a, " b is ", b)
字符串不能直接修改,可以先转成字符切片
a := "hello world"
// a[0] = "H" //语法错误
b := []byte(a)
b[0] = 'H'
c := string(b)
//a is hello world c is Hello world
fmt.Println("a is ", a, " c is ", c)
字符串切片
a := "hello world"
b := a[:7]
// b is hello w
fmt.Println("b is ", b)
c := a[2:]
// c is llo world
fmt.Println("c is ", c)
d := a[2:4]
//d is ll
fmt.Println("d is ", d)
// 字符串可以和字符切片运算
f := "靓仔" + a[3:]
//f is 靓仔lo world
fmt.Println("f is ", f)
多行字符串的定义
// 多行字符串使用3个点包含
var a:= ```hello
world```
字符串方法
s1 := "Hello"
s2 := " world"
// 字符串拼接
s3 := fmt.Sprintf("%v%v", s1, s2)
fmt.Println("s3 is ", s3)
// 字符串分隔
s4 := "aabaaabaaac"
a1 := strings.Split(s4, "a")
fmt.Println("a1 is ", a1)
// 字符串包含
s5 := "Hello"
fmt.Println("conain ", strings.Contains(s5, "o"))
// 前缀和后缀
a := strings.HasPrefix(s5, "He")
b := strings.HasSuffix(s5, "o")
fmt.Println("a is ", a)
fmt.Println("b is ", b)
// 索引-从前搜索
// 索引-从后搜索
c := strings.Index(s5, "l")
d := strings.LastIndex(s5, "l")
fmt.Println("c is ", c)
fmt.Println("c is ", d)
a := []string{"hello", "world", "!"}
full := strings.Join(a, " ")
fmt.Println("", full)
// hello world !
// Repeat重复
fmt.Println("", strings.Repeat("hello world", 3)) // hello worldhello worldhello world
func Replace(s, old, new string, n int) string
** 字符串替换, n取0不替换,小于0全部替换
fmt.Println("", strings.Replace("hello world", "l", "-", -1)) // he--o wor-d
fmt.Println("", strings.Replace("hello world", "l", "-", 0)) // hello world
fmt.Println("", strings.Replace("hello world", "l", "-", 2)) // he--o world
func Fields(s string) []string
按空格(1个或者多个)切割字符串
fmt.Printf("%#v", strings.Fields("a, b , cag")) // []string{"a,", "b", ",", "cag"}
strconv
package main
import (
"fmt"
"strconv"
)
func main() {
str := make([]byte, 0, 100)
str = strconv.AppendInt(str, 4567, 10)
str = strconv.AppendBool(str, false)
str = strconv.AppendQuote(str, "abcdefg")
str = strconv.AppendQuoteRune(str, '单')
fmt.Println(string(str))
//4567false"abcdefg"'单'
}
类型转换
a := strconv.FormatBool(false)
b := strconv.FormatFloat(123.23, 'g', 12, 64)
c := strconv.FormatInt(1234, 10)
d := strconv.FormatUint(12345, 10)
e := strconv.Itoa(1023)
fmt.Println(a, b, c, d, e)
//false 123.23 1234 12345 1023
字符串转数据类型
package main
import (
"fmt"
"strconv"
)
func main() {
a, err := strconv.ParseBool("false")
if err != nil {
fmt.Println(err)
}
b, err := strconv.ParseFloat("123.23", 64)
if err != nil {
fmt.Println(err)
}
c, err := strconv.ParseInt("1234", 10, 64)
if err != nil {
fmt.Println(err)
}
d, err := strconv.ParseUint("12345", 10, 64)
if err != nil {
fmt.Println(err)
}
fmt.Println(a, b, c, d)
}
数组
数组的定义
// 语法 var arr [n]数据类型
var arr [10]int
arr[0] = 100
fmt.Println(arr[0])
//100
fmt.Println(arr[1])
// 0
数组也可以使用简短声明
arr1 := [3]int{1, 2, 3}
// 其它值为0
arr2 := [100]int{1, 2, 3}
// 自动推断获取数组的长度
arr3 := [...]int{1, 2}
数组的长度也是数组类型的一部分,因此[3]int和[4]int不是相同的数据类型。
二维数组
// 二维数组
a := [2][4]int{[...]int{1, 2, 3, 4}, [...]int{5, 6, 7, 8}}
// 里面的数据类型和外面的数据类型一致,里面的数据类型可以省略
a1 := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
slice 切片
slice是引用类型,内部使用数组实现。区别在于数组的长度是固定的,而slice的容量是可变的。可以将切片理解为动态数组。
切片结构体由三部分组成。array,len,cap。其中array为底层存储数据的数组指针,len当前切片的长度,cap当前切片的容量。cap总是大于或者等于len。
// 定义切片
var a []byte
// 也可以通过数组生成切片
// 定义数组
a1 := [...]int{1, 2, 3, 4, 5}
// 通过数组获取切片
// 获取切片的语法为 souce[begin:end] 不包含end
slice1 := a1[1:2]
切片的长度和容量
a1 := [...]int{1, 2, 3, 4, 5}
s1 := a1[1:2]
fmt.Println(len(s1)) // 1
fmt.Println(cap(s1)) // 4
// 虽然s1只有一个值,但是引用的还是原来的内容
// 从s1的第一个元素,往后查找数组指针的数据
fmt.Println(s1[0:2]) // 2, 3
切片是可以修改原来的值,因为是引用类型
a := [...]int{1, 2, 3, 4}
slice := a[:]
slice[0] = 10
fmt.Println("a is ", a)
//a is [10 2 3 4]
动态创建切片: make([]T, size, cap)
// 定义一个切片,此时还没有分配内存
// var slice []int
// slice = make([]int, 20, 100)
// 上面2行代码可以进行合并
var slice []int = make([]int, 20, 100)
fmt.Println("slice is", slice, " len is ", len(slice), " cap is ", cap(slice))
//slice is [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] len is 20 cap is 100
切片的扩容, 使用append扩容,如果容量够,还是在之前的数组,如果容量不够,会申请新的内存空间,并把之前的数据复制过去。
var a = []int{1, 2, 3}
fmt.Println("a is ", a)
a = append(a, 100)
fmt.Println("a is ", a)
map
map也叫字典,是以键值对的方式存储数据。
// 定义map语法 其中string为key, int为value
var a map[string]int
// 初始化,
a = make(map[string]int)
fmt.Println("a is ", a)
// 赋值
a["age"] = 18
a["id"] = 10010
// 取值
age := a["age"]
id := a["id"]
fmt.Println("age is ", age)
fmt.Println("id is ", id)
/*
a is map[]
age is 18
id is 10010
*/
可以直接定义和初始化一起,取值的时候,可以判断该值是否存在。
// 直接初始化一个map
a := map[string]int{"age": 20, "id": 1001}
age, res := a["age"]
fmt.Println("age is ", age, " res is ", res)
age1, res1 := a["age1"]
fmt.Println("age1 is ", age1, " res1 is ", res1)
/*
age is 20 res is true
age1 is 0 res1 is false
*/
流程控制
- if
- switch
- for
if 条件判断。
var a = 10
if a >= 10 {
fmt.Println("a >= 10")
}
var b = 20
if b > 100 {
fmt.Println("b > 100")
} else {
fmt.Println("b < 100")
}
var c = 30
if c > 100 {
fmt.Println("c > 100")
} else if c > 50 {
fmt.Println(" > 50")
} else if c > 0 {
fmt.Println("c > 0")
}
/*
a >= 10
b < 100
c > 0
*/
switch。 默认switch就会执行break,如果需要穿透,添加fallthrough
// switch
name := "李白"
switch name {
case "张良":
fmt.Println("张良")
case "鲁班七号":
fmt.Println("猥琐发育别浪")
fmt.Println("救救我")
case "李白":
fmt.Println("神一样的打野")
default:
fmt.Println("你是谁")
}
switch的case还可以支持表达式
var age int = 100
// 这里switch不要跟内容
switch {
// 这里age可以使用表达式
case age >= 200:
fmt.Println("修仙的吧")
case age >= 90:
fmt.Println("高寿")
case age > 60 && age < 80:
fmt.Println("老年人")
}
goto
goto结合标签使用,可以跳到指定的代码开始执行。
a := 10
if a > 0 {
goto Tag1
}
fmt.Println("第一条")
Tag1:
fmt.Println("第二条")
for
语法 for 初始化;条件;判断
total := 0
for begin := 0; begin <= 100; begin++ {
total += begin
}
fmt.Println("total is", total)
for可以不跟条件,此时会死循环,要注意循环退出条件。
begin := 0
for {
fmt.Println("")
begin += 1
if begin == 5 {
// 如果不退出,就是死循环
break
}
}
容器类型的遍历
// 数组的遍历
array := [...]int{1, 2, 3, 4}
for index, value := range array {
fmt.Println("index is ", index, " value is ", value)
}
// slice的遍历
slice := []int{1, 2, 3, 3, 4}
for index, value := range slice {
fmt.Println("index is ", index, " value is ", value)
}
// map的遍历
map1 := map[string]string{"name": "libai", "addr": "china"}
for key, value := range map1 {
fmt.Println("key is ", key, "value is ", value)
}
函数
// 无返回值的函数
func sum1(a int, b int) {
fmt.Println("total is ", a+b)
}
// 如果参数类型一致,可以只写一个
func sum2(a, b int) {
fmt.Println("total is ", a+b)
}
// 有返回值的函数
func sum3(a int, b int) int {
return a + b
}
// 有多个返回值的函数
func min_max(a, b int) (int, int) {
if a > b {
return b, a
} else {
return a, b
}
}
// 对返回值进行命名
func min_max1(a int, b int) (min, max int) {
if a > b {
min = b
max = a
} else {
min = a
max = b
}
return
}
可变参数的函数
func get_sum(a ...int) int {
fmt.Printf(" %T %v \n", a, a)
sum := 0
for index, value := range a {
fmt.Println("index is ", index, "value is ", value)
sum += value
}
return sum
}
fmt.Println("", get_sum(1, 2, 3, 4))
/*
[]int [1 2 3 4]
index is 0 value is 1
index is 1 value is 2
index is 2 value is 3
index is 3 value is 4
10
*/
匿名函数
// 匿名函数
value := func(a, b int) int {
return a + b
}(10, 200)
fmt.Println("value is ", value)
匿名函数作为参数实现回调
func proc(input string, processor func(str string)) {
processor(input)
}
proc("Hello world", func(str string) {
for _, value := range str {
fmt.Printf("%c %d \n", value, value)
}
})
闭包函数
闭包函数可以携带状态,捕获当前的上下文环境,闭包的实现是通过返回一个函数的函数。
func counter(a int) func() int {
return func() int {
a++
return a
}
}
f := counter(1)
v1 := f()
fmt.Println("v1 is ", v1)
v2 := f()
fmt.Println("v2 is ", v2)
递归函数
函数自己调用自己的函数,称之为递归函数。
func sum(a int) int {
if a == 0 {
return 0
}
return a + sum(a-1)
}
fmt.Println("sumt to 100 is ", sum(100))
函数的值传递和引用传递
值传递不会影响原来的值,传递的是原来的值的拷贝。
引用传递会影响原来的值,传递的是原来的值的引用。
// 值传递
func add1(a int) {
a++
}
// 引用传递
func add2(a *int) {
*a++
}
a := 10
add1(a)
fmt.Println("a is ", a)
add2(&a)
fmt.Println("a is ", a)
/*
a is 10
a is 11
*/
结构体
结构体的定义
type person struct {
name string
age int
}
结构体的三种初始化方式
var p person
p.name = "李白"
p.age = 20
fmt.Println("p is ", p)
p1 := person{"鲁班七号", 20} // 没有名称,只能按照定义的顺序赋值
fmt.Println("p1 is ", p1)
p2 := person{age: 10, name: "瑶瑶"} // 有名称的话可以乱序
fmt.Println("p2 is ", p2)
结构体匿名字段
type person struct {
name string
age int
}
type student struct {
person
grade string
}
luban := student{person{"鲁班大师", 20}, "一年级"}
fmt.Println("luban is ", luban)
fmt.Println("name is", luban.name)
fmt.Println("name is", luban.person.name)
luban.name = "鲁班七号"
fmt.Println("name is", luban.name)
luban.person.name = "李白"
fmt.Println("name is", luban.name)
/*
luban is {{鲁班大师 20} 一年级}
name is 鲁班大师
name is 鲁班大师
name is 鲁班七号
name is 李白
*/
结构体是值类型
type student struct {
age int
}
func new_year(s student) {
s.age++
}
func new_year1(s *student) {
// 下面2句是等效的,对于指针类型,会自动添加星号
// s.age++
(*s).age++
}
xiaoming := student{20}
new_year(xiaoming)
fmt.Println("", xiaoming)
new_year1(&xiaoming)
fmt.Println("", xiaoming)
/*
{20}
{21}
*/
method 结构体方法
// method 语法
// func (r 接收者类型) 方法名称(参数列表)(返回值)
type student struct {
name string
}
func (r student) say() string {
return fmt.Sprintf("%s欢迎您的到来", r.name)
}
// 基本数据类型不能添加方法
// func (r int) pingfang() int {
// return r * r
// }
// 可以给int取别名,这个时候就可以添加方法了
type myint int
func (c myint) pingfang() myint {
return c * c
}
s1 := student{"李白"}
fmt.Println("", s1.say())
var a myint = 10
fmt.Println("", a.pingfang())
method传递引用
type student struct {
age int
}
func (s student) add1() {
s.age++
}
func (s *student) add2() {
s.age++
}
xiaoming := student{20}
xiaoming.add1()
fmt.Println("", xiaoming)
// 这里不需要也不能添加*,会自动传递指针
xiaoming.add2()
fmt.Println("", xiaoming)
/*
{20}
{21}
*/
method继承和重写
如果A有B的匿名字段,则A可以使用B的方法。
如果A有B的匿名字段,如果A和B都有相同的方法,则会使用A的方法。
type person struct {
name string
}
func (p person) say() {
fmt.Println("[person method]:my name is ", p.name)
}
func (p person) say1() {
fmt.Println("[person method]:my name is ", p.name)
}
type student struct {
person
grade string
}
func (s student) say1() {
fmt.Println("[student method]: name is ", s.name)
}
xiaoming := student{person{"小明"}, "1年级"}
// 调用的是person的方法,
xiaoming.say()
// person和student都有该方法,调用的是person 的方法
xiaoming.say1()
结构体和json
package main
import (
"encoding/json"
"fmt"
)
type student struct {
Name string `json:"name"`
Age int `json:age xml:"age1"`
}
func main() {
s := student{"小明", 20}
// 转json 返回的是字节切片
str, err := json.Marshal(s)
if err != nil {
return
}
fmt.Println("str is :", string(str))
// json转student
var s1 student
err2 := json.Unmarshal([]byte(str), &s1)
if err2 != nil {
return
}
fmt.Println("s1 is ", s1)
/*
str is : {"name":"小明","Age":20}
s1 is {小明 20}
*/
}
接口
接口是一种类型。接口统一了类型,满足某个接口的类型都必须实现接口的所有方法。
语法
type 接口名 interface{
方法名(参数..)(返回值..)
方法名(参数..)(返回值..)
}
下面只要实现了makevoice接口中的方法就可以传递给该接口。
package main
import "fmt"
type cat struct{}
type dog struct{}
func (c cat) speak() {
fmt.Println("miao")
}
func (d dog) speak() {
fmt.Println("汪汪")
}
// 定义接口
type makevoice interface {
speak()
}
func test(a makevoice) {
a.speak()
}
func main() {
var c cat
var d = dog{}
test(c)
test(d)
}
接口嵌套
// 嵌套接口
type animal interface {
run()
eat
move
}
type eat interface {
}
type move interface {
}
空接口
空接口可以传递所有类型的参数。
package main
import "fmt"
type my interface{}
func main() {
var a my
a = 10
a = "Hello"
fmt.Println("a is ", a)
}
可以直接interface{}
使用空接口。
package main
import "fmt"
func show(a interface{}) {
fmt.Printf("type %T value %v \n", a, a)
}
func main() {
show(10)
show("hello world")
}
接口的值传递和指针传递
指针数据传递给值类型的时候,会进行拷贝
package main
import "fmt"
type animal interface {
move()
eat(string)
}
type cat struct {
name string
feet int8
weight int
}
func (c cat) move() {
c.weight--
fmt.Println("cat move")
}
func (c cat) eat(f string) {
c.weight++
fmt.Println("cat eat", f)
}
func main() {
var a animal
c1 := cat{"tom", 3, 3}
c2 := &cat{"aa", 2, 3}
a = c1
a.move()
fmt.Println("c1.weight", c1.weight)
a.eat("小鱼干")
fmt.Println("", a)
a = c2
a.move() //虽然是指针类型,但是在传递给move的时候还是进行了拷贝,所以不会影响值
fmt.Println("c2.weight", c2.weight)
a.eat("猫粮")
fmt.Println("", a)
}
指针类型的接口只能传递指针,不能传递普通对象。
package main
import "fmt"
type animal interface {
move()
eat(string)
}
type cat struct {
name string
feet int8
weight int
}
func (c *cat) move() {
c.weight--
fmt.Println("cat move")
}
func (c *cat) eat(f string) {
c.weight++
fmt.Println("cat eat", f)
}
func main() {
var a animal
c1 := cat{"tom", 3, 3}
c2 := &cat{"aa", 2, 3}
// a = c1 如果接收者为指针,则这里不能传递普通对象,必须传递指针类型
a = &c1
a.move()
fmt.Println("c1.weight", c1.weight)
a.eat("小鱼干")
fmt.Println("", a)
a = c2
a.move() // 指针类型,函数的修改会影响原来的值
fmt.Println("c2.weight", c2.weight)
a.eat("猫粮")
fmt.Println("", a)
}
并发编程
使用go关键字
使用go关键字开启并发编程。其中go开启的任务称之为goroutine,其中main函数自身也是一个goroutine。
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("hello world")
time.Sleep(time.Second)
fmt.Println("hello world finish")
}
func main() {
fmt.Println("main begin")
go hello()
fmt.Println("main finish")
// 休眠2秒,等待hello结束,如果提前结束,则hello可能不会被执行
time.Sleep(time.Second * 2)
}
匿名函数开启goroutine
由于匿名函数中,都是对变量i的访问,因此会造成数据错误。
for i := 0; i < 3; i++ {
go func() {
fmt.Println("i is ", i)
}()
}
修复方式1: 定义新的临时变量。这样每个goroutin引用的都是不同的数据。
for i := 0; i < 3; i++ {
var temp = i
go func() {
fmt.Println("temp is ", temp)
}()
}
修复方式2: 直接将值传递给匿名函数,因为参数传递的过程实现的是值拷贝操作。
for i := 0; i < 3; i++ {
go func(a int) {
fmt.Println("a is ", a)
}(i)
}
WaitGroup
如果使用time.sleep的方式等待,是有局限性的,因为我们不确定子任务需要多长时间,可能是1秒,可能是1分钟,因此需要使用waitgroup来实现。
package main
import (
"fmt"
"sync"
)
// 定义一个计数器
var wg1 sync.WaitGroup
func main() {
for i := 0; i < 3; i++ {
wg1.Add(1) // 每开启一个任务,加1
go func(a int) {
defer wg1.Done() // 每结束一个任务, 减1
fmt.Println("a is ", a)
}(i)
}
wg1.Wait()
fmt.Println("all task finished")
}
runtime.GOMAXPROCS
限制CPU核心数,是限制CPU的使用率,比如10核心的CPU,使用2核心。无论处理压力多大,都不会导致CPU满载,从而保证其他程序正常运行。
限制CPU核心数,并不会影响goroutine的并发执行。核心数为1,也可以有很多goroutine快速切换执行。
// 默认是CPU核心数,
// 设置使用核心数,返回之前的核心数
pre := runtime.GOMAXPROCS(2)
// runtime.NumCPU()获取CPU核心数
runtime.GOMAXPROCS(runtime.NumCPU())
sync.one
实现只执行一次的效果,当执行到Do的时候,会根据不同的状态,进行不同的处理。
- 如果之前once任务执行结束,直接跳过
- 如果之前的once任务开启,但是没有完全结束,等待之前的任务执行结束
- 如果之前的once任务没有开启,则执行once任务。
package main
import (
"fmt"
"sync"
)
var once_task sync.Once
var wg sync.WaitGroup
type student struct {
name string
age int
}
var s student
func task1() {
defer wg.Done()
once_task.Do(
func() {
s.name = "李白"
s.age = 20
},
)
fmt.Println("task1 finished ", s.name)
}
func task2() {
defer wg.Done()
once_task.Do(
func() {
s.name = "阿珂"
s.age = 18
},
)
fmt.Println("task2 finished :", s.name)
}
func main() {
wg.Add(2)
go task1()
go task2()
wg.Wait()
}
chan通信
不同的goroutine之间的通信,使用chan。
语法
定义channel
var 变量名称 chan 数据类型
如:
var a chan int
var b chan bool
var c chan []int
channel定义之后必须初始化a = make(chan int)
a <- 10
往chan添加数据
x := <- a
从chan取数据, 或者x, ok := <-a
当Ok为false,表示通道被关闭了。
close(a)
关闭chan
不带缓冲区的chan
不带缓冲区的chan在发送数据的时候,会一直等待数据被接收。如果一直没有被接收,则会进行阻塞。
package main
import (
"fmt"
"time"
)
var a chan int
func hello() {
// 初始化
a = make(chan int)
for i := 0; i < 10; i++ {
a <- i
fmt.Println("i is ", i)
if i == 5 {
close(a)
// 如果不break,在关闭的chan中发送消息,会引用panic
break
}
}
}
func main() {
go hello()
// 等待10秒后,开始接收数据
time.Sleep(time.Second * 10)
for i := 0; i < 10; i++ {
value, ok := <-a
if !ok {
fmt.Println("main chain is closed")
break
}
fmt.Println("value is :", value)
}
}
带缓冲区的chan
带缓冲区的chan在往chan发送数据的时候,里面会一个队列,先把数据存到队列中,从而实现非阻塞。如果缓冲区满了,还是会阻塞。
只需要初始化的时候修改a = make(chan int, 3)
如果chan被关闭了,则之前还在队列中的数据可以继续被消费。
package main
import (
"fmt"
"time"
)
var a chan int
func hello() {
// 初始化
a = make(chan int, 8)
for i := 0; i < 10; i++ {
a <- i
fmt.Println("i is ", i)
if i == 5 {
close(a)
// 如果不break,在关闭的chan中发送消息,会引用panic
break
}
}
}
func main() {
go hello()
time.Sleep(time.Second)
for i := 0; i < 10; i++ {
value, ok := <-a
if !ok {
fmt.Println("main chain is closed")
break
}
fmt.Println("value is :", value)
}
}
chan的性能
10秒完成1个亿,每秒完成1千万的通信。每秒10G的吞吐。IO几乎是固定的,无法代码优化,但是可以提升每次IO的数据听声吞吐。比如每次IO都传递一个数组。一次传递多个参数。
package main
import (
"fmt"
"sync"
"time"
)
var t = time.Now()
var chan_msg chan string
var wg sync.WaitGroup
func get_msg() {
defer wg.Done()
var len_text int64
var count int64
for {
i, ok := <-chan_msg
if !ok {
break
}
len_text += int64(len(i))
count++
if count == 10000_0000 {
use_time := time.Now().UnixNano() - t.UnixNano()
fmt.Println("time used ", use_time, "ns")
fmt.Println("time used ", use_time/1000, "微s")
fmt.Println("time used ", use_time/1000/1000, "ms")
fmt.Println("time used ", use_time/1000/1000/1000, "s")
fmt.Println("recived ", len_text/1024/1024, "MB")
fmt.Println("recived ", len_text/1024/1024/1024, "GB")
}
}
}
func main() {
chan_msg = make(chan string, 100)
wg.Add(1)
go get_msg()
for {
chan_msg <- "Hello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world,Hello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello worldHello world"
}
wg.Wait()
}
// 输出
time used 11427421000 ns
time used 11427421 微s
time used 11427 ms
time used 11 s
recived 205802 MB
recived 200 GB
单向通信
限制chan只能发送或者接收数据。
func test(a chan<- int , b <-chain int)
select多路复用
如果在接收chan的时候,该chan没有数据,将会被阻塞。可以通过select实现多路复用,不会被阻塞
package main
import (
"fmt"
"sync"
"time"
)
var a chan int
var b chan string
var wg sync.WaitGroup
func hello() {
defer wg.Done()
for {
select {
case int_value, ok := <-a:
if !ok {
break
}
fmt.Println("int value is ", int_value)
case string_value, ok := <-b:
if !ok {
break
}
fmt.Println("string value is ", string_value)
// 不建议使用default,不然如果没有数据,会疯狂打印
// default:
// fmt.Println("no data comming")
}
}
}
func task1() {
defer wg.Done()
for {
time.Sleep(time.Second * 2)
a <- 100
}
}
func task2() {
defer wg.Done()
for {
time.Sleep(time.Second * 5)
b <- "hello"
}
}
func main() {
a = make(chan int, 100)
b = make(chan string, 100)
wg.Add(3)
go task1()
go task2()
go hello()
wg.Wait()
}
通过select可以进行超时验证
package main
import (
"fmt"
"time"
)
func main() {
var a chan int
a = make(chan int)
chan_res := make(chan bool)
go func() {
for {
select {
case v := <-a:
fmt.Println("v is ", v)
case value := <-time.After(time.Second * 5):
fmt.Println("value is ", value)
fmt.Println("timeout in 5 seconds")
chan_res <- false
break
}
}
}()
//等待结果
<-chan_res
}
context
用于取消goroutine的任务。
package main
import (
"context"
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
var int_chan chan int
func hello(ctx context.Context) {
defer wg.Done()
label1:
for {
select {
case a := <-int_chan:
fmt.Println("a is ", a)
case <-ctx.Done():
break label1
}
}
}
func produce_msg(ctx context.Context) {
defer wg.Done()
label1:
for {
time.Sleep(time.Second * 1)
select {
case <-ctx.Done():
// break
break label1
default:
int_chan <- 100
}
}
}
func main() {
int_chan = make(chan int)
ctx, cancel := context.WithCancel(context.Background())
wg.Add(2)
go hello(ctx)
go produce_msg(ctx)
time.Sleep(time.Second * 10)
cancel()
wg.Wait()
fmt.Println("main finished")
}
/*
a is 100
a is 100
a is 100
a is 100
a is 100
a is 100
a is 100
a is 100
a is 100
main finished
*/
常用runtime函数
runtime.Goexit()
退出当前的goroutine,但是会继续执行函数的defer代码。runtime.Gosched()
让出当前的执行权限runtime.NumCPU()
获取当前CPU核心数runtime.NumGoroutine()
获取所有的goroutine,其中休眠的也算。
锁
定义并初始化锁var lock sync.Mutex
Lock()
加锁,Unlock()
解锁,Trylock()
尝试加锁,如果返回true,表示加锁成功,如果是false表示加锁失败,此时可以执行其它任务。等其他任务执行结束之后再尝试加锁,如果没有其他任务只有当前任务,就执行Lock()
package main
import (
"fmt"
"sync"
)
var x int = 0
var wg sync.WaitGroup
var lock sync.Mutex
func test() {
for i := 0; i < 10_0000; i++ {
lock.Lock()
x++
lock.Unlock()
}
wg.Done()
}
func main() {
wg.Add(2)
go test()
go test()
wg.Wait()
fmt.Println("x is ", x)
}
读写互斥锁
只需要换一把锁var rwlock sync.RWMutex
用法一致。用于读多写少的场景。如果读写次数差不多性能不如互斥锁。
安全的sync.map
func (m *Map) Range(f func(key, value any) bool)
无序遍历元素,如果返回false,则终止遍历func (m *Map) Store(key, value any)
存储key-valuefunc (m *Map) Load(key any) (value any, ok bool)
获取key对应的value,如果没有纸,则ok为false。func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool)
如果存在,则返回内容,且loaded为true,否则存储该值func (m *Map) Delete(key any)
删除元素。
package main
import (
"fmt"
"sync"
)
var safe_map sync.Map
var wg sync.WaitGroup
func addNumber(begin int) {
defer wg.Done()
for i := begin; i < begin+3; i++ {
// 设置key value
//func (m *Map) Store(key, value any) {
safe_map.Store(i, i)
}
}
func main() {
size := 5
for i := 0; i < size; i++ {
wg.Add(1)
go addNumber(i)
}
wg.Wait()
var size1 int
// 使用ragne遍历所有元素,如果返回false,则结束遍历
safe_map.Range(func(key, value interface{}) bool {
size1++
return true
})
fmt.Println(" size is ", size1)
// 根据key取value
//func (m *Map) Load(key any) (value any, ok bool) {
if value, ok := safe_map.Load(0); ok {
fmt.Println("value is ", value)
}
}
go module
go mudule是Go1.11版本之后官方推出的版本管理工具,从1.13开始是默认的依赖管理工具。
启用go module
支持,需要设置环境变量GO111MODULE
。默认值auto
GO111MODULE=off
禁用模块支持,编译时会从GOPATH和vendor文件夹中查找包。GO111MODULE=on
启动模块支持,编译时会忽略GOPATH和vender文件夹,只根据go.mod下载依赖。GO111MODULE=auto
当项目在$GOPATH/src
外且项目根目录外有go.mod文件时,开启模块支持。
使用go module
管理依赖后会在项目根目录下生成两个文件go.mod
和go.sum
。
通过查看go env
可以找到GO111MODULE=""
,这个时候,默认就是开启状态,auto。
也可以通过go env -w GO111MODULE=auto
进行修改。也可以直接设置系统的环境变量,但是如果设置了系统的环境变量,再次使用go env -w
则会报错。
GOPROXY
go1.13之后的GOPORXY默认为https://proxy.golang.org
,国内无法访问。1.18的版本为GOPROXY="https://goproxy.io,direct"
go env -w GOPROXY=https://goproxy.cn,direct
go module的使用
创建项目mkdir go_hello
, cd go_hello
初始化go module go mod init myweb.cn/aa/bb
,名称可以随便取,一般会填写对应的github地址。
初始化go module之后,会在当前的目录下生成一个go.mod
文件,文件内容如下:
module myweb.cn/aa/bb
go 1.18
该文件指明了模块的名称和当前的go版本。
go mod命令
go mod download
下载依赖的包达到本地cache(默认为$GOPATH/pkg/mod目录)go mod edit
编辑go.mod文件go mod graph
打印模块依赖图go mod init
初始化当前文件夹,创建go.mod文件go mod tidy
增加缺少的module,删除无用的modulego mod vendor
导出依赖到vendor中go mod verify
校验依赖是否被修改过go mod why
解释为什么需要依赖
go.mod
该文件记录了项目所有的依赖信息
其中indirect表示间接引用,就是我们自己没有使用,而是我们使用的一些第三方依赖了该模块。
本地包的使用
目录结构
.
├── go.mod
├── hello
│ ├── world.go
│ └── x1.go
└── main.go
其中go.mod如下
module testlocalpackage
go 1.18
hello目录有两个go文件,注意,一个目录下只能有一个package名称。一般推荐命名是package的名称和目录相同,不相同也不会编译报错。
// world.go
package abc
import "fmt"
func SomeWhat() {
fmt.Println("this is from hello/abc.go SomeWhat()")
}
// x1.go
package abc // 这里不能取abc以外的名称
import "fmt"
func SomePrint() {
fmt.Println("someprint")
}
main.go的代码如下abc "testlocalpackage/hello"
其中因为目录名称和package的名称不一致,所以需用重新命名。VSCode会自动帮我们取名package的名称,这里也可以改成其它名称。
package main
import (
"fmt"
abc "testlocalpackage/hello"
)
func main() {
fmt.Println("main")
abc.SomeWhat()
abc.SomePrint()
}
文件操作
文件权限
创建文件夹最后一个参数是权限设置。一共三位。比如777,755。
第一个数字是所有者权限
第二个数字是同组用户权限
第三个数字是其它用户权限
具体的权限由数字控制,读r操作权限是4,写w操作权限是2,执行x操作权限是1,0表示没有任何权限。
7可以分解为4 + 2 + 1表示读写执行
6可以分解为4 + 2表示可读可写
5=4 + 1 可读可执行
3 = 2 + 1 可写可执行
777表示所有用户可读可写可执行。比较危险
750表示当前用户可以执行所有权限,所属组可以读和执行,比如开发一个可执行的程序,其它用户修改这个文件就会导致不安全,因此需要屏蔽修改权限。
创建文件和文件夹
创建文件夹
// 创建文件夹,如果该文件夹存在,则会报错
err := os.Mkdir("hello world", 0750)
if err != nil {
fmt.Println("", err)
}
err = os.Mkdir("hello world", 0750)
if err != nil {
// mkdir hello world: file exists
fmt.Println("", err)
}
创建多级文件夹。父目录会自动创建。比如hello/world
中存在一个文件为hello则会报错。
// 可以创建多级文件夹
err := os.MkdirAll("hello1/world", 0750)
if err != nil {
fmt.Println("", err)
}
// 即使该文件夹存在,也不会报错
err = os.MkdirAll("hello1/world", 0750)
if err != nil {
fmt.Println("", err)
}
删除文件夹,remove()删除单层文件夹,RemoveAll()文件夹及其子文件夹。如果路径不存在也不会报错,如果出错,可能是权限问题。
err := os.RemoveAll("hello1")
if err != nil {
fmt.Println("", err)
}
读取文件操作, os.Open打开文件,打开之后需要f.Close()进行关闭。
// 如果文件不存在,会报错
f, err := os.Open("main.go")
if err != nil {
fmt.Println("open failed:", err)
return
}
//打开文件要关闭
defer f.Close()
// 读取文件的内容
var buffer [10]uint8
for {
// 将文件的内容读取到buffer
read_bytes, err1 := f.Read(buffer[:])
if err1 != nil {
// 如果读取完毕,继续读取,就会
// 读取失败: EOF
fmt.Println("读取失败:", err1)
return
}
if read_bytes == 0 {
// 这里不会到来的,因为如果没有数据,上面就会报错了
fmt.Println("读取完毕")
return
}
// 打印内容
fmt.Print("", string(buffer[:read_bytes]))
if read_bytes < 10 {
fmt.Println("读取完毕1")
return
}
}
写入文件
f, err := os.OpenFile("你好.txt", 750, os.O_CREATE)
if err != nil {
fmt.Println("打开文件失败:", err)
return
}
defer f.Close()
// 写入字符串
f.WriteString("hello world")
// 写入字节
f.Write([]byte("你好"))
模式 | 说明 |
---|---|
os.O_WRONLY | 只写 |
os.O_CREATE | 创建文件 |
os.O_RONLY | 只读 |
os.O_RDWR | 读写 |
os.O_TRUNC | 清空 |
os.O_APPEND | 追加 |
bufio
一次读取一行
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// 打开文件,可以是相对路径,也可以是绝对路径
f, err := os.Open("./hello.go")
if err != nil {
fmt.Println("err", err)
return
}
defer f.Close()
reader := bufio.NewReader(f)
for {
// 每次读取一行
str, err := reader.ReadString('\n')
if err != nil {
break
}
fmt.Print("", str)
}
}
ioutil
一次读取所有文件
package main
import (
"fmt"
"io/ioutil"
)
func main() {
content, err := ioutil.ReadFile("hello.go")
if err != nil {
return
}
fmt.Println("", string(content))
}
写入文件。
f, err := os.OpenFile("./main1.go", os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0664)
if err != nil {
fmt.Println("打开文件失败:", err)
return
}
defer f.Close()
w := bufio.NewWriter(f)
w.Write([]byte("hello world"))
w.WriteRune(10)
w.WriteByte('!')
w.WriteString("你好")
//刷新缓冲
w.Flush()
命令行参数
使用os模块,利用os.Args
获取参数列表,其中第一个参数为可执行文件的本身。
go run main.go
获取参数列表的时候,因为go run
本身的作用是执行go命令编译代码生成可执行文件,本质还是执行编译之后的main可执行文件。
package main
import (
"fmt"
"os"
)
func main() {
// 返回的是字符串切片类型[]string
args := os.Args
fmt.Printf("type is %T args is %#v\n", args, args)
for index, value := range args {
fmt.Println("index is ", index, " value is ", value)
}
/*
index is 1 value is hello
index is 2 value is world
*/
}
使用flag获取命令行参数
package main
import (
"flag"
"fmt"
)
func main() {
// 使用flag获取参数
// 获取指针类型的参数
// 第一个参数是参数的key
// 第二个参数是默认值
// 第三个参数是备注
name := flag.String("name", "李白", "名称")
// 也可以先声明字符串变量,然后传递地址
var country string
flag.StringVar(&country, "country", "中国", "国家")
// 获取int类型的数据
age := flag.Int("age", 20, "年龄")
// 开始解析,
flag.Parse()
fmt.Println("name is ", *name, " country is ", country, " age is ", *age)
}
// 使用flag可以通过--help获取帮助信息
./main --help
Usage of ./main:
-age int
年龄 (default 20)
-country string
国家 (default "中国")
-name string
名称 (default "李白")
传递参数的多种方式
./main --name "李白" --country "唐朝" --age 18
./main --name="李白" --country="唐朝" --age=18
./main -name="李白" -country="唐朝" -age=18
./main -name "李白" -country "唐朝" -age 18
时间
package main
import (
"fmt"
"time"
)
func main() {
// 时间
// 获取当前的时间
t := time.Now()
fmt.Println("t is ", t)
// 获取当前的时间,并转成标准时间。标准时间和当前时间相差8个时区
t1 := time.Now().UTC()
fmt.Println("t1 is ", t1)
// 纳秒时间,当前时间减去秒之后的时间
fmt.Println("纳秒", t.Nanosecond())
// 将秒也转成纳秒
fmt.Println("纳秒", t.UnixNano())
/*
纳秒 943870000
纳秒 1658281149943870000
*/
// 格式化时间
time_str := t.Format("2006-1-2 15:04:05")
fmt.Println("time is ", time_str)
// time is 2022-7-20 09:39:09
}
panic和recover
pinic是程序发生异常,无法继续执行了。
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func hello() {
defer wg.Done()
for i := 0; i < 10; i++ {
fmt.Println("i is ", i)
if i == 5 {
panic("a == 5")
}
}
}
func main() {
wg.Add(1)
go hello()
wg.Wait()
fmt.Println("main finished")
}
// 输出
i is 0
i is 1
i is 2
i is 3
i is 4
i is 5
panic: a == 5
goroutine 6 [running]:
main.hello()
/Users/wang/Desktop/gocode/main.go:15 +0x104
created by main.main
/Users/wang/Desktop/gocode/main.go:21 +0x38
exit status 2
从异常中恢复,使用recover。比如网络请求中,某个网络请求失败了,不能导致其它的网络请求也失败。
package main
import (
"fmt"
"sync"
)
var wg sync.WaitGroup
func hello() {
defer func() {
value := recover()
fmt.Println("value is ", value)
wg.Done()
}()
for i := 0; i < 10; i++ {
fmt.Println("i is ", i)
if i == 5 {
panic("a == 5")
}
}
}
func main() {
wg.Add(1)
go hello()
wg.Wait()
fmt.Println("main finished")
}
// 输出
i is 0
i is 1
i is 2
i is 3
i is 4
i is 5
value is a == 5
main finished