Go核心开发学习笔记(十三) !—— 函数调用机制,递归

方法: 完成一个功能的代码或集合。
大致过程:给方法必要的输入,方法返回结果。

函数调用机制的底层分析:
栈区:基本数据类型一般分配到栈区,编译器存在一个逃逸分析。
堆区:引用数据类型一般分配到堆区,编译器存在一个逃逸分析。
代码区:存放代码的内存区域

通过一个调用过程,深刻理解函数调用都做了什么:

package main
import "fmt"

func calcu (a float64,b float64) (float64,float64) {
	
	plus := a + b
	minus := a - b
	return plus,minus
}
func main() {
	/*
	第一步 main是一切程序的入口,首先现在内存栈区开辟一块空间存储变量
	      a=5 b=3 都是基本变量,一般存在内存栈区
	 */
	var (
		a float64 = 5
		b float64 = 3
	)
	/*
	第二步 调用函数,将两个变量的值传入函数中,系统在内存栈区开辟另一块儿空间放入被调用函数,
	       这个地方的函数的内存与main程序的内存空间完全是两个用户空间,这也就是说为何被调用函数如果
	       没有返回值,就是两个完全不相干的函数调用,也就失去了调用意义,调用结束返回后,return结束
	       被调用函数,并且返回值,编译器就会将这部分垃圾回收,回到main()中继续走完
	 */
	max, min := calcu(a,b)
	
	/*
	第三部 执行接下来main中的其他语句,直到执行完成后,编译器回收所有占据内存的垃圾。
	 */
	fmt.Println(max)
	fmt.Println(min)
}

函数的递归调用

  1. 一个函数在函数体中本身又调用了自身,就是递归调用。
  2. 案例:

package main
import “fmt”
func recu (n float64) {
if n > 2 {
n–
recu(n) //recu里面迭代recu(),每次迭代成功的部分都有独立的用户空间,变量彼此互不影响。
} else {
fmt.Println(“n =”,n)
} //直到函数结尾没有任何返回值,所以没有任何新值产生
}
func main() {
var a float64 = 4
recu(a) //没有得到任何返回值,所以接下来调用后也不会产生任何输出
}

  1. 递归永远要向终止递归的条件无限接近,否则就是无限递归,死循环。

  2. 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁,不再占内存空间。

  3. 递归调用练习题:使用递归方式求出斐波那契数列,随机给第n个整数,求数列的值是多少?

    package main
    import "fmt"
    func fbnq(n int) int {
    
    	if n == 1 || n==2 {
    		return 1
    	} else {
    		return fbnq(n-1) + fbnq(n-2)
    	}
    }
    func main() {
    	res := fbnq(6)
    	fmt.Println(res)
    }
    
  4. 求函数值,已知f(1)=3,f(n) = 2 * f(n) + 1, 当给一个n赋值,求出f(n)应该是多少?

    package main
    import "fmt"
    func f(n int) int {
    
    	if n == 1 {
    		return 3
    	} else {
    		return 2*f(n-1) + 1
    	}
    }
    
    func main() {
    	res := f(6)
    	fmt.Println(res)
    }
    
  5. 猴子吃桃子,第一天吃桃子总数的一半,再多吃一个,第十天发现只剩下一个桃子,求总共有多少个桃子

    package main
    import (
    	"fmt"
    )
    func f(n int) int {
    
    	if n == 10 {
    		return 1
    	} else {
    		return (f(n+1) + 1) * 2      //其实这个else有问题,需要限定范围,1<n<10
    	}
    }
    
    func main() {
    	res := f(1)
    	fmt.Println(res)
    }
    

函数注意事项和细节讨论
8. 形参列表可以是多个,返回值列表也可以是多个,多个返回值的话必须加括号。
9. 形参列表和返回值列表数据类型可以是值类型也可以是引用类型。
10. 函数命名首字母不能是数字,因为首字母大小写决定函数可否被其他包调用,所以就好理解了,大写类似java的public,小写是private。
11. 函数的作用域:函数变量是局部的,函数外不生效,一个函数相当于一个用户空间。
12. 基本数据类型和数组默认都是值传递,在函数内修改,不会影响到原来的值。
或者理解为值拷贝,并不会影响调用函数本身的值。

13. 如果想通过函数内变量修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量,也就是说引用传递会影响main中的值。

**举例值传递,函数在变量+10后,并不会影响主函数中n值不变。**
```
package main                                               
import "fmt"
func test(n int) int {                               
	n = n + 10                           
	fmt.Println("test n = ",n)                 
	return n               
}
func main() {
	var n int = 10
	test(n)
	fmt.Println("main n = ",n)
}                                // main n=10
```

**举例指针传递,函数在指针取值后,影响到主函数中的n,使n+10变为了20:**
```
package main
import "fmt"
func test1(n *int) int {
	*n = *n + 10
	fmt.Println("test n = ",*n)
	return *n
}
func main() {
	var n int = 10
	test1(&n)
	fmt.Println("main n = ",n)
}                                //main n=20
```
  1. Golang不支持传统的函数重载,不允许两个同名函数存在,即便形参个数不一样也不行。

  2. Golang中函数也是一种数据类型,可以赋值给一个变量,该变量就是一个函数类型的变量,通过该变量可以对函数调用, 方便记忆可以理解 变量=函数 给函数加了个变量的别名,所以函数()没问题,那么 变量()也没问题。

  3. 函数也可以做调用函数的参数,

    package main
    import "fmt"
    func getSum (n1 int,n2 int) int {
    	return n1 + n2
    }
    func ff (ff func(int,int) int, num1 int, num2 int) int {     // ff类型为函数类型,与getSum一致,再传 n1 ,n2 形参, 再返回一个int返回值
    	return ff(num1 , num2)
    }
    
    func main() {
    	res2 := ff(getSum,10,20)
    	fmt.Println(res2)
    }
    
  4. 为了简化数据类型定义,Go支持自定义数据类型: type myint int //相当于为int加了个别名,var xx myint(实际上就是int)
    最好的写法是处理函数作为形参,type myfunc func(int,int) int //相当于用myfunc类型就双形参单return函数类型

  5. 支持对函数返回值命名:在func定义的时候就定义return值 func (n1 int,n2 int) (sum int,sub int) {…} //这样就直接定义了两个返回值,没有顺序之分了。 这样函数最后只需要return了,并不需要再添加返回什么。

  6. 两个以上返回值可以用 _ 忽略,我只想得到其中一个,不想要其他。

  7. Go支持可变参数,例如 func getSum (args… int) (sum int) {…} // args… 七个bytes缺一不可,传入0~多个参数。
    func getSum (n1 float64 , args… int) (sum int) {…} // 传入一个float64,传入1~多个int参数
    args是一个slice,通过args[i]可以访问到各个值,理解为动态数组。

    案例:针对func getSum (args… int) (sum int) {…} 如何传入参数:

    package main
    import "fmt"
    func get1Sum (n1 int, args... int)  int {      //args可以变成其他的字符,vars也可以,但是一般约定俗成,不要乱改。
    	sum := n1                                  //可变参数args一定要放在形参列表里的最后
    	for i := 0 ; i < len(args) ; i++ {
    		sum += args[i]
    	}
    	return sum
    }
    func main() {
    	res := get1Sum(1,2,3,4,5)
    	fmt.Println(res)
    }
    
发布了50 篇原创文章 · 获赞 18 · 访问量 4016

猜你喜欢

转载自blog.csdn.net/weixin_41047549/article/details/89684168