Go语言学习
–
0.安装
–
- 下载对应版本的安装包
下载
- 解压安装包并安装
docs
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.22.1.linux-amd64.tar.gz
- 配置环境变量
1
2
3
4
|
sudo vim ~/.bashrc
export PATH=$PATH:/usr/local/go/bin
source ~/.bashrc
|
- 验证安装
- 一键安装
1
2
3
4
5
6
7
8
9
10
| # linux
wget https://golang.org/dl/go1.22.1.linux-amd64.tar.gz
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.22.1.linux-amd64.tar.gz
sudo vim ~/.bashrc
export PATH=$PATH:/usr/local/go/bin
source ~/.bashrc
go version
|
1.main
–
1
2
3
4
5
6
7
8
| package main
//定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包
import "fmt"
//告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数
func main() {
fmt.Println("Hello Go")//Print不加\n,Println自带加\n
}
//程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)
|
1
2
3
| //运行
go run test1_hello.go
Hello Go
|
2.变量
–
2.1 指定变量类型
1
2
3
4
5
6
7
8
9
10
| // var v_name v_type
// v_name = value
package main
import "fmt"
func main() {
var a int
fmt.Printf(" = %d\n", a)
}
|
–
2.2 根据值自行判定变量类型
–
2.3 省略var,
- 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误。
1
2
3
4
5
6
7
| v_name := value
// 例如
var a int = 10
var b = 10
c : = 10
|
–
2.4 例子
–
1
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 main
import "fmt"
func main() {
//第一种 使用默认值
var a int
fmt.Printf("a = %d\n", a)
//第二种
var b int = 10
fmt.Printf("b = %d\n", b)
//第三种 省略后面的数据类型,自动匹配类型
var c = 20
fmt.Printf("c = %d\n", c)
//第四种 省略var关键字
d := 3.14
fmt.Printf("d = %f\n", d)
}
|
1
2
3
4
5
| go run var.go
a = 0
b = 10
c = 20
d = 3.140000
|
–
1
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
30
31
32
33
34
| \\多变量声明
package main
import "fmt"
var x, y int
var ( //这种分解的写法,一般用于声明全局变量
a int
b bool
)
var c, d int = 1, 2
var e, f = 123, "liudanbing"
//这种不带声明格式的只能在函数体内声明
//g, h := 123, "需要在func函数体内实现"
func main() {
g, h := 123, "需要在func函数体内实现"
fmt.Println(x, y, a, b, c, d, e, f, g, h)
//不能对g变量再次做初始化声明
//g := 400
_, value := 7, 5 //实际上7的赋值被废弃,变量 _ 不具备读特性
//fmt.Println(_) //_变量的是读不出来的
fmt.Println(value) //5
}
|
1
2
3
4
| go run mutiVar.go
0 0 0 false 1 2 123 liudanbing 123 需要在func函数体内实现
5
|
3.常量
–
- 常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
–
3.1 常量定义格式
1
2
3
4
5
| const identifier [type] = value
//显示类型定义
const b string = "abc"
//隐式类型定义
const b = "abc"
|
–
3.2 常量枚举
1
2
3
4
5
| const (
Unknown = 0
Female = 1
Male = 2
)//数字 0、1 和 2 分别代表未知性别、女性和男性
|
–
3.3 常量表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| //常量可以用len(), cap(), unsafe.Sizeof()常量计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过:
package main
import "unsafe"
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a)
)
func main(){
println(a, b, c)
}
|
1
| abc, 3, 16//字符串类型在 go 里是个结构, 包含指向底层数组的指针和长度,这两部分每部分都是 8 个字节,所以字符串类型大小为 16 个字节。
|
–
3.4 常量iota
1
2
3
4
5
6
7
8
9
10
| type Allergen int
const (
_ = iota // ignore first value by assigning to blank identifier
IgEggs Allergen = 1 // 1 << 0 which is 00000001
IgChocolate // 1 << 1 which is 00000010
IgNuts = 1 << iota // 1 << 2 which is 00000100
IgStrawberries // 1 << 3 which is 00001000
IgShellfish // 1 << 4 which is 00010000
)
|
–
1
2
3
4
5
| const (
Apple, Banana = iota + 1, iota + 2
Cherimoya, Durian
Elderberry, Fig
)
|
1
2
3
4
5
6
| // Apple: 1
// Banana: 2
// Cherimoya: 2
// Durian: 3
// Elderberry: 3
// Fig: 4
|
- iota:自增,不在乎位置,每一行自增
- const组中仅仅有一个标示符在一行的时候,它将使用增长的iota取得前面的表达式并且再运用它
4.函数
1
2
3
| func function_name( [parameter list] ) [return_types] {
函数体
}
|
–
4.1 函数多返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Mahesh", "Kumar")
fmt.Println(a, b)
}
//Kumar Mahesh
|
–
4.2 ini/main和import
- init 函数可以在package main中或其他package中,可在同一package多次出现
- main 函数只能在package main中
- 这两个函数在定义时不能有参数和返回值,且会自动执行。
- package main就必须包含一个main函数
- 如果一个包会被多个包同时导入,那么它只会被导入一次
- 当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。
–
–
4.3 go指针
- Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。
- 函数中引用传递’*'
1
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
30
31
32
33
34
| package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 100
var b int= 200
fmt.Printf("交换前,a 的值 : %d\n", a )
fmt.Printf("交换前,b 的值 : %d\n", b )
/* 调用 swap() 函数
* &a 指向 a 指针,a 变量的地址
* &b 指向 b 指针,b 变量的地址
*/
swap(&a, &b)
fmt.Printf("交换后,a 的值 : %d\n", a )
fmt.Printf("交换后,b 的值 : %d\n", b )
}
func swap(x *int, y *int) {
var temp int
temp = *x /* 保存 x 地址上的值 */
*x = *y /* 将 y 值赋给 x */
*y = temp /* 将 temp 值赋给 y */
}
|
–
4.4 defer函数
defer在Go里可以放在某个函数或者方法调用的前面,让该函数或方法延迟执行
1
2
3
| //语法
defer function([parameter_list]) // 延迟执行函数
defer method([parameter_list]) // 延迟执行方法
|
defer在函数体内执行,在函数A内调用了defer func(),只要defer func()这行代码被执行到,func这个函数就会被延迟到函数A return或panic之前执行。
- 函数调用了os.Exit()退出,defer不会被执行了。
- 如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。
–
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| func Demo(){
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
defer fmt.Println("4")
}
func main() {
Demo()
}
/*
4
3
2
1
*/
|
–
4.5 recover错误拦截
运行时panic异常一旦被引发就会导致程序崩溃。
Go语言提供了专用于“拦截”运行时panic的内建函数“recover”。它可以是当前的程序从运行时panic的状态中恢复并重新获得流程控制权。
–
1
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
30
31
| package main
import "fmt"
func Demo(i int) {
//定义10个元素的数组
var arr [10]int
//错误拦截要在产生错误前设置
defer func() {
//设置recover拦截错误信息
err := recover()
//产生panic异常 打印错误信息
if err != nil {
fmt.Println(err)
}
}()
//根据函数参数为数组元素赋值
//如果i的值超过数组下标 会报错误:数组下标越界
arr[i] = 10
}
func main() {
Demo(10)
//产生错误后 程序继续
fmt.Println("程序继续执行...")
}
//输出:
//runtime error: index out of range
//程序继续执行...
|
5.输入输出
–
–
5.1 输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| //Print系列函数会将内容输出到系统的标准输出,区别在于Print函数直接输出内容,Printf函数支持格式化输出字符串,Println函数会在输出内容的结尾添加一个换行符。
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)
func main() {
name := "沙河小王子"
fmt.Print("在终端打印该信息。")
fmt.Printf("我是:%s\n", name)
fmt.Println("在终端打印单独一行显示")
}
|
–
5.2 输入
1
2
3
4
5
6
7
8
9
10
| func Scan(a ...interface{}) (n int, err error)
func Scanf(format string, a ...interface{}) (n int, err error)
func Scanln(a ...interface{}) (n int, err error)
fmt.Scan(&name, &age, &isMale)//fmt.Scan在没有扫描完所有变量之前是不会结束扫描的
fmt.Scanf("name:%s age:%d isMale:%v", &name, &age, &isMale)//遇到换行即结束扫描,如果还有没输入的变量值,该变量会按默认值处理
fmt.Scanln(&name, &age, &married)//遇到换行即结束扫描,如果还有没输入的变量值,该变量会按默认值处理
|
6.slice切片、map
–
6.1 数组
- Go 数组的长度不可改变,不同大小的数组是不同的变量类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| //声明数组
var arrayName [size]dataType
var balance [10]float32
//初始化数组
var numbers = [5]int{1, 2, 3, 4, 5}
numbers := [5]int{1, 2, 3, 4, 5}
// 将索引为 1 和 3 的元素初始化
balance := [5]float32{1:2.0,3:7.0}
//二维数组初始化
a := [3][4]int{
{0, 1, 2, 3} , /* 第一行索引为 0 */
{4, 5, 6, 7} , /* 第二行索引为 1 */
{8, 9, 10, 11}} /* 第三行索引为 2 */
//数组传参
void myFunction(param [10]int)//param []int
{
}
|
–
6.2 slice
- Go 语言切片是对数组的抽象
- 与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
- 切片不需要说明长度。
1
2
3
4
5
6
7
8
| var identifier []type
//make()创建切片
var slice1 []type = make([]type, len)
//简写为
slice1 := make([]type, len, cap)
//len有值的元素数量 切片长度
//cap可省略,切片容量
|
–
应用举例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| //切片在未初始化之前默认为 nil,长度为 0
s :=[] int {1,2,3 }
//直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3.其cap=len=3
s := arr[:]
//初始化切片s,是数组arr的引用
s := arr[startIndex:endIndex]
//将arr中从下标startIndex到endIndex-1 下的元素创建为一个新的切片,是数组arr的部分引用
s1 := s[startIndex:endIndex]
//通过切片s初始化切片s1,仍然是引用
//len() 获取长度 cap() 获取容量
len(x)
cap(x)
|
–
增加切片的容量
创建新的大切片,拷贝原分片的内容
1
2
3
4
5
6
7
8
| var numbers []int
/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
|
–
6.3 map
一种无序的键值对集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| //第一种声明
var test1 map[string]string
//在使用map前,需要先make,make的作用就是给ma配数据空间
test1 = make(map[string]string, 10)
//第二种声明
test2 := make(map[string]string)
//第三种声明
test3 := map[string]string{
"one" : "php",
"two" : "golang",
"three" : "java",
}
// 增改键值对
myMap["apple"] = 5
// 删除键值对
delete(myMap, "apple")
// 检索值
value = myMap["banana"]
|
–
1
2
3
4
5
6
7
8
| //嵌套map
language := make(map[string]map[string]string)
language["php"] = make(map[string]string, 2)
language["php"]["id"] = "1"
language["php"]["desc"] = "php是世界上最美言"
language["golang"] = make(map[string]string2)
language["golang"]["id"] = "2"
language["golang"]["desc"] = "golang抗并发good"
|
7.结构体
–
7.1 结构体定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| type MyStruct struct {
Field1 Type1
Field2 Type2
// 可以有更多字段
}
//例如
type Person struct {
FirstName string
LastName string
Age int
}
//创建实例
p1 := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
//访问结构体字段
fmt.Println(p1.FirstName) // 输出 "John"
fmt.Println(p2.LastName) // 输出 "Smith"
|
–
7.2 结构体方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| func (receiver ReceiverType) MethodName() ReturnType {
// 方法的实现
}
//例如
func (p Person) FullName() string {
return p.FirstName + " " + p.LastName
}
//调用
fmt.Println(p1.FullName()) // 输出 "John Doe"
fmt.Println(p2.FullName()) // 输出 "Alice Smith"
//使用指针接收器,方法接收的是结构体的指针而不是副本,使得修改能够影响原始结构体
func (receiver *ReceiverType) MethodName() {
// 方法的实现
}
func (p *Person) IncrementAge() {
p.Age++
}
p1.IncrementAge()
fmt.Println(p1.Age) // 输出 31
|
8.方法值和方法表达式
–
8.1 方法值
方法值是一种将方法绑定到特定实例的函数。它允许您将一个方法作为普通的函数值进行传递、存储和调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| package main
import "fmt"
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func main() {
c := Circle{Radius: 5}
// 方法值的用法
areaFunc := c.Area
area := areaFunc()
fmt.Printf("圆的面积:%f\n", area)
}
|
–
8.2 方法表达式
方法表达式允许您将方法绑定到类型而不是实例,并将其作为函数值进行传递、存储和调用
接口类型可以由多种不同的类型实现。
1
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
30
31
| package main
import "fmt"
type Geometry interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func CalculateArea(g Geometry) float64 {
return g.Area()
}
func main() {
c := Circle{Radius: 5}
// 方法表达式的用法
areaFunc := (*Circle).Area
area := areaFunc(&c)
fmt.Printf("圆的面积:%f\n", area)
// 使用接口调用方法表达式
area2 := CalculateArea(&c)
fmt.Printf("通过接口计算的圆的面积:%f\n", area2)
}
|
–
8.3
方法值允许将方法绑定到特定实例并将其用作函数值
方法表达式允许将方法绑定到类型并将其用作函数值。
这两个概念对于 Go 中的面向对象编程和接口实现非常有用。
9.interface与类型断言
–
9.1 interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| //定义接口
type Person interface {
// 声明方法
method1(参数列表)返回值列表
method2(参数列表)返回值列表
}
//实现接口
func (t 自定义类型)method1(参数列表)返回值列表 {
//方法实现
}
func (t 自定义类型)method2(参数列表)返回值列表 {
//方法实现
}
|
–
1
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
| //在Go中,接口的实现是隐式的。如果一个类型包含了接口中定义的所有方法,那么它被视为实现了该接口。
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
//使用接口可以编写通用的代码,不依赖于具体的类型。例如,可以编写一个函数来计算任何形状的面积和周长:
func PrintShapeDetails(s Shape) {
fmt.Printf("Area: %f\n", s.Area())
fmt.Printf("Perimeter: %f\n", s.Perimeter())
}
func main() {
c := Circle{Radius: 5}
PrintShapeDetails(c)
}
|
9.2 类型断言
- golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}的接口
- var a interface{} 定义了一个类型为空接口的变量,可以储存任何值
1
2
3
4
5
6
7
8
9
10
11
12
13
| var i interface{}
i = 42
value, ok := i.(int)
if ok {
fmt.Printf("i 是一个整数: %d\n", value)
} else {
fmt.Println("i 不是一个整数")
}
//直接断言使用
var a interface{}
fmt.Println("Where are you,Jonny?", a.(string))
|
9.3 工厂函数
1
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
30
31
32
33
34
35
36
37
| //创建并返回一个新的类型实例
package main
import "fmt"
// 定义一个接口
type Shape interface {
Area() float64
}
// 定义一个类型,实现了 Shape 接口
type Circle struct {
Radius float64
}
// Circle 类型的方法,用于计算面积
func (c Circle) Area() float64 {
return 3.14159265359 * c.Radius * c.Radius
}
// 工厂函数,用于创建 Circle 类型的实例
func NewCircle(radius float64) *Circle {
return &Circle{Radius: radius}
}
func main() {
// 使用工厂函数创建 Circle 对象
c1 := NewCircle(5.0)
c2 := NewCircle(2.0)
// 调用 Circle 对象的方法
area1 := c1.Area()
area2 := c2.Area()
fmt.Printf("Circle 1 Area: %f\n", area1)
fmt.Printf("Circle 2 Area: %f\n", area2)
}
|
10.反射reflect
–
- 变量包括(type, value)两部分
- type 包括 static type和concrete type. 简单来说 static type是你在编码是看见的类型(如int、string),concrete type是runtime系统看见的类型
- 类型断言能否成功,取决于变量的concrete type,而不是static type. 因此,一个 reader变量如果它的concrete type也实现了write方法的话,它也可以被类型断言为writer.
–
比如:
1
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
30
31
32
33
34
35
36
| package main
import "fmt"
type Reader interface {
ReadBook()
}
type Writer interface {
WriteBook()
}
//具体类型
type Book struct {
}
func (this *Book) ReadBook() {
fmt.Println("Read a book.")
}
func (this *Book) WriteBook() {
fmt.Println("Write a book.")
}
func main() {
b := &Book{}
var r Reader
r = b
r.ReadBook()
var w Writer
w = r.(Writer)
w.WriteBook()
}
|
–
reflect.TypeOf()是获取pair中的type
reflect.ValueOf()获取pair中的value
示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.2345
fmt.Println("type: ", reflect.TypeOf(num))
fmt.Println("value: ", reflect.ValueOf(num))
}
运行结果:
type: float64
value: 1.2345
|
–
从已知原有类型创建新的变量
1
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
| realValue := value.Interface().(已知的类型)
package main
import (
"fmt"
"reflect"
)
func main() {
var num float64 = 1.2345
pointer := reflect.ValueOf(&num)
value := reflect.ValueOf(num)
// 可以理解为“强制转换”,但是需要注意的时候,转换的时候,如果转换的类型不完全符合,则直接panic
// Golang 对类型要求非常严格,类型一定要完全符合
// 如下两个,一个是*float64,一个是float64,如果弄混,则会panic
convertPointer := pointer.Interface().(*float64)
convertValue := value.Interface().(float64)
fmt.Println(convertPointer)
fmt.Println(convertValue)
}
运行结果:
0xc42000e238
1.2345
|
–
未知原有类型
1
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
| package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (u User) ReflectCallFunc() {
fmt.Println("Allen.Wu ReflectCallFunc")
}
func main() {
user := User{1, "Allen.Wu", 25}
DoFiledAndMethod(user)
}
// 通过接口来获取任意参数,然后一一揭晓
func DoFiledAndMethod(input interface{}) {
getType := reflect.TypeOf(input)
fmt.Println("get Type is :", getType.Name())
getValue := reflect.ValueOf(input)
fmt.Println("get all Fields is:", getValue)
// 获取方法字段
// 1. 先获取interface的reflect.Type,然后通过NumField进行遍历
// 2. 再通过reflect.Type的Field获取其Field
// 3. 最后通过Field的Interface()得到对应的value
for i := 0; i < getType.NumField(); i++ {
field := getType.Field(i)
value := getValue.Field(i).Interface()
fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
}
// 获取方法
// 1. 先获取interface的reflect.Type,然后通过.NumMethod进行遍历
for i := 0; i < getType.NumMethod(); i++ {
m := getType.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type)
}
}
运行结果:
get Type is : User
get all Fields is: {1 Allen.Wu 25}
Id: int = 1
Name: string = Allen.Wu
Age: int = 25
ReflectCallFunc: func(main.User)
|
11.结构体标签
12.goroutine
–
- 协程:coroutine。也叫轻量级线程。
- 可以轻松创建上万个而不会导致系统资源衰竭。而线程和进程通常很难超过1万个。
- 一个线程中可以有任意多个协程,但某一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源。
- 有人把Go比作21世纪的C语言。第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而Go从语言层面就支持并发。同时,并发程序的内存管理有时候是非常复杂的,而Go语言提供了自动垃圾回收机制。
- Goroutine从量级上看很像协程,它比线程更小,十几个goroutine可能体现在底层就是五六个线程
- 主goroutine退出后,其它的工作goroutine也会自动退出
- 只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。
- runtime.Goexit()只需在函数调⽤语句前添加 go 关键字,就可创建并发执⾏单元。
–
1
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 main
import (
"fmt"
"runtime"
)
func main() {
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
runtime.Goexit() // 终止当前 goroutine, import "runtime"
fmt.Println("B") // 不会执行
}()
fmt.Println("A") // 不会执行
}() //不要忘记()
//死循环,目的不让主goroutine结束
for {
}
}
|
13.channel
–
- goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。goroutine 奉行通过通信来共享内存,而不是共享内存来通信。
- channel是一个数据类型,主要用来解决go程的同步问题以及go程之间数据共享(数据传递)的问题。
- 引⽤类型 channel可用于多个 goroutine 通讯。其内部实现了同步,确保并发安全。
- 我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。
- 和其它的引用类型一样,channel的零值也是nil。
–
13.1定义channel变量
1
2
3
4
5
| //chan是创建channel所需使用的关键字。Type 代表指定channel收发数据的类型。
make(chan Type) //等价于make(chan Type, 0)
make(chan Type, capacity)
//当 参数capacity= 0 时,channel 是无缓冲阻塞读写的;当capacity > 0 时,channel 有缓冲、是非阻塞的,直到写满 capacity个元素才阻塞写入。
|
–
13.2接收发送数据
1
2
3
4
| channel <- value //发送value到channel
<-channel //接收并将其丢弃
x := <-channel //从channel中接收数据,并赋值给x
x, ok := <-channel //功能同上,同时检查通道是否已关闭或者是否为空
|
–
默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得goroutine同步变的更加的简单,而不需要显式的lock。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
defer fmt.Println("子go程结束")
fmt.Println("子go程正在运行……")
c <- 666 //666发送到c
}()
num := <-c //从c中接收数据,并赋值给num
fmt.Println("num = ", num)
fmt.Println("main go程结束")
}
|
–
无缓冲的channel
无缓冲的通道(unbuffered channel)是指在接收前没有能力保存任何数据值的通道。
这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作。否则,通道会导致先执行发送或接收操作的 goroutine 阻塞等待。
这种对通道进行发送和接收的交互行为本身就是同步的。其中任意一个操作都无法离开另一个操作单独存在。
1
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
30
31
| package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 0) //创建无缓冲的通道 c
//内置函数 len 返回未被读取的缓冲元素数量,cap 返回缓冲区大小
fmt.Printf("len(c)=%d, cap(c)=%d\n", len(c), cap(c))
go func() {
defer fmt.Println("子go程结束")
for i := 0; i < 3; i++ {
c <- i
fmt.Printf("子go程正在运行[%d]: len(c)=%d, cap(c)=%d\n", i, len(c), cap(c))
}
}()
time.Sleep(2 * time.Second) //延时2s
for i := 0; i < 3; i++ {
num := <-c //从c中接收数据,并赋值给num
fmt.Println("num = ", num)
}
fmt.Println("main进程结束")
}
|
–
有缓冲的channel
有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个数据值的通道。
这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也不同。
只有通道中没有要接收的值时,接收动作才会阻塞。
只有通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。
这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道没有这种保证。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| func main() {
c := make(chan int, 3) //带缓冲的通道
//内置函数 len 返回未被读取的缓冲元素数量, cap 返回缓冲区大小
fmt.Printf("len(c)=%d, cap(c)=%d\n", len(c), cap(c))
go func() {
defer fmt.Println("子go程结束")
for i := 0; i < 3; i++ {
c <- i
fmt.Printf("子go程正在运行[%d]: len(c)=%d, cap(c)=%d\n", i, len(c), cap(c))
}
}()
time.Sleep(2 * time.Second) //延时2s
for i := 0; i < 3; i++ {
num := <-c //从c中接收数据,并赋值给num
fmt.Println("num = ", num)
}
fmt.Println("main进程结束")
}
|
–
13.3 关闭channel
- 关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
- 关闭channel后,可以继续从channel接收数据
1
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
| package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
close(c)
}()
for {
//ok为true说明channel没有关闭,为false说明管道已经关闭
if data, ok := <-c; ok {
fmt.Println(data)
} else {
break
}
}
fmt.Println("Finished")
}
|
–
13.4单向channel及应用
声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| var ch1 chan int // ch1是一个正常的channel,是双向的
var ch2 chan<- float64 // ch2是单向channel,只用于写float64数据
var ch3 <-chan int // ch3是单向channel,只用于读int数据
//可以将 channel 隐式转换为单向队列,只收或只发,不能将单向 channel 转换为普通 channel:
c := make(chan int, 3)
var send chan<- int = c // send-only
var recv <-chan int = c // receive-only
send <- 1
//<-send //invalid operation: <-send (receive from send-only type chan<- int)
<-recv
//recv <- 2 //invalid operation: recv <- 2 (send to receive-only type <-chan int)
//不能将单向 channel 转换为普通 channel
d1 := (chan int)(send) //cannot convert send (type chan<- int) to type chan int
d2 := (chan int)(recv) //cannot convert recv (type <-chan int) to type chan int
|
14.封装
–
1
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| package model
import(
"fmt"
)
type person struct{
Name string
age int //其他包不能访问
sal float64 //其他包不能访问
}
//写一个工程模式的函数,相对于一个构造函数
func NewPerson(name string) *person{
return &person{
Name : name
}
}
//为了访问age和sal我们编写一对SetXxx的方法和GetXxx的方法
func (p *person) SetAge(age int) {
if age > 0 && age < 150 {
p.age = age
}else{
fmt.Println("年龄范围不正确")
}
}
func (p *person) GetAge() int {
return p.age
}
func (p *person) SetSal(sal float64) {
if sal >= 3000 && sal <= 30000 {
p.sal = sal
}else{
fmt.Println("薪水范围不正确")
}
}
func (p *person) GetSal() float64 {
return p.sal
}
|
15.Select
–
1
2
3
4
5
6
7
8
| select {
case <- chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
|
16. GO Modules
–
GO111MODULE
Go语言提供了 GO111MODULE这个环境变量来作为 Go modules 的开关,其允许设置以下参数:
● auto:只要项目包含了 go.mod 文件的话启用 Go modules,目前在 Go1.11 至 Go1.14 中仍然是默认值。
● on:启用 Go modules,推荐设置,将会是未来版本中的默认值。
● off:禁用 Go modules,不推荐设置。
1
| $ go env -w GO111MODULE=on
|
–
GOPROXY
这个环境变量主要是用于设置 Go 模块代理(Go module proxy),其作用是用于使 Go 在后续拉取模块版本时直接通过镜像站点来快速拉取。
GOPROXY 的默认值是:https://proxy.golang.org,direct
proxy.golang.org国内访问不了,需要设置国内的代理.
● 阿里云
https://mirrors.aliyun.com/goproxy/
● 七牛云
https://goproxy.cn,direct
1
2
| $ go env -w GOPROXY=https://goproxy.cn,direct
//假设你不想使用,也可以将其设置为 “off” ,这将会禁止 Go 在后续操作中使用任何 Go 模块代理。
|
–
GONOPROXY/GONOSUMDB/GOPRIVATE
这三个环境变量都是用在当前项目依赖了私有模块,例如像是你公司的私有 git 仓库,又或是 github 中的私有库,都是属于私有模块,都是要进行设置的,否则会拉取失败。
更细致来讲,就是依赖了由 GOPROXY 指定的 Go 模块代理或由 GOSUMDB 指定 Go checksum database 都无法访问到的模块时的场景。
而一般建议直接设置 GOPRIVATE,它的值将作为 GONOPROXY 和 GONOSUMDB 的默认值,所以建议的最佳姿势是直接使用 GOPRIVATE。
并且它们的值都是一个以英文逗号 “,” 分割的模块路径前缀,也就是可以设置多个,例如:
1
2
3
4
5
6
7
8
9
| $ go env -w GOPRIVATE="git.example.com,github.com/eddycjy/mquote"
设置后,前缀为 git.xxx.com 和 github.com/eddycjy/mquote 的模块都会被认为是私有模块。
如果不想每次都重新设置,我们也可以利用通配符,例如:
$ go env -w GOPRIVATE="*.example.com"
这样子设置的话,所有模块路径为 example.com 的子域名(例如:git.example.com)都将不经过 Go module proxy 和 Go checksum database,需要注意的是不包括 example.com 本身。
|
–
使用Go Modules初始化项目
- 开启 go modules
1
| $ go env -w GO111MODULE=on
|
- 初始化项目
1
2
3
4
5
6
7
8
| //创建项目目录
$ mkdir -p $HOME/aceld/modules_test
$ cd $HOME/aceld/modules_test
//执行Go modules 初始化
//go mod init [myproject]
$ go mod init github.com/aceld/modules_test
go: creating new go.mod: module github.com/aceld/modules_test
//如果你的项目位于 GitHub 上的 github.com/yourusername/myproject 代码库中,你可以将模块名称设置为 github.com/yourusername/myproject,以确保模块名称在全局范围内都是唯一的。这样,其他人在使用你的项目作为依赖项时,就可以通过该模块名称来引用它。
|
初始化后,会:
创建一个新的 go.mod 文件,该文件位于你的项目根目录中。这个文件将用于管理项目的依赖关系。
在 go.mod 文件中指定项目的模块名称为 myproject。
设置 Go 版本,它是当前项目所使用的 Go 语言版本。
- 依赖管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| //添加一个依赖
go get github.com/gin-gonic/gin
//这将下载依赖项并将其添加到你的go.mod文件中
//更新依赖
go get -u github.com/gin-gonic/gin
//移除依赖
go get -u -d github.com/gin-gonic/gin@none
//清理未使用的依赖
go mod tidy
//下载依赖
go mod download
|
在 Go Modules 模式下,项目的依赖项通常是保存在项目文件夹外部的。依赖项会被下载并保存在模块缓存目录中,而不是直接存储在项目文件夹内。
tips
1.用切片操作string性能最佳
2.正则表达式
1
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
30
31
32
33
34
|
//正则表达式包
import (
"regexp"
)
//编译正则表达式
pattern := "ab+c"
regex, err := regexp.Compile(pattern)
if err != nil {
// 处理编译错误
}
//匹配文本
text := "abbc"
if regex.MatchString(text) {
fmt.Println("文本与模式匹配")
} else {
fmt.Println("文本与模式不匹配")
}
//提取匹配内容
text := "abbc"
matched := regex.FindString(text)
fmt.Println("匹配的文本:", matched)
//匹配多次
text := "abbcabccab"
matches := regex.FindAllString(text, -1)
fmt.Println("所有匹配的文本:", matches)
//替换匹配文本
text := "abbcabccab"
replacement := "X"
newText := regex.ReplaceAllString(text, replacement)
fmt.Println("替换后的文本:", newText)
|