• defer 方法
  • panic 和 recover 方法
  • time 包下的 日期函数 + 定时器
  • make 函数
  • 结构体方法

defer 方法:

被 defer 关键字标注的函数需要明确其 注册顺序 和 执行顺序。defer 注册要延迟执行的函数时,该函数的所有参数都已经确定其值

示例代码:

package main

import (
	"fmt"
)

func calc(index string, a, b int) int {
	ret := a + b
	fmt.Println(index, a, b, ret)
	return ret
}

func main() {
	x := 1
	y := 2
	defer calc("AA", x, calc("A", x, y))
	x = 10
	defer calc("BB", x, calc("B", x, y))
	y = 20

}

输出结果:

分析流程:

明确 注册顺序 和 执行顺序,注册顺序是指按先来后到顺序进行注册(队列思想,先进先出),而执行顺序是指后来的先执行(栈思想,后进先出)。

本示例的注册顺序:

1、defer calc(“AA”, x, calc(“A”, x, y))

    此时 x = 1  y= 2

    (1)calc(“A”, x, y) 打印 A 1 2 3 返回 3

    2、defer calc(“BB”, x, calc(“B”, x, y))

    此时 x = 10 y 2

    (2)calc(“B”, x, y) 打印 B 10 2 12 返回 12

执行顺序

    3、defer calc(“BB”, x, calc(“B”, x, y))

    此时 x = 10  calc(“B”, x, y) = 12  打印 BB 10 12 22 返回 22

    4、defer calc(“AA”, x, calc(“A”, x, y))

    此时 x = 1 calc(“A”, x, y) = 3 打印 AA 1 3 4 返回 7

panic 和 recover 方法

panic 和 recover 是用来进行错误处理的。panic 可以在任何地方引发,但 recover 只有在 defer 调用的函数中有效。

在 Java 中,处理异常可以使用 try-catch 代码块来实现,而 Go 中可以用 defer + recover 来实现监听异常进而抛出

示例代码:

package main

import (
	"fmt"
)

func fn1(a, b int) int {
	//处理异常
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("抛出异常:", err)
		}
	}()
	return a / b
}

func main() {

	if fn1(10, 0) != 0 {
		fmt.Println(fn1(10, 0))
	} else {
		fmt.Println("逻辑错误")
	}

	fmt.Println(fn1(10, 5))
	fmt.Println("继续执行")

}

输出结果:

而 panic 可以主动抛出异常

defer + panic + recover 三者联合使用构成业务逻辑的异常捕获

示例代码:

package main

import (
	"errors"
	"fmt"
)

// 读取文件的方法
func readFile(filename string) error { //返回值类型是 error
	if filename == "main.go" {
		return nil
	} else {
		return errors.New("读取文件失败")
	}
}

// 捕获 方法readFile 的异常
func myFunc() {
	//创建 匿名自执行方法 监听 异常
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("给管理员发邮件")
		} else {
			fmt.Println("一切正常")
		}
	}()

	err := readFile("xxx.go")
	if err != nil {
		//主动抛异常
		panic(err)
	}
}

func main() {

	myFunc()
	fmt.Println("程序继续执行~~")

}

输出结果:

Golang time 包以及日期函数

time包下的 Now() 方法可以获取当前系统时间对象,通过年月日时分秒方法获取对应数字,按要求打印格式

package main

import (
	"fmt"
	"time"
)

func main() {
	timeObj := time.Now()
	year := timeObj.Year()
	month := timeObj.Month()
	day := timeObj.Day()
	hour := timeObj.Hour()
	minute := timeObj.Minute()
	second := timeObj.Second()
        //%02d 中的 2 表示宽度,如果整数不够 2 列就补上 0 
	fmt.Printf("%d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second)

}

输出结果:

另外一种方式是通过 Format(“2006-01-02 03:04:05”) 的格式化方法实现

Go 的诞生时间是 2006年1月2日下午3点4分5秒,按照 2006 1 2 3 4 5 来快速记忆

示例代码:

package main

import (
	"fmt"
	"time"
)

func main() {
	timeObj := time.Now()
	fmt.Println(timeObj.Format("2006-01-02 03:04:05"))
}

输出结果:

其中 Format 中日期格式的小时位,写为 03 表示 12 小时制;写为 15 表示 24 小时制。

获取当前时间戳:time.Now().Unix() 可以获取时间戳,时间戳是自 1970 年 1 月 1 日 (08:00:00GMT)至当前时间的总毫秒数。

需要掌握:时间戳 <——>日期字符串 之间的转换方法

在实际开发过程中,我们在数据库中存入的时间数据就是时间戳的形式,返回给前端显示的数据格式是日期字符串的格式。

示例代码:

package main

import (
    "fmt"
    "time"
)

func main(){
    //获取当前时间的默认日期时间格式
    timeObj := time.Now()
    fmt.Println(timeObj)
    //默认日期时间格式 转成 格式化日期时间格式
    fmt.Println(timeObj.Format("2006-01-02 15:04:05"))
    //日期时间格式转成时间戳
    unixTime := timeObj.Unix()
    fmt.Println(unixTime)
    //时间戳转成 日期时间格式
    timeObj1 := time.Unix(int64(unixTime), 0)
    fmt.Println(timeObj1.Format("2006-01-02 15:04:05"))
    //日期字符串转换成时间戳
    var str = "2020-04-26 15:38:04" //这是我们想转换的日期字符串
    var tmp = "2006-01-02 15:04:05" //这是转换的模板参数
    timeObj2, _  := time.ParseInLocation(tmp, str, time.Local)
    unixTime1 := timeObj2.Unix()
    fmt.Println(unixTime1)
}

输出结果:

解析一下 time.Unix( ) 方法的参数设置,底层源码如下:

func Unix(sec int64, nsec int64) Time {
	if nsec < 0 || nsec >= 1e9 {
		n := nsec / 1e9
		sec += n
		nsec -= n * 1e9
		if nsec < 0 {
			nsec += 1e9
			sec--
		}
	}
	return unixTime(sec, int32(nsec))
}

第一个参数 sec 代表毫秒的时间戳,第二个参数 nsec 代表的纳秒的时间戳。

只需要转毫秒的时候,纳秒参数传入 0 ,反之亦然。

time.ParseInLocation(tmp, str, time.Local) 方法需要传入三个参数:模板参数 tmp,要转换的日期字符串参数 str ,*time.Location 的指针参数。返回的有两个结果,一个是 Time 类型的时间对象,一个是 error 类型的错误信息

定时器

方法一:采用 time.NewTicker(time.Second) 来定义一个 1 秒的定时器,开启一个协程,每秒执行一次,结束时要调用 time.Stop() 方法销毁 NewTicker

示例代码:

package main

import (
	"fmt"
	"time"
)

func main() {

	//定义一个 1 秒 的定时器
	ticker := time.NewTicker(time.Second)

	//每秒执行一次
	n := 0
	for i := range ticker.C {
		fmt.Println(i)
		n++
		if n > 5 {
			ticker.Stop()
			return
		}
	}
}

方法二,采用 time.Sleep(time.Second) 来执行延迟 1 秒的操作。

for {
    time.Sleep(time.Second)
    fmt.Println("我在定时执行任务")
}

make 函数

在对引用数据类型: make 函数可以给引用数据类型分配内存空间,且 make 函数只用于 slicemap 以及 channel 的内存创建,而且它返回的类型就是这三个类型本身,而不是它们的指针类型,因为这三种类型就是引用数据类型。我们在使用 slice、map 以及 channel的时候,都需要使用 make 进行初始化分配内存空间,然后才可以对这些数据类型进行操作。

示例代码:

package main

import (
	"fmt"
)

func main() {

	var userInfo map[string]string //这里声明map数据类型userInfo时没有分配内存空间,是会报错的。
	userInfo["name"] = "张三"

	fmt.Println(userInfo)

}

正确代码为:

package main

import (
	"fmt"
)

func main() {

	var userInfo = make(map[string]string) 
        //或者写成 userInfo := make(map[string]string)
	userInfo["name"] = "张三"

	fmt.Println(userInfo)

}

结构体方法和接收者

在 go 语言中,没有类的概念但是可以给类型(结构体、自定义类型)定义方法。所谓方法就是定义了接收者的函数。接收者的概念就类似于其它语言中的 this 或 self。

方法的定义格式如下:

func (接收者变量 接收者类型) 方法名(参数列表) (返回类型){
    函数体
}

接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是 self、this 之类的命名。例如,Person 类型的接收者变量应该命名为 p,Connector 类型的接收者变量应该命名为 c 等。

接收者类型:可以是指针类型也可以是非指针类型。非指针类型不能修改结构体对象原本的内容,而指针类型可以修改。

包的引入 go mod 命令的使用

创建一个文件夹,cmd 进入到该文件夹中,执行 go mod init 包名 初始化该项目,生成一个 go.mod 文件。

在该包中的 main 或其它 .go 文件中配置第三方包,需要登录 go 的仓库网站 https://pkg.go.dev/ 查找该包的引入方式,然后在 cmd 中执行 go mod tidy 引入该包,生成一个 go.sum 文件。

总结四点:

1、go mod init 文件夹的名字 进行初始化项目

2、配置第三方包

3、go mod tidy 下载依赖 下载当前项目缺少的依赖

4、运行项目

接口

Golang 中每个接口由数个方法组成,接口的定义格式如下:

type 接口名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
}

如果接口里面有方法的话,必须通过结构体或者自定义类型实现这个接口。具体实现方式就是该结构体变量含有接口类型中的所有方法,那么这个变量就实现了这个接口。

空接口

Golang 中的接口可以不定义任何方法,没有定义任何方法的接口就是空接口。空接口表示没有任何约束,因此任何类型变量都可以实现空接口。

空接口可以直接当作类型来使用,可以表示任意类型。

var a interface{}

上述声明变量 a 中,可以指代任何数据类型。之前声明变量时,比如 var b int 此时的变量 b 只能接收 int 类型的数据。

空接口的应用:

  • interface{} 空接口可以作为函数的参数,见例1
  • map 的值可以实现空接口 interface{} 类型,类似 Java 中的 Object,见例2
  • 切片的元素类型也可以实现空接口 interface{} ,见例3

例1:

func getInfo(c interface{}){

}

例2:

var m := make(map[string]interface{})
m["username"] = "张三"
m["age"] = 14
m["married] = true

例3

var s := make([]interface{}, 3, 3)
s[0] = "张三"
s[1] = 14
s[2] = true

接口类型断言

如果我们想要判断空接口中值的类型,那么可以使用类型断言,语法格式为:

x.(T)

其中:x 表示类型为 interface{} 的变量,T 表示断言 x 可能的类型。

示例代码:

func justifyType(x interface{}){
    if _, ok := x.(string); ok {
        fmt.Printf("x 是字符串")
    }else if _, ok := x.(int); ok{
        fmt.Printf("x 是int")
    }else{
        fmt.Printf("x 是bool")
    }
}

一般 .(type) 结合 if-else 语句或者 switch 语句来实现不同业务逻辑

示例代码:

func justifyType(x interface{}) {
    switch v, ok := x.(type) {
        case string:
            fmt.Printf("x is string, value is %v\n", v)
        case int:
            fmt.Printf("x is int, value is %v\n", v)
        default:
            fmt.Printf("unsupport type !")
    }
}

结构体值接收者和指针接收者实现接口的区别

func (p Phone) stop() { //结构体值接收者

}

func (p *Phone) start(){ //结构体指针接收者

}

结构体中的方法是值接收者,那么实例化后的结构体值类型和结构体指针类型都可以赋值给接口变量。

结构体中的方法是指针接收者,那么只能是结构体指针类型赋值给接口变量,而结构体值类型无法赋值给指针变量。

Categories:

Tags:

No responses yet

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注