go语言函数
函数特性:
- Go 函数 不支持 嵌套、重载和默认参数
- 函数无需声明原型、支持不定长度变参、多返回值、命名返回值参数
- 支持匿名函数、闭包
- 函数也可以作为一种类型使用
- 函数是一等公民,可作为参数传递
- 函数传递是值的拷贝或者是指针的拷贝,区别在于会不会影响原始值
- 不定长变参,如果传入slice,一定要用 …展开传入
1、函数定义
定义函数使用关键字 func,且左大括号不能另起一行
func A(a int,b string) int{
// 参数 a, b 名在前,类型在后
// 如果只有一个返回值,直接写个int就行。表示返回值是一个int型
}
func A(a int,b string) (int,string){
//有多个返回值的时候,小括号括起来,用逗号隔开
}
func A(a,b,c int){
//如果 多个参数类型一样,可以这样写
}
func A()(a,b,c int){ //对于返回值也可以这样写,简写一定要命名返回的是谁,如a,b,c
a,b,c=1,2,3 //在返回值的时候已经命名了a,b,c 已经分配好内存地址,
//所以在这里直接进行赋值就可以, 不需要加 :=
return a,b,c // 返回的时候不用说明返回的是谁,因为函数已经命名返回的名称类型。
//可以直接写一个return 就可以,但是为了代码可读性,还是写全
}
func B()(int,int,int){ // 这样写不命名返回值,但是return时要注意
a,b,c :=1,2,3 //没有开辟内存控件,所以要用 :=
return a,b,c //由于没有命名返回值,所以必须要把a,b,c返回
}
函数是一种类型,建议将复杂签名定义为函数类型,以便于阅读。
func test(fn func() int) int {
return fn()
}
type FormatFunc func(s string, x, y int) string // 定义函数类型。
func format(fn FormatFunc, s string, x, y int) string {
return fn(s, x, y)
}
func main() {
s1 := test(func() int { return 100 }) // 直接将匿名函数当参数。
s2 := format(func(s string, x, y int) string {
return fmt.Sprintf(s, x, y)
}, "%d, %d", 10, 20)
println(s1, s2)
}
2、参数
实参:在调用有参函数时,函数名后面括号中的参数称为“实际参数”,实参可以是常量、变量或表达式。
形参:自定义函数中的“形参”全称为”形式参数” 由于它不是实际存在变量,所以又称虚拟变量。实参和形参可以重名。形参的作用域是整个函数体就像定义在函数体内的局部变量
变参
go语言的变参本质上就是 slice。只能有⼀个,且必须是最后⼀个。
func test(s string, n ...int) string {//使用 变量名 ...类型声明可变参,只能是最后一个
var x int
for _, i := range n {
x += i
}
return fmt.Sprintf(s, x)
}
func main() {
println(test("sum: %d", 1, 2, 3))
}
注意:在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”。即:给可变参数传一个slice时,必须用…展开
func main() {
s := []int{1, 2, 3}
println(test("sum: %d", s...))//test函数接收int型可变参,这里传入int型slice, s...展开了
}
参数传递:
(1)值传递:
传递的是值得拷贝,函数中对传入的变量修改,不会影响到实际参数。
func sum(x, y int) int { //接收int型 的x,y 其实是出入实参的拷贝
return x + y
}
(2)引用传递
将实际参数的地址传递到函数中,比如传入指针,在函数中修改会改变实际参数。
func swap(x, y *string) {//接受的是指针类型,通过指针直接操作内存
var temp string
temp = *x
*x = *y
*y = temp
}
(3)固定类型可变参数
就是最后一个参数是可变参数,可以传入不固定的参数,但是类型相同。
func variable(str string, source ...string){//可变参数,固定类型
fmt.Println("可变参数的长度:",len(source))
}
(4)任意类型的不定参数
函数和每个参数的类型都不固定。 形参用 interface{}类型的可变参数声明
func variable(values ...interface{}) {//接收一个可变参数,空接口类型(任何类型都实现了空接口)
for _, val := range values {
switch v := val.(type) {
case int:
fmt.Println("val type is int ", v)
case float64:
fmt.Println("val type is float ", v)
case string:
fmt.Println("val type is string ", v)
case bool:
fmt.Println("val type is bool ", v)
default:
fmt.Println("val type is unknow ", v)
}
}
}
(5)函数类型参数
函数类型赋值给变量,作为参数传递引用
// 定义函数类型--这个函数接收两个string的参数
type myfunc func(string, string)
func addperfix(perfix, name string) {//这个函数正好符合myfunc这个函数类型
fmt.Println(perfix, name)
}
// 第二个参数接收的是一个 myfunc类型的参数
func sayhello(name string, f myfunc) {
f("hello", name)
}
func main() {
sayhello("helloworld", addperfix)//将对应类型的参数传入
}
小结:
1、Go语言函数中的参数不支持默认值。
2、无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。
3、map、slice、chan、指针、interface默认以引用的方式传递。
4、函数的可变参数只能有一个,且必须是最后一个。
5、在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可
3、返回值
(1)有返回值的函数必须写return,否则报错
(2)可以返回多个返回值,声明的时候用括号括起来
(3)返回值可以被命名,命名返回参数可看做与形参类似的局部变量,最后由 return 隐式返回。 命名返回参数
可被同名局部变量遮蔽,此时需要显式返回
(4)不能⽤容器对象接收多返回值。只能⽤多个变量,或 “_” 忽略。
func test() (int, int) {
return 1, 2
}
func main() {
// s := make([]int, 2)
// s = test() // Error: multiple-value test() in single-value context
x, _ := test() //有返回值必须用多个变量接。
println(x)
}
(5)多返回值可直接作为其他函数调⽤实参。
func test() (int, int) {
return 1, 2
}
func add(x, y int) int {
return x + y
}
func sum(n ...int) int {
var x int
for _, i := range n {
x += i
}
return x
}
func main() {
println(add(test()))
println(sum(test()))
}
(6)命名返回参数允许 defer 延迟调⽤通过闭包读取和修改。
func add(x, y int) (z int) {
defer func() {
z += 100
}()
z = x + y
return
}
func main() {
println(add(1, 2)) // 输出: 103
}
显式 return 返回前,会先修改命名返回参数。
//这段代码,命名返回值z,就相当于局部变量,返回z+200,即z=z+200
func add(x, y int) (z int) {
defer func() {
println(z) // 输出: 203
}()
z = x + y
return z + 200 // 执⾏顺序: (z = z + 200) -> (call defer) -> (ret)
}
func main() {
println(add(1, 2)) // 输出: 203
}
defer、return、返回值三者的执行顺序应该是:return最先给返回值赋值;接着defer开始执行一些收尾工作;最后RET指令携带返回值退出函数。
4、匿名函数
匿名函数可赋值给变量,做为结构字段,或者在 channel ⾥传送。
匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量,不必声明
fn := func() { println("Hello, World!") }
fn()
// --- function collection ---
fns := [](func(x int) int){
func(x int) int { return x + 1 },
func(x int) int { return x + 2 },
}
println(fns[0](100))
// --- function as field ---
d := struct {
fn func() string
}{
fn: func() string { return "Hello, World!" },
}
println(d.fn())
// --- channel of function ---
fc := make(chan func() string, 2)
fc <- func() string { return "Hello, World!" }
println((<-fc)())
5、闭包
闭包的概念:
闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。
个人理解
也就是,函数返回值是一个函数,返回的函数能做一些事情,通过主函数得到这个返回值函数,然后就可以多次调用去做这个函数可以做的事情。这个函数应用改的所有的局部变量都将成为闭包中的变量,持续引用。
可以把 闭包理解成,返回值是一个对象,可以做一些操作。 我们传值得到这个对象(其实是函数),然后就可以传值做这个函数能做的事情了。函数式编程的概念这里不深究,大概了解闭包的使用。
注意:闭包引用的原局部变量也是拷贝,是对指针的拷贝。
func main(){
f:=closure(10)
fmt.Println(f(1))
fmt.Println(f(2))
fmt.Println(f(3))
fmt.Println(f(4))
}
func closure(x int) func(int)int{
return func(y int) int{
return x+y
}
}
6、递归
递归,就是在运行的过程中调用自己。一个函数调用自己,就叫做递归函数。
构成递归需具备的条件:
1、子问题须与原始问题为同样的事,且更为简单。
2、不能无限制地调用本身,须有个出口,化简为非递归状况处理。
递归函数可以解决许多数学问题,如计算给定数字阶乘、产生斐波系列等;
package main
import "fmt"
//菲波那切数列
func fibonaci(i int) int {
if i == 0 {
return 0
}
if i == 1 {
return 1
}
return fibonaci(i-1) + fibonaci(i-2)
}
func main() {
var i int
for i = 0; i < 10; i++ {
fmt.Printf("%d\n", fibonaci(i))
}
}
7、延迟调用-defer
- defer的执行方式类似其它语言中的析构函数,在函数体执行结束后按照调用顺序的相反顺序逐个执行
- 即使函数发生严重错误也会执行
- 支持匿名函数的调用
- 常用于资源清理、文件关闭、解锁以及记录时间等操作
- 通过与匿名函数配合可在return之后修改函数计算结果
- 如果函数体内某个变量作为defer后的语句参数,则在定义defer时即已经获得了拷贝,如果是匿名函数的参数,则是引用某个变量的地址
- Go 没有异常机制,但有 panic/recover 模式来处理错误
- Panic 可以在任何地方引发,但recover只有在defer调用的函数中有效
func main(){ //defer 逆序调用
for i:=0;i<10;i++{
defer fmt.Println(i) //值的拷贝
}
}
//执行结果是: 从9 到0
看一个有趣的例子:
func main(){
for i:=0;i<10;i++{
defer func(){
fmt.Println(i) //引用变量地址
}()
}
}
//执行结果 全部都是10
当defer 的是个匿名函数的时候,这里就会出现闭包。每次执行循环体,都只是 对i的引用,可以理解成 把defer 存入栈,但是存的是func这个匿名函数并没有赋值i,等到执行完循环体开始执行defer 语句的时候,就会给i赋值,这个时候的i就是10,所以输出了10个10
8、错误处理
这里简单介绍下go语言错误处理,(会有一篇详解错误处理的)。
- defer 在函数发声严重错误的时候也会执行,类似java中 try..catch 中的finally 。
- go 语言没有异常机制,但有 panic/recover 模式来处理错误
- Panic 可以在任何地方引发,但recover只有在defer调用的函数中有效
func main() {
A()
B()
C()
}
func A(){
fmt.Println("func A")
}
func B() {
defer func() { //注意defer 要在panic之前注册函数
if err:=recover();err!=nil{ //调用recover(),返回一个panic的信息,
// 如果返回nil,说明没错,如果返回不是nil,说明引发了panic,在进行recover操作
fmt.Println("Recover in B")
}
}()
panic("Panic in B")
}
func C(){
fmt.Println("func C")
}
//打印结果
//func A
//Recover in B
//func C