前言
熬过了七夕,谢不杀之恩,赶紧写篇博客压压惊。
函数声明
函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。
func name(parameter-list) (result-list) {
body
}
当然函数名前面还可以加上结构体绑定,参考上一节 Go 语言系列教程(八) : 结构体深入解析
Tip: 如果函数返回一个无名变量或者 没有返回值,返回值列表的括号是可以省略的
如果一组形参或返回值有相同的类型,我们不必为每个形参都写出参数类型。下面2个声明是等价的
func f(i , j ,k int, s, t string)
func f(i int, j int,k int, s string, t string)
Go语言的值传递
- 思考Go语言中有没有引用传递??
答:没有
实参通过值传递的方式传递,因此函数的形参是实参的拷贝。对形参进行修改不会影响实参。但是,如果实参包括引用类型,如指针,slice(切片)、map、function、channel等类型,实参可能会由于函数的间接引用被修改
Tip:在Go语言中 不存在引用传递,要么传递值的副本要么从传递指针的副本
下面我们写个小案例来说明
i := 10
p := &i
i的内存地址为: 0xc042060080,i的指针的内存地址为 0xc042080018
比如 我们创建一个整型变量 i,该变量的值为10,有一个指向整型变量 i 的指针ip,该ip包含了 i 的内存地址 0xc042060080 。但是p也有自己的内存地址 0xc042080018。
- 值类型的值传递
package main
import "fmt"
func main() {
i := 10 //整形变量 i
p := &i //指向整型变量 i 的指针ip,包含了 i 的内存地址
fmt.Printf("main中i的值为:%v,i 的内存地址为:%v,i的指针的内存地址为:%v\n",i,p,&p)
search(i)
fmt.Printf("main中i的值为:%v,i 的内存地址为:%v,i的指针的内存地址为:%v\n",i,p,&p)
}
func search(i int) {
fmt.Printf("search i 为:%v,i的指针的内存地址为:%v\n",i,&i)
i = 11
}
结果
main中i的值为:10,i 的内存地址为:0xc00000a0c8,i的指针的内存地址为:0xc000006028
search i 为:10,i的指针的内存地址为:0xc00000a0e8
main中i的值为:10,i 的内存地址为:0xc00000a0c8,i的指针的内存地址为:0xc000006028
内存示意图
- 普通类型的指针传递
先整个简单版本,便于理解
package main
import "fmt"
func main() {
i := 10 //整形变量 i
//p := &i //指向整型变量 i 的指针ip,包含了 i 的内存地址
fmt.Printf("main中i的值为:%v,i 的内存地址为:%v\n",i,&i)
searchByP(&i)
fmt.Printf("main中i的值为:%v,i 的内存地址为:%v\n",i,&i)
}
func searchByP(i *int) {
fmt.Printf("指向i的指针的内存地址为:%v\n",&i)
*i = 11
}
结果
main中i的值为:10,i 的内存地址为:0xc00000a0c8
指向i的指针的内存地址为:0xc000006030
main中i的值为:11,i 的内存地址为:0xc00000a0c8
大家可以发现 i在main的内存地址 和传递在searchByP的指针的内存地址不一样
package main
import "fmt"
func main() {
i := 10 //整形变量 i
p := &i //指向整型变量 i 的指针ip,包含了 i 的内存地址
fmt.Printf("main中i的值为:%v,i 的内存地址为:%v,i的指针的内存地址为:%v\n",i,p,&p)
searchByP(p)
fmt.Printf("main中i的值为:%v,i 的内存地址为:%v,i的指针的内存地址为:%v\n",i,p,&p)
}
func searchByP(i *int) {
fmt.Printf("searchByP i 为:%v,i的指针的内存地址为:%v\n",i,&i)
*i = 11
}
结果
main中i的值为:10,i 的内存地址为:0xc00000a0c8,i的指针的内存地址为:0xc000006028
searchByP i 为:0xc00000a0c8,i的指针的内存地址为:0xc000006038
main中i的值为:11,i 的内存地址为:0xc00000a0c8,i的指针的内存地址为:0xc000006028
内存示意图
非引用类型(值类型)和指针的参数传递都是传递副本,那么对于引用类型的参数传递又是如何的呢?
- 引用类型的值传递
参考 [简书–小杰的快乐时光] https://www.jianshu.com/p/f201d6da488a
感谢
引用类型(映射(map),数组切片(slice),通道(channel),方法与函数)
总结:在Go语言中只存在值传递(要么是该值的副本,要么是指针的副本),不存在引用传递。之所以对于引用类型的传递可以修改原内容数据,是因为在底层默认使用该引用类型的指针进行传递,但是也是使用指针的副本,依旧是值传递。
函数的多返回值
许多标准库中的函数返回2 个值,一个是期望得到的返回值,另一个是函数出错时的错误信
package main
import (
"errors"
"fmt"
)
func Add(a, b int) (sub int, err error) {
if a < 0 || b < 0 {
err = errors.New("a,b不同为负数")
return 0, err
}
return a + b, nil
}
func main() {
fmt.Println(Add(-1, 2))
}
结果
0 a,b不同为负数
Tip: errors.New() 方法返回的是什么? 一下是它的源码
package errors
// New returns an error that formats as the given text.
// New 返回一个给定文本格式的错误。
func New(text string) error {
return &errorString{
text}
}
// errorString is a trivial implementation of error.
// errorString 是 error 的一个琐碎的实现。
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
函数的递归
递归想必大家都比较熟悉,下面我们来用Go的递归来计算n!
func Cacule(n int) (sum int) {
if n > 0 {
return Cacule(n-1) * n
}
return 1
}
func main() {
fmt.Println(Cacule(10)) // 3628800
}
匿名函数和闭包
匿名函数就是没有名字的函数
闭包就是一个函数“捕获”和它在同一作用域的其他常量或变量形成的环境,一个函数和与其相关的引用环境组合的一个整体(实体)
package main
import "fmt"
func add(x , y , i , k int) func() (int,int,int,int) {
k++
p := 0
fmt.Println("p k 初始化执行")
return func() (int,int,int,int) {
p++
i++
fmt.Println("i,k,p,x+y")
return i ,k ,p , x + y
}
}
func main() {
fmt.Println("--------result--------")
result := add(1,2,0,0)
fmt.Println(result())
fmt.Println(result())
fmt.Println(result())
fmt.Println("--------result2--------")
result2 := add(3,4,0,0)
fmt.Println(result2())
}
结果
--------result--------
p k 初始化执行
i,k,p,x+y
1 1 1 3
i,k,p,x+y
2 1 2 3
i,k,p,x+y
3 1 3 3
--------result2--------
p k 初始化执行
i,k,p,x+y
1 1 1 7
解释:
return func() (int,int,int,int) {
p++
i++
fmt.Println("i,k,p,x+y")
return i ,k ,p , x + y
}
这里的匿名函数很明显形成了一个闭包,它的引用环境有 i ,p ,k, x,y
但是外部的
k++
p := 0
只在 result := add(1,2,0,0) 发生一次初始化的作用,之后匿名函数形成闭包环境,k 在匿名函数内部并没有发生其他操作。所以值还是保持传进来初始化后的值,这点我们从打印的 “p k 初始化执行” 也可以看出来只打印了一次,而i,p 不停的在自增,所以每一次调用一次相同的闭包环境他们就不停的新增。result2 显然 和 result 属于两个闭包环境,所以互不影响。
可变参数
在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符号“…”,这表示 该函数会接收任意数量的该类型参数。
package main
import "fmt"
func sumNumber(paras ...int) int {
var sum int
for _,v := range paras{
sum += v
}
return sum
}
func main() {
fmt.Println(sumNumber(1)) // 1
fmt.Println(sumNumber(1,2,3,4,5)) // 15
}
在上面的代码中 , 调用者隐式的创建一个数组,并将原始参数复制到数组中,再把数组的一 个切片作为参数传给被调函数
如果原始参数已经是切片类型,我们该如何传递给sum?只需在最后一个参数后加上省略符
values := []int{
1,2,3,4,5}
fmt.Println(sumNumber(values...)) // 15
-----不管遇上什么问题,一颗炸弹总能解决你的困扰——如果不行就两颗。解决不了问题那是你当量不够,这是普世真理。