Golang——5、函数详解、time包及日期函数

函数详解、time包及日期函数

1、函数

1.1、函数定义

函数是组织好的、可重复使用的、用于执行指定任务的代码块。本文介绍了Go语言中函数的相关内容。
Go语言中支持:函数、匿名函数和闭包。
Go语言中定义函数使用 func 关键字,具体格式如下:

定义两个函数sumFn和subFn,分别对两个整数进行求和与差值。

go 复制代码
package main

import "fmt"

func sumFn(x int, y int) int {
	res := x + y
	return res
}

func subFn(x int, y int) int {
	res := x - y
	return res
}

func main() {
	res1 := sumFn(10, 2)
	res2 := subFn(5, 3)
	fmt.Println(res1, res2)
}

1.2、函数参数

类型简写,函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:

go 复制代码
package main

import "fmt"

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

func main() {
	fmt.Println(sumFn(5, 10))
}

可变参数:可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加...来标识。
注意: 可变参数通常要作为函数的最后一个参数。

go 复制代码
package main

import "fmt"

func sumFn(x ...int) {
	fmt.Printf("值: %v, 类型: %T\n", x, x)
}

func main() {
	sumFn(1, 2, 3, 4, 5)
}


可以看到可变参数x的类型是切片,因此我们可以遍历切片计算出总和。

go 复制代码
package main

import "fmt"

func sumFn(x ...int) int {
	fmt.Printf("值: %v, 类型: %T\n", x, x)
	sum := 0
	for _, v := range x {
		sum += v
	}
	return sum
}

func main() {
	res := sumFn(1, 2, 3, 4, 5)
	fmt.Println(res)
}

固定参数搭配可变参数使用如下:

go 复制代码
package main

import "fmt"

func sumFn(x int, y ...int) int {
	fmt.Printf("值: %v, 类型: %T\n", y, y)
	sum := x
	for _, v := range y {
		sum += v
	}
	return sum
}

func main() {
	res := sumFn(100, 1, 2, 3, 4)
	fmt.Println(res)
}

1.3、函数返回值

Go语言中通过return关键字向外输出返回值。
Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。

例如实现一个clac函数,既计算两数之和,也计算两数之差,返回两个返回值。

go 复制代码
package main

import "fmt"

func clac(x, y int) (int, int) {
	sum := x + y
	sub := x - y
	return sum, sub
}

func main() {
	a, b := clac(10, 2)
	fmt.Println(a, b)
}

返回值命名:函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。

go 复制代码
package main

import "fmt"

func clac(x, y int) (sum, sub int) {
	sum = x + y
	sub = x - y
	return
}

func main() {
	a, b := clac(8, 3)
	fmt.Println(a, b)
}

将之前我们写的选择排序和冒泡排序实现成一个函数:

go 复制代码
package main

import "fmt"

func SelectSort(arr []int) {
	for i := 0; i < len(arr); i++ {
		for j := i + 1; j < len(arr); j++ {
			if arr[i] > arr[j] {
				tmp := arr[i]
				arr[i] = arr[j]
				arr[j] = tmp
			}
		}
	}
}

func BubbleSort(arr []int) {
	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr)-i-1; j++ {
			if arr[j] < arr[j+1] {
				tmp := arr[j]
				arr[j] = arr[j+1]
				arr[j+1] = tmp
			}
		}
	}
}

func main() {
	var sliceA = []int{11, 3, 213, 45, 63, 0, 10}
	SelectSort(sliceA)
	fmt.Println(sliceA)
	BubbleSort(sliceA)
	fmt.Println(sliceA)
}

下面实现一个函数将map类型数据按照key进行排序,把key=>value添加到字符串中返回。

go 复制代码
package main

import (
	"fmt"
	"sort"
)

func mapSort(map1 map[string]string) string {
	var arr []string
	for k, _ := range map1 {
		arr = append(arr, k)
	}
	sort.Strings(arr)
	var res string
	for _, v := range arr {
		res += fmt.Sprintf("%v=>%v", v, map1[v])
	}
	return res
}

func main() {
	m1 := map[string]string{
		"username": "张三",
		"age":      "20",
		"sex":      "男",
		"height":   "180cm",
	}
	res := mapSort(m1)
	fmt.Println(res)
}

函数变量作用域:
全局变量:全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效 (全局作用域)
局部变量:局部变量是函数内部定义的变量,函数内定义的变量无法在该函数外使用 (局部作用域)


1.4、函数类型与变量

定义函数类型:我们可以使用type关键字来定义一个函数类型,具体格式如下:

上面语句定义了一个calculation类型,它是一种函数类型,这种函数接收两个int类型的参数并且返回一个int类型的返回值。

go 复制代码
package main

import "fmt"

type clac func(int, int) int
type myInt int

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

func sub(x, y int) int {
	return x - y
}

func main() {
	var c clac
	c = add
	fmt.Printf("值: %v, 类型: %T\n", c, c)
	fmt.Println(c(3, 6))

	f := sub
	fmt.Printf("值: %v, 类型: %T\n", f, f)
	fmt.Println(f(10, 5))

	var a int = 10
	var b myInt = 20
	fmt.Printf("a的类型: %T, b的类型: %T\n", a, b)
	fmt.Println(a + int(b))
}


可以看到c的类型为main.clac,而f的类型则是func(int, int) int。另外a和b的类型也是不同的,所以相加时需要进行强转。


1.5、函数作参数和返回值

函数可以作为另一个函数的参数:

go 复制代码
package main

import "fmt"

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

func sub(x, y int) int {
	return x - y
}

type clacType func(int, int) int

func clac(x, y int, cb clacType) int {
	return cb(x, y)
}

func main() {
	res1 := clac(10, 5, add)
	res2 := clac(10, 5, sub)
	res3 := clac(10, 5, func(x, y int) int {
		return x * y
	})
	fmt.Println(res1, res2, res3)
}

对于前两个计算,我们传入了已有的函数add和sub。第三个计算我们传入了一个匿名函数,通俗来说就是没有名字的函数。

函数也可以作为返回值:

go 复制代码
package main

import "fmt"

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

func sub(x, y int) int {
	return x - y
}

type clacType func(int, int) int

func do(op string) clacType {
	switch op {
	case "+":
		return add
	case "-":
		return sub
	case "*":
		return func(x, y int) int {
			return x * y
		}
	default:
		return nil
	}
}

func main() {
	f1 := do("+")
	f2 := do("*")
	fmt.Println(f1(3, 4), f2(3, 4))
}

1.6、匿名函数、函数递归和闭包

匿名函数就是没有函数名的函数, 匿名函数的定义格式如下:

匿名函数因为没有函数名, 所以没办法像普通函数那样调用, 所以匿名函数需要保存到某个变量或者作为立即执行函数。

go 复制代码
package main

import "fmt"

func main() {
	// 匿名自执行函数
	func() {
		fmt.Println("test...")
	}()

	var fn = func(x, y int) int {
		return x * y
	}
	fmt.Println(fn(3, 4))

	// 匿名自执行函数接收参数
	func(x, y int) {
		fmt.Println(x + y)
	}(3, 4)
}

函数内部可以再调用其他函数,也可以调用本函数,调用本函数就是函数递归。
比如,利用递归计算出1 + 2 + 3 + ... + 100

go 复制代码
package main

import "fmt"

func sum(x int) int {
	if x == 1 {
		return 1
	}
	return x + sum(x-1)
}

func main() {
	fmt.Println(sum(100))
}

全局变量的特点:常驻内存,污染全局。
局部变量的特点:不常住内存,不污染全局。
这里的污染全局就比如,在全局定义了一个变量a,在某个函数内容又定义了一个变量a,那么在访问的时候就会优先访问局部变量a。
闭包:可以让一个变量常驻内存且不污染全局。

闭包:
1、闭包是指有权访问另一个函数作用域中的变量的函数。
2、创建闭包的常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。

如下:

go 复制代码
package main

import "fmt"

func adder1() func() int {
	var x = 10
	return func() int {
		return x + 1
	}
}

func adder2() func(int) int {
	var x = 10
	return func(y int) int {
		x += y
		return x
	}
}

func main() {
	var fn1 = adder1()
	fmt.Println(fn1())
	fmt.Println(fn1())
	fmt.Println(fn1())

	fn2 := adder2()
	fmt.Println(fn2(10))
	fmt.Println(fn2(10))
	fmt.Println(fn2(10))
}


由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存。过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包。


1.7、defer语句

Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被defer的语句,最先被执行。

go 复制代码
package main

import "fmt"

func main() {
	fmt.Println("开始")
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println("结束")
}


上面的中间三条打印语句被延迟执行了,并且执行的时候会逆序执行,有点像栈的结构特点。

go 复制代码
package main

import "fmt"

func test() {
	fmt.Println("开始")
	defer func() {
		fmt.Println("aaa")
		fmt.Println("bbb")
	}()
	fmt.Println("结束")
}

func main() {
	test()
}

defer的执行时机:


defer在命名返回值和匿名返回函数中的表现不同。

go 复制代码
package main

import "fmt"

func f1() int {
	var x = 0
	defer func() {
		x++
	}()
	return x
}

func f2() (x int) {
	defer func() {
		x++
	}()
	return x
}

func main() {
	fmt.Println(f1())
	fmt.Println(f2())
}


f1()的分析:
​​返回值机制​​:匿名返回值时,return x 实际上是将x的值(此时为0)​​复制到一个临时返回值变量​​中。
​​defer的执行​​:defer在return之后执行,修改的是局部变量x(从0→1),但​​临时返回值变量不受影响​​。
​​结果​​:函数返回临时变量存储的值0。

f2()的分析:
​​返回值机制​​:命名返回值时,x既是局部变量又是返回值变量。return x 直接返回x变量本身(此时值为0)。
​​defer的执行​​:defer在return之后执行,直接修改返回值变量x(从0→1)。
​​结果​​:函数返回被修改后的 x(值为 1)。


下面再来看一个例子,思考一下输出什么?

go 复制代码
package main

import "fmt"

func f1() int {
	x := 5
	defer func() {
		x++
	}()
	return x
}

func f2() (x int) {
	defer func() {
		x++
	}()
	return 5
}

func f3() (y int) {
	x := 5
	defer func() {
		x++
	}()
	return x
}
func f4() (x int) {
	defer func(x int) {
		x++
	}(x) //defer注册要延迟执行的函数时该函数所有的参数都需要确定其值
	return 5
}

func main() {
	fmt.Println(f1())
	fmt.Println(f2())
	fmt.Println(f3())
	fmt.Println(f4())
}

f1()分析:
1、匿名返回值(未命名)
2、return x 将 x 的当前值 (5) 复制到​​临时返回值变量​​
3、defer 增加局部变量 x 的值 (5→6),但​​不影响临时返回值​​
4、返回临时变量的值 (5)

f2()分析:
1、命名返回值 x
2、return 5 等同于 x = 5
3、defer 直接修改返回值变量 x (5→6)
4、返回被修改后的x

f3()分析:
1、命名返回值 y
2、return x 等同于 y = x (将局部变量 x 的值 5 复制给 y)
3、defer 修改局部变量 x (5→6),但​​不影响返回值变量 y​​
4、返回 y (5)

f4()分析:
1、命名返回值 x
2、defer 接受参数时,​​参数值在注册时确定​​:
此时 x 还是初始零值 0(命名返回值初始化为0)
传入的 x 是值拷贝 (0)
3、return 5 将返回值变量 x 设为 5
4、defer 执行时,操作的是​​参数拷贝​​(0→1),不影响返回值变量 x
5、返回 x (5)


再来看另一个案例:

go 复制代码
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.8、panic和recover

Go 语言中目前是没有异常机制,但是可以使用panic/recover模式来处理错误。

go 复制代码
package main

import "fmt"

func fn() {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("err:", err)
		}
	}()
	panic("抛出一个异常")
}

func main() {
	fn()
}

实现一个计算两数相除的函数,如果被除数为0,那么就会程序崩溃,这时候我们就可以使用defer + recover来处理,此时程序不会直接崩溃,会继续向后执行。

go 复制代码
package main

import "fmt"

func div(x, y int) int {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("err:", err)
		}
	}()
	return x / y
}

func main() {
	fmt.Println(div(10, 0))
	fmt.Println(div(10, 2))
	fmt.Println(div(10, 5))
}

defer、panic、recover的配合使用:

go 复制代码
package main

import (
	"errors"
	"fmt"
)

func readFile(fileName string) error {
	if fileName == "main.go" {
		return nil
	} else {
		return errors.New("读取文件失败")
	}
}

func myFn() {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("err:", err)
		}
	}()
	err := readFile("xxx.go")
	if err != nil {
		panic(err)
	}
}

func main() {
	myFn()
	fmt.Println("继续执行...")
}

2、time包以及日期函数

时间和日期是我们编程中经常会用到的,在golang中time包提供了时间的显示和测量用的函数。

2.1、time.Now()获取当前时间

go 复制代码
package main

import (
	"fmt"
	"time"
)

func main() {
	timeObj := time.Now()
	fmt.Println(timeObj)
	year := timeObj.Year()
	month := timeObj.Month()
	day := timeObj.Day()
	hour := timeObj.Hour()
	minute := timeObj.Minute()
	second := timeObj.Second()
	fmt.Printf("%d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second)
}

2.2、Format方法格式化输出日期字符串

格式化的模板为Go的出生时间2006年1月2号15点04分。
2006->年
01->月
02->日
03->时, 03表示12小时制,15表示24小时制。
04->分
05->秒

go 复制代码
package main

import (
	"fmt"
	"time"
)

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

	str2 := timeObj.Format("2006-01-02 03:04:05")
	fmt.Println(str2)
}

2.3、获取当前时间戳

时间戳是自1970年1月1日(08:00:00GMT)至当前时间的总秒数。 它也被称为Unix时间戳(UnixTimestamp)。

go 复制代码
timeObj := time.Now()
unixtime := timeObj.Unix()
fmt.Println("当前时间戳:", unixtime)

unixNatime := timeObj.UnixNano()
fmt.Println("当前时间戳:", unixNatime)


Unix获取秒级别的时间戳,UnixNano获取纳秒级别的时间戳。


2.4、时间戳转换成日期字符串

go 复制代码
unixTime := 1748842817
timeObj := time.Unix(int64(unixTime), 0)
var str = timeObj.Format("2006-01-02 15:04:05")
fmt.Println(timeObj)
fmt.Println(str)


使用time.Unix函数用于将时间戳转换成一个时间对象,第一个参数是int64的秒级时间戳,第二个参数是int64的纳秒级时间戳。


2.5、日期字符串转时间戳

go 复制代码
func time.ParseInLocation(layout string, value string, loc *time.Location) (time.Time, error)

这是该函数的声明,第一个参数表示格式化模板,第二个参数表示要格式化的字符串,第三个参数传入time.Local即可,该函数有两个返回值,第一个返回值就是一个时间对象,然后我们通过这个事件对象调用Unix函数即可获取时间戳。

go 复制代码
var str = "2025-06-03 09:00:00"
var tmp = "2006-01-02 15:04:05"
timeObj, _ := time.ParseInLocation(tmp, str, time.Local)
fmt.Println(timeObj)
fmt.Println(timeObj.Unix())

2.6、时间间隔和时间操作函数

time.Duration是time包定义的一个类型,它代表两个时间点之间经过的时间,以纳秒为单位。time.Duration表示一段时间间隔,可表示的最长时间段大约290年。
time 包中定义的时间间隔类型的常量如下:

go 复制代码
const (
	Nanosecond Duration = 1
	Microsecond = 1000 * Nanosecond
	Millisecond = 1000 * Microsecond
	Second = 1000 * Millisecond
	Minute = 60 * Second
	Hour = 60 * Minute)
}

例如:time.Duration表示1纳秒,time.Second表示1秒。
上面的常量可以搭配时间操作函数来使用,例如Add函数的使用:

go 复制代码
timeObj := time.Now()
fmt.Println(timeObj)
timeObj = timeObj.Add(time.Hour) // 增加一个小时
fmt.Println(timeObj)

2.7、定时器

1、使用time.NewTicker(时间间隔)来设置定时器。

go 复制代码
ticker := time.NewTicker(time.Second)
n := 5
for t := range ticker.C {
	fmt.Println(t)
	n--
	if n == 0 {
		ticker.Stop()
		break
	}
}

2、time.Sleep(time.Second)实现定时器。

go 复制代码
for i := 0; i < 5; i++ {
	fmt.Println("我正在执行定时任务...")
	time.Sleep(time.Second)
}
相关推荐
晨曦5432109 分钟前
Python迭代器与生成器:高效数据处理指南
开发语言·python
昕冉13 分钟前
云上教室选座项目分析
vue.js·后端
程序小武20 分钟前
Python 运算符详解
后端
史迪仔011220 分钟前
爬虫入门:从基础到实战全攻略
开发语言·python
bobz96522 分钟前
正向代理与反向代理
后端
大巨头23 分钟前
C# 定义字典
后端
thatway198924 分钟前
AI系统-15国内AI芯片介绍
后端
百锦再1 小时前
# Vue + OpenLayers 完整项目开发指南
开发语言·前端·javascript·vue.js·python·ecmascript·tkinter
MX_93591 小时前
JSON基础知识
开发语言·javascript·json
MacroZheng1 小时前
IDEA官方中文文档正式发布,太全了!
java·后端·intellij idea