函数声明和调用
go语言是通过func关键字声明一个函数的,声明语法格式如下
func 函数名(形式参数) (返回值) {函数体return 返回值 // 函数终止语句
}
函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
形式参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
函数体:实现指定功能的代码块。
func cal_sum100() {// 计算1-100的和var s = 0for i := 1; i <= 100; i++ {s += i}fmt.Println(s)
}
声明一个函数并不会执行函数内代码,只是完成一个一个包裹的作用。真正运行函数内的代码还需要对声明的函数进行调用,一个函数可以在任意位置多次调用。调用一次,即执行一次该函数内的代码。
func cal_sum100() {// 计算1-100的和var s = 0for i := 1; i <= 100; i++ {s += i}fmt.Println(s)
}
cal_sum100()
函数参数
什么是参数,函数为什么需要参数呢?将上面的打印的两个菱形换乘打印一个6行的和一个8行的,如何实现呢?这就涉及到了函数参数。
再比如上面我们将计算1-100的和通过函数实现了,但是完成新的需求:
分别计算并在终端打印1-100的和,1-150的和以及1-200的和
package mainimport "fmt"func cal_sum100() {// 计算1-100的和var s = 0for i := 1; i <= 100; i++ {s += i}fmt.Println(s)
}func cal_sum150() {// 计算1-100的和var s = 0for i := 1; i <= 150; i++ {s += i}fmt.Println(s)
}func cal_sum200() {// 计算1-100的和var s = 0for i := 1; i <= 200; i++ {s += i}fmt.Println(s)
}func main() {cal_sum100()cal_sum150()cal_sum200()
}
这样当然可以实现,但是是不是依然有大量重复代码,一会发现三个函数出了一个变量值不同以外其他都是相同的,所以为了能够在函数调用的时候动态传入一些值给函数,就有了参数的概念。
参数从位置上区分分为形式参数和实际参数。
// 函数声明
func 函数名(形式参数1 参数1类型,形式参数2 参数2类型,...){函数体
}
// 调用函数
函数名(实际参数1,实际参数2,...)
函数每次调用可以传入不同的实际参数,传参的过程其实就是变量赋值的过程,将实际参数按位置分别赋值给形参。
还是刚才的案例,用参数实现:
package mainimport "fmt"func cal_sum(n int) {// 计算1-100的和var s = 0for i := 1; i <= n; i++ {s += i}fmt.Println(s)
}func main() {cal_sum(100)cal_sum(101)cal_sum(200)
}
这样是不是就灵活很多了呢,所以基本上一个功能强大的函数都会有自己需要的参数,让整个业务实现更加灵活。
位置参数
位置参数,有时也称必备参数,指的是必须按照正确的顺序将实际参数传到函数中,换句话说,调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。
// 函数声明 两个形参:x,y
func add_cal(x int,y int){fmt.Println(x+y)
}func main() {// 函数调用,按顺序传参// add_cal(2) // 报错// add_cal(232,123,12) // 报错add_cal(100,12)
}
不定长参数
如果想要一个函数能接收任意多个参数,或者这个函数的参数个数你无法确认,就可以使用不定长参数,也叫可变长参数。Go语言中的可变参数通过在参数名后加…来标识。
package mainimport "fmt"func sum(nums ...int) { //变参函数fmt.Println("nums",nums)total := 0for _, num := range nums {total += num}fmt.Println(total)
}func main() {sum(12,23)sum(1,2,3,4)}
注意:可变参数通常要作为函数的最后一个参数。
package mainimport "fmt"func sum(base int, nums ...int) int {fmt.Println(base, nums)sum := basefor _, v := range nums {sum = sum + v}return sum
}func main() {ret := sum(10,2,3,4)fmt.Println(ret)}
go的函数强调显示表达的设计哲学,没有默认参数
函数返回值
函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 return 语句返回。return 语句将被调函数中的一个确定的值带回到主调函数中,供主调函数使用。函数的返回值类型是在定义函数时指定的。return 语句中表达式的类型应与定义函数时指定的返回值类型必须一致。
func 函数名(形参 形参类型)(返回值类型){// 函数体return 返回值
}变量 = 函数(实参) // return 返回的值赋值给某个变量,程序就可以使用这个返回值了。
同样是设计一个加法计算函数:
func add_cal(x,y int) int{return x+y
}func main() {ret := add_cal(1,2)fmt.Println(ret)
}
无返回值,声明函数时没有定义返回值,函数调用的结果不能作为值使用
func foo(){fmt.Printf("hi,yuan!")return // 不写return默认return空
}func main() {// ret := foo() // 报错:无返回值不能将调用的结果作为值使用foo()}
返回多个值,函数可以返回多个值
func get_name_age() (string, int) {return "yuan",18
}func main() {a, b := get_name_age()fmt.Println(a, b)
}
返回值命名
函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。
func calc(x, y int) (sum, sub int) {sum = x + ysub = x - yreturn // return sum sub
}
作用域
所谓变量作用域,即变量可以作用的范围。
作用域(scope)通常来说,程序中的标识符并不是在任何位置都是有效可用的,而限定这个标识符的可用性的范围就是这个名字的作用域。
变量根据所在位置的不同可以划分为全局变量和局部变量
局部变量 :写在{}中或者函数中或者函数的形参, 都是局部变量
1、局部变量的作用域是从定义的那一行开始, 直到遇到 } 结束或者遇到return为止
2、局部变量, 只有执行了才会分配存储空间, 只要离开作用域就会自动释放
3、局部变量存储在栈区
4、局部变量如果没有使用, 编译会报错。全局变量如果没有使用, 编译不会报错
5、:=只能用于局部变量, 不能用于全局变量
全局变量 :函数外面的就是全局变量
1、全局变量的作用域是从定义的那一行开始, 直到文件末尾为止
2、全局变量, 只要程序一启动就会分配存储空间, 只有程序关闭才会释放存储空间,
3、全局变量存储在静态区(数据区)
func foo() {// var x =10x = 10fmt.Println(x)
}var x = 30 // 全局变量func main() {// var x = 20foo()fmt.Println(x)
}
注意,if,for语句具备独立开辟作用域的能力:
// if的局部空间
if true{x:=10fmt.Println(x)
}fmt.Println(x)// for的局部空间
for i:=0;i<10 ;i++ {}
fmt.Println(i)
值传递
// 案例1
var x = 10
fmt.Printf("x的地址%p\n", &x)
y := x
fmt.Printf("y的地址%p\n", &y)
x = 100
fmt.Println(y)// 案例2
var a = []int{1, 2, 3}
b := a
a[0] = 100
fmt.Println(b)// 案例3
var m1 = map[string]string{"name":"yuan"}
var m2 = m1
m2["age"] = "22"
fmt.Println(m1)
函数传参
package mainimport "fmt"func swap(a int, b int) {c := aa = bb = cfmt.Println("a", a)fmt.Println("b", b)
}func main() {var x = 10var y = 20swap(x, y)fmt.Println("x", x)fmt.Println("y", y)
}
package mainimport "fmt"func func01(x int) {x = 100
}func func02(s []int) {fmt.Printf("func02的s的地址:%p\n",&s)s[0] = 100// s = append(s, 1000)
}func func03(p *int) {*p = 100
}
func main() {// 案例1var x = 10func01(x)fmt.Println(x)// 案例2var s = []int{1, 2, 3}fmt.Printf("main的s的地址:%p\n",&s)func02(s)fmt.Println(s)//案例3var a = 10var p *int = &afunc03(p)fmt.Println(a)}
思考之前的scan函数为什么一定传参&
Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。
func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
}
make函数创建的map就是一个指针类型,工作原理类似于案例3,所以map数据和切片数据一样虽然值拷贝但依然可以修改原值。
匿名函数
匿名函数,顾名思义,没有函数名的函数。
匿名函数的定义格式如下:
func(参数列表)(返回参数列表){函数体
}
匿名函数可以在使用函数的时候再声明调用
//(1)
(func() {fmt.Println("yuan")
})()
//(2)
var z =(func(x,y int) int {return x + y})(1,2)fmt.Println(z)
也可以将匿名函数作为一个func类型数据赋值给变量
var f = func() {fmt.Println("yuan")
}fmt.Println(reflect.TypeOf(f)) // funcf() // 赋值调用调用
Go语言不支持在函数内部声明普通函数,只能声明匿名函数。
func foo() {fmt.Println("foo功能")f := func(){fmt.Println("bar功能")}fmt.Println(f)
}
高阶函数
一个高阶函数应该具备下面至少一个特点:
将一个或者多个函数作为形参
返回一个函数作为其结果
首先明确一件事情:函数名亦是一个变量
package mainimport ("fmt""reflect"
)func addCal(x int, y int)int{return x + y
}func main() {var a = addCala(2, 3)fmt.Println(a)fmt.Println(addCal)fmt.Println(reflect.TypeOf(addCal)) // func(int, int) int}
结论:函数参数是一个变量,所以,函数名当然可以作为一个参数传入函数体,也可以作为一个返回值。
函数参数
package mainimport ("fmt""time"
)func timer(f func()){timeBefore := time.Now().Unix()f()timeAfter := time.Now().Unix()fmt.Println("运行时间:", timeAfter - timeBefore)}func foo() {fmt.Println("foo function... start")time.Sleep(time.Second * 2)fmt.Println("foo function... end")
}func bar() {fmt.Println("bar function... start")time.Sleep(time.Second * 3)fmt.Println("bar function... end")
}func main() {timer(foo)timer(bar)
}
注意如果函数参数也有参数该怎么写呢?
package mainimport "fmt"func add(x,y int ) int {return x+y
}func mul(x,y int ) int {return x*y
}// 双值计算器
func cal(f func(x,y int) int,x,y int,) int{return f(x,y)}func main() {ret1 := cal(add,12,3,)fmt.Println(ret1)ret2 := cal(mul,12,3,)fmt.Println(ret2)}
函数返回值
package mainimport ("fmt"
)func foo() func(){inner := func() {fmt.Printf("函数inner执行")}return inner
}func main() {foo()()}
闭包函数
闭包并不只是一个go中的概念,在函数式编程语言中应用较为广泛。
首先看一下维基上对闭包的解释:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量(外部非全局)的函数。
简单来说就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包函数。
闭包就是当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之 外执行。
需要注意的是,自由变量不一定是在局部环境中定义的,也有可能是以参数的形式传进局部环境;另外在 Go 中,函数也可以作为参数传递,因此函数也可能是自由变量。
闭包中,自由变量的生命周期等同于闭包函数的生命周期,和局部环境的周期无关。
闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
实现一个计数器函数,不考虑闭包的情况下,最简单的方式就是声明全局变量:
package mainimport "fmt"var i = 0func counter() {i++fmt.Println(i)
}func main() {counter()counter()counter()
}
这种方法的一个缺点是全局变量容易被修改,安全性较差。闭包可以解决这个问题,从而实现数据隔离。
package mainimport "fmt"func getCounter() func() { var i = 0return func() {i++fmt.Println(i)}}func main() {counter := getCounter()counter()counter()counter()counter2 := getCounter()counter2()counter2()counter2()
}
getCounter完成了对变量i以及counter函数的封装,然后重新赋值给counter变量,counter函数和上面案例的counter函数的区别就是将需要操作的自由变量转化为闭包环境。
闭包函数应用案例
在go语言中可以使用闭包函数来实现装饰器
案例1:计算函数调用次数
package mainimport ("fmt""reflect""runtime"
)// 函数计数器
func getCounter(f func()) func() {calledNum := 0 // 数据隔离return func() {f()calledNum += 1// 获取函数名fn := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()fmt.Printf("函数%s第%d次被调用\n", fn, calledNum)}
}// 测试的调用函数
func foo() {fmt.Println("foo function执行")
}func bar() {fmt.Println("bar function执行")
}func main() {/*fooAndCounter := getCounter(foo) // 针对foo的计数器fooAndCounter()fooAndCounter()fooAndCounter()barAndCounter := getCounter(bar)barAndCounter()barAndCounter()barAndCounter()*/foo := getCounter(foo) // 开放原则foo()foo()foo()bar := getCounter(bar)bar()bar()
}
案例2:计算函数运行时间
package mainimport ("fmt""time"
)func GetTimer(f func(t time.Duration)) func(duration time.Duration) {return func(t time.Duration) {t1 := time.Now().Unix()f(t)t2 := time.Now().Unix()fmt.Println("运行时间:", t2-t1)}}func foo(t time.Duration) {fmt.Println("foo功能开始")time.Sleep(time.Second * t)fmt.Println("foo功能结束")
}func bar(t time.Duration) {fmt.Println("bar功能开始")time.Sleep(time.Second * t)fmt.Println("bar功能结束")
}func main() {var foo = GetTimer(foo)foo(3)var bar = GetTimer(bar)bar(2)}
关键点:将一个功能函数作为自由变量与一个装饰函数封装成一个整体作为返回值,赋值给一个新的函数变量,这个新的函数变量在调用的时候,既可以完成原本的功能函数又可以实现装饰功能。