函数和方法,大大提高了我们的代码复用率,也提高了代码的可读性。 go语言的函数和方法的定义十分简单,直接使用 func 关键字即可。

不同于python、C++等有的概念,而这些类中,又有自己的方法,或者父类的方法。go语言中,我们可以定义一个数据结构,然后给这个数据结构实现自己的方法,同样可以实现【】的效果,通过组合,同一个结构体中,也可以使用或者覆写其他结构体的方法。

函数

函数的组成:

  1. 关键字 func
  2. 函数名 funcName
  3. 参数 params
  4. 返回值(返回值类型)
  5. 函数体
func funcName(params) result {
	// body
}
// 示例1:普通的函数定义
func sum(a int, b int) int {
	return a + b
}
// 示例2:参数表的类型合并,(a int, b int) -> (a, b int)
func sum2(a, b int) int {
	return a + b
}
// 示例3:多值返回,用 () 包裹起来
func sum3(a, b int) (int, error) {
	if a < 0 || b < 0 {
		return 0, errors.New("invalid params")
	}
	return a + b, nil
}
// 示例4:命名返回参数
// 这里命名的返回值是 res 和 err
// 我们可以在函数体中直接使用这两个变量
func sum4(a, b int) (res int, err error) {
	if a < 0 || b < 0 {
		// 可以直接返回
		return 0, errors.New("invalid params")
	}
	// 也可以给参数赋值,然后return
	res = a + b
	err = nil
	return
}

可变参数

// 这里的可变参数类型是一个切片
func sum5(params ...int) int {
	res := 0
	for _, num := range params {
		res += num
	}
	return res
}

需要注意的是,当函数中有普通参数,也有可变参数的时候,那么可变参数一定是放在参数表的最后的。

包级函数

  1. 函数名称的首字母小写代表私有函数,只有在同一个包中才能调用
  2. 函数名称的首字母大写代表公有函数,可以在不同的包中被调用
  3. 任何一个函数都会从属于一个包

匿名函数和闭包

匿名函数就是没有名称的函数,可以赋值给一个变量

sum6 := func(a, b int) int {
	return a + b
}
fmt.Println(sum6(1, 2))

闭包:在函数中再定义函数(匿名函数),称为内部函数,这个内部函数可以使用外部函数的变量,这种定义方式称为闭包

func closure() func() int {
	// 这个闭包函数返回一个函数A,这个函数A的返回值类型是 int [func() int]
	i := 0
	return func() int {
		i++		// 这里使用的i是外部函数的变量 i
		return i
	}
}

func main(){
	cl := closure()
	fmt.Println(cl())		// 输出 1
	fmt.Println(cl())		// 输出 2
	fmt.Println(cl())		// 输出 3
}

方法

方法不同于函数,方法需要一个接收者,而函数不需要

这个接收者是一个类型,这样方法就和这个类型绑定在一起了,称为这个类型的方法

// 定义一个Age类型
type Age uint
func (age Age) String() {
  fmt.Println("age is ", age)
}

上述的 String() 就是Age的方法,类型Age是String()的接收者(接收者写在方法名前面,使用小括号()包裹)

调用方法,可以使用点操作符

age := Age(20)
age.String()

值类型接收者和指针类型接收者

  • 对指针类型接收者的修改是有效的
  • 对值类型接收者的修改是无效的
// 指针类型接收者方法
func (age *Age) Modify(){
  *age = Age(30)
}

age := Age(20)
age.String() // age is 20
age.Modify() // 使用一个值类型变量调用指针类型接收者方法,Go会自动帮我们取指针调用
// (&age).Modify() 跟上述语句是一样的
// 使用指针类型变量调用值类型接收者方法,go会帮我们【解引用调用】
// (&age).String() 同 age.String()
age.String() // age is 30

简单地理解为:

值接收者使用的是值的副本来调用方法,而指针接收者使用实际的值来调用方法。

方法是否可以赋值给一个变量

可以

type Age uint
// 值类型接收者方法
func (age Age) String() {
  fmt.Println("age is ", age)
}
// 指针类型接收者方法
func (age *Age) Incr() {
  *age++
}

age := Age(10)
// 值类型接收者方法赋值给变量
sm := Age.String
sm(age) 	// 调用
// 指针类型接收者方法赋值给变量
im := (*Age).Incr
im(&age)	// 调用
sm(age)

不管方法是否有参数,通过方法表达式调用,第一个参数必须是接收者,然后才是方法自身的参数。