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
}
函数也可以赋值给变量,也可以当成实参赋值给另一个函数作为形参。
说明:
// Output:
固定格式的输出
// Unordered Output:
非固定格式的输出
可变参数
说明:
- 函数可以使用
...<类型>
接受一个 []<类型>
的 切片
,该定义称为 可变参数
可变参数
必须放到函数形参列表的最后面
可变参数
传递是,可以使用 <变量名>...
格式解包切片后传递给其他函数的作为 可变参数
类型的形参
下面示例中,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
}
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 主要用来处理运行时错误
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
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
}