首页 > 其他分享 >golang学习笔记13-函数(二):init函数,匿名函数,闭包,defer

golang学习笔记13-函数(二):init函数,匿名函数,闭包,defer

时间:2024-09-25 17:52:58浏览次数:10  
标签:闭包 13 函数 int fmt func Println main

注:本人已有C,C++,Python基础,只写本人认为的重点。
这个知识点基本属于go的特性,比较重要,需要认真分析。

一、init函数

每个文件都可以定义init函数,它会在main函数执行前被调用,无论它的定义位置是在main后还是前。而全局变量的优先级又高于init,所以优先级是这样的:全局变量>init>main。示例如下:

package main

import "fmt"

var a = test()

func test() int {
	fmt.Println("test已执行")
	return 1
}

func init() {
	fmt.Println("init已执行")
}

func main() {
	fmt.Println("main已执行")
}

上述程序的输出是:

test已执行
init已执行
main已执行

当多个文件存在init时,比如main所依赖的包中也有init,结果会怎样呢?假设main和依赖的包testutils内容如下:
main

package main

import (
	"fmt"
	"mod05/demo07/testutils"
)

var a = test()

func test() int {
	fmt.Println("test已执行")
	return 1
}

func init() {
	fmt.Println("main中的init已执行")
}

func main() {
	fmt.Println("main已执行")
	fmt.Println("age=", testutils.Age, "sex=",
		testutils.Sex, "name=", testutils.Name)
}


testutils

package testutils

import "fmt"

var Age int
var Sex string
var Name string

func init() {
	fmt.Println("testutils中的init已执行")
	Age, Sex, Name = 19, "女", "张三"
}

则程序运行结果为

testutils中的init已执行
test已执行
main中的init已执行
main已执行
age= 19 sex= 女 name= 张三

显然,导入的包先执行,然后main中的test执行前先初始化全局变量,再执行test,最后执行init和main。所以顺序是:utils的全局变量->utils的init->main文件的全局变量>main文件的init->main文件的main函数。
总结下init的优先级:文件之间,被导包>当前包,文件内,全局变量>init>main

二、匿名函数

相对于C++和python的匿名函数,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)
	result02 := sub(30, 70)
	fmt.Println(result02)
}

需要注意的是,匿名函数定义后如果不用括号,那么这个变量就是匿名函数本身,如果用括号就是调用一次匿名函数,得到的是这个匿名函数的返回值,这个要好好理解,后面会有相关练习。

三、闭包(closure)

当函数返回一个匿名函数,且该匿名函数使用了它之外的变量,这个外部变量+该匿名函数就组成了一个闭包(closure),闭包形成后,该外部变量会一直留在内存中,示例如下:

package main

import "fmt"

func getSum() func(int) int {
	var sum int = 0            // 闭包中使用的变量
	return func(num int) int { // 函数中返回一个匿名函数
		sum = sum + num // 引用外部变量sum
		return sum
	}
	//返回的匿名函数+匿名函数以外的变量sum形成了闭包
}

func main() {
	f := getSum()
	// 调用闭包
	fmt.Println(f(1)) //1
	fmt.Println(f(2)) //3
	fmt.Println(f(3)) //6
	fmt.Println(f(4)) //10
	// 这里,变量 sum 仍然存活,因为闭包仍然在使用它
	// 让闭包的引用消失
	f = nil // 现在没有任何引用指向 sum
	// 之后,如果没有其他地方引用 sum,它将被垃圾回收
	fmt.Println("----------------------")
	fmt.Println(getSum01(0, 1)) //1
	fmt.Println(getSum01(1, 2)) //3
	fmt.Println(getSum01(3, 3)) //6
	fmt.Println(getSum01(6, 4)) //10
}

//不使用闭包的时候:我想保留的值,不可以反复使用
//闭包应用场景:闭包可以保留上次引用的某个值,我们传入一次就可以反复使用了
func getSum01(sum int, num int) int {
	sum = sum + num
	return sum
}

闭包进阶:匿名函数的闭包
练习1:分析以下几段代码,它们的输出分别是?(如果是地址就答地址即可)
代码1

func main() {
	counter := func() func() int {
		count := 0
		return func() int {
			count++
			return count
		}
	}()
	fmt.Println(counter())
	fmt.Println(counter())
	fmt.Println(counter())
}

代码2

func main() {
	counter := func() func() int {
		count := 0
		return func() int {
			count++
			return count
		}
	}
	fmt.Println(counter())
	fmt.Println(counter())
	fmt.Println(counter())
}

代码3

func main() {
	counter := func() func() int {
		count := 0
		return func() int {
			count++
			return count
		}
	}
	fmt.Println(counter()())
	fmt.Println(counter()())
	fmt.Println(counter()())
}

这个分析起来还是有一定难度的,留到文末讲,读者可先思考一会儿。

四、defer关键字

defer是go的一个关键字,用于推迟执行函数或函数调用语句,直到外层函数返回后再执行这个函数或函数调用语句。具体来说就是将当前函数或函数调用语句压入一个栈中,等外层函数执行完后,再按栈的顺序(后进先出,数据结构的内容)取出栈顶元素。注意,压入栈中时,函数或调用语句中的变量的值也会一起保存,属于值传递,示例如下:

package main

import "fmt"

func main() {
	res := func(num1 int, num2 int) int {
		defer fmt.Println("num1=", num1)
		defer fmt.Println("num2=", num2)
		num1 += 90
		num2 += 50
		return num1 + num2
	}(30, 60)
	fmt.Println("sum=", res)
}

显然,num1和num2的值在函数体开头就被保存到栈中了,所以程序输出如下:

num2= 60
num1= 30
sum= 230

OK,我们再来看前面的练习,其关键在于匿名闭包:

counter := func() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}()

首先,不用管返回值具体是什么,你得先搞清楚匿名函数的概念,之前说过:无括号,返回的就是匿名函数本身,有括号,就是匿名函数返回值。所以代码2和3就能做出来了:代码2调用了三次匿名函数,由于返回的是闭包,所以得到的是函数(闭包的本质就是匿名函数),将打印三次函数的地址(引用)。由于每次是重新调用匿名函数,所以是也就是刷新了三次闭包,得到了三个一样的闭包地址。代码3的counter也是函数,但调用语句多了括号,所以每次是重新调用匿名函数并调用闭包,所以是得到了三个一样的闭包的返回值,即三个1。理解了这两个,代码1就好理解了,有括号说明得到的是闭包,匿名函数就调用了这一次,所以之后操作的都是同一个闭包,并不会刷新。而闭包中的外部变量是一直存在的,所以结果是1 2 3。
到这里,如果你能完全理解,说明你对闭包和匿名函数的掌握到位了。这题呢,其实是本人和ChatGPT共同制作的一个题,因为我在问它闭包知识的时候,它给我的是代码1,然后我就在这基础上改了下并加以思考。这说明学习要多举一反三,多扩展一些情况,那么你对这个知识点的理解就比别人更深,这也是提高学习效率的方式之一,因为如果理解太浅,到后面就得补来补去,会浪费不少时间。

标签:闭包,13,函数,int,fmt,func,Println,main
From: https://blog.csdn.net/weixin_54259326/article/details/142490625

相关文章

  • 类和对象(2)(六个默认成员函数)
     个人主页:Jason_from_China-CSDN博客所属栏目:C++系统性学习_Jason_from_China的博客-CSDN博客所属栏目:C++知识点的补充_Jason_from_China的博客-CSDN博客类的默认成员函数概念概述默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。一个......
  • 阿里云函数计算 x NVIDIA 加速企业 AI 应用落地
    前言阿里云函数计算(FunctionCompute,FC)是一种无服务器(Serverless)计算服务,允许用户在无需管理底层基础设施的情况下,直接运行代码。与传统的计算架构相比,函数计算具有高灵活性和弹性扩展的特点,用户只需专注于业务逻辑的开发,系统自动处理计算资源的分配、扩展和维护。同时,函数计算......
  • 大数据-137 - ClickHouse 集群 表引擎详解2 - MergeTree 存储结构 一级索引 跳数索引
    点一下关注吧!!!非常感谢!!持续更新!!!目前已经更新到了:Hadoop(已更完)HDFS(已更完)MapReduce(已更完)Hive(已更完)Flume(已更完)Sqoop(已更完)Zookeeper(已更完)HBase(已更完)Redis(已更完)Kafka(已更完)Spark(已更完)Flink(已更完)ClickHouse(正在更新···)章节内容上节我们完成了如下的内容:表引擎详解介绍日志......
  • 大数据-139 - ClickHouse 集群 表引擎详解4 - MergeTree 实测案例 ReplacingMergeTree
    点一下关注吧!!!非常感谢!!持续更新!!!目前已经更新到了:Hadoop(已更完)HDFS(已更完)MapReduce(已更完)Hive(已更完)Flume(已更完)Sqoop(已更完)Zookeeper(已更完)HBase(已更完)Redis(已更完)Kafka(已更完)Spark(已更完)Flink(已更完)ClickHouse(正在更新···)章节内容上节我们完成了如下的内容:MergeTree存储结构Me......
  • 如何在低成本ARM平台部署LVGL免费图形库,基于全志T113-i
    LVGL简介LVGL(LittlevGraphicsLibrary)是一个开源的图形库,主要用于嵌入式系统创建图形用户界面(GUI),采用C语言编写,具有高效性和可定制性,在各种微控制器平台和显示硬件上开发用户界面时备受欢迎。LVGL具社区免费开源、控件资源丰富、跨平台可移植等特点。社区免费开源:完全免费,......
  • python调用另一个.py文件中的类和函数或直接运行另一个.py文件
    同一文件夹下的调用1.调用函数A.py文件如下:defadd(x,y):print('和为:%d'%(x+y))在B.py文件中调用A.py的add函数如下:importAA.add(1,2)或fromAimportaddadd(1,2)2.调用类A.py文件如下:classA:def__init__(self,xx,yy):self.x=xxself.y=y......
  • AI产品经理必知的133个专业术语
     一、机器学习与数据科学1、监督学习(SupervisedLearning)监督学习是机器学习的一种形式,其中模型通过带标签的数据集进行训练。训练数据包括输入特征(X)和对应的输出标签(Y),模型从中学习输入与输出的关系。2、无监督学习(UnsupervisedLearning)无监督学习是另一种机器学习形式,......
  • 线性判别分析 (LDA)中目标函数的每个部分的具体说明
    公式:F=∥w......
  • 广州C++信奥赛老师解一本通题 1389:亲戚
    ​ 【题目描述】若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的某个人所在家族的人数。规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。【输入】第一行:三个整数n,(n......
  • 134页满分PPT | 某酒业集团智能工厂信息化顶层架构设计
    项目背景及需求理解客户正面临新的竞争环境,需要通过“三网融合建设工程”来应对市场变化和需求。企业战略和业务战略的制定,旨在通过整合前端市场掌控、中端运营保障和后端支持,以实现销售收入的增长和市场地位的稳固。在这样的背景下,需要对智能工厂进行信息化顶层架构设计,以支撑企业......