Golang ”一等公民“——函数(function)

一、何为“一等公民(first Class)”——函数

  1. 函数允许多返回值,这是与目前主流的C/C++、Java存在差异的。
  2. 函数本身可以作为值进行传递
  3. 函数可以作为变量的值
  4. 函数可以作为参数和返回值
  5. 支持匿名函数(没有名字的函数)一般用于工厂模式。
  6. 可以满足接口。
    函数 是可重复使用的、实现单一或相关联的代码片段,其目的是模块化编程,提高模块性和代码的复用率。

二、声明函数:普通函数必须先声明才能调用

普通函数声明规则
  1. 编译器通过声明才能了解函数应该如何调用代码和函数体之间的交互(产地参数、返回参数)
  2. 函数名:第一个字母非数字的字母、下划线、数字组成。
  3. 在同一个包(同一个文件夹下)内,函数名不允许重名。
  4. 参数列表中的变量作为局部变量存在。
  5. 函数在声明有返回值时,必须在函数体中使用 return 语句提供返回值列表。
  6. 在参数列表中。多个参数变量,以逗号分隔。
  7. 命名的返回值变量的默认值为类型的默认值,即数值为0,字符串为空字符串,布尔值为false,引用为nil。

函数基本结构格式:
func 函数名(参数列表) (返回值列表){
函数体
}

三、调用函数:代码之间的跳转

函数在定义后,通过调用的方式,让当前代码跳转到被调用的函数中进行执行。被调用的函数(调用前的函数)局部变量都会保存起来(栈)不会丢失;当被调用的函数结束后,会恢复到被调用函数的下一行继续执行代码,之前的局部变量也能继续访问。
小结: 函数的局部变量只能在函数体内使用,且调用结束后,这些局部变量都会被释放且失效(GC回收)。

Go 语言中函数调用格式:
返回值变量列表 = 函数名(参数列表)

四、参数传递——Golang 中所有参数都是值传递

Go 语言中,传入和返回参数在调用和返回时都使用 值传递
我们需要注意的是:注意切片(slice)、指针(pointer)、映射(map)等引用型对象指向的内容在参数传递中不会发生复制,而是将指针进行复制,类似创建了一次引用。即参数传递过程中实际复制的地址(指针),没有对引用类型指向的内容进行复制。
代码实践下:

type Data struct {
	complex  []int     //测试slice 在参数传递过程中的效果
	instance InnerData //实例分配的 innerdata 结构体成员
	ptr      *InnerData //指针
}

//代表各种结构体字段
type InnerData struct {
	a int
}

func passByValue(inFunc Data) Data {
	// 输出参数的成员情况
	fmt.Printf("inFunc value %+v:\n", inFunc)

	fmt.Printf("inFunc ptr:%p\n", &inFunc)
	return inFunc

}

func TestInfunc(t *testing.T) {
	in := Data{
		complex: []int{2, 4, 9},
		instance: InnerData{
			7,
		},
		ptr: &InnerData{5},
	}
	// 输入结构的成员及指针情况
	t.Logf("in value:%+v", in)
	t.Logf("in ptr:%p", &in)

	// 输出结构的成员及指针情况
	out := passByValue(in)
	t.Logf("out value:%+v", out)
	t.Logf("out ptr:%p", &out)
}

//Run Result:
  === RUN   TestInfunc
  inFunc value {complex:[2 4 9] instance:{a:7} ptr:0xc000044170}:
  inFunc ptr:0xc000058420
  --- PASS: TestInfunc (0.00s)
      DataTrasfer_test.go:38: in value:{complex:[2 4 9] instance:{a:7} ptr:0xc000044170}
      DataTrasfer_test.go:39: in ptr:0xc000058390
      DataTrasfer_test.go:42: out value:{complex:[2 4 9] instance:{a:7} ptr:0xc000044170}
      DataTrasfer_test.go:43: out ptr:0xc0000583f0
  PASS
由以上结果发现:
  1. 所有的 Data 结构的指针地址发生了变化,(inptr、outptr、infuncptr)意味着所有的结构存储在不同的内存中,无论将Date 结构传入函数内部,还是通过返回值传回Data 都是发生复制行为,即将Data 拷贝到一块新的内存。
  2. 所有的Data 结构中的成员值均没有发生变化,原样传递,即所有参数都是值传递。
  3. Data 结构的 ptr 成员在传递过程中,未发生变化,表示指针在函数参数传递中传递的只是指针值,不会对指针指向的内容进行拷贝。

五、函数变量——函数作为值保存到变量

Go中,函数也被视作一种类型,因此可以同基本类型一样被保存到变量。

func hello(str string) string {
	return str
}

func TestFuncVar(t *testing.T) {
	// 声明相同的函数变量用于保存函数
	var f func(string) string

	// 将函数作为值赋值到函数变量中
	f = hello
	t.Log(f("Golang"))
}

//Run restult:
  === RUN   TestFuncVar
  --- PASS: TestFuncVar (0.00s)
      FuncVar_test.go:17: Golang
  PASS

如以上所示,将函数作为值传递给函数变量,同样利用这个特性,可以通过把函数赋值给各类型的变量,例如将数据的处理函数赋值给切片类型函数变量,可以对数据进行链式处理等等。

六、匿名函数——没有名字的普通函数

  1. 函数 可以作为一种类型被赋值给函数类型的变量,利用这个特性,匿名函数同样可以 以变量方式被传递。
  2. 匿名函数通常被用于实现 回调函数闭包(closure)
  3. 匿名函数 本身是一种值(value),可以方便的存储 在各种容器类型中 实现 回调函数和操作封装

匿名函数声明、调用

  1. 在声明后直接调用
func TestUnamefunDir(t *testing.T) {
    // s 用于接收匿名函数的返回值
	s := func(a string) string {
		return a
	}("hello")

	t.Log(s)
}

/*
  === RUN   TestUnamefunDir
  --- PASS: TestUnamefunDir (0.00s)
      Unamefunc_test.go:10: hello
  PASS
*/

注意:匿名函数最后的 }(“hello”) 表示对匿名函数直接调用。

  1. 匿名函数赋值给变量
func TestUnamefunVar(t *testing.T) {
	// 匿名函数赋值给变量
	s := func(a string) string {
		return a
	}
	// 通过   s() 调用匿名函数
	t.Log(s("hello"))
}

/*
  === RUN   TestUnamefunVar
  --- PASS: TestUnamefunVar (0.00s)
      Unamefunc_test.go:25: hello
  PASS
*/

注意和上面声明后直接调用匿名函数区分,前者是将匿名函数的返回值赋值给变量 s,后者是将匿名函数赋值给函数变量 s。

匿名函数实现回调函数

通过对切片的遍历实现匿名函数作为回调函数。同时不同的回调函数将会实现对切片的不同的遍历操作。

func Range(list []int, f func(int)) { //slice
	for _, v := range list {
		f(v)
	}
}

func TestRangeList(t *testing.T) {
	Range([]int{1, 2, 5, 9}, func(b int) { //slice
		t.Log(b)
	})
}

/*
  === RUN   TestRangeList
  --- PASS: TestRangeList (0.00s)
      Unamefunc_test.go:49: 1
      Unamefunc_test.go:49: 2
      Unamefunc_test.go:49: 5
      Unamefunc_test.go:49: 9
  PASS
*/

匿名函数实现操作封装

通过对行为的定义,将匿名函数作为map 的键值,通过这个可以实现命令的子命令。


// 匿名函数实现操作封装
func TestUnameFuncPatch(t *testing.T) {
	behavior := map[string]func(){
		"walk": func() {
			t.Log("Go Walking")
		},
		"fly": func() {
			t.Log("Go Flying")
		},
		"jump": func() {
			t.Log("Go Jumpping")
		},
	}

	do := []string{"walk", "fly", "jump", "play"}
	for i := range do {
		if f, ok := behavior[do[i]]; ok {
			f()
		} else {
			t.Log("NO Behavior!")
		}
	}
}
/*
  === RUN   TestUnameFuncPatch
  --- PASS: TestUnameFuncPatch (0.00s)
      Unamefunc_test.go:68: Go Walking
      Unamefunc_test.go:71: Go Flying
      Unamefunc_test.go:74: Go Jumpping
      Unamefunc_test.go:83: NO Behavior!
  PASS
 */
  • 函数
  • 声明函数
  • 调用函数
  • 参数传递
  • 函数作为值保存
  • 匿名函数
    • 匿名函数声明调用
    • 匿名函数实现回调函数
    • 匿名函数实现操作封装
    • 匿名函数—工厂模式
  • 函数作为接口使用

学习总结,并不断地实践,并分享知识。

©️2020 CSDN 皮肤主题: 点我我会动 设计师:上身试试 返回首页