【Golang】第五弹----函数

发布于:2025-03-15 ⋅ 阅读:(19) ⋅ 点赞:(0)

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

🔥 个人主页:星云爱编程

🔥 所属专栏:Golang

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

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

  

目录

一、函数

1.1基本介绍

1.2基本语法

1.3函数调用机制

1.4函数使用细节

二、常见函数

2.1 init函数

2.1.1基本介绍

2.1.2案例

2.1.3使用细节

2.2匿名函数

2.2.1基本介绍

2.2.2使用方式

2.2.3全局匿名函数

2.3字符串函数

2.3.1函数列举

2.3.2使用案例

2.4时间和日期函数

2.4.1基本介绍

2.4.2时间常量

2.4.3时间戳

2.4.4案例

2.5内置函数

三、闭包和函数的defer

3.1闭包

3.1.1基本介绍

3.1.2闭包的本质

3.1.3常用场景

3.1.4注意事项

3.2defer

3.2.1基本介绍

3.2.2使用案例

结语


一、函数

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)
}

说明:

(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(),二者区别如下

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

  • Sub 

 案例:计算目前到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:用来分配内存,主要用来分配引用类型

 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栈时,会将相关的值进行拷贝,即在入栈后对其相关的值进行修改不会影响

结语

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

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