Golang 学习之路(三)函数

本文详细探讨了Go语言中的函数特性,包括函数声明、匿名函数、函数值和Deferred函数的概念及应用。通过实例展示了如何利用这些特性提高代码的复用性和效率。

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

函数声明

函数可以为我们隐藏某一个分解的任务的细节实现,暴露出需要的参数和返回结果。

在 Go 语言中,如果一组形参或者返回值有相同的类型,我们不必为每个形参都写出参数类型。

func f(i int, j int, k int,  s string, t string) { /* ... */ }
===> 等价于
func f(i, j, k int, s, t string)                 { /* ... */ }

在函数体中,函数的形参作为局部变量,被初始化为调用者提供的值。函数的形参和有名返回值作为函数最外层的局部变量,被存储在相同的词法块中。

//Go函数的返回值变量能被提前声明,并且作用于整个函数的区块内
func f(x,y int) (z int,a string)  {
	z = x + y
	a = "OK"
	return
}

fmt.Println(f(1,1)) 		//2	OK	

实参通过值的方式传递,因此函数的形参是实参的拷贝。对形参进行修改不会影响实参。但是,如果实参包括引用类型,如指针,slicemapfunctionchannel等类型,实参可能会由于函数的间接引用被修改。

函数值

在Go中,函数像其他值一样,拥有类型,可以被赋值给其他变量,传递给函数,从函数返回。对函数值的调用类似函数调用一样,函数可以赋值给一个变量,然后通过变量来实现

func square(n int) int {
	return n * n
}

func show(m,n int) int {
	return m + n
}

f1 := square
fmt.Printf("%T\n",f1)			//func(int) int
fmt.Println(f1(2))				//4

f2 := show
fmt.Printf("%T\n",f2)			//func(int, int) int
fmt.Println(f2(3,12))			//15

函数值可以减少重复代码的使用,增加复用

package part1
//获取url网页内容的函数
func FetchUrl(urls []string) (s string){
	for _, url := range urls {
		resp, err := http.Get(url)
		if err != nil {
			log.Fatalf("fetch failed : %v\n",err)
		}
		byte, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			log.Fatalf("fetch reading content failed: %v\n",err)
		}
		s += string(byte)
	}
	return
}
var depth int
func startElement(n *html.Node) {
	if n.Type == html.ElementNode {
		fmt.Printf("%*s<%s>\n", depth*2, "", n.Data)
		depth++
	}
}

func endElement(n *html.Node) {
	if n.Type == html.ElementNode {
		depth--
		fmt.Printf("%*s</%s>\n", depth*2, "", n.Data)
	}
}
//遍历节点之前,通过前置和后置函数处理添加空格
//pre函数和end函数都是可选的,并且作为参数传递
func forEachElement(n *html.Node,pre,end func(n *html.Node))  {

	if pre != nil {
		pre(n)
	}

	for c := n.FirstChild;c != nil;c = c.NextSibling{
		forEachElement(c,pre,end)
	}

	if end != nil {
		end(n)
	}
}

func TestForEachElement(t *testing.T) {
	var url = []string{"https://www.baidu.cn"}
	s := part1.FetchUrl(url)
	doc, err := html.Parse(strings.NewReader(s))
	if err != nil {
		fmt.Printf("findlinks1: %v\n", err)
	}
    //这里直接传入startElement和endElement函数作为参数
    forEachElement(doc,startElement,endElement)
}

匿名函数

拥有函数名的函数只能在包级语法块中被声明,通过函数字面量,我们可绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明相似,区别在于func关键字后没有函数名。函数值字面量是一种表达式,它的值被称为匿名函数。

把上面的square函数来改写下

func square() func() int{
	var x int
	return func() int{
		x++
		return x*x
	}
}

没有名称的函数func() int作为函数square的返回值,然后该匿名函数每次被调用的时候都会返回下一个数的平方,每次调用匿名函数时,该函数都会先使x的值加 1 ,再返回x的平方。第二次调用square时,会生成第二个x变量,并返回一个新的匿名函数。新匿名函数操作的是第二个x变量。

//测试调用square函数
func TestTwo(t *testing.T)  {
	f3 := square()				//将该函数值赋予变量f3
	
	fmt.Println(f3())			//1
	fmt.Println(f3())			//4
	fmt.Println(f3())			//9			
	fmt.Println(f3())			//16
	fmt.Println(f3())			//25

}

在连续调用函数f3之后,打印的值表明在匿名函数中x递增的,函数square和匿名函数之间存在着变量引用,函数值可以记录状态。也可以理解为为什么函数值是引用类型并且函数值无法比较。Go 使用闭包技术实现函数值,函数值也叫做闭包。

通过这个例子,我们看到变量的生命周期不由它的作用域决定:square返回后,变量x仍然隐式的存在于f中。

Deferred函数

defer后面的函数总是会在包含defer的外围函数执行完毕后才会执行,也就是说延时执行,不论包含defer语句的函数是通过return正常结束,还是由于panic导致的异常结束。你可以在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。

func TestDeferred1(t *testing.T) {
	a, b := 1, 2

	defer func() {
		fmt.Println(a, b)

		a = a * 3
		b = 0

		fmt.Println(a, b)

	}()

	a, b = 10, 20

	fmt.Printf("----->%d\t%d\n", a, b)

}
//输出:
----->10	20
10 20
30 0

可以看到,一开始并没有按照顺序执行defer后面匿名函数的内容,而是执行了后面的赋值a, b = 10, 20操作和打印,并且后续匿名函数的a,b的值也不是初始化的 1 和 2,而是 10 和 20。

在匿名函数传参之后

func TestDeferred2(t *testing.T) {
	a, b := 1, 2

	defer func(a, b int) {
		fmt.Println(a, b)

		a = a * 3
		b = 0

		fmt.Println(a, b)

	}(a, b)

	a, b = 10, 20

	fmt.Printf("----->%d\t%d\n", a, b)

}
//输出
----->10	20
1 2
3 0

在将参数传入到匿名函数之后,匿名函数保留着传入之前的值,不再受到延迟执行之前其他的变量赋值操作,等价于下面的:

func TestDeferred3(t *testing.T) {
	a, b := 1, 2

	defer sh(a,b)

	a, b = 10, 20

	fmt.Printf("----->%d\t%d\n", a, b)
}

func sh(a, b int) {
	fmt.Println(a, b)

	a = a * 3
	b = 0

	fmt.Println(a, b)
}
//输出
----->10	20
1 2
3 0

defer语句经常被用于处理类似开始-关闭的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。

resp, err := http.Get(url)
defer resp.Body.Close()
....
内容概要:本文档详细介绍了基于Google Earth Engine (GEE) 构建的阿比让绿地分析仪表盘的设计与实现。首先,定义了研究区域的几何图形并将其可视化。接着,通过云掩膜函数和裁剪操作预处理Sentinel-2遥感影像,筛选出高质量的数据用于后续分析。然后,计算中值图像并提取NDVI(归一化差异植被指数),进而识别绿地及其面积。此外,还实现了多个高级分析功能,如多年变化趋势分析、人口-绿地交叉分析、城市热岛效应分析、生物多样性评估、交通可达性分析、城市扩张分析以及自动生成优化建议等。最后,提供了数据导出、移动端适配和报告生成功能,确保系统的实用性和便捷性。 适合人群:具备一定地理信息系统(GIS)和遥感基础知识的专业人士,如城市规划师、环境科学家、生态学家等。 使用场景及目标:①评估城市绿地分布及其变化趋势;②分析绿地与人口的关系,为城市规划提供依据;③研究城市热岛效应及生物多样性,支持环境保护决策;④评估交通可达性,优化城市交通网络;⑤监测城市扩张情况,辅助土地利用管理。 其他说明:该系统不仅提供了丰富的可视化工具,还集成了多种空间分析方法,能够帮助用户深入理解城市绿地的空间特征及其对环境和社会的影响。同时,系统支持移动端适配,方便随时随地进行分析。用户可以根据实际需求选择不同的分析模块,生成定制化的报告,为城市管理提供科学依据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值