首页 > 其他分享 >七、函数

七、函数

时间:2022-11-20 14:44:21浏览次数:31  
标签:函数 int fmt func Println main

七、函数

简单说,函数就是一段封装好的,可以重复使用的代码,它使得我们的程序更加模块化,避免大量重复的代码。

基本语法

func 函数名 (形参列表) (返回值类型列表) {
    执行语句...
    return + 返回值列表
}

函数的定义和函数的调用案例:

package main

import "fmt"

func cal(num1 int, num2 int) int { // 如果返回值类型就一个的话,那么()是可以省略不写的
	var sum int = 0
	sum += num1
	sum += num2
	return sum
}

func main() {
	// 功能: 10+ 20
	// 调用函数:
	sum := cal(10, 20)
	fmt.Println(sum)
}

7.1、函数声明和调用

go语言是通过func关键字生命一个函数的,声明方法 语法格式如下

func 函数名(形式参数) (返回值) {
        函数体
        return 返回值   // 函数终止语句
}

其中:

  • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
  • 形式参数:参数由参数变量和参数变量的类型组成, 可以是一个参数,可以是n个参数,可以是0个参数,多个参数之间使用,分隔。
  • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
  • 函数体:实现指定功能的代码块。

遵循标识符命名规范:见名知意,驼峰命名

声明一个函数并不会执行函数内代码,只是完成一个一个包裹的作用。真正运行函数内的代码还需要对声明的函数进行调用,一个函数可以在任意位置多次调用。调用一次,即执行一次该函数内的代码。

调用语法:

函数名()     // 无参数的情况

内存分析:

package main

import "fmt"

// 自定义函数: 功能,交换两个数
func exchangeNum(num1 int, num2 int) {
   var t int
   t = num1
   num1 = num2
   num2 = t
}

func main() {
   // 调用函数:交换10和20
   var num1 int = 10
   var num2 int = 10
   fmt.Printf("交换前的两个数: num1 = %v,num2 = %v \n", num1, num2)
   exchangeNum(num1, num2)
   fmt.Printf("交换后的两个数:num1 = %v,num2 = %v \n", num1, num2)
}

Golang中函数不支持重载

Golang中支持可变参数(如果希望函数带有可变数量的参数)

package main

import "fmt"

// 定一个函数,函数的参数为:可变参数... 参数的数量 可变
// arge...int 可以传入任意多个数量的int类型的数据,传入0个,1个 ...N个
func test(args ...int) {
	// 函数内部处理的可变参数的时候,将可变参数当做切片来处理
	// 遍历可变参数
	for i := 0; i < len(args); i++ {
		fmt.Println(args[i])
	}
}

func main() {
	test()
	fmt.Println("_______________")
	test(3)
	fmt.Println("----------")
	test(37, 58, 49, 59, 47)
}

基本数据类型和数组默认都是值传递的,即进行值拷贝,在函数内修改,不会影响到原来的值。

package main

import "fmt"

func test(num int) {
	num = 30
	fmt.Println("test---", num)
}

func main() {
	var num int = 10
	test(num)
	fmt.Println("main----", num)
}

以值传递方式的数据类型,如果希望在函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果来看类似引用传递。

package main

import "fmt"

// 参数的类型为指针
func test(num *int) {
	// 对地址对应的变量进行改变数值。
	*num = 30
}

func main() {
	var num int = 10
	fmt.Println(&num)
	test(&num) // 调用test函数的时候,传入的是num的地址
	fmt.Println("main----", num)
}

在Go中,函数也是一种数据类型,可以赋值给一个变量,则改变量就是一个函数类型的变量。通过该变量可以对函数调用。

package main

import "fmt"

// 定一个函数
func test(num int) {
	fmt.Println(num)
}

func main() {
	// 函数也是一种数据类型,可以赋值给一个变量
	a := test                                         // 变量就是一个函数类型的变量
	fmt.Printf("a的类型是: %T,test函数的类型是:%T \n", a, test) //a的类型是: func(int),test函数的类型是:func(int)
	// 通过该变量可以对函数调用
	a(10)
}

函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用(把函数本身当做一种数据类型)

package main

import "fmt"

// 定一个函数
func test(num int) {
	fmt.Println(num)
}

// 定义一个函数,另外一个函数作为形参
func test02(num1 int, num2 float32, testFunc func(int)) {
	fmt.Println("-------test02")
}

func main() {
	// 函数也是一种数据类型,可以赋值给一个变量
	a := test                                         // 变量就是一个函数类型的变量
	fmt.Printf("a的类型是: %T,test函数的类型是:%T \n", a, test) //a的类型是: func(int),test函数的类型是:func(int)
	// 通过该变量可以对函数调用
	a(10)

	// 调用test02函数
	test02(10, 3.14, test)
	test02(10, 3.14, a)
}

为了简化数据类型定义,Go支持自定义数据类型

基本语法:type自定义数据类型名,数据类型

可以理解为:相当于起了一个别名

例如:type myInt int ----> 这时myInt就等价int来使用了。

func main() {
    type myInt int
	var num1 myInt = 30
	fmt.Println("num1", num1)
	var num2 int = 30
	num2 = int(num1)
	fmt.Println("num2", num2)
}

例如:type mySum func(int,int) int ----> 这时mySum就等价一个函数类型func(int,int)int

type myFunc func(int)

func test03(num1 int, num2 float32, testFunc myFunc) {
	fmt.Println("--------test02")
}

7.3、函数参数

7.3.1、什么是参数

什么是参数,函数为什么需要参数呢?

分别计算并在终端打印1-100的和,1-150的和以及1-200的和

package main

import (
	"fmt"
)

func cal_sum100() {
	// 计算1-100的和
	var s = 0
	for i := 1; i <= 100; i++ {
		s += i
	}
	fmt.Println(s)
}

func cal_sum150() {
	// 算1-150的和
	var s = 0
	for i := 1; i <= 150; i++ {
		s += i
	}
	fmt.Println(s)
}

func cal_sum200() {
	// 算1-200的和
	var s = 0
	for 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 main

import "fmt"

func cal_sum(n int) {
	// 计算1-100的和
	var s = 0
	for i := 1; i <= n; i++ {
		s += i
	}
	fmt.Println(s)
}

func main() {
	cal_sum(100)
	cal_sum(150)
	cal_sum(200)
}

这样是不是就灵活很多了呢,所以基本上一个功能强大的函数都会有自己需要的参数,让整个业务实现更加灵活。

7.3.2、位置参数

位置参数,有时也称必备参数,指的是必须按照正确的顺序将实际参数传到函数中,换句话说,调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。

package main

import "fmt"

// 函数声明 两个形参: 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)
}

7.3.3、不定长参数

如果想要一个函数能接收任意多个参数,或者这个函数的参数个数你无法确认,就可以使用不定长参数,也叫可变长参数。Go语言中的可变参数通过在参数名后加...来标识。

package main

import "fmt"

func sum(nums ...int) { // 变参函数
	fmt.Println("nums", nums)
	total := 0
	for _, num := range nums {
		total += num
	}
	fmt.Println(total)
}

func main() {
	sum(12, 23)
	sum(1, 2, 3, 4)
}

注意:可变参数通常要作为函数的最后一个参数。

package main

import "fmt"

func sum(base int, nums ...int) int {
	fmt.Println(base, nums)
	sum := base
	for _, v := range nums {
		sum = sum + v
	}
	return sum
}

func main() {
	ret := sum(10, 2, 3, 4)
	fmt.Println(ret)
}

go的函数强调显示表达的设计哲学,没有默认参数

7.4、函数返回值

7.4.1、返回值的基本使用

函数的返回值是指函数被调用之后,执行函数体中的代码所得到的结果,这个结果通过 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)
}

7.4.2、无返回值

声明函数时没有定义返回值,函数调用的结果不能作为值使用

func foo() {
    fmt.Printf("hi summer")
    return // 不写return默认return空
}


func main() {
    // ret := foo()  // 报错:返回值不能将调用的结果作为值使用
    foo()
}

7.4.3、返回多个值

函数可以返回多个值

func get_name_age() (string, int) {
    return "summer",18
}

func main() {
    a, b := get_name_age()
    fmt.Println(a,b)
}

7.4.4、返回值命名

函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。

func calc(x,y int) (sum, sub int) {
    sum = x + y
    sub = x - y
    return // return  sum sub
}

7.5、作用域

所谓变量作用域,即变量可以作用的范围。

作用域(scope)通常来说,程序中的标识符并不是在任何位置都是有效可用的,而限定这个标识符的可用性的范围就是这个名字的作用域。

变量根据所在位置的不同可以划分为全局变量和局部变量

  • 局部变量 :写在{}中或者函数中或者函数的形参, 都是局部变量

1、局部变量的作用域是从定义的那一行开始, 直到遇到 } 结束或者遇到return为止

2、局部变量, 只有执行了才会分配存储空间, 只要离开作用域就会自动释放

3、局部变量存储在栈区

4、局部变量如果没有使用, 编译会报错。全局变量如果没有使用, 编译不会报错

5、:=只能用于局部变量, 不能用于全局变量

  • 全局变量 :函数外面的就是全局变量

1、全局变量的作用域是从定义的那一行开始, 直到文件末尾为止

2、全局变量, 只要程序一启动就会分配存储空间, 只有程序关闭才会释放存储空间,

3、全局变量存储在静态区(数据区)

func foo() {
    // var x = 10
    x = 10
    fmt.Println(x)
}


var x = 30  // 全局变量

func main() {
  // var x = 20
    foo()
    fmt.Println(x)
}

注意,if 、for语句具备独立开辟作用域的能力:

// if的局部空间
if true{
    x:=10
    fmt.Println(x)
}

fmt.Println(x)

// for的局部空间
for i:=0;i<10 ;i++  {

}
fmt.Println(i)

7.6、值传递

7.6.1、赋值操作

// 案例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)

7.6.2、函数传参

package main

import "fmt"

func swap(a int, b int) {
	c := a
	a = b
	b = c
	fmt.Println("a", a)
	fmt.Println("b", b)
}

func main() {

	var x = 10
	var y = 20
	swap(x, y)
	fmt.Println("x", x)
	fmt.Println("y", y)

}
package main

import "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() {

    // 案例1
    var x = 10
    func01(x)
    fmt.Println(x)

    // 案例2
    var s = []int{1, 2, 3}
    fmt.Printf("main的s的地址:%p\n",&s)
    func02(s)
    fmt.Println(s)

    //案例3
    var a = 10
    var p *int = &a
    func03(p)
    fmt.Println(a)

}

Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。

样就可以修改原内容数据。

 

func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
}

make函数创建的map就是一个指针类型,工作原理类似于案例3,所以map数据和切片数据一样虽然值拷贝但依然可以修改原值。

7.7、匿名函数

匿名函数,顾名思义,没有函数名的函数。

匿名函数的定义格式如下:

func (参数列表) (返回参数列表) {
    函数体
}

匿名函数可以在使用函数的时候再声明调用

也可以将匿名函数作为一个func类型数据赋值给变量

Go语言不支持在函数内部声明普通函数,只能声明匿名函数。

package main

import "fmt"

// 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次 (常用)
func main() {
	// 定义匿名函数 :定义的同时调用
	result := func(num1 int, num2 int) int {
		return num1 + num2
	}(10, 20)
	fmt.Println(result)

	//  将匿名函数赋给一个变量,这个变量实际就是函数类型的变量
	// sub等价于匿名函数
	sub := func(num1 int, num2 int) int {
		return num1 - num2
	}

	// 直接调用sub就是调用这个匿名函数
	result01 := sub(30, 70)
	fmt.Println(result01)
}

7.8、高阶函数

一个高阶函数应该具备下面至少一个特点:

  • 将一个或者多个函数作为形参
  • 返回一个函数作为其结果

首先明确一件事情:函数名亦是一个变量

package main

import (
    "fmt"
    "reflect"
)

func addCal(x int, y int)int{
    return x + y
}

func main() {

    var a = addCal
    a(2, 3)
    fmt.Println(a)
    fmt.Println(addCal)
    fmt.Println(reflect.TypeOf(addCal))  // func(int, int) int

}

结论:函数参数是一个变量,所以,函数名当然可以作为一个参数传入函数体,也可以作为一个返回值。

7.8.1、函数参数

package main

import (
    "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 main

import "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)

}

7.8.2、函数返回值

package main

import (
    "fmt"
)

func foo() func(){

    inner := func() {
        fmt.Printf("函数inner执行")
    }

    return inner
}

func main() {

    foo()()

}

7.9、闭包

7.9.1、闭包函数

闭包并不只是一个go中的概念,在函数式编程语言中应用较为广泛。

首先看一下维基上对闭包的解释:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量(外部非全局)的函数。

简单来说就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包函数。

  1. 闭包就是当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之 外执行。
  2. 需要注意的是,自由变量不一定是在局部环境中定义的,也有可能是以参数的形式传进局部环境;另外在 Go 中,函数也可以作为参数传递,因此函数也可能是自由变量。
  3. 闭包中,自由变量的生命周期等同于闭包函数的生命周期,和局部环境的周期无关。
  4. 闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

闭包就一个函数与其相关的引用环境组合的一个整体

闭包的本质: 闭包本质依旧是一个匿名函数,只是这个函数引用了外界的变量/参数 匿名函数+ 引用的变量/参数 = 闭包 特点: 1.返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数,因此这个匿名函数就和变量/参数形成一个整体,构成闭包。 2.闭包中使用的变量/参数会一直保存在内存中,所以会一直使用 意味着闭包不可以滥用(对内存消耗大)

 

package main

import "fmt"

// 函数功能 : 求和
// 函数的名字,getSum 参数为空
//  getSum函数返回值为一个函数,这个函数的参数是一个int类型参数,返回值也是int类型

func getSum() func(int) int {
	var sum int = 0
	return func(num int) int {
		sum = sum + num
		return sum
	}
}

// 闭包:返回的匿名函数+匿名函数以外的变量num
// 匿名函数中引用的那个变量会一直保存在内存中,可以一直使用
func main() {
	f := getSum()
	fmt.Println(f(1)) // 1
	fmt.Println(f(2)) // 3
	fmt.Println(f(3)) // 6
	fmt.Println(f(4)) // 10

	fmt.Println("-------------------------")
	fmt.Println(getSum1(0, 1)) // 1
	fmt.Println(getSum1(1, 2)) // 3
	fmt.Println(getSum1(3, 3)) // 4
	fmt.Println(getSum1(6, 4)) // 10
}

func getSum1(sum int, num int) int {
	sum = sum + num
	return sum
}

// 不使用闭包的时候: 想保留的值,不可以反复使用
// 闭包应用的场景:闭包可以留在上次引用的某个值,我们传入一次就可以反复使用了
package main

import "fmt"

func getCounter() func() {   
    var i = 0
    return func() {
        i++
        fmt.Println(i)
    }

}

func main() {
    counter := getCounter()
    counter()
    counter()
    counter()

    counter2 := getCounter()
    counter2()
    counter2()
    counter2()
}

getCounter完成了对变量i以及counter函数的封装,然后重新赋值给counter变量,counter函数和上面案例的counter函数的区别就是将需要操作的自由变量转化为闭包环境。

7.9.2、闭包函数应用案例

在go语言中可以使用闭包函数来实现装饰器

案例1:计算函数调用次数

package main

import (
    "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 main

import (
    "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)

}

关键点:将一个功能函数作为自由变量与一个装饰函数封装成一个整体作为返回值,赋值给一个新的函数变量,这个新的函数变量在调用的时候,既可以完成原本的功能函数又可以实现装饰功能。

7.10、defer语句

defer语句是go语言提供的一种用于注册延迟调用的机制,是go语言中一种很有用的特性。

7.10.1、defer的用法

defer语句注册了一个函数调用,这个调用会延迟到defer语句所在的函数执行完毕后执行,所谓执行完毕是指该函数执行了return语句、函数体已执行完最后一条语句或函数所在协程发生了恐慌。

 

编程经常会需要申请一些资源,比如数据库连接、打开文件句柄、申请锁、获取可用网络连接、申请内存空间等,这些资源都有一个共同点那就是在我们使用完之后都需要将其释放掉,否则会造成内存泄漏或死锁等其它问题。但操作完资源忘记关闭释放是正常的,而defer可以很好解决这个问题

 

7.10.2、多个defer执行顺序

当一个函数中有很多defer语句时,会按defer定义的顺序逆序执行,也就是说最闲注册的defer函数的用最后执行。

package main

import "fmt"

func main() {
	fmt.Println(add(30, 60))
}

func add(num1 int, num2 int) int {
	// 在Golang中,程序遇到defer关键字,不会立即执行defer后的语句,而是将defer后的语句压入一个栈中,然后继续执行函数后面的语句
	defer fmt.Println("num1=", num1)
	defer fmt.Println("num2=", num1)

	// 栈的特点是:先进后出
	// 在函数执行完毕以后,从栈中取出语句开始执行,按照先进后出的规则执行语句
	var sum int = num1 + num2
	fmt.Println("sum", sum)
	return sum
}

/*
sum 90
num2= 30
num1= 30
90

 */

defer应用场景

package main

import "fmt"

func main() {
   fmt.Println(add(30, 60))
}

func add(num1 int, num2 int) int {
   // 在Golang中,程序遇到defer关键字,不会立即执行defer后的语句,而是将defer后的语句压入一个栈中,然后继续执行函数后面的语句
   defer fmt.Println("num1=", num1)
   defer fmt.Println("num2=", num2)
   num1 += 90
   num2 += 50

   // 栈的特点是:先进后出
   // 在函数执行完毕以后,从栈中取出语句开始执行,按照先进后出的规则执行语句
   var sum int = num1 + num2
   fmt.Println("sum", sum)
   return sum
}

// 遇到defer关键字,会将后面的代码语句压入栈中,也会将相关的值同时拷贝入栈中,不会随着函数后面的变化而变化。
// 不如你想关闭某个场景使用的资源,在使用的时候直接随手defer,因为defer有延迟执行机制,(函数执行完毕后再执行defer压入栈的语句)
// 所以你用完随手写了关闭,比较省心,省事
/*
sum 90
num2= 60
num1= 30
90

*/

7.10.3、defer的拷贝机制

当执行defer语句时,函数调用不会马上发生,会先把defer注册的函数及变量拷贝到defer栈中保存,直到函数return前才执行defer中的函数调用。需要格外注意的是,这一拷贝拷贝的是那一刻函数的值和参数的值。注册之后再修改函数值或参数值时,不会生效。

// 案例1
foo := func() {
fmt.Println("I am function foo1")
}
defer foo()
foo = func() {
fmt.Println("I am function foo2")
}

// 案例2
x := 10
defer func(a int) {
fmt.Println(a)
}(x)    
x++

// 案例3
x := 10
defer func() {
    fmt.Println(x)   // 保留x的地址
}()
x++

7.10.4、defer执行时机

在Go语言的函数,return语句不是原子操作,而是被拆成了两步

rval = xxx
ret

而defer语句就是这两条语句之间执行,也就是

rval = xxx
defer_func
ret rval


defer x = 100
x := 10
return x  // rval=10.   x = 100, ret rval

经典面试题:

package main

import "fmt"

func f1() int {
    i := 5
    defer func() {
        i++
    }()
    return i
}
func f2() *int {

    i := 5
    defer func() {
        i++
        fmt.Printf(":::%p\n", &i)
    }()
    fmt.Printf(":::%p\n", &i)
    return &i
}

func f3() (result int) {
    defer func() {
        result++
    }()
    return 5 // result = 5;ret result(result替换了rval)
}

func f4() (result int) {
    defer func() {
        result++
    }()
    return result // ret result变量的值
}

func f5() (r int) {
    t := 5
    defer func() {
        t = t + 1
    }()
    return t // ret r = 5 (拷贝t的值5赋值给r)
}

func f6() (r int) {
    fmt.Println(&r)
    defer func(r int) {
        r = r + 1
        fmt.Println(&r)
    }(r)
    return 5
}

func f7() (r int) {
    defer func(x int) {
        r = x + 1
    }(r)
    return 5
}

func main() {

    // println(f1())
    // println(*f2())
    // println(f3())
    // println(f4())
    // println(f5())
    // println(f6())
    // println(f7())

}

在命名返回方式中,最终函数返回的就是命名返回变量的值,因此,对该命名返回变量的修改会影响到最终的函数返回值!

7.11、递归函数

一种计算过程,如果其中每一步都要用到前一步或前几步的结果,称为递归的。用递归过程定义的函数,称为递归函数,例如连加、连乘及阶乘等。

递归特性:

  1. 调用自身函数
  2. 必须有一个明确的结束条件
  3. 在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返 回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

案例1

package main

import "fmt"

func factorial(n int)int{
    if n == 0{
        return 1
    }
    return n * factorial(n-1)

}

func main() {

    // 计算n的阶乘,即 n!
    var ret = factorial(4)
    fmt.Println(ret)
}

 

案例2

这个数列生成规则很简单,每一项都是前两项的和,举例 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……

package main

import "fmt"

func fib(n int) int {
    if n == 2 || n == 1 {
        return 1
    }
    return fib(n-1) + fib(n-2)

}

func main() {

    // 计算n的阶乘,即 n!
    ret:=fib(6)
    fmt.Println(ret)
}

 

 

标签:函数,int,fmt,func,Println,main
From: https://www.cnblogs.com/xiaohaoge/p/16860538.html

相关文章

  • respository coun函数报错
    在使用springdatar2dbcrespositorycount时报上面错误,排查后了现是这句this.handbookRepository.count()代码报错。看了下实体类主键ID上没有加@Id注解,加上后运行正......
  • 绝对值函数
    取绝对值函数在多个头文件均有定义一、stdlib.h_Check_return_int__cdeclabs(_In_int_Number);_Check_return_long__cdecllabs(_In_lo......
  • init函数总开关 然后再里面监听事件
    init函数绑定事件......
  • gcc 好玩的 builtin 函数
    gcc好玩的builtin函数前言在本篇文章当中主要想给大家介绍一些在gcc编译器当中给我们提供的一些好玩的内嵌函数(builtinfunction)......
  • python中的函数(1)
     #1.函数#函数就是将一段具有独特功能的代码段整合到一个整体并命名#在需要的位置调用这个名称即可完成对应的需求#函数的作用:封装代码(方便管理),实现代码重用......
  • Animator工具函数收集
    1)获取AnimationClippublicstaticboolTryGetAnimatorClip(Animatoranimator,stringclipName,outAnimationClipoutClip){varclips=animator.runtimeAn......
  • 构造函数
    作用:帮助我们初始化对象(给对象的每个属性依次赋值) 构造函数是一个特殊的方法:1)构造函数没有返回值,连void也不能写2)构造函数的名称必须和类名一样(你的类叫Person,你的构......
  • 【C语言进阶】三.字符串函数
    (一)字符串函数1.strlen(计算字符串元素数)(1)用法size_tstrlen(constchar*str)字符串已经'\0'作为结束标志,strlen函数返回的是在字符串中'\0'前面出现的字符个数(不包......
  • 三种回归损失函数
    详细介绍这里,清楚的介绍了三种损失函数。我这里重点记录一下他们的异同,方便自己消化理解。1、对于回归损失函数,通常主要有MSE(均方误差),MAE(平均绝对误差),HuberLoss。其中,Hub......
  • 欧拉函数
    欧拉函数欧拉函数\(\phi(n)\)表示小于等于\(n\)的和\(n\)互质的数的个数。求一个数\(n\)的欧拉函数,设将\(n\)质因数分解后的质因数集合为\(p_{1...m}\),则\(......