Golang 学习之路(三)函数

函数

  • 函数声明
  • 函数值
  • 匿名函数
  • Deferred函数

函数声明

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

在 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\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()
....

你可能感兴趣的:(Golang,入门,Golang,Go,入门,Go,函数,go)