函数、闭包与指针
函数
语法格式:
func 函数名(参数列表)(返回参数列表){
//函数体
}
- (1) go语言中的函数支持可变参数,此函数的参数数量是不定的
arg …int告诉GO这个函数接收不定数量的参数,这些参数类型全是int,变量arg是一个int的slice
func myfunc(arg ...int){ }
+ (2) GO语言的函数可以返回多个值
+ (3) 返回参数列表可以是数据的数据类型,或者是:变量名+变量类型的组合
func Test() (a int,b uint){}
func Test() (a,b uint){}
(4) 若只有一个返回值且不声明返回值变量,那么可以省略()
func Test() int{}
函数变量(函数作为值)
- 在GO语言中函数也是一种类型,可以保存在变量中
- 可以通过type来定义一个自定义类型。函数的参数完全相同(包括:参数类型,个数,顺序),函数返回值相同
1. 定义一个函数类型
2. 创建这个类型的函数
3. 作为参数调用
下例: 函数被当成参数传递到另一个函数中
func getString(str string) string {
return str + "666"
}
//第二参数为函数(要标明返回值类型和参数)
func Test02(str string, myfunc func(string) string) string {
return myfunc(str)
}
func main() {
str := "zzx"
fmt.Println(Test02(str, getString))
}
下例:使用type定义一个函数的类型,被传参时函数类型就可以写出自己定义的类型
type getFunc func(string) string
func getString(str string) string {
return str + "666"
}
func Test02(str string, myfunc getFunc) string {
return myfunc(str)
}
func main() {
str := "zzx"
fmt.Println(Test02(str, getString))
}
匿名函数
- 匿名函数没有函数名,只有函数体,函数可以作为一种类型被赋值给变量,匿名函数往往以变量方式被传递
- 匿名函数经常被用于实现回调函数、闭包等
1. 下例中:在定义时调用匿名函数(没有函数名最后的(100)是实参)
func main() {
func(i int) {
fmt.Println(i)
}(100)
}
2. 下例:将匿名函数赋值给变量(这个变量是函数类型)
func main() {
f := func(str string) {
fmt.Println(str)
}
f("世界你好!")
}
3. 作为回调函数
type genFunc func(float64) string
func main() {
arr := []float64{1, 4, 9, 16, 25, 30}
//进行求平方根操作
result := getgenSlice(arr, func(val float64) string {
val = math.Sqrt(val)
return fmt.Sprintf("%.2f", val)
})
fmt.Print(result)
}
//遍历切片,对其中每个值进行运算处理
func getgenSlice(arr []float64, f genFunc) []string {
var result []string
for _, value := range arr {
result = append(result, f(value))
}
return result
}
闭包
- 闭包是由函数和与其相关的引用环境组合而成的实体
- 闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例
一个编程语言需要有一下特性来支持闭包: - 函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值
- 在一个函数内部可以定义另一个函数
- 允许定义匿名函数
- 可以捕获引用环境,并把引用环境和函数代码组成一个可调用的实体
- 闭包函数的返回值一定是一个函数类型的
- 闭包函数可以不写函数名,闭包函数里面要有匿名函数
下例使用闭包实现计数器: (adder函数内的sum不清零,main函数中变量res是一直存在的adder中的sum也是一直存在的,所以没有被当做垃圾回收)
示例1:
func main() {
res := adder()
fmt.Printf("%T \n", res)
for i := 0; i < 5; i++ {
fmt.Printf("i=%d \t", i)
fmt.Println(res(i))
}
}
//实现计数器的闭包函数
func adder() func(int) int {
sum := 0
result := func(num int) int {
sum += num
return sum
}
return result
}
输出结果:
func(int) int
i=0 0
i=1 1
i=2 3
i=3 6
i=4 10
示例2:
func main() {
res := Counter()
fmt.Printf("%T\n", res)
fmt.Println("res:", res)
fmt.Println("res():", res())
fmt.Println("res():", res())
fmt.Println("res():", res())
res2 := Counter()
fmt.Printf("%T\n", res2)
fmt.Println("res2:", res2)
fmt.Println("res2():", res2())
fmt.Println("res2():", res2())
fmt.Println("res2():", res2())
}
//闭包函数,实现计数器功能
func Counter() func() int {
i := 0
res := func() int {
i++
return i
}
fmt.Println("Countern内部:", res)
return res
}
输出结果为:
Countern内部: 0x48d7c0
func() int
res: 0x48d7c0
res(): 1
res(): 2
res(): 3
Countern内部: 0x48d7c0
func() int
res2: 0x48d7c0
res2(): 1
res2(): 2
res2(): 3
示例3:
func main() {
res := func() func() int {
i := 10
return func() int {
i++
return i
}
}()
fmt.Println(res) //结果:0x48b640
fmt.Println(res()) //结果:11
}
可变参数
- 如果一个函数的参数,类型一致,但个数不定,可以使用函数的可变参数
- 语法格式
- (在一个变量之后加上三个点…表示从该处开始接受不定参数)
- (当要传递若干参数时,可以手动书写每个参数,也可以将一个slice传递给该函数注意要在后面加“…”)
- 一个函数中只能有一个可变参数
- 参数列表中若有其他参数,则可变参数写在所有参数的最后
func 函数名(参数名 ...类型)[(返回值列表)]{
函数体
}
func main() {
//传n个成绩
user, sum, avg, count := getScore("张子祥", 90.0, 45.5, 56.5, 77.0, 12.5)
fmt.Printf("学员姓名:%s共有%d门成绩,总成绩为%.2f,平均成绩为%.2f", user, count, sum, avg)
scores := []float64{90.0, 45.5, 56.5, 77.0, 12.5}
fmt.Println()
user1, sum1, avg1, count1 := getScore("张子祥", scores...)
fmt.Printf("学员姓名:%s共有%d门成绩,总成绩为%.2f,平均成绩为%.2f", user1, count1, sum1, avg1)
}
func getScore(name string, scores ...float64) (user string, sum, avg float64, count int) {
for _, value := range scores {
sum += value
count++
}
user = name
avg = sum / float64(count)
return
}
递归函数
- 使用递归函数要注意栈溢出,在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会增加一层栈,每当函数返回,栈就会减少一层,由于栈的大小不是无限的,所以递归调用的次数过多,会导致栈溢出
func main() {
fmt.Println(Factory(5))
}
func Factory(n int) int {
if n == 0 {
return 1
}
return n * Factory(n-1)
}
指针
- 指针是存储另一个变量的内存地址的变量
- 指针不参与运算
- 获取变量地址:
- ==取地址符&==,一个变量前使用&,会返回变量的内存地址
func main() {
a := 10
fmt.Println(&a) //0xc042056080
}
(一):声明指针
(1) 声明指针
- *用于指定变量是一个指针
- var ip *int //指向整型的指针
- var fp *float32 //指向浮点型的指针
- 获取原始数据*fp
(2):使用指针 (指针变量前加*表示原始数据,指针前加&还是指针)
func main() {
//实际变量
a := 120
//声明指针变量
var ip *int
//给指针变量赋值,将变量a的地址赋值给ip
ip = &a
fmt.Printf("&a的类型%T,值是%v\n", &a, &a) //&a的类型*int,值是0xc042056080
fmt.Printf("&a的类型%T,值是%v", ip, ip) //ip的类型*int,值是0xc042056080
fmt.Printf("*ip的类型%T,值是%v\n", *ip, *ip) //*ip的类型int,值是120
fmt.Printf("*&a的类型%T,值是%v\n", *&a, *&a) //*&a的类型int,值是120
fmt.Println(ip, *ip, &ip, *(&ip), &(*ip))
//0xc042056080 120 0xc042076018 0xc042056080 0xc042056080
}
(二):复合数据类型指针
func main() {
s1 := Student{"zzx", 23, 1, false}
var a = &s1
fmt.Printf("s1的类型为%T,值为%v\n", s1, s1)
//s1的类型为main.Student,值为{zzx 23 1 false}
fmt.Printf("*a的类型为%T,值为%v\n", *a, *a)
//*a的类型为main.Student,值为{zzx 23 1 false}
fmt.Printf("a的类型为%T,值为%v\n", a, a)
//a的类型为*main.Student,值为&{zzx 23 1 false}
}
(三):空指针
+ 当一个指针被定义后没有分配到任何变量时,它的值为nul
+ nil指针也称为空指针
+ 一个指针变量通常缩写为ptr
(1)空指针的判断:
+ if(ptr != nil) //ptr不是空指针
+ if(ptr == nil) //ptr是空指针
(四):指针作为函数参数
例:基本类型数据交换(不用这种写法)
func main() {
x, y := 10, 22
swap(&x, &y)
fmt.Println(x, y)
}
func swap(x, y *int) {
*x, *y = *y, *x
}
例:改变切片的值
func main() {
arr := []int{10, 20, 33, 44}
change(&arr)
fmt.Println(arr)
}
func change(arr *[]int) {
(*arr)[0] = 55
}
(五):指针数组
const COUNT int = 4
func main() {
a := [COUNT]string{"abc", "123", "ABC", "一二三"}
//数组指针(数组的指针)
fmt.Printf("数组指针类型:%T,值:%v\n", &a, &a) //数组指针类型:*[4]string,值:&[abc 123 ABC 一二三]
//定义指针数组ptr(指针类型的数组)
var ptr [COUNT]*string
fmt.Printf("指针数组类型:%T,值:%v\n", ptr, ptr) //指针数组类型:[4]*string,值:[<nil> <nil> <nil> <nil>]
for i := 0; i < COUNT; i++ {
//将数组中每个元素的地址赋值给指针数组的每个元素
ptr[i] = &a[i]
}
fmt.Printf("%T,%v\n", ptr, ptr) //[4]*string,[0xc04203e0c0 0xc04203e0d0 0xc04203e0e0 0xc04203e0f0]
//根据指针数组元素的每个地址获取该地址所指向的元素的真实数值
for i := 0; i < COUNT; i++ {
fmt.Println(*ptr[i]) // abc 123 ABC 一二三
}
for _, value := range ptr {
fmt.Println(*value) // abc 123 ABC 一二三
}
}
(六):指针的指针:
+ 如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指针的指针变量
+ 指向指针的指针变量值需要使用两个*号
1. var ptr **int
2. 获取原始值**ptr
案例代码:
func main() {
a := 10
var ptr *int
var pptr **int
//为指针赋值
ptr = &a
fmt.Println(ptr) //0xc042056080
//为pptr赋值
pptr = &ptr
fmt.Println(pptr) //0xc042076018
//获取指针指向的值
fmt.Printf("指向到指针的变量 **pptr:%d\n", **pptr) //指向到指针的变量 **pptr:10
}
函数的参数传递(值传递,引用传递)
(一):值传递(java里只有值传递)
+ 默认情况下,Go语言使用的是值传递,
+ 每次调用函数,都将实参拷贝一份在传递到函数中
(二):引用传递(go语言中传指针)
+ 概念:在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到原内容数据
+ GO语言中严格意义是没有引用传递的,但可以借助传指针到达引用传递的效果,函数参数使用指针参数,传参就是拷贝一份指针,也就是拷贝一份变量地址
示例代码:(从始至终a的内存地址没变)
func main() {
a := "abcd"
fmt.Printf("变量a的内存地址:%p,值为%v\n", &a, a)
changeStringVal(a)
fmt.Printf("调用changeStringVal后变量a的内存地址:%p,值为%v\n", &a, a)
changeStringPtr(&a)
fmt.Printf("调用changeStringPtr后变量a的内存地址:%p,值为%v\n", &a, a)
}
//值传递
func changeStringVal(a string) {
fmt.Printf("------changeStringVal函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
a = strings.ToUpper(a)
}
//传引用
func changeStringPtr(a *string) {
fmt.Printf("-----changeStringPtr函数内:指针参数a的内存地址:%p,值:%v \n", &a, a)
*a = strings.ToUpper(*a)
}
结果:
变量a的内存地址:0xc04204a1c0,值为abcd
------changeStringVal函数内:值参数a的内存地址:0xc04204a1e0,值为:abcd
调用changeStringVal后变量a的内存地址:0xc04204a1c0,值为abcd
-----changeStringPtr函数内:指针参数a的内存地址:0xc042076020,值:0xc04204a1c0
调用changeStringPtr后变量a的内存地址:0xc04204a1c0,值为ABCD
注意:
- go语言中所有的传参都是值传递,
(1):拷贝的内容有时候是非引用类型(int,string,bool,数组,struct属于非引用类型),这样就在函数中就无法修改原内容数据
(2):有的是引用类型(指针,slice,map,chan属于引用类型(传值和传指针效果一样可以修改原内容数据)),这样就可以修改原内容数据
+ 是否可以修改原内容数据,和传值、传引用没有必然关系,在go语言中虽然只有传值,但也可以修改原内容数据,因为参数是引用类型
代码实例:
func main() {
a := []int{4, 6, 7, 9}
fmt.Printf("变量a的内存地址:%p,值为%v\n", &a, a)
changeStringVal(a)
fmt.Printf("调用changeStringVal后变量a的内存地址:%p,值为%v\n", &a, a)
changeStringPtr(&a)
fmt.Printf("调用changeStringPtr后变量a的内存地址:%p,值为%v\n", &a, a)
}
//值传递
func changeStringVal(a []int) {
fmt.Printf("------changeStringVal函数内:值参数a的内存地址:%p,值为:%v \n", &a, a)
a[0] = 0
}
//传引用
func changeStringPtr(a *[]int) {
fmt.Printf("-----changeStringPtr函数内:指针参数a的内存地址:%p,值:%v \n", &a, a)
(*a)[1] = 0
}
输出结果:
变量a的内存地址:0xc0420503e0,值为[4 6 7 9]
------changeStringVal函数内:值参数a的内存地址:0xc042050440,值为:[4 6 7 9]
调用changeStringVal后变量a的内存地址:0xc0420503e0,值为[0 6 7 9]
-----changeStringPtr函数内:指针参数a的内存地址:0xc042076020,值:&[0 6 7 9]
调用changeStringPtr后变量a的内存地址:0xc0420503e0,值为[0 0 7 9]