Go 1.19.4 泛型、时间处理-Day 13

1. 泛型

1.1 基本介绍

Go 语言的泛型是在1.18版本中加入的一个特性,它允许开发者为函数、类型或接口定义通用的、可重用的代码,这些代码可以在不同的数据类型上运行而无需重复编写。

any可以表示所有数据类型,但是注意:少用,因为泛型不是泛滥。

1.2 泛型函数

1.2.1 没有泛型

在没有泛型时,同一种类型需要用重载overload完成,虽然Go没有重载,但是我们可以定义出来如下形式:

假要写一个整数加法函数,可以如下:

func add(x, y int) int {
	return x + y
}

在其他语言中,可以如下,实现重载:

func add(x, y int) int {
	return x + y
}

func add(x, y string) string {
	return x + y
}

但是在Go中,是不允许定义两个相同名称的函数的,那怎么办呢?可以如下:

func addInt(x, y int) int {
	return x + y
}

func addString(x, y string) string {
	return x + y
}

func addFloat(x, y float32) float32 {
	return x + y
}

但是上述代码又有一个问题,那就是函数签名,过于相似,且函数体体内部的代码,严重的重复了,那怎么办呢?可以使用下面这种形式:

// T,是某种数据类型,我希望它是可以变的
func add(a, b T) T {
    return a + b
}

T被称为 类型形参(type parameter) ,也就是说参数的类型也可以变, a, b T 说明a、b要类型一 致。

T最终一定会确定是某种类型,例如是int。

Go语言中,使用泛型的类型形参就可以解决上面的问题。

1.2.2 使用泛型

package main

import "fmt"


func addString(x, y string) string {
	return x + y
}


// [T int | string | float64],是对类型形参的约束,称为类型约束。同时也叫“类型参数列表”
// 有了类型参数列表的函数,就被称为“泛型函数”
func add[T int | string | float64](x, y T) T {
	return x + y
}

func main() {
	// 该方式等价于addString("a", "bc")
	fmt.Println(add("a", "bc")) // 也等价于add[int]("a", "bc"),但go会自动推断,所以[int]可以省略
	fmt.Println(addString("a", "bc"))
}
===========调试结果===========
abc
abc

1.3 类型约束

类型约束是一个接口。为了支持泛型,Go 1.18对接口语法进行了扩展。

用在泛型中,接口含义是符合这些特征的类型的集合。

Go内置了2个约束:

  • any 表示任意类型(interface{})
  • comparable 表示类型的值,应该可以使用==和!=进行比较
[T int] 等价于 [T interface{int}],表示T只能是int类型

type Constraint1 interface {
    int|string
}

[T int|string] 、[T interface{int|string}] 、[T Constraint1]三者等价,表示类型只
能是int或string类型

1.4 自定义泛型类型

1.4.1 map结合泛型

map没有办法直接结合泛型,因为语法不支持,所以只能自定义类型。

1.4.1.1 自定义泛型类型
type MyMap[V int|string] map[string]V

// 如果k与v都定义为泛型,那么推荐v为any,因为map中k是用来定位的,v不管是什么数据类型都可以
type MyMap[K string | int, V any] map[K]V
1.4.1.2 泛型类型实例化
简单版
package main

import "fmt"

// type MyMap[k int | string, v any] map[k]v
// p[k int | string]: 类型参数列表
type MyMap[k int | string] map[k]any // 如果允许value为any,那么类型参数列表中的any可以省略

func main() {
	var d = MyMap[int]{} // MyMap[int]等价于MyMap[int, any]
	d[100] = 1
	d[200] = "二"

	fmt.Println(d)
}
===========调试结果===========
map[100:1 200:二]
复杂版

any太泛了,我需要v必须实现某接口类型。

如果我需要限制value的数据类型,可以如下:

package main

import "fmt"

type Runner interface {
	run()
}

type MyMap[k int | string, v Runner] map[k]v

type MyStr string // 新类型定义

func (s MyStr) run() {}

func main() {
	var d = MyMap[int, MyStr]{}

	d[100] = "111"
	d[200] = "222"
	fmt.Println(d)
}
===========调试结果===========
map[100:111 200:222]

2. 时间

2.1 基本介绍

在Go语言中,时间定义为Time结构体。

package main

import (
	"fmt"
	"time"
)

func main() {
	var t = time.Now() // 取当前时区的当前时间
	fmt.Printf("t的类型=%T|t的值=%[1]v\n", t)

	fmt.Println(t)       // 普通时间
	fmt.Println(t.UTC()) // UTC时间
}
========调试结果========
t的类型=time.Time|t的值=2024-08-19 13:17:58.3375689 +0800 CST m=+0.004153401
2024-08-19 13:17:58.3375689 +0800 CST m=+0.004153401
2024-08-19 05:17:58.3375689 +0000 UTC
  • CST:中国标准时间(+0800)
  • UTC+8:中国北京时间
  • UTC:世界协调时(+0000),它是一种全球统一的时间标准,也是最准确的时间,不依赖硬件或者软件。
  • m=+0.004153401:为单调时间,利用的是晶体振荡器的间隔时间,很多时间函数计算都舍弃了它。如果 不是非常精准的时间间隔计算,请忽略它。

2.2 时间格式化

时间格式化表达式,参考该格式:

var t = time.Now()
fmt.Printf("t的类型=%T|t的值=%#[1]v\n", t)
====输出====
time.Date(2024, time.August, 19, 13, 51, 49, 322589500, time.Local)
time.Date(年, 月, 日, 时, 分, 秒, 纳秒, 本地时区)

固定格式化字符(GO的出生时间):2006-01-02 15:04:05。格式化时,必须使用这个固定的字符串,否则报错。

Go中支持的格式符:

官网:https://golang.google.cn/pkg/time/

Year(年): "2006" "06"
Month(月): "Jan" "January" "01" "1"
Day of the week(星期几): "Mon" "Monday"
Day of the month(每月的哪一天): "2" "_2" "02"
Day of the year(一年中的一天): "__2" "002"
Hour(时): "15" "3" "03" (PM or AM)
Minute(分): "4" "04"
Second(秒): "5" "05"
AM/PM mark: "PM"

package main

import (
	"fmt"
	"time"
)

func main() {
	var t = time.Now()
	// time.Date(2024, time.August, 19, 13, 51, 49, 322589500, time.Local)
	// time.Date(年, 月, 日, 时, 分, 秒, 纳秒, 本地时区)
	fmt.Println(t.Format("06"))
	fmt.Println(t.Format("2006"))

    // 不带时区(-为普通字符,可以自定义)
    fmt.Println(t.Format("2006-01-02 15:04:05"))

    // 带时区(/为普通字符,可以自定义)。使用时,最好带上时区
    fmt.Println(t.Format("2006/01/02 15:04:05 -0700"))

    // t.UTC,替换默认时区为世界协调时。t(time.Now())不接时区,默认是调用本地时区。
    fmt.Println(t.UTC().Format("2006/01/02 15:04:05 -0700"))
}
========调试结果========
24
2024
2024-08-19 14:36:13
2024/08/19 14:36:13 +0800
2024/08/19 06:36:13 +0000

2.3 时间解析

上述代码,时间格式化后输出的只是字符串,如果想把时间进行运算,就必须解析。

格式为:time.Parse("格式化符", "时间字符串", "时区字符串")

time.Parse:将一个时间格式的字符串转换成一个时间对象,这样你就可以对时间进行操作,比如比较、计算时间差等。

注意:格式化字符串必须和时间字符串格式一模一样,错一个符号都不行。

如:

timeStr := "2024-08-19 14:36:13"

time.Parse("2006/01/02 15:04:05", timeStr),这里把-替换成了/,这种解析就会报错。

2.3.1 不带时区(不推荐)

不推荐这样使用,解析时不带时区,会解析出一个错误的时区出来。 

package main

import (
	"fmt"
	"time"
)

func main() {
	timeStr := "2024-08-19 14:36:13"

	// 注意:格式化字符"2006-01-02 15:04:05",格式必须和timeStr一样,否则解析报错
	t, err := time.Parse("2006-01-02 15:04:05", timeStr)
	if err != nil {
		fmt.Println("解析失败!", t)
	} else {
		fmt.Println("解析成功!", t)
	}
}
========调试结果========
解析成功! 2024-08-19 14:36:13 +0000 UTC // 由于没带时区解析,这里解析出来了一个错误的时区

2.3.2 带时区(推荐)

package main

import (
	"fmt"
	"time"
)

func main() {
	timeStr := "2024-08-19 14:36:13 +0800"

	t, err := time.Parse("2006-01-02 15:04:05 -0700", timeStr)
	if err != nil {
		fmt.Println("解析失败!", t)
	} else {
		fmt.Println("解析成功!", t)
	}
}
========调试结果========
解析成功! 2024-08-19 14:36:13 +0800 CST

2.4 时间成份

2.4.1 取年月日

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()

	fmt.Println(t.Date()) // 直接输出年月日

	fmt.Println(t.Year(), t.Month(), t.Day()) // 选择性输入年月日

	fmt.Println(t.Year(), int(t.Month()), t.Day()) // 把英文月转换成数字月
}
========调试结果========
2024 August 19
2024 August 19
2024 8 19

2.4.2 今天是今年的第多少天

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()

	fmt.Println("今天是今年的第", t.YearDay(), "天")
}
========调试结果========
今天是今年的第 232 天

2.4.3 取时分秒

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()

    // 时、分、秒、纳秒
	fmt.Println(t.Hour(), t.Minute(), t.Second(), t.Nanosecond())
}
========调试结果========
15 51 43 135581900

2.4.4 取周(今天是周几)

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()

	fmt.Println(t.Weekday(), int(t.Weekday()))
}
========调试结果========
Monday 1

2.4.5 今天是一年中的第几周

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()

	fmt.Println(t.ISOWeek())
}
========调试结果========
2024 34

2.5 时区处理

2.5.1 示例一:不指定时区

Parse默认加载UTC时区

package main

import (
	"fmt"
	"time"
)

func main() {
	timeStr := "2024-08-19 16:29:00"
	t1, err := time.Parse("2006-01-02 15:04:05", timeStr)
	if err != nil {
		fmt.Println("解析失败!", t1)
	} else {
        fmt.Println(t1)
		fmt.Println("解析成功!", t1.Location()) // t1自己的时区
		fmt.Println("解析成功!", t1.Local()) // 按照当前时区转换
	}

}
========调试结果========
2024-08-19 16:29:00 +0000 UTC
解析成功! UTC
解析成功! 2024-08-20 00:29:00 +0800 CST

2.5.2 示例二:指定时区

示例一,虽然时间对,但是Parse默认使用了UTC时区,且t1.Local最终转换时区后,导致时间不对,时区对,这种可以通过指定时区来解决。

time.ParseInLocation:使用指定时区。

package main

import (
	"fmt"
	"time"
)

func main() {
	// 构造时区
	//Shanghai是固定写法,区分大小写,写错了会报错
	loc, err := time.LoadLocation("Asia/Shanghai") // 加载亚洲上海时区
	if err != nil {
		fmt.Println(err)
		panic(err)
	}

	timeStr := "2024-08-19 16:29:00"
	t1, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, loc)
	if err != nil {
		fmt.Println("解析失败!", t1)
	} else {
		fmt.Println(t1)
		fmt.Println("解析成功!", t1.Location())
		fmt.Println("解析成功!", t1.Local())
	}

}
========调试结果========
2024-08-19 16:29:00 +0800 CST
解析成功! Asia/Shanghai
解析成功! 2024-08-19 16:29:00 +0800 CST

2.6 时间运算

time + time = 错误,没这个概念
time - time = 时间增量或者时间差(时间-时间=不同数据类型)
time + 增量 = 未来的时间(time)
time - 增量 = 以前的时间(time)

2.6.1 时间差

time.Time类型提供了一个Sub方法,用于计算两个时间点之间的时间差,以纳秒为单位。

package main

import (
	"fmt"
	"time"
)

func main() {
	loc, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		fmt.Println(err)
		panic(err)
	}

	timeStr := "2024-08-19 16:29:00"
	t1, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, loc)
	if err != nil {
		fmt.Println("解析失败!", t1)
	} else {
		fmt.Println(t1)
		fmt.Println("解析成功!", t1.Location())
		fmt.Println("解析成功!", t1.Local())
		fmt.Println("==========================")
	}

	t2 := time.Now()
	delta := t2.Sub(t1)
	fmt.Println("t2和t1的时间差为:", delta)
	fmt.Println("t2和t1的时间差为:", delta.Hours())
	fmt.Println("t2和t1的时间差为:", delta.Minutes())
	fmt.Println("t2和t1的时间差为:", delta.Seconds())
}
========调试结果========
省略部分内容
===================
t2和t1的时间差为: 18h0m55.7297985s
t2和t1的时间差为: 18.015480499583333
t2和t1的时间差为: 64855.7297985
t2和t1的时间差为: 1080.928829975

2.6.2 持续时间

time.Duration 是 Go 语言中的一个内置类型,它用于表示时间的持续时间,单位是纳秒(ns)。

package main

import (
	"fmt"
	"time"
)

func main() {
	loc, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		fmt.Println(err)
		panic(err)
	}

	timeStr := "2024-08-19 16:29:00"
	t1, _ := time.ParseInLocation("2006-01-02 15:04:05", timeStr, loc)

	d1 := time.Duration(3) // 默认为3纳秒
	fmt.Println(d1)
	fmt.Println(time.Duration(3 * time.Second)) // 3秒
	fmt.Println(time.Duration(3 * time.Hour))   // 3小时
}
========调试结果======
3ns
3s
3h0m0s

2.6.3 时间偏移

time.Time 类型提供了一个 Add 方法,该方法允许你将一个 time.Duration 与一个time.Time 实例相加,从而得到一个新的 time.Time 实例,表示原始时间加上持续时间后的时间点(可正可负)。

2.6.4 时间先后

time.Time 类型提供了一个 After 方法,这个方法用于比较两个时间点,返回值为bool。

package main

import (
	"fmt"
	"time"
)

func main() {
	loc, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		fmt.Println(err)
		panic(err)
	}

	timeStr := "2024-08-19 16:29:00"
	t1, _ := time.ParseInLocation("2006-01-02 15:04:05", timeStr, loc)

	d1 := time.Duration(3)
	t2 := t1.Add(d1 * time.Second) // t1时间+3秒,赋值给t2

	fmt.Printf("t1的时间=%s\nt2的时间=%s\nt2的时间在t1之后吗?%v", t1, t2, t2.After(t1))
}
========调试结果======
t1的时间=2024-08-19 16:29:00 +0800 CST
t2的时间=2024-08-19 16:29:03 +0800 CST
t2的时间在t1之后吗?true

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值