Featured image of post golang基础学习

golang基础学习

golang基础语法学习(VScode+ubuntu22.04.3LTS)

Go语言学习



0.安装

  1. 下载对应版本的安装包

下载

  1. 解压安装包并安装

docs

sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.22.1.linux-amd64.tar.gz

  1. 配置环境变量
1
2
3
4

sudo vim ~/.bashrc
export PATH=$PATH:/usr/local/go/bin
source ~/.bashrc
  1. 验证安装
1
go version
  1. 一键安装
 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 指定变量类型

  • 声明后若不赋值,使用默认值0
 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 根据值自行判定变量类型

1
var v_name = value

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
1 2 4 8 16

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的状态中恢复并重新获得流程控制权。

  • recover只有在defer调用的函数中有效。

 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接收发送数据

  • 阻塞:由于某种原因数据没有到达,当前go程(线程)持续处于等待状态,直到条件满足,才解除阻塞。

  • 同步:在两个或多个go程(线程)间,保持数据内容一致性的机制。

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

  • select可以监听channel上的数据流动。

  • select的用法与switch语言非常类似,由select开始一个新的选择块,每个选择条件由case语句来描述。

  • 与switch语句相比,select有比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作,大致的结构如下:

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初始化项目

  1. 开启 go modules
1
$ go env -w GO111MODULE=on
  1. 初始化项目
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. 依赖管理
 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)
Licensed under CC BY-NC-SA 4.0
潇洒人间一键仙
使用 Hugo 构建
主题 StackJimmy 设计