Go func 函数

发布时间: 更新时间: 总字数:2986 阅读时间:6m 作者: IP属地: 分享 复制网址

Golang函数包含函数名、形参列表、函数体和返回值列表,使用func进行声明,函数无参数或返回值时则形参列表和返回值列表省略。

介绍

函数定义

func 函数名(形参列表) 返回类型列表 {
  函数体
}

说明:

  • 使用 func 声明函数
  • 函数名一般为驼峰式,符合标识符的定义规则
  • 形参列表需要描述参数名以及参数类型,所有形参为函数块的局部变量。返回值需要描述返回值类型

函数调用

  • 函数可以通过 函数名(实参列表),在调用过程中实参的每个数据会赋值给形参中的对应变量(实参列表类型和数量需要与形参一一对应)
  • 函数参数可以使用 /* xxx */ 添加备注
printInfo("foo", true /* isLocal */, true /* done */)

基础函数示例

  • 若函数声明中存在多个相同类型的连续形参,可以只保留最后一个形参的类型
package main

import "fmt"

// 无参函数
func hello() {
	fmt.Println("Hello World!")
}

// 有参函数,name 称为形参
func helloSomeone(name string) {
	fmt.Println("Hello", name, "!")
}

// 有返回值函数
//func add(a int, b int) int {
func add(a, b int) int {
	return a + b
}

func ExampleFunc() {
	// 无参函数调用
	hello()
	// 函数的标识符为 func(),调用函数需要加 ()
	fmt.Printf("%T\n", hello)

	// 有参函数调用
	helloSomeone("xianbin") // name = "xianbin" 为实参,调用函数时,传递给函数的形参
	fmt.Printf("%T\n", helloSomeone)

	// 有返回值函数调用
	sum := add(1, 2)
	fmt.Println(sum)
	fmt.Printf("%T\n", add)

	// 函数的类型
	var f func(int, int) int
	f = add
	fmt.Println(f(1, 2))

	// Output:
	//Hello World!
	//func()
	//Hello xianbin !
	//func(string)
	//3
	//func(int, int) int
	//3
}

函数也可以赋值给变量,也可以当成实参赋值给另一个函数作为形参。

可变参数

说明:

  • 函数可以使用 ...<类型> 接受一个 []<类型>切片,该定义称为 可变参数
  • 可变参数 必须放到函数形参列表的最后面
  • 可变参数 传递是,可以使用 <变量名>... 格式解包切片后传递给其他函数的作为 可变参数 类型的形参

下面示例中,addN 函数至少需要传递两个参数。

package main

import "fmt"

// 包含可变参数类型的函数
func addN(a, b int, args ...int) int {
	fmt.Printf("%T %T %T %#v %#v %#v\n", a, b, args, a, b, args)
	sum := a + b
	for _, v := range args {
		sum += v
	}
	return sum
}

func calc(op string, a, b int, args ...int) int {
	switch op {
	case "+":
		// 可变参数通过 ... unpack 切片示例
		return addN(a, b, args...)
	}

	return -1
}

func ExampleFunc() {
	// 调用可变参数类型
	// 不包含可变参数,args 为 []int(nil)
	fmt.Println(addN(1, 2))
	// 包含可变参数
	fmt.Println(addN(1, 2, 3, 4))

	// 可变参数传递示例
	fmt.Println(calc("+", 1, 2, 3, 4))
	args := []int{3, 4} // 传递切片
	fmt.Println(calc("+", 1, 2, args...))

	// Output:
	//int int []int 1 2 []int(nil)
	//3
	//int int []int 1 2 []int{3, 4}
	//10
	//int int []int 1 2 []int{3, 4}
	//10
	//int int []int 1 2 []int{3, 4}
	//10
}

返回值

支持如下:

  • 多返回值,也支持相同类型合并
  • 命名返回值
package main

import "fmt"

func calc(a, b int) (int, int, int, float64) {
	return a + b, a - b, a * b, float64(a) / float64(b)
}

// 命名返回值,return 为空,通过名称自动获取返回值,默认返回命名类型的零值
func add(a, b int) (sum int) {
	sum = a + b
	return
}

func ExampleFunc() {
	// 函数返回值
	sum, diff, prod, quot := calc(1, 2)
	fmt.Println(sum, diff, prod, quot)

	// 使用空语句介绍返回值
	sum, _, _, _ = calc(1, 2)
	fmt.Println(sum)

	// 命名返回值
	fmt.Println(add(1, 2))

	// Output:
	//3 -1 2 0.5
	//3
	//3
}

值传递 & 引用类型

  • Go 语言中的所有东西都是以值传递的
    • 值传递(pass by value):指在调用函数时,将实际参数复制一份传递到函数中
    • 引用传递(pass by reference):指在调用函数时,将实际参数的地址直接传递到函数中
  • 值类型:指向实际的值,将变量赋值给新变量后(申请新地址),修改新变量的值互不影响
    • 值类型包括:数值(int/float32/float64)、string、bool、数组array、指针、结构体等
    • 修改值类型的变量可以通过指针实现
    • 形参传递默认均为值传递(形参为实参变量的副本)
  • 引用类型:指向的是指针,将变量赋值给新变量后(申请新地址),新地址为变量的地址,内容为指针(通过指针指向原变量的地址共享数据结构),新增新变量的值可能会产生影响(比如slice append。引用类型底层通过共享数据结构实现,因此取决于地址是否重新分配)
    • 引用类型包括:切片slice映射map接口
      • func makemap(t *maptype, hint int, h *hmap) *hmap {}
      • func makechan(t *chantype, size int) *hchan {}
    • 在函数内对引用类型的数据进行修改,可以影响到函数外部原有变量的信息
  • 形参和实参的地址是不一样的,但在引用类型中,他们的值是相同的
package main

import "fmt"

func updateInt(a int) {
	a += 1
}

func updateIntP(a *int) {
	*a += 1
}

func updateSlice(nums []int) {
	for i, _ := range nums {
		nums[i] += 1
	}
}

func ExampleValueRef() {
	// 数组是值类型
	a := [3]int{1, 2, 3}
	m := map[string]int{}
	m["xiaoming"] = 18
	// 切片是引用类型
	s := []int{1, 2, 3}

	a1 := a
	m1 := m
	s1 := s

	a1[0] = 6
	m1["xiaoming"] = 16
	s1[0] = 6

	fmt.Println(a, a1, a == a1)
	fmt.Println(m, m1)
	fmt.Println(s, s1)

	// slice 和 map 通过 & 取地址的值(地址)是不一样的,但他们的值都执行同一个地址
	fmt.Printf("%p %p %p %p %p %p\n", &a, &a1, m, m1, s, s1)

	// 修改值类型的变量可以通过指针实现
	n := 1
	pn := &n
	*pn = 2
	fmt.Println(n)

	// 值传递与引用传递区分
	n = 1
	s = []int{1, 2, 3}

	updateInt(n)
	updateSlice(s)
	fmt.Println(n, s)

	// 通过指针传递
	n = 1
	updateIntP(&n)
	fmt.Println(n)

	// Output:
	//[1 2 3] [6 2 3] false
	//map[xiaoming:16] map[xiaoming:16]
	//[6 2 3] [6 2 3]
	//0xc000016180 0xc0000161b0 0xc000078390 0xc000078390 0xc000016198 0xc000016198
	//2
	//1 [2 3 4]
	//2
}

函数传递示例

函数可以作为实参传递,也可以作为返回值返回

package main

import (
	"fmt"
	"strings"
)

// 调用 callback 函数
func call(callback func(...string), args ...string) {
	callback(args...)
}

func printSomething(args ...string) {
	fmt.Println(strings.Join(args, " "))
}

func ExampleCallback() {
	args := []string{"hello", "callback"}
	call(printSomething, args...)

	// Output:
	//hello callback
}

匿名函数

说明:匿名函数只能在定义作用域内被使用,使用场景:

  • 作为函数的扩展使用,如 strings.Map/sort.Slice 等方法
package main

import (
	"fmt"
	"strings"
)

// 调用 callback 函数
func call(callback func(...string), args ...string) {
	callback(args...)
}

func ExampleAnonymousFunc() {
	// 方式一
	hello := func(name string) {
		fmt.Println("hello", name)
	}
	hello("xianbin")

	// 方式二
	func(name string) {
		fmt.Println("hi", name)
	}("xianbin")

	// 方式三
	printSomething := func(args ...string) {
		fmt.Println(strings.Join(args, " "))
	}

	call(printSomething, "hi", "xianbin!")

	// 方式四
	call(func(args ...string) {
		fmt.Println(strings.Join(args, " "))
	}, "hello", "xianbin!")

	// Output:
	//hello xianbin
	//hi xianbin
	//hi xianbin!
	//hello xianbin!
}

闭包

闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。

package main

import "fmt"

func ExampleClosure() {
	addBase := func(base int) func(int) int {
		// 返回值是一个函数
		return func(x int) int {
			return base + x
		}
	}

	add10 := addBase(10)
	fmt.Printf("%T\n", add10)
	fmt.Println(add10((10)))

	fmt.Println(addBase(10)(2))

	// Output:
	//func(int) int
	//20
	//12
}

函数错误处理

  • Golang 中没有如python的异常,通过 error 处理业务逻辑中报错
  • Golang 中错误类型为:error
  • Golang 中通过 error 接口 实现错误处理的标准模式,可以在函数返回值中返回错误信息(一般放到最后一个),错误信息需要在程序调用处处理。

创建 err 的初始化 方法:

  • errors.New()

  • fmt.Errorf()

    • 一般使用 %w 参数返回一个被包装的 error参考
  • errors.Unwrap 拆开一个被包装的 error

  • errors.Is 通过递归调用 errors.Unwrap 判断每一层的 err 是否相等,返回 bool 值

  • errors.As 判断 error 类型是否相同

  • 示例1:err 嵌套

package main

import (
	"errors"
	"fmt"
)

type ErrorString struct {
	s string
}

func (e *ErrorString) Error() string {
	return e.s
}

func main() {
	err1 := errors.New("new error")
	err2 := fmt.Errorf("err2: [%w]", err1)
	err3 := fmt.Errorf("err3: [%w]", err2)
	fmt.Println(err3)

	// unwrap
	fmt.Println(errors.Unwrap(err3))
	fmt.Println(errors.Unwrap(errors.Unwrap(err3)))

	// errors.Is
	fmt.Println(errors.Is(err3, err2))
	fmt.Println(errors.Is(err3, err1))

	// errors.As
	var targetErr *ErrorString
	err := fmt.Errorf("new error:[%w]", &ErrorString{s: "target err"})
	fmt.Println(errors.As(err, &targetErr))

	// Output:
	//err3: [err2: [new error]]
	//err2: [new error]
	//new error
	//true
	//true
	//true
}
  • 示例2
package main

import (
	"errors"
	"fmt"
)

func division(a, b int) (float64, error) {
	if b == 0 {
		return -1, errors.New("b is error")
	}
	return float64(a) / float64(b), nil
}

func ExampleError() {
	if r, ok := division(1, 2); ok != nil {
		fmt.Println(ok.Error())
	} else {
		fmt.Println(r)
	}

	if r, err := division(1, 0); err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println(r)
	}

	e := fmt.Errorf("%s xxxx", "i'am error")
	fmt.Printf("%T %#v", e, e)

	// Output:
	//0.5
	//b is error
	//*errors.errorString &errors.errorString{s:"i'am error xxxx"}
}

延迟执行defer

defer 关键字用来声明函数,无论函数是否发生错误都在函数退出前执行

  • 执行顺序:按defer定义顺序的倒序执行(defer 栈:先进后出)
  • 使用场景:
    • 资源释放,如文件流关闭
    • 记录日志等
package main

import "fmt"

func ExampleDefer() {

	defer func() {
		fmt.Println("run defer1")
	}()

	defer func() {
		fmt.Println("run defer2")
	}()

	fmt.Println("run main done")

	// Output:
	//run main done
	//run defer2
	//run defer1
}

panic & recover

panic 和 recover 主要用来处理运行时错误

  • 当使用 panic 函数抛出错误时,会中断所有的控制流程,常用来标记不可修复的错误

  • recover 函数用于终止错误处理流程,仅用在 defer 定义的函数中,用于截取错误信息,recover 只能捕获到最后一个错误

  • panic 示例

package main

import "fmt"

func main() {
	fmt.Println("main start")
	panic("occur  error")
	fmt.Println("main end") // 永远不会被执行到
}

日志:

$ go run main.go
main start
panic: occur  error

goroutine 1 [running]:
main.main()
        /Users/xiexianbin/workspace/code/go/src/leetcode/tests/main.go:7 +0x65

Process finished with the exit code 2
  • recover 示例
package main

import "fmt"

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("%T %v\n", err, err)
		}
	}()

	fmt.Println("main start")
	panic("occur error")
	fmt.Println("main end") // 永远不会被执行到
}

日志:

$ go run recover.go
main start
string occur error

Process finished with the exit code 0
  • 方法级别的 recover,常用于第三方库抛出的 panic
package main

import "fmt"

func testRecover() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("%v", e)
		}
	}()
	panic("func testRecover occur err")
	//return
}

func main() {
	err := testRecover()
	if err != nil {
		fmt.Println(err.Error())
	}
}

日志:

$ go run recover2.go
func testRecover occur err

Process finished with the exit code 0

基础算法

递归

递归是指函数直接或间接调用自己,常用于解决分治问题,将大问题拆分为相同类型的小问题

package main

import "fmt"

// N 的和
func recursion(n int) int {
	if n == 1 {
		return 1
	}
	return n + recursion(n-1)
}

// N的阶乘
func factorial(n int) int {
	if n == 0 {
		return 1
	}
	return n * factorial(n-1)
}

func ExampleRecursion() {
	// 递归
	fmt.Println(recursion(5))

	fmt.Println(factorial(5))

	// Output:
	//15
	//120
}
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数