ch13、函数-一等公民

本文详细介绍了Go语言中函数作为一等公民的特性,包括函数的声明格式、返回值、值传递与引用传递的区别,以及匿名函数和闭包的概念和应用场景。重点阐述了Go函数如何处理不同类型的参数,特别是值类型和引用类型在函数调用中的差异,以及匿名函数在函数嵌套、一次性任务和不确定操作中的用途。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ch13、函数-“一等公民”

1、特性

声明格式:

func 函数名称(形参列表)(返回值列表){
    函数体;
}

匿名函数:
func(parameter) returnType {}

主要特性:

  • 可以有多个返回值
  • 所有参数可以说都是值传递: slice, map, channel 和指针会有传引用的错觉(拷贝的是内存地址的值)
  • 函数可以作为变量的值
  • 函数可以作为参数和返回值
package function

import (
	"fmt"
	"math/rand"
	"testing"
	"time"
)

func returnMultiValues() (int, int) {// 可以有多个返回值
	return rand.Intn(10), rand.Intn(20)
}

func timeSpent(inner func(op int) int) func(op int) int {   // 参数是函数,返回值也是函数
	return func(n int) int {
		start := time.Now()
		ret := inner(n)
		fmt.Println("time spent: ", time.Since(start).Seconds())
		return ret
	}
}

func slowFunc(op int) int {
	time.Sleep(time.Second * 1)
	return op
}

func TestFn(t *testing.T) {
	_, b := returnMultiValues()
	t.Log(b)

	tsSF := timeSpent(slowFunc) // 传入的是函数,返回的也是函数实例 - 函数式编程
	t.Log(tsSF(10))
}

学习函数式编程,推荐书籍:《计算机程序的构造和解释》

2、函数返回值

  • Go语言中可以给函数的返回值指定名称
package main

import "fmt"

func main() {
	fmt.Println(sum())
}

// 给返回值指定了一个名称叫做res, return时会自动将函数体内部res作为返回值
// 其实本质就是提前定义了一个局部变量res, 在函数体中使用的res就是这个局部变量,返回的也是这个局部变量
func sum() (res int) {
	res = 1 + 1
	return
}

/*
output:
2
*/
  • Go语言中的函数允许有多个返回值函数
package main

import "fmt"

func main() {
	fmt.Println(calculate(2, 1))
}

// a, b都是int类型, 所以只需要在b后面添加int即可
func calculate(a, b int) (sum, sub int) {
	sum = a + b
	sub = a - b
	return
}

/*
output:
3 1
*/

3、值传递和引用传递

  • Go语言中值类型有: int系列、float系列、bool、string、数组、结构体
    • 值类型通常在栈中分配存储空间
    • 值类型作为函数参数传递, 是拷贝传递
    • 在函数体内修改值类型参数, 不会影响到函数外的值(此处对于指针、slice、map、channel类型是有差异的)
// int
package main
import "fmt"
func main() {
	num := 10
	change(num)
	fmt.Println(num) // 10
}
func change(num int)  {
	num = 998
}
// array
package main
import "fmt"
func main() {
	arr := [3]int{1, 3, 5}
	change(arr)
	fmt.Println(arr) // 1, 3, 5
}
func change(arr [3]int)  {
	arr[1] = 8
}
// struct
package main
import "fmt"
type Person struct {
	name string
	age int
}
func main() {
	p := Person{"lnj", 33}
	change(p)
	fmt.Println(p.name) // lnj
}
func change(p Person)  {
	p.name = "zs"
}
  • Go语言中引用类型有: 指针、slice、map、channel
    • 引用类型通常在堆中分配存储空间
    • 引用类型作为函数参数传递,是引用传递(也可以理解是值传递,拷贝的是内存地址)
    • 在函数体内修改引用类型参数,会影响到函数外的值(这才是主要差别)
// 指针类型
package main
import "fmt"
func main() {
	num := 10
	change(&num)
	fmt.Println(num) // 998
}
func change(num *int)  {
	*num = 998
}
// 指针类型
package main

import "fmt"

type Person struct {
	name string
	age  int
}

func main() {
	p := Person{"lnj", 33}
	change(&p)
	fmt.Println(p.name) // zs
}
func change(p *Person) {
	p.name = "zs"
}
// slice
package main
import "fmt"
func main() {
	arr := []int{1, 3, 5}
	change(arr)
	fmt.Println(arr) // 1, 8, 5
}
func change(arr []int)  {
	arr[1] = 8
}
// map
package main
import "fmt"
func main() {
	mp := map[string]string{"name":"lnj", "age":"33"}
	change(mp)
	fmt.Println(mp["name"]) // zs
}
func change(mp map[string]string)  {
	mp["name"] = "zs"
}

4、匿名函数

  • 匿名函数也是函数的一种, 它的格式和普通函数一模一样,只不过没有名字而已

    • 普通函数的函数名称是固定的, 匿名函数的函数名称是系统随机的
  • 匿名函数可以定义在函数外(全局匿名函数),也可以定义在函数内(局部匿名函数), Go语言中的普通函数不能嵌套定义, 但是可以通过匿名函数来实现函数的嵌套定义

    • 全局匿名函数(一般情况下很少使用全局匿名函数)
    package main
    
    import "fmt"
    
    // 方式一
    var a = func() {
    	fmt.Println("hello world1")
    }
    
    // 方式二
    var (
    	b = func() {
    		fmt.Println("hello world2")
    	}
    )
    
    func main() {
    	a()
    	b()
    }
    
  • 一般情况下很少使用全局匿名函数, 大多数情况都是使用局部匿名函数, 匿名函数可以直接调用、保存到变量、作为参数或者返回值

    • 直接调用
package main

import "fmt"

func main() {
	func(s string) {
		fmt.Println(s)
	}("hello lnj")
}
  • 保存到变量
package main
import "fmt"
func main() {
	a := func(s string) {
		fmt.Println(s)
	}
	a("hello lnj")
}
  • 作为参数
package main
import "fmt"
func main() {
	test(func(s string) {
		fmt.Println(s)
	})
}
func test(f func(s string))  {
	f("hello lnj")
}
  • 作为返回值
package main
import "fmt"
func main() {
	res := test()
	res(10, 20)
}
func test() func(int, int) {
	return func(a int, b int) {
		fmt.Println(a + b)
	}
}
  • 匿名函数应用场景
    • 当某个函数只需要被调用一次时, 可以使用匿名函数
    • 需要执行一些不确定的操作时,可以使用匿名函数

5、闭包

  • 闭包是一个特殊的匿名函数, 它是匿名函数和相关引用环境组成的一个整体
    • 也就是说只要匿名函数中用到了外界的变量, 那么这个匿名函数就是一个闭包
package main

import "fmt"

func main() {
	num := 10
	a := func() {
		num++            // 在闭包中用到了main函数中的num, 所以这个匿名函数就是一个闭包
		fmt.Println(num) // 11
	}
	a()
}
  • 闭包中使用的变量和外界的变量是同一个变量, 所以可以闭包中可以修改外界变量
  • 只要闭包还在使用外界的变量, 那么外界的变量就会一直存在
package main

import "fmt"

func main() {
	res := addUpper()  // 执行addUpper函数,得到一个闭包
	fmt.Println(res()) // 2
	fmt.Println(res()) // 3
	fmt.Println(res()) // 4
	fmt.Println(res()) // 5
}
func addUpper() func() int {
	x := 1
	return func() int {
		x++ // 匿名函数中用到了addUpper中的x,所以这是一个闭包
		return x
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值