Go语言的函数

Go语言支持普通函数、匿名函数和闭包。

声明函数

普通函数的声明形式如下:

func 函数名 (参数列表) (返回参数列表) {

        函数体

        return

}

Go语言函数的基本组成是:关键字func、函数名、参数列表、返回值 、函数体和返回语句。

func FunctionName Signature [FunctionBody]

func为定义函数的关键字,FunctionName为函数名,Signature为函数签名,FunctionBody为函数体。函数签名由函数参数、返回值以及它们的类型组成。

一个完整的函数声明结构格式如下:

func funName(input1 type1, input2 type2)(output1 type1 output2 type2){
    //逻辑代码
    return value1,value2    //返回多值
}

Go语言函数不支持嵌套(nested)、重载(overload)和默认参数(default parameter)。

返回单个值的代码如下:

package main

import(
  "fmt"
)
func isEven(i int) bool{
  return i%2==0
}
func main(){
  fmt.Printf("%v\n",isEven(1))
  fmt.Printf("%v\n",isEven(2))
}

运行结果如下:

false
true

返回多个值的代码如下:

package main

import(
  "fmt"
)
func getPrize()(int,string){
  i:=2
  s:="goldfish"
  return i,s
}
func main(){
  quantity,prize:=getPrize()
  fmt.Printf("You won %v %v\n",quantity,prize)
}

运行结果如下:

You won 2 goldfish

定义不定参数函数的代码如下:

package main

import(
  "fmt"
)
func sumNumbers(numbers...int)int{
  total:=0
  for _,number:=range numbers{
    total+=number
  }
  return total
}
func main(){
  result:=sumNumbers(1,2,3,4)
  fmt.Printf("The result is %v\n",result)
}

运行结果如下:

The result is 10

代码如下:

package main

import "fmt"

func MyPrintf(args ...interface{}) {
	for _, arg := range args {
		switch arg.(type) {
		case int:
			fmt.Println(arg, "is an int value.")
		case string:
			fmt.Println(arg, "is a string value.")
		case int64:
			fmt.Println(arg, "is an int64 value.")
		default:
			fmt.Println(arg, "is an unknown type.")
		}
	}
}

func main() {
	var v1 int = 1
	var v2 int64 = 234
	var v3 string = "hello"
	var v4 float32 = 1.234
	MyPrintf(v1, v2, v3, v4)
}

运行结果如下:

[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\main.go"
1 is an int value.
234 is an int64 value.
hello is a string value.
1.234 is an unknown type.

[Done] exited with code=0 in 1.146 seconds

使用具名返回值的代码如下:

package main

import(
  "fmt"
)
func sayHi()(x,y string){
  x="hello"
  y="world"
  return
}
func main(){
  fmt.Println(sayHi())
}

运行结果如下:

hello world

使用递归函数的代码如下:

package main

import(
  "fmt"
)
func feedMe(portion int, eaten int)int{
  eaten=portion+eaten
  if eaten>=5{
    fmt.Printf("I'm full! I've eaten %d\n",eaten)
    return eaten
  }
  fmt.Printf("I'm still hungry! I've eated %d\n",eaten)
  return feedMe(portion,eaten)
}
func main(){
  fmt.Println(feedMe(1,0))
}

运行结果如下:

I'm still hungry! I've eated 1
I'm still hungry! I've eated 2
I'm still hungry! I've eated 3
I'm still hungry! I've eated 4
I'm full! I've eaten 5
5

将函数作为值传递的代码如下:

package main

import(
  "fmt"
)
func anotherFunction(f func() string) string{
  return f()
}
func main(){
  fn:=func()string{
    return "function called"
  }
  fmt.Println(anotherFunction(fn))
}

运行结果如下:

function called

返回多个值的函数,代码如下:

package main

import (
	"fmt"
)

func swapNumber(a, b int) (int, int) {
	return b, a
}
func main() {
	fmt.Println(swapNumber(1, 2))
}

运行结果如下:

2 1

如果不需要函数的多个返回值,可以用下画线“_”字符来代替不需要赋值的变量,代码如下:

package main

import (
	"fmt"
)

func swapNumber(a, b int) (int, int) {
	return b, a
}
func main() {
	n1 := 1
	n2 := 2
	n3, _ := swapNumber(n1, n2)
	fmt.Println(n3)
}

运行结果如下:

2

空白标识符“_”自动丢弃掉。

多返回值,代码如下:

package main

import (
	"fmt"
)

func SumAndProduct(A, B int) (int, int) {
	return A + B, A * B
}

func main() {
	x := 3
	y := 4
	xPLUSy, xTIMESy := SumAndProduct(x, y)
	fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
	fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
}

运行结果如下:

3 + 4 = 7
3 * 4 = 12

函数作为类型,代码如下:

package main

import (
	"fmt"
)

func isOdd(v int) bool {
	if v%2 == 0 {
		return false
	}
	return true
}
func isEven(v int) bool {
	if v%2 == 0 {
		return true
	}
	return false
}

type bool1Func func(int) bool	//声明一个函数类型
//函数作为一个参数使用
func filter(slice []int, f bool1Func) []int {
	var result []int
	for _, value := range slice {
		if f(value) {
			result = append(result, value)
		}
	}
	return result
}

func main() {
	slice := []int{3, 1, 4, 5, 9, 2}
	fmt.Println("slice= ", slice)
	odd := filter(slice, isOdd)		//函数当作值来传递
	fmt.Println("odd:", odd)
	even := filter(slice, isEven)	//函数当作值来传递。
	fmt.Println("even:", even)
}

运行结果如下:

slice=  [3 1 4 5 9 2]
odd: [3 1 5 9]
even: [4 2]

可变参数:

函数有着不定数量的参数在很多时候都会遇到。Go语言函数支持可变参数。需要定义函数使其接受变参:

func myfunc(arg ...int) {}

在Go语言中,函数的最后一个参数如果是...type的形式,那这个函数是一个变参函数,它可以处理变长的参数,而这个长度可以是0。注意的是在变参函数中,无论变参多少个,它们的类型全部都一样的。

代码如下:

package main

import (
	"fmt"
)

func main() {
	age := ageMinOrMax("min", 1, 3, 2, 0)
	fmt.Printf("年龄最小的是%d岁。\n", age)
	ageArr := []int{7, 9, 3, 5, 1}
	age = ageMinOrMax("max", ageArr...)
	fmt.Printf("年龄最大的是%d岁。", age)
}

func ageMinOrMax(m string, a ...int) int {
	if len(a) == 0 {
		return 0
	}
	if m == "max" {
		max := a[0]
		for _, v := range a {
			if v > max {
				max = v
			}
		}
		return max
	} else if m == "min" {
		min := a[0]
		for _, v := range a {
			if v < min {
				min = v
			}
		}
		return min
	} else {
		e := -1
		return e
	}
}

运行结果如下:

年龄最小的是0岁。
年龄最大的是9岁。

代码如下:

package main

import (
	"fmt"
)

func main() {
	ageArr := []int{7, 9, 3, 5, 1}
	f1(ageArr...)
}

func f1(arr ...int) {
	f2(arr...)
	fmt.Println("")
	f3(arr)
}

func f2(arr ...int) {    //f2(1,3,7,13)
	for _, char := range arr {
		fmt.Printf("%d ", char)
	}
}

func f3(arr []int) {    //f3([]int{1,3,7,13})
	for _, char := range arr {
		fmt.Printf("%d ", char)
	}
}

运行结果如下:

7 9 3 5 1 
7 9 3 5 1 

如果希望传递任意类型,可以指定类型为interface{},也就是接口。下面是Go语言标准库中fmt.Printf()的函数原型:

func Printf(format string, args ...interface{}){
    // ...
}

常用的Printf函数就是一个变参函数,而且不限制参数类型。用interface{}传递任意类型数据是Go语言的习惯用法。

匿名函数

匿名函数的例子:

func(x,y int) int {return x+y}(3,4)

fpluse:=func(x,y int) int {return x+y}

fplus(3,4)

代码如下:

package main

import (
	"fmt"
)

func main() {
	func(num int) int {
		sum := 0
		for i := 1; i <= num; i++ {
			sum += i
		}
		fmt.Println(sum)
		return sum
	}(100)
}

运行结果如下:

5050

闭包:(没看懂)

闭包允许调用定义在其它环境下的变量,可使得某个函数捕捉到一些外部状态。

一个闭包继承了函数声明时的作用域。

代码如下:

package main

import (
	"fmt"
)

func main() {
	fmt.Printf("%d\n", Add()(3))
	fmt.Printf("%d\n", Add2(6)(3))
}

//Add无参函数,返回值是一个匿名函数,匿名函数返回一个int类型的值
func Add() func(b int) int {
	return func(b int) int {
		return b + 2
	}
}

//Add2无参函数,返回值是一个匿名函数,匿名函数返回一个int类型的值
func Add2(a int) func(b int) int {
	return func(b int) int {
		return a + b
	}
}

运行结果如下:

5
9

代码如下:

package main

import (
	"fmt"
)

func main() {
	j := 5
	a := func() func() {
		i := 10
		return func() {
			fmt.Printf("i=%d j=%d\n", i, j)
		}
	}()
	a()
	j = 10
	a()
}

运行结果如下:

i=10 j=5
i=10 j=10

代码如下:

package main

import (
	"fmt"
)

func main() {
	var f = adder()
	fmt.Println(f(1))		//fmt.Println(adder()(1))
	fmt.Println(f(2))
	fmt.Println(f(3))
}
func adder() func(int) int {
	var x int
	return func(d int) int {
		fmt.Println("x = ", x, "d = ", d)
		x += d
		return x
	}
}

运行结果如下:

x =  0 d =  1
1
x =  1 d =  2
3
x =  3 d =  3
6

代码如下:

package main

import (
	"fmt"
)

func getSequence() func() int {
	i := 0
	return func() int {
		i++
		return i
	}
}

func main() {
	nextNumber := getSequence()
	fmt.Printf("%d ", nextNumber())
	fmt.Printf("%d ", nextNumber())
	fmt.Printf("%d ", nextNumber())

	nextNumber1 := getSequence()
	fmt.Printf("%d ", nextNumber1())
	fmt.Printf("%d ", nextNumber1())
}

运行结果如下:

1 2 3 1 2 

代码如下:

package main

import "fmt"

func main() {
	var j int = 5
	a := func() func() {
		var i int = 10
		return func() {
			fmt.Printf("i,j: %d, %d\n", i, j)
		}
	}()
	a()
	j *= 2
	a()
}

运行结果如下:

[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go\main.go"
i,j: 10, 5
i,j: 10, 10

[Done] exited with code=0 in 1.282 seconds

递归函数:

递归,就是在运行的过程中调用自己。语法格式如下:

func recursion(){
    recursion()    //函数调用自身
}

func main(){
    recursion()
}

通过Go语言的递归函数实现阶乘运算,代码如下:

package main

import (
	"fmt"
)

func Factorial(n uint64) (result uint64) {
	if n > 0 {
		result = n * Factorial(n-1)
		return result
	}
	return 1
}

func main() {
	var i int = 15
	fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(uint64(i)))
}

运行结果如下:

15 的阶乘是 1307674368000

通过Go语言的递归函数实现斐波那契数列,代码如下:

package main

import (
	"fmt"
)

func fibonacci(n int) int {
	if n < 2 {
		return n
	}
	return fibonacci(n-2) + fibonacci(n-1)
}

func main() {
	var i int
	for i = 0; i < 10; i++ {
		fmt.Printf("%d\t", fibonacci(i))
	}
}

运行结果如下:

0	1	1	2	3	5	8	13	21	34	

内置函数:

close:用于管道通信。

len:用于返回某个类型的长度或数量(字符串、数组、切片、map和管道)。

cap:容量的意思,用于返回某个类型的最大容量(只能用于切片和map)。

new、make:均用于分配内存,不管new用于值类型和用户定义的类型,make用于内置引用类型(切片、map和管道)。new(T)分配类型T的零值并返回其地址,也就是指向类型T的指针。make(T)返回类型T的初始化之后的值。new()是一个函数,不要忘记它的括号。

copy、append:用于复制和连接切片。

panic、recover:两者均用于错误处理机制。

print、println:底层打印函数(部署环境中建议使用fmt包)。

complex、real imag:用于创建和操作复数。

函数进阶

参数传递机制:

Go语言的参数传递分为“按值传递”和“按引用传递”。

按值传递:传递的是参数的副本,函数接收参数副本后,使用变量的过程中可能对副本的值进行更改,但不会影响原来的变量。调用函数时修改参数的值,不会影响原来的实参的值,因此数值变化只作用在副本上。

按引用传递:如果让函数直接修改参数的值,而不是对参数的副本进行修改,就需要将参数的地址(变量名前面添加&符号)传递给函数。传递给函数数的是一个指针。

代码如下:(部分代码没看懂)

package main

import (
	"fmt"
)

func main() {
	x := 3
	fmt.Println("x =", x, "&x =", &x)

	y := add(x)
	fmt.Println("x =", x, "y =", y)

	z := addP(&x)
	fmt.Println("x =", x, "z =", z)
	fmt.Println("&x =", &x)
}

func add(a int) int {
	a++
	return a
}
func addP(a *int) int {
	*a++
	return *a
}

运行结果如下:

x = 3 &x = 0xc0000b2008
x = 3 y = 4
x = 4 z = 4
&x = 0xc0000b2008

代码如下:

package main

import (
	"fmt"
)

func main() {
	n := 0
	res := &n
	multiply(2, 4, res)
	fmt.Println("Result:", *res)
}

func multiply(a, b int, res *int) {
	*res = a * b
}

运行结果如下:

Result: 8

defer与跟踪

1.defer延迟语句

当函数执行到最后时(return语句执行之前),这些defer语句会按照“逆序”执行,最后该函数才退出。

defer的用途:在进行一些IO操作的时候,如果遇到错误,便需要提前返回,而返回之前应该关闭相应的资源,否则容易造成资源泄露等问题,defer语句就可以出色地解决这个问题。

如果多次调用defer,那么defer采用后进先出模式,代码如下:

package main

import (
	"fmt"
)

func main() {
	for i := 0; i < 5; i++ {
		defer fmt.Printf("%d ", i)
	}
}

运行结果如下:

4 3 2 1 0 

执行顺序,代码如下:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("return:", a())
}
func a() int {
	var i int
	defer func() {
		i++
		fmt.Println("defer2:", i)
	}()
	defer func() {
		i++
		fmt.Println("defer1:", i)
	}()
	return i
}

运行结果如下:

defer1: 1
defer2: 2
return: 0

有名返回值的情况,代码如下:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("return:", b())
}
func b() (i int) {
	defer func() {
		i++
		fmt.Println("defer2:", i)
	}()
	defer func() {
		i++
		fmt.Println("defer1:", i)
	}()
	return i
}

运行结果如下:

defer1: 1
defer2: 2
return: 2

多个defer语句的执行顺序为“逆序”,defer、return、返回值三者的执行逻辑应该是:defer最先执行一些收尾工作;然后return执行,return负责将结果写入返回值中;最后函数携带当前返回值退出。

defer、return、返回值三者的执行顺应应该是:return最先给返回值赋值;接着defer开始执行一些收尾工作;最后RET指令携带返回值退出函数。

代码如下:

package main

import (
	"fmt"
	"time"
)

func main() {
	i, p := a()
	fmt.Println("return:", i, p, time.Now())
}

func a() (i int, p *int) {
	defer func(i int) {
		fmt.Println("defer3:", i, &i, time.Now())
	}(i)
	defer fmt.Println("defer2:", i, &i, time.Now())
	defer func() {
		fmt.Println("defer1:", i, &i, time.Now())
	}()
	i++
	func() {
		fmt.Println("print1:", i, &i, time.Now())
	}()
	fmt.Println("print2:", i, &i, time.Now())
	return i, &i
}

运行结果如下:

print1: 1 0xc0000b2008 2022-01-30 21:10:52.533105 +0800 CST m=+0.000106679
print2: 1 0xc0000b2008 2022-01-30 21:10:52.53332 +0800 CST m=+0.000321996
defer1: 1 0xc0000b2008 2022-01-30 21:10:52.533327 +0800 CST m=+0.000328647
defer2: 0 0xc0000b2008 2022-01-30 21:10:52.533104 +0800 CST m=+0.000106002
defer3: 0 0xc0000b20d8 2022-01-30 21:10:52.533337 +0800 CST m=+0.000338399
return: 1 0xc0000b2008 2022-01-30 21:10:52.533341 +0800 CST m=+0.000342559

2.跟踪

defer经常用于代码执行追踪。

代码如下:(代码没看懂)

package main

import (
	"fmt"
)

func main() {
	b()
}

func a() {
	defer un(trace("a"))
	fmt.Println("a的逻辑代码")
}
func b() {
	defer un(trace("b"))
	fmt.Println("b的逻辑代码")
	a()
}
func trace(s string) string {
	fmt.Println("开始执行", s)
	return s
}
func un(s string) {
	fmt.Println("结束执行", s)
}

运行结果如下:

开始执行 b
b的逻辑代码
开始执行 a
a的逻辑代码
结束执行 a
结束执行 b

错误与恢复

func panic(interface{})

func recover() interface {}

1.error

Go语言通过error接口实现错误处理的标准模式,该接口的定义如下:

type error interface{

        Error() string

}

2.panic

区别使用panic和error两种方式,导致关键流程出现不可修复性错误的情况使用panic,其它情况使用error。

3.recover

代码如下:

package main

import "fmt"

func main() {
	test()
}

func test() {
	defer func() {
		fmt.Println(recover())
	}()
	defer func() {
		func() {
			recover()
		}()
	}()
	defer fmt.Println(recover())
	defer recover()
	panic("发生错误")
}

运行结果如下:

<nil>
发生错误

代码如下:

package main

import "log"

func main() {
	test()
}

func test() {
	defer func() {
		if r := recover(); r != nil {
			log.Printf("捕获到的异常:%v", r)
		}
	}()
	defer func() {
		panic("第二个错误")
	}()
	panic("第一个错误")
}

运行结果如下:

2022/01/30 21:50:02 捕获到的异常:第二个错误

代码如下:

package main

import (
	"fmt"
	"time"
)

func main() {
	throwPanic(genErr)
}

func genErr() {
	fmt.Println(time.Now(), "正常的语句")
	defer func() {
		fmt.Println(time.Now(), "defer正常的语句")
		panic("第二个错误")
	}()
	panic("第一个错误")
}
func throwPanic(f func()) (b bool) {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println(time.Now(), "捕获到的异常:", r)
			b = true
		}
	}()
	f()
	return
}

运行结果如下:

2022-01-30 22:00:31.410723 +0800 CST m=+0.000230501 正常的语句
2022-01-30 22:00:31.411162 +0800 CST m=+0.000669905 defer正常的语句
2022-01-30 22:00:31.411185 +0800 CST m=+0.000692985 捕获到的异常: 第二个错误

示例:将“秒”解析为时间位置

代码如下:

package main

import (
	"fmt"
)

const (
	//定义每分钟的秒数
	SecondsPerMinute = 60

	//定义每小时的秒数
	SecondsPerHour   = SecondsPerMinute * 60
	
	//定义每天的秒数
	SecondsPerDay    = SecondsPerHour * 24
)

//将传入的“秒”解析为3种时间单位
func resolveTime(seconds int) (day int, hour int, minute int) {
	day = seconds / SecondsPerDay
	hour = seconds / SecondsPerHour
	minute = seconds / SecondsPerMinute
	return
}

func main() {
	//将返回值作为打印参数
	fmt.Println(resolveTime(1000))

	//只获取消息和分钟
	_, hour, minute := resolveTime(18000)
	fmt.Println(hour, minute)

	//只获取天
	day, _, _ := resolveTime(90000)
	fmt.Println(day)
}

运行结果如下:

0 0 16
5 300
1

示例:函数中的参数传递效果测试

代码如下:

package main

import (
	"fmt"
)

type Data struct {
	complax  []int
	instance 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 main() {
	in := Data{
		complax: []int{1, 2, 3},
		instance: InnerData{
			5,
		},
		ptr: &InnerData{1},
	}
	fmt.Printf("in value: %+v\n", in)
	fmt.Printf("in ptr: %p\n", &in)
	out := passByValue(in)
	fmt.Printf("out value:%+v\n", out)
	fmt.Printf("out ptr: %p\n", &out)
}

运行结果如下:

in value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000aa058}
in ptr: 0xc0000c2450
inFunc value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000aa058}
inFunc ptr: 0xc0000c24e0
out value:{complax:[1 2 3] instance:{a:5} ptr:0xc0000aa058}
out ptr: 0xc0000c24b0

函数变量——把函数作为值保存到变量中

代码如下:

package main

import (
	"fmt"
)

func fire() {
	fmt.Println("fire")
}

func main() {
	var f func()
	f = fire
	f()
}

运行结果如下:

fire

示例:字符串的链式处理——操作与数据分离的设计技巧

代码如下:

package main

import (
	"fmt"
	"strings"
)
//字符串处理函数,传入字符串切片和处理链
func StringProccess(list []string, chain []func(string) string) {
	//遍历每一个字符串
	for index, str := range list {
		//第一个需要处理的字符串
		result := str
		//遍历每一个处理链
		for _, proc := range chain {
			//输入一个字符串进行处理,返回数据作为下一个处理链的输入
			result = proc(result)
		}
		//将结果放回切片
		list[index] = result
	}
}

//自定义的移除前缀的处理函数
func removePrefix(str string) string {
	return strings.TrimPrefix(str, "go")
}

func main() {
	//待处理的字符串列表
	list := []string{
		"go scanner",
		"go parser",
		"go compiler",
		"go printer",
		"go formater",
	}
	//处理函数链
	chain := []func(string) string{
		removePrefix,
		strings.TrimSpace,
		strings.ToUpper,
	}
	//处理字符串
	StringProccess(list, chain)
	//输出处理好的字符串
	for _, str := range list {
		fmt.Println(str)
	}
}

运行结果如下:

SCANNER
PARSER
COMPILER
PRINTER
FORMATER

匿名函数——没有函数名字的函数

1、定义一个匿名函数

匿名函数的定义格式如下:

func (参数列表) (返回参数列表) {

        函数体

}

2、匿名函数用作回调函数

代码如下:

package main

import (
	"fmt"
)

//遍历切片的每个元素,通过给定函数进行元素访问
func visit(list []int, f func(int)) {
	for _, v := range list {
		f(v)
	}
}

func main() {
	//使用匿名函数打印切片内容
	visit([]int{1, 2, 3, 4}, func(v int) {
		fmt.Println(v)
	})
}

运行结果如下:

1
2
3
4

3、使用匿名函数实现操作封装

代码如下:

package main

import (
	"flag"
	"fmt"
)

var skillParam = flag.String("skill", "", "skill to perform")

func main() {
	flag.Parse()
	var skill = map[string]func(){
		"fire": func() {
			fmt.Println("chicken fire")
		},
		"run": func() {
			fmt.Println("soldier run")
		},
		"fly": func() {
			fmt.Println("angel fly")
		},
	}
	if f, ok := skill[*skillParam]; ok {
		f()
	} else {
		fmt.Println("skill not found")
	}
}

运行结果如下:

PS C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go> go run main.go
skill not found
PS C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go> go run main.go --skill=fire
chicken fire
PS C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go> go run main.go --skill=run
soldier run
PS C:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\Go> go run main.go --skill=fly
angel fly

函数类型实现接口——把函数作为接口来调用

1、结体体实现接口

2、函数体实现接口

3、HTTP包中的例子

闭包(Closure)——引用了外部变量的匿名函数

函数+引用环境=闭包

代码如下:

package main

import (
	"fmt"
)

func main() {
	add := func(base int) func(int) int {
		return func(n int) int {
			return base + n
		}
	}
	add5 := add(5)
	fmt.Println(add5(10))
}

运行结果如下:

15

1、在闭包内部修改引用的变量

代码如下:

package main

func main() {
	//准备一个字符串
	str := "hello world"
	//创建一个匿名函数
	foo := func() {
		//匿名函数中访问str
		str = "hello dude"
	}
	//调用匿名函数
	foo()
}

代码输出:hello dude

2、示例:闭包的记忆效应

代码如下:

package main

import (
	"fmt"
)

//提供一个值,每次调用函数会指定对值进行累加
func Accumulate(value int) func() int {
	//返回一个闭包
	return func() int {
		//累加
		value++
		//返回一个累加值
		return value
	}
}
func main() {
	//创建一个累加器,初始值为1
	accumulator := Accumulate(1)
	//累加1并打印
	fmt.Println(accumulator())
	fmt.Println(accumulator())
	//打印累加器的函数地址
	fmt.Printf("%p\n", &accumulator)
	//创建一个累加器,初始值为1
	accumulator2 := Accumulate(10)
	//累加1并打印
	fmt.Println(accumulator2())
	//打印累加器的函数地址
	fmt.Printf("%p\n", &accumulator2)
}

运行结果如下:

2
3
0xc000006028
11
0xc000006038

3、示例:闭包实现生成器

代码如下:

package main

import (
	"fmt"
)

//创建一个玩家生成器,输入名称,输出生成器
func playerGen(name string) func() (string, int) {
	//血量一直为150
	hp := 150
	//返回创建的闭包
	return func() (string, int) {
		//将变量引用到闭包中
		return name, hp
	}
}
func main() {
	//创建一个玩家生成器
	generator := playerGen("high noon")
	//返回玩家的名字和血量
	name, hp := generator()
	//打印值
	fmt.Println(name, hp)
}

运行结果如下:

high noon 150

可变参数——参数数量不固定的函数形式

Go语言的可变参数格式如下:

func 函数名 (固定参数列表,v… T) (返回参数列表) {

函数体

}

可变参数一般被放置在函数列表的末尾,前面是固定参数列表,当没有固定参数时,所有变量就将是可变参数。

v为可变参数变量,类型为[]T,也就是拥有多个T元素的T类型切片,v和T之间由“…”即3个点组成。

T为可变参数的类型,当T为interface{}时,传入的可以是任意类型。

1、fmt包中的例子

2、遍历可变参数列表——获取每一个参数的值

代码如下:

package main

import (
	"bytes"
	"fmt"
)

//定义一个函数,参数数量为0~n,类型约束为字符串
func joinStrings(slist ...string) string {
	//定义一个字节缓冲,快速地连接字符串
	var b bytes.Buffer
	//遍历可变参数列表slist,类型为[]string
	for _, s := range slist {
		//将遍历出的字符串连续写入字节数组
		b.WriteString(s)
	}
	//将连续好的字节数组转换为字符串并输出
	return b.String()
}

func main() {
	//创建一个玩家生成器
	fmt.Println(joinStrings("pig", " and", " rat"))
	fmt.Println(joinStrings("hammer", " mom", " and", " hawk"))
}

运行结果如下:

pig and rat
hammer mom and hawk

3、获得可变参数类型——获得每一个参数的类型

代码如下:

package main

import (
	"bytes"
	"fmt"
)

func printTypeValue(slist ...interface{}) string {
	//字节缓冲作为快速字符串连接
	var b bytes.Buffer
	//遍历参数
	for _, s := range slist {
		//将interface{}类型格式化为字符串
		str := fmt.Sprintf("%v", s)
		//类型的字符串描述
		var typeString string
		//对s进行类型断言
		switch s.(type) {
		case bool: //当s为布尔类型时
			typeString = "bool"
		case string: //当s为字符串类型时
			typeString = "string"
		case int: //当s为整形类型时
			typeString = "int"
		}
		//写值字符串前缀
		b.WriteString("value:")
		//写入值
		b.WriteString(str)
		//写类型前缀
		b.WriteString(" type: ")
		//写类型字符串
		b.WriteString(typeString)
		//写入换行符
		b.WriteString("\n")
	}
	return b.String()
}

func main() {
	//将不同类型的变量通过printTypeValue()打印出来
	fmt.Println(printTypeValue(100, "str", true))
}

运行结果如下:

value:100 type: int
value:str type: string
value:true type: bool

4、在多个可变参数函数中传递参数

代码如下:

package main

import (
	"fmt"
)

//实际打印的函数
func rawPrint(rawList ...interface{}) {
	//遍历可变参数切片
	for _, a := range rawList {
		//打印参数
		fmt.Println(a)
	}
}

//打印函数封装
func print(slist ...interface{}) {
	//将slist可变参数切片完整传凝视给下一个函数
	rawPrint(slist...)
}

func main() {
	print(1, 2, 3)
}

运行结果如下:

1
2
3

延迟执行语句(defer)

一、多个延迟执行语句的处理顺序

代码如下:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("defer begin")
	//将defer放入延迟调用栈
	defer fmt.Println(1)
	defer fmt.Println(2)
	//最后一个放入,位于栈顶,最先调用
	defer fmt.Println(3)
	fmt.Println("defer end")
}

代码输出如下:

defer begin
defer end
3
2
1

二、使用延迟执行语句在函数退出时释放资源

1、使用延迟并发解锁

2、使用延迟释放文件句柄

处理运行时发生的错误

一、net包中的例子

net.Dial()是Go语言系统包net即中的一个函数,一般用于创建一个Socket连接。

net.Dial拥有两个返回值,即Conn和error。这个函数阻塞的,因此在Socket操作后,会返回Conn连接对象和error;如果发生错误,error会告知错误的类型,Conn会返回空。

二、错误接口的定义格式

type error interface{

        Error() string

}

三、自定义一个错误

代码如下:

package main

import (
	"errors"
	"fmt"
)

//定义除数为0的错误
var errDivisionByZero = errors.New("division by zoro")

func div(dividend, divisor int) (int, error) {
	//判断除数为0的情况并返回
	if divisor == 0 {
		return 0, errDivisionByZero
	}
	//正常计算,返回空错误
	return dividend / divisor, nil
}

func main() {
	fmt.Println(div(1, 0))
}

运行结果如下:

0 division by zoro

四、示例:在解析中使用自定义错误

代码如下:

package main

import (
	"fmt"
)

//声明一个解析错误
type ParseError struct {
	Filename string //文件名
	Line     int    //行号
}

//实现error接口,返回错误描述
func (e *ParseError) Error() string {
	return fmt.Sprintf("%s:%d", e.Filename, e.Line)
}

//创建一些解析错误
func newParseError(filename string, line int) error {
	return &ParseError{filename, line}
}

func main() {
	var e error
	//创建一个错误实例,包含文件名和行号
	e = newParseError("main.go", 1)
	//通过error接口查看错误描述
	fmt.Println(e.Error())
	//根据错误接口的具体类型,获取详细的错误信息
	switch detail := e.(type) {
	case *ParseError:		//这是一个解析错误
		fmt.Printf("Filename:%s Line:%d\n", detail.Filename, detail.Line)
	default:				//其他类型的错误
		fmt.Println("other error")
	}
}

运行结果如下:

main.go:1
Filename:main.go Line:1

宕机(panic)——程序终止运行

一、手动触发宕机

代码如下 :

package main

func main() {
	panic("crash")
}

运行结果如下:

panic: crash

goroutine 1 [running]:
main.main()
        C:/Users/a-xiaobodou/OneDrive - Microsoft/Projects/Go/main.go:4 +0x27
exit status 2

二、在运行依赖的必备资源缺失时主动触发宕机。

func Compile(expr string) (*Regexp, error)

func MustCompile(str string) *Regexp

三、在宕机时触发延迟执行语句

代码如下:

package main

import (
	"fmt"
)

func main() {
	defer fmt.Println("宕机后要做的事情1")
	defer fmt.Println("宕机后要做的事情2")
	panic("宕机")
}

运行结果如下:

宕机后要做的事情2
宕机后要做的事情1
panic: 宕机

goroutine 1 [running]:
main.main()
        C:/Users/a-xiaobodou/OneDrive - Microsoft/Projects/Go/main.go:10 +0xac
exit status 2

宕机恢复(recover)——防止程序崩溃

一、让程序在崩溃时继续执行

代码如下:

package main

import (
	"fmt"
	"runtime"
)

//崩溃时需要传递的上下文信息
type panicContext struct {
	function string //所在函数
}

//保护方式允许一个函数
func ProtectRun(entry func()) {
	//延迟处理的函数
	defer func() {
		//发生宕机时,获取panic传递的上下文并打印
		err := recover()
		switch err.(type) {
		case runtime.Error: //运行时错误
			fmt.Println("runtime error:", err)
		default:
			fmt.Println("error:", err)
		}
	}()
	entry()
}

func main() {
	fmt.Println("运行前")
	//允许一段手动触发的错误
	ProtectRun(func() {
		fmt.Println("手动宕机前")
		//使用panic传递上下文
		panic(&panicContext{
			"手动触发panic",
		})
		fmt.Println("手动宕机后")
	})
	//故意造成空指针访问错误
	ProtectRun(func() {
		fmt.Println("赋值宕机前")
		var a *int
		*a = 1
		fmt.Println("赋值宕机后")
	})
	fmt.Println("运行后")
}

运行结果如下:

运行前
手动宕机前
error: &{手动触发panic}
赋值宕机前
runtime error: runtime error: invalid memory address or nil pointer dereference
运行后

二、panic和recover的关系

有panic没recover,程序宕机。

有panic也有recover捕获,程序不会宕机。执行完对应的defer后,从宕机点退出当前函数后继续执行。

派错和恢复:

Go语言提供丙个内置函数来处理运行时的程序错误:panic结束函数的执行并报告一个错误信息;recover恢复被panic的函数。

代码如下:

package main

import (
	"log"
)

func protect(g func()) {
	defer func() {
		log.Println("done")
		if x := recover(); x != nil {
			log.Printf("run time panic: %v", x)
		}
	}()
	log.Println("start")
	g()
}

func main() {
	var s []byte
	protect(func() { s[0] = 0 })
	protect(func() { panic(42) })
	s[0] = 42
}

运行结果如下:

2022/03/24 16:03:44 start
2022/03/24 16:03:44 done
2022/03/24 16:03:44 run time panic: runtime error: index out of range [0] with length 0
2022/03/24 16:03:44 start
2022/03/24 16:03:44 done
2022/03/24 16:03:44 run time panic: 42
panic: runtime error: index out of range [0] with length 0

goroutine 1 [running]:
main.main()
        C:/Users/a-xiaobodou/OneDrive - Microsoft/Projects/Go/main.go:22 +0x5b
exit status 2

方法:

代码如下:

package main

import (
	"fmt"
)

type Point struct {
	x, y float64
}
type Rect struct {
	min, max Point
}

func (r *Rect) Area() float64 {
	w := r.max.x - r.min.x
	h := r.max.y - r.min.y
	return w * h
}

func main() {
	var r Rect
	r.max = Point{2, 2}
	fmt.Println(r.Area())
}

运行结果如下:

4

代码如下:

package main

import (
	"fmt"
)

type Point struct {
	x, y float64
}
type Rect struct {
	min, max Point
}

func (r *Rect) Area() float64 {
	w := r.max.x - r.min.x
	h := r.max.y - r.min.y
	return w * h
}

func (r Point) Area() float64 {
	return 0
}

func main() {
	var r Rect
	r.max = Point{2, 2}
	fmt.Println(r.Area(), (&r).Area(), (*Rect).Area(&r))
	p := &r.min
	fmt.Println(p.Area(), (*p).Area(), Point.Area(r.min))
}

运行结果如下:

4 4 4
0 0 0

显式与隐式代码块

代码如下:

package main

import (
	"fmt"
)

var (
	Ga int = 99
)

const (
	v int = 199
)

func GetGa() func() int {
	if Ga := 55; Ga < 60 {
		fmt.Println("GetGa if 中:", Ga)
	}
	for Ga := 2; ; {
		fmt.Println("GetGa循环中:", Ga)
		break
	}
	fmt.Println("GetGa函数中:", Ga)
	return func() int {
		Ga += 1
		return Ga
	}
}

func main() {
	Ga := "string"
	fmt.Println("main函数中:", Ga)
	b := GetGa()
	fmt.Println("main函数中:", b(), b(), b(), b())
	v := 1
	{
		v := 2
		fmt.Println(v)
		{
			v := 3
			fmt.Println(v)
		}
	}
	fmt.Println(v)
}

运行结果如下:

main函数中: string
GetGa if 中: 55
GetGa循环中: 2
GetGa函数中: 99
main函数中: 100 101 102 103
2
3
1

代码如下:

package main

import (
	"fmt"
	"time"
)

type funcType func(time.Time) // 定义函数类型funcType

func main() {
	f := func(t time.Time) time.Time { return t } // 直接赋值给变量
	fmt.Println(f(time.Now()))

	var timer funcType = CurrentTime // 定义函数类型funcType变量timer
	timer(time.Now())

	funcType(CurrentTime)(time.Now()) // 先把CurrentTime函数转为funcType类型,然后传入参数调用
	// 这种处理方式在Go 中比较常见
}

func CurrentTime(start time.Time) {
	fmt.Println(start)
}

运行结果如下:

[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\tempCodeRunnerFile.go"
2022-05-30 16:33:11.1125094 +0800 CST m=+0.004932401
2022-05-30 16:33:11.1334169 +0800 CST m=+0.025839901
2022-05-30 16:33:11.1334169 +0800 CST m=+0.025839901

[Done] exited with code=0 in 11.801 seconds

代码如下:

package main

import (
	"fmt"
)

// 变参函数,参数不定长
func list(nums ...int) {
	fmt.Println(nums)
}

func main() {
	// 常规调用,参数可以多个
	list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

	// 在参数同类型时,可以组成slice使用 parms... 进行参数传递
	numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	list(numbers...) // slice时使用
}

运行结果如下:

[Running] go run "c:\Program Files\Go\src\main.go"
[1 2 3 4 5 6 7 8 9 10]
[1 2 3 4 5 6 7 8 9 10]

[Done] exited with code=0 in 3.617 seconds

递归与回调:

代码如下:

package main

import "fmt"

// Factorial函数递归调用
func Factorial(n uint64) (result uint64) {
	if n > 0 {
		result = n * Factorial(n-1)
		return result
	}
	return 1
}

// Fac2函数循环计算
func Fac2(n uint64) (result uint64) {
	result = 1
	var un uint64 = 1
	for i := un; i <= n; i++ {
		result *= i
	}
	return
}

func main() {
	var i uint64 = 7
	fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(i))
	fmt.Printf("%d 的阶乘是 %d\n", i, Fac2(i))

}

运行结果如下:

[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\tempCodeRunnerFile.go"
7 的阶乘是 5040
7 的阶乘是 5040

[Done] exited with code=0 in 2.442 seconds

代码如下:

package main

import (
	"fmt"
)

func main() {
	callback(1, Add)
}

func Add(a, b int) {
	fmt.Printf("%d 与 %d 相加的和是: %d\n", a, b, a+b)
}

func callback(y int, f func(int, int)) {
	f(y, 2) // 回调函数f
}

运行结果如下:

[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\tempCodeRunnerFile.go"
1 与 2 相加的和是: 3

[Done] exited with code=0 in 15.635 seconds

匿名函数:

代码如下:

package main

import (
	"fmt"
)

func main() {
	fn := func() {
		fmt.Println("hello")
	}
	fn()

	fmt.Println("匿名函数加法求和:", func(x, y int) int { return x + y }(3, 4))

	func() {
		sum := 0
		for i := 1; i <= 1e6; i++ {
			sum += i
		}
		fmt.Println("匿名函数加法循环求和:", sum)
	}()
}

运行结果如下:

[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\tempCodeRunnerFile.go"
hello
匿名函数加法求和: 7
匿名函数加法循环求和: 500000500000

[Done] exited with code=0 in 14.025 seconds

代码如下:

package main

import "fmt"

var G int = 7

func main() {
	// 影响全局变量G,代码块状态持续
	y := func() int {
		fmt.Printf("G: %d, G的地址:%p\n", G, &G)
		G += 1
		return G
	}
	fmt.Println(y(), y)
	fmt.Println(y(), y)
	fmt.Println(y(), y) //y的地址

	// 影响全局变量G,注意z的匿名函数是直接执行,所以结果不变
	z := func() int {
		G += 1
		return G
	}()
	fmt.Println(z, &z)
	fmt.Println(z, &z)
	fmt.Println(z, &z)

	// 影响外层(自由)变量i,代码块状态持续
	var f = N()
	fmt.Println(f(1), &f)
	fmt.Println(f(1), &f)
	fmt.Println(f(1), &f)

	var f1 = N()
	fmt.Println(f1(1), &f1)

}

func N() func(int) int {
	var i int
	return func(d int) int {
		fmt.Printf("i: %d, i的地址:%p\n", i, &i)
		i += d
		return i
	}
}

运行结果如下:

[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\tempCodeRunnerFile.go"
G: 7, G的地址:0x9fd258
8 0x95e440
G: 8, G的地址:0x9fd258
9 0x95e440
G: 9, G的地址:0x9fd258
10 0x95e440
11 0xc0000aa080
11 0xc0000aa080
11 0xc0000aa080
i: 0, i的地址:0xc0000aa088
1 0xc0000ce020
i: 1, i的地址:0xc0000aa088
2 0xc0000ce020
i: 2, i的地址:0xc0000aa088
3 0xc0000ce020
i: 0, i的地址:0xc0000aa090
1 0xc0000ce028

[Done] exited with code=0 in 3.033 seconds

变参函数:

代码如下:

package main

import "fmt"

func Greeting(who ...string) {
	for k, v := range who {

		fmt.Println(k, v)
	}
}

func main() {
	s := []string{"James", "Jasmine"}
	Greeting(s...) // 注意这里切片s... ,把切片打散传入,与s具有相同底层数组的值。
}

运行结果如下:

[Running] go run "c:\Users\a-xiaobodou\OneDrive - Microsoft\Projects\深入学习Go语言\63072_go42\63072_go42\chapter-8\8.1\7\tempCodeRunnerFile.go"
0 James
1 Jasmine

[Done] exited with code=0 in 11.987 seconds

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值