笔上得来终觉浅,绝知此事要躬行
🔥 个人主页:星云爱编程
🔥 所属专栏:Golang
🌷追光的人,终会万丈光芒
🎉欢迎大家点赞👍评论📝收藏⭐文章
![]()
目录
一、函数
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函数列举
- len:统计字符串的长度,按字节统计
- []rune(str):字符串遍历,同时处理中文字符,使其正常输出
- strconv.Atoi(str):字符串转整数
- strconv.Itoa(int):整数转字符串
- []byte(str):字符串转[]byte
- string([]byte):[]byte转字符串
- strconv.FormatInt():将整数转成指定进制的字符串
- strconv.ParseInt():字符串转任一进制的整数
- strings.Contains():查看子串是否在字符串中,返回bool值
- strings.Count():统计字符串中指定子串的数目
- strings.Index():返回子串在字符串中第一次出现的位置,没有则返回-1
- strings.Replace():将指定的子串替换为另一个子串
- strings.Split():按照指定的字符分割字符串,返回字符串数组
- strings.ToLower():将字符串的字母全替换为小写
- strings.ToUpper():将字符串的字母全替换为大写
- strings.TrimSpace():将字符串左右两边的空格去除
- strings.Trim():将字符串左右两边指定的字符去除
- strings.TrimLeft():将字符串左边指定的字符去除
- strings.TrimRight():将字符串右边指定的字符去除
- strings.HasPrefix():判断字符串是否以指定的字符串开头
- 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注意事项
- 变量逃逸:闭包捕获的变量会逃逸到堆上,影响内存分配。
- 性能:闭包略微增加运行时开销,但通常可忽略。
- 循环变量陷阱:在循环中谨慎处理闭包捕获的变量(如前文所述)。
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栈时,会将相关的值进行拷贝,即在入栈后对其相关的值进行修改不会影响
结语
感谢您的耐心阅读,希望这篇博客能够为您带来新的视角和启发。如果您觉得内容有价值,不妨动动手指,给个赞👍,让更多的朋友看到。同时,点击关注🔔,不错过我们的每一次精彩分享。若想随时回顾这些知识点,别忘了收藏⭐,让知识触手可及。您的支持是我们前进的动力,期待与您在下一次分享中相遇!
路漫漫其修远兮,吾将上下而求索。