函数详解、time包及日期函数
- 1、函数
- 1.1、函数定义
- 1.2、函数参数
- 1.3、函数返回值
- 1.4、函数类型与变量
- 1.5、函数作参数和返回值
- 1.6、匿名函数、函数递归和闭包
- 1.7、defer语句
- 1.8、panic和recover
- 2、time包以及日期函数
- 2.1、time.Now()获取当前时间
- 2.2、Format方法格式化输出日期字符串
- 2.3、获取当前时间戳
- 2.4、时间戳转换成日期字符串
- 2.5、日期字符串转时间戳
- 2.6、时间间隔和时间操作函数
- 2.7、定时器
1、函数
1.1、函数定义
函数是组织好的、可重复使用的、用于执行指定任务的代码块。本文介绍了Go语言中函数的相关内容。
Go语言中支持:函数、匿名函数和闭包。
Go语言中定义函数使用 func 关键字,具体格式如下:
定义两个函数sumFn和subFn,分别对两个整数进行求和与差值。
package mainimport "fmt"func sumFn(x int, y int) int {res := x + yreturn res
}func subFn(x int, y int) int {res := x - yreturn res
}func main() {res1 := sumFn(10, 2)res2 := subFn(5, 3)fmt.Println(res1, res2)
}
1.2、函数参数
类型简写,函数的参数中如果相邻变量的类型相同,则可以省略类型,例如:
package mainimport "fmt"func sumFn(x, y int) int {return x + y
}func main() {fmt.Println(sumFn(5, 10))
}
可变参数:可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加…来标识。
注意: 可变参数通常要作为函数的最后一个参数。
package mainimport "fmt"func sumFn(x ...int) {fmt.Printf("值: %v, 类型: %T\n", x, x)
}func main() {sumFn(1, 2, 3, 4, 5)
}
可以看到可变参数x的类型是切片,因此我们可以遍历切片计算出总和。
package mainimport "fmt"func sumFn(x ...int) int {fmt.Printf("值: %v, 类型: %T\n", x, x)sum := 0for _, v := range x {sum += v}return sum
}func main() {res := sumFn(1, 2, 3, 4, 5)fmt.Println(res)
}
固定参数搭配可变参数使用如下:
package mainimport "fmt"func sumFn(x int, y ...int) int {fmt.Printf("值: %v, 类型: %T\n", y, y)sum := xfor _, 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函数,既计算两数之和,也计算两数之差,返回两个返回值。
package mainimport "fmt"func clac(x, y int) (int, int) {sum := x + ysub := x - yreturn sum, sub
}func main() {a, b := clac(10, 2)fmt.Println(a, b)
}
返回值命名:函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。
package mainimport "fmt"func clac(x, y int) (sum, sub int) {sum = x + ysub = x - yreturn
}func main() {a, b := clac(8, 3)fmt.Println(a, b)
}
将之前我们写的选择排序和冒泡排序实现成一个函数:
package mainimport "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添加到字符串中返回。
package mainimport ("fmt""sort"
)func mapSort(map1 map[string]string) string {var arr []stringfor k, _ := range map1 {arr = append(arr, k)}sort.Strings(arr)var res stringfor _, 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类型的返回值。
package mainimport "fmt"type clac func(int, int) int
type myInt intfunc add(x, y int) int {return x + y
}func sub(x, y int) int {return x - y
}func main() {var c clacc = addfmt.Printf("值: %v, 类型: %T\n", c, c)fmt.Println(c(3, 6))f := subfmt.Printf("值: %v, 类型: %T\n", f, f)fmt.Println(f(10, 5))var a int = 10var b myInt = 20fmt.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、函数作参数和返回值
函数可以作为另一个函数的参数:
package mainimport "fmt"func add(x, y int) int {return x + y
}func sub(x, y int) int {return x - y
}type clacType func(int, int) intfunc 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。第三个计算我们传入了一个匿名函数,通俗来说就是没有名字的函数。
函数也可以作为返回值:
package mainimport "fmt"func add(x, y int) int {return x + y
}func sub(x, y int) int {return x - y
}type clacType func(int, int) intfunc do(op string) clacType {switch op {case "+":return addcase "-":return subcase "*":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、匿名函数、函数递归和闭包
匿名函数就是没有函数名的函数, 匿名函数的定义格式如下:
匿名函数因为没有函数名, 所以没办法像普通函数那样调用, 所以匿名函数需要保存到某个变量或者作为立即执行函数。
package mainimport "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
package mainimport "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、创建闭包的常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。
如下:
package mainimport "fmt"func adder1() func() int {var x = 10return func() int {return x + 1}
}func adder2() func(int) int {var x = 10return func(y int) int {x += yreturn 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的语句,最先被执行。
package mainimport "fmt"func main() {fmt.Println("开始")defer fmt.Println(1)defer fmt.Println(2)defer fmt.Println(3)fmt.Println("结束")
}
上面的中间三条打印语句被延迟执行了,并且执行的时候会逆序执行,有点像栈的结构特点。
package mainimport "fmt"func test() {fmt.Println("开始")defer func() {fmt.Println("aaa")fmt.Println("bbb")}()fmt.Println("结束")
}func main() {test()
}
defer的执行时机:
defer在命名返回值和匿名返回函数中的表现不同。
package mainimport "fmt"func f1() int {var x = 0defer 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)。
下面再来看一个例子,思考一下输出什么?
package mainimport "fmt"func f1() int {x := 5defer func() {x++}()return x
}func f2() (x int) {defer func() {x++}()return 5
}func f3() (y int) {x := 5defer 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)
再来看另一个案例:
package mainimport "fmt"func calc(index string, a, b int) int {ret := a + bfmt.Println(index, a, b, ret)return ret
}func main() {x := 1y := 2defer calc("AA", x, calc("A", x, y))x = 10defer calc("BB", x, calc("B", x, y))y = 20
}
1.8、panic和recover
Go 语言中目前是没有异常机制,但是可以使用panic/recover模式来处理错误。
package mainimport "fmt"func fn() {defer func() {err := recover()if err != nil {fmt.Println("err:", err)}}()panic("抛出一个异常")
}func main() {fn()
}
实现一个计算两数相除的函数,如果被除数为0,那么就会程序崩溃,这时候我们就可以使用defer + recover来处理,此时程序不会直接崩溃,会继续向后执行。
package mainimport "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的配合使用:
package mainimport ("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()获取当前时间
package mainimport ("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->秒
package mainimport ("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)。
timeObj := time.Now()
unixtime := timeObj.Unix()
fmt.Println("当前时间戳:", unixtime)unixNatime := timeObj.UnixNano()
fmt.Println("当前时间戳:", unixNatime)
Unix获取秒级别的时间戳,UnixNano获取纳秒级别的时间戳。
2.4、时间戳转换成日期字符串
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、日期字符串转时间戳
func time.ParseInLocation(layout string, value string, loc *time.Location) (time.Time, error)
这是该函数的声明,第一个参数表示格式化模板,第二个参数表示要格式化的字符串,第三个参数传入time.Local即可,该函数有两个返回值,第一个返回值就是一个时间对象,然后我们通过这个事件对象调用Unix函数即可获取时间戳。
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 包中定义的时间间隔类型的常量如下:
const (Nanosecond Duration = 1Microsecond = 1000 * NanosecondMillisecond = 1000 * MicrosecondSecond = 1000 * MillisecondMinute = 60 * SecondHour = 60 * Minute)
}
例如:time.Duration表示1纳秒,time.Second表示1秒。
上面的常量可以搭配时间操作函数来使用,例如Add函数的使用:
timeObj := time.Now()
fmt.Println(timeObj)
timeObj = timeObj.Add(time.Hour) // 增加一个小时
fmt.Println(timeObj)
2.7、定时器
1、使用time.NewTicker(时间间隔)来设置定时器。
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)实现定时器。
for i := 0; i < 5; i++ {fmt.Println("我正在执行定时任务...")time.Sleep(time.Second)
}