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