目录

Golang第五弹-函数

【Golang】第五弹—-函数

https://i-blog.csdnimg.cn/direct/f2e74c5db86c49cfb3695c86429651c4.gif

笔上得来终觉浅,绝知此事要躬行

🔥 个人主页:

🔥 所属专栏:

🌷追光的人,终会万丈光芒

🎉欢迎大家点赞👍评论📝收藏⭐文章

https://i-blog.csdnimg.cn/direct/00c0893abe8c4f809d6ee73f8b913fdd.gif


一、函数

1.1基本介绍

为完成某一功能的程序指令(语句)的集合,称为函数。

在Go中函数分为:系统函数、自定义函数。

1.2基本语法

func 函数名称参数名 参数类型返回类型{
	执行语句
	return 返回值列表
}

说明:

有些函数可以没有return语句

1.3函数调用机制

先看下图代码,有个add函数,我们来分析下该函数是怎么执行的

func add(n1,n2 int)int{
	return n1+n2
}

func main(){
	n1:=10
	n2:=5
	sum:=add(n1,n2)
	fmt.Printf("sum=%d\n",sum)
}

https://i-blog.csdnimg.cn/direct/128a19958e6f44c2913465254b34b7bb.png

说明:

(1)当程序执行到函数时,就会开辟一个独立的空间,也就是栈空间;

(2)当函数执行完毕,或者执行到return语句时,就会返回

(3)返回到调用方法的地方

(4)返回后,继续执行方法后面的代码

(5)当main函数(栈)执行完毕,整个程序退出

1.4函数使用细节

  • 函数的形参可以有多个,返回值也可以有多个
  • 形参列表和返回值列表的数据类型可以是值,也可以是引用类型
  • 函数的命名遵循标识符命名规则,首字母不能是数字,首字母大写表示其他包可以使用(即首字母大写为public),首字母小写其他包不能使用(首字母小写为private)
  • 函数内的变量只能在函数内使用
  • 基本数据类型和数组默认是值传递,即进行值拷贝,在函数内修改,不会影响到原来的值
  • Go函数不支持函数重载
  • 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量数据类型就为函数类型,通过该变量可以对函数进行调用
  • 在Go中,因为函数是一种数据类型,所以函数可以作为形参,并且调用
  • Go支持自定义类型,使用type对函数取别名,目的是简化数据类型定义
  • 使用_标识符,忽略返回值

二、常见函数

2.1 init函数

2.1.1基本介绍

每一个源文件丢可以包含一个init函数,该函数会在main函数前被Go运行框架调用,即init函数在main函数前先执行,可以用来作初始化处理。

2.1.2案例

案例1:

func init(){
	fmt.Println("init()执行...")
}

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

说明:运行上面代码可以看到 init函数的确在main函数前执行

案例2:

var n int=test()

func init(){
	fmt.Println("init()执行...")
}

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

func test() int{
	fmt.Println("test()执行...")
	return 10
}

说明:这次可以看到执行顺序:test()>init()>main(),无论怎么变换他们三个的位置,结果都是一样的,则全局变量的声明在init()前执行

2.1.3使用细节

  • 如果一个文件同时包含全局变量定义、init函数和main函数,则执行的流程是:全局变量定义>init函数>main函数
  • init最主要的用法是完成初始化工作

2.2匿名函数

2.2.1基本介绍

Go支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用

2.2.2使用方式

  • 方式一:在定义匿名函数时直接调用
func main(){
	//使用匿名函数实现求和
	sum:=func (n1,n2 int) int{
		return n1+n2
	}(10,20);

	fmt.Printf("sum=%d",sum)
}
  • 方式二:将匿名函数赋给一个变量,在通过变量来调用匿名函数
	//匿名函数赋给变量
	f:=func (n1,n2 int) int{
		return n1-n2
	}
	//此时f为 函数类型变量,可以通过f调用函数
	num1:=f(100,10)
	num2:=f(6,3)

	fmt.Printf("num1=%d\n",num1)
	fmt.Printf("num2=%d\n",num2)

2.2.3全局匿名函数

将匿名函数赋给一个全局变量,那么这个匿名函数就成为了全局匿名函数,在程序中全局有效

//定义全局匿名函数
var(
	f2=func (n1,n2 int)int{
		return n1*n2
	}
)


func main(){
	//使用匿名函数实现求和
	sum:=func (n1,n2 int) int{
		return n1+n2
	}(10,20);

	fmt.Printf("sum=%d\n",sum)

	//匿名函数赋给变量
	f:=func (n1,n2 int) int{
		return n1-n2
	}
	//此时f为 函数类型变量,可以通过f调用函数
	num1:=f(100,10)
	num2:=f(6,3)
	num3:=f2(2,5)

	fmt.Printf("num1=%d\n",num1)
	fmt.Printf("num2=%d\n",num2)
	fmt.Printf("num3=%d\n",num3)

}

2.3字符串函数

在这里列举一些常用的字符串函数

2.3.1函数列举

  1. len:统计字符串的长度,按字节统计
  2. []rune(str):字符串遍历,同时处理中文字符,使其正常输出
  3. strconv.Atoi(str):字符串转整数
  4. strconv.Itoa(int):整数转字符串
  5. []byte(str):字符串转[]byte
  6. string([]byte):[]byte转字符串
  7. strconv.FormatInt():将整数转成指定进制的字符串
  8. strconv.ParseInt():字符串转任一进制的整数
  9. strings.Contains():查看子串是否在字符串中,返回bool值
  10. strings.Count():统计字符串中指定子串的数目
  11. strings.Index():返回子串在字符串中第一次出现的位置,没有则返回-1
  12. strings.Replace():将指定的子串替换为另一个子串
  13. strings.Split():按照指定的字符分割字符串,返回字符串数组
  14. strings.ToLower():将字符串的字母全替换为小写
  15. strings.ToUpper():将字符串的字母全替换为大写
  16. strings.TrimSpace():将字符串左右两边的空格去除
  17. strings.Trim():将字符串左右两边指定的字符去除
  18. strings.TrimLeft():将字符串左边指定的字符去除
  19. strings.TrimRight():将字符串右边指定的字符去除
  20. strings.HasPrefix():判断字符串是否以指定的字符串开头
  21. strings.HasSuffix():判断字符串是否以指定的字符串结尾

2.3.2使用案例

案例一:1~4函数使用

import (
	"fmt"
	"strconv"
)
//字符串函数使用
func main(){
	//(1)len:统计字符串的长度,按字节统计
	var str string="hello,world"
	n:=len(str)
	fmt.Printf("str的长度为:%d\n",n)//11
	//Go统一使用utf-8编码,一个英文字母1byte,一个中文3byte
	//(2)[]rune(str):字符串遍历,同时处理中文字符,使其正常输出
	str2:="hello,李星云"
	for i:=0;i<len(str2);i++{
		fmt.Printf("%c",str2[i])//有中文,直接输出会有乱码
	}
	fmt.Println("")
	str3:=[]rune(str2)
	for i:=0;i<len(str3);i++{
		fmt.Printf("%c",str3[i])//此时没有乱码
	}
	fmt.Println("")

	//(3)
	//strconv.Atoi(str):字符串转整数
    //strconv.Itoa(int):整数转字符串
	str4:="52354"
	num1,_:=strconv.Atoi(str4)
	fmt.Printf("str4的类型为%T,值为%v\n num1的类型为%T,值为%v\n",str4,str4,num1,num1)
    str5:=strconv.Itoa(num1)
	fmt.Printf("str5的类型为%T,值为%v\n num1的类型为%T,值为%v\n",str5,str5,num1,num1)
}

案例二:5~8

func main(){
	//(5)[]byte(str):字符串转[]byte
	str1:="hello,Go"
	var bytes =[]byte(str1)
	fmt.Printf("bytes的类型为%T,值为%v\n",bytes,bytes)
	//(6)string([]byte):[]byte转字符串
	str2:=string(bytes)
	fmt.Printf("str2的类型为%T,值为%v\n",str2,str2)

	//(7)strconv.FormatInt():
	//func FormatInt(i int64, base int) string
	//返回i的base进制的字符串表示。
	// base 必须在2到36之间,结果中会使用小写字母'a'到'z'表示大于10的数字
	str3:=strconv.FormatInt(4329,10)
	fmt.Printf("str3的类型为%T,值为%v\n",str3,str3)
	//(8)func ParseInt(s string, base int, bitSize int) (i int64, err error)
	//返回字符串表示的整数值,接受正负号。
	// base指定进制(2到36),如果base为0,则会从字符串前置判断,
	// "0x"是16进制,"0"是8进制,否则是10进制;
	// bitSize指定结果必须能无溢出赋值的整数类型,
	// 0、8、16、32、64 分别代表 int、int8、int16、int32、int64;
	// 返回的err是*NumErr类型的,如果语法有误,err.Error = ErrSyntax;
	// 如果结果超出类型范围err.Error = ErrRange。
	str4:="58023"
	n3,_:=strconv.ParseInt(str4,10,64)
	fmt.Printf("n3的类型为%T,值为%v\n",n3,n3)
}

案例三: 9~13

import (
	"fmt"
	"strings"
)

func main(){
	/*
	strings.Contains():查看子串是否在字符串中,返回bool值
	strings.Count():统计字符串中指定子串的数目
	strings.Index():返回子串在字符串中第一次出现的位置,没有则返回-1
	strings.Replace():将指定的子串替换为另一个子串
	strings.Split():按照指定的字符分割字符串,返回字符串数组	
	*/

	//(1)func Contains(s, substr string) bool
	//判断字符串s是否包含子串substr。
	str1:="hello123"
	res1:=strings.Contains(str1,"llo")
	res2:=strings.Contains(str1,"zbc")
	fmt.Printf("res1=%v\nres2=%v\n",res1,res2)
	//(2)func Count(s, sep string) int
	//返回字符串s中有几个不重复的sep子串。
	res3:=strings.Count(str1,"l")
	res4:=strings.Count("abcababdacababsadc","ab")
	fmt.Printf("res3=%v\nres4=%v\n",res3,res4)
	//(3)func Index(s, sep string) int
	//子串sep在字符串s中第一次出现的位置,不存在则返回-1。
	res5:=strings.Index(str1,"l")
	res6:=strings.Index("oioihahaha","ha")
	fmt.Printf("res5=%v\nres6=%v\n",res5,res6)
	//(4)func Replace(s, old, new string, n int) string
	//返回将s中前n个不重叠old子串都替换为new的新字符串,如果n<0会替换所有old子串。
	res7:=strings.Replace(str1,"l","ha",1)
	res8:=strings.Replace(str1,"h","xx",-1)
	fmt.Printf("res7=%v\nres8=%v\n",res7,res8)

	//(4)func Split(s, sep string) []string
	/*
	用去掉s中出现的sep的方式进行分割,会分割到结尾,
	并返回生成的所有片段组成的切片(每一个sep都会进行一次切割,
	即使两个sep相邻,也会进行两次切割)。
	如果sep为空字符,Split会将s切分成每一个unicode码值一个字符串。
	*/
	str6:="悟已往之不谏,知来者之可追,实迷途其未远,觉今是而昨非"
	res9:=strings.Split(str6,",")
	// str7:=[]rune(res9)
	fmt.Printf("RES9类型为%T\n",res9)
	for i:=0;i<len(res9);i++{
		fmt.Printf("res9[%v]=%v\n",i,res9[i])
	}
	
}

案例四:14~21

import (
	"fmt"
	"strings"
 )

 func main(){
	/*
	strings.ToLower():将字符串的字母全替换为小写
	strings.ToUpper():将字符串的字母全替换为大写
	strings.TrimSpace():将字符串左右两边的空格去除
	strings.Trim():将字符串左右两边指定的字符去除
	strings.TrimLeft():将字符串左边指定的字符去除
	strings.TrimRight():将字符串右边指定的字符去除
	strings.HasPrefix():判断字符串是否以指定的字符串开头
	strings.HasSuffix():判断字符串是否以指定的字符串结尾
	*/
	//(1)func ToLower(s string) string
	//返回将所有字母都转为对应的小写版本的拷贝。
	//(2)func ToUpper(s string) string
	//返回将所有字母都转为对应的大写版本的拷贝。
	str1:="AbCdEf"
	str2:=strings.ToLower(str1)
	str3:=strings.ToUpper(str1)
	fmt.Printf("str2=%v\nstr3=%v\n",str2,str3)

	//(3)func TrimSpace(s string) string
	//返回将s前后端所有空白(unicode.IsSpace指定)都去掉的字符串。
	str4:=" !Ab cdb nb ! "
	res1:=strings.TrimSpace(str4)
	//(4)func Trim(s string, cutset string) string
	//返回将s前后端所有cutset包含的utf-8码值都去掉的字符串。
	res2:=strings.Trim(str4,"! ")
	//(5)func TrimLeft(s string, cutset string) string
	//返回将s前端所有cutset包含的utf-8码值都去掉的字符串。
	//(6)func TrimRight(s string, cutset string) string
	//返回将s后端所有cutset包含的utf-8码值都去掉的字符串。
	res3:=strings.TrimLeft(str4," !")
	res4:=strings.TrimRight(str4," !")
	fmt.Printf("res1=%v\nres2=%v\n",res1,res2)
	fmt.Printf("res3=%v\nres4=%v\n",res3,res4)


	//(7)func HasPrefix(s, prefix string) bool
	//判断s是否有前缀字符串prefix。
	str5:="abaefgcd"
	res5:=strings.HasPrefix(str5,"ab")
	//(8)func HasSuffix(s, suffix string) bool
	//判断s是否有后缀字符串suffix。
	res6:=strings.HasSuffix(str5,"cde")

	fmt.Printf("res5=%v\nres6=%v\n",res5,res6)
}

2.4时间和日期函数

2.4.1基本介绍

  • 使用时间和日期函数,需要导入time包
  • 时间变量的类型为time.Time类型
  • 通过time.Now获到当前时间
  • 通过now可以获取独立的时间元素
  • 可以用Format对时间进行格式化输出
  • 可以使用time.Sleep进行程序休眠

2.4.2时间常量

Nanosecond      纳秒

Microsecond      微秒=1000纳秒

Millisecond         毫秒=1000微秒

Second               秒=1000毫秒

Minute                 分

Hour                    时

2.4.3时间戳

  • 时间戳(Timestamp)是用于标识特定时间点的数据,通常表示从某一固定时间点(如1970年1月1日00:00:00 UTC,即Unix纪元)到当前时间的秒数或毫秒数。
  • 时间戳的作用是可以用来获取随机数
  • 有两种方式获取时间戳,分别是Unix()和UnixNano(),二者区别如下

https://i-blog.csdnimg.cn/direct/65eca85538e24d0ca79f3a0dfc68e782.png

2.4.4使用时间戳获取随机数

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    // 设置随机数种子,确保每次运行生成的随机数不同
    rand.Seed(time.Now().UnixNano())

    // 生成一个0~99的随机整数
    num1 := rand.Intn(100)
    fmt.Println("随机整数:", num1)

    // 生成一个0.0~1.0的随机浮点数
    num2 := rand.Float64()
    fmt.Println("随机浮点数:", num2)
}

2.4.5两个具体时间运算

相关函数:

  • Date

https://i-blog.csdnimg.cn/direct/d67ec2ebd2e948b59d5821e67537c401.png

  • Sub

https://i-blog.csdnimg.cn/direct/43392fc7a5cf4681a832cfba6613b54f.png 案例:计算目前到2025/1/1的天数

package main

import(
	"fmt"
	"time"
)

func main(){
	now:=time.Now()
	//func Date(year int, month Month, day, hour, 
	// min, sec, nsec int, loc *Location) Time
	date:=time.Date(now.Year(),now.Month(),now.Day(),0,0,0,0,time.UTC)
	baseDate:=time.Date(2025,1,1,0,0,0,0,time.UTC)
	//计算天数差
	//func (t Time) Sub(u Time) Duration
	days:=int(date.Sub(baseDate).Hours()/24)

	fmt.Printf("天数差为%d\n",days)
}

2.4.6案例

案例一:

import (
	"fmt"
	"time"
)

func main(){
	now:=time.Now()  //获取当前时间
	fmt.Printf("now为%T类型,值为%v\n",now,now)

	//通过now可以获取到具体时间
	fmt.Printf("年=%v\n",now.Year())
	fmt.Printf("月=%v\n",now.Month())
	fmt.Printf("日=%v\n",now.Day())
	fmt.Printf("时=%v\n",now.Hour())
	fmt.Printf("分=%v\n",now.Minute())
	fmt.Printf("秒=%v\n",now.Second())
}

案例二:格式化输出时间

func main(){
	now:=time.Now()  //获取当前时间
	fmt.Printf("now为%T类型,值为%v\n",now,now)
	//格式化时间日期
	//方式一:
	fmt.Printf("当前时间:%v年%v月%v日%v时%v分%v秒\n",now.Year(),
	int(now.Month()),now.Day(),now.Hour(),now.Minute(),now.Second())
	//方式二:
	fmt.Printf(now.Format("2006/01/02  15:04:05"))
	//说明:"2006/01/02  15:04:05"数字是固定的,不过其结构可以自定义
}

案例三:休眠sleep的使用

func main(){
	now:=time.Now()  //获取当前时间
	fmt.Printf(now.Format("15:04:05\n"))
	time.Sleep(1000*time.Millisecond)//休眠1秒
	now2:=time.Now()
	fmt.Printf(now2.Format("15:04:05"))

}

案例四:时间戳使用

func main(){
	now:=time.Now()  //获取当前时间
	fmt.Println(now.Unix())//Unix时间戳
	fmt.Println(now.UnixNano())//UnixNano时间戳
}

2.5内置函数

  • len:用来求长度,例如string 、array、slice、map、channel
  • new:用来分配内存,主要用来分配值类型
  • make:用来分配内存,主要用来分配引用类型

https://i-blog.csdnimg.cn/direct/76711ba746704aa785693dff6e24169d.png new使用案例

func main(){
	n:=new(int)
	fmt.Printf("n的类型为%T,值为%v,地址为%v\n",n,n,&n)
}

三、闭包和函数的defer

3.1闭包

3.1.1基本介绍

  • 闭包就是一个函数与其相关的引用环境组成的一个整体
  • 在Go中,闭包通常通过匿名函数实现
//计数器
func counter()func()int{
	count:=0//被闭包捕获的变量
	return func()int{
		count++
		return count
	}
}

func main(){
	n:=counter()
	fmt.Println(n())//1
	fmt.Println(n())//2 闭包持有count的状态
}

说明:

  • counter()返回一个闭包,每次调用闭包时,count的会递增
  • count变量的生命周期被延长,与闭包绑定

3.1.2闭包的本质

闭包捕获的是变量的引用,而不是值的拷贝。

  • 多个闭包引用同一变量时,会共享该变量
  • 循环中直接使用闭包可能导致意外行为

经典陷阱:

func main(){
	var funcs []func()
	for i:+0;i<3;i++{
		//错误写法:所有闭包共享变量i的引用!
		funcs=append(funcs,func(){
			fmt.Println(i)
		})
	}
	for _,f:=range funcs{
		f()//输出3 3 3
	}
}

解决方法:传递变量副本

for i := 0; i < 3; i++ {
    i := i // 创建局部变量i的副本
    funcs = append(funcs, func() { fmt.Println(i) }) // 正确:捕获副本
}
// 或通过参数传递
for i := 0; i < 3; i++ {
    funcs = append(funcs, func(x int) func() {
        return func() { fmt.Println(x) }
    }(i))
}

3.1.3常用场景

  • 延迟执行
func main() {
    x := 123
    defer func() { fmt.Println("x =", x) }() // 闭包捕获当前x的值
    x = 456
}
// 输出:x = 456(闭包捕获的是引用,最终值)
  • 生成器
func generateEvenNumbers() func() int {
    n := 0
    return func() int {
        n += 2
        return n
    }
}

func main() {
    nextEven := generateEvenNumbers()
    fmt.Println(nextEven()) // 2
    fmt.Println(nextEven()) // 4
}

3.1.4注意事项

  1. 变量逃逸:闭包捕获的变量会逃逸到堆上,影响内存分配。
  2. 性能:闭包略微增加运行时开销,但通常可忽略。
  3. 循环变量陷阱:在循环中谨慎处理闭包捕获的变量(如前文所述)。

3.2defer

3.2.1基本介绍

  • defer意为推迟、延缓,在函数中它的作用也是推迟指定语句的执行,
  • 在开发中,程序员需要经常创建资源(例如:数据库连接、文件句柄、锁等)为了函数在执行完毕后,及时的释放资源,Go的设计者提供defer延时机制

3.2.2使用案例

func sum(n1,n2 int)int{
	defer fmt.Printf("ok1~ n1=%v\n",n1)//defer
	defer fmt.Printf("ok2~ n2=%v\n",n2)//defer
	defer fmt.Printf("ok3~\n")//defer

	res:=n1+n2
	n1++
	n2+=5
	fmt.Printf("ok4~\n")
	return res
}

func main(){
	sum:=sum(10,5)
	fmt.Printf("sum=%v\n",sum)
}

说明:

  • 在语句前加上defer,该语句会被压入到独立的栈中(defer栈),当函数执行完毕后,再按先入后出的方式出defer栈,执行
  • 当语句入defer栈时,会将相关的值进行拷贝,即在入栈后对其相关的值进行修改不会影响

结语

感谢您的耐心阅读,希望这篇博客能够为您带来新的视角和启发。如果您觉得内容有价值,不妨动动手指,给个赞👍,让更多的朋友看到。同时,点击关注🔔,不错过我们的每一次精彩分享。若想随时回顾这些知识点,别忘了收藏⭐,让知识触手可及。您的支持是我们前进的动力,期待与您在下一次分享中相遇!

路漫漫其修远兮,吾将上下而求索。