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("发生错误")
}
运行结果如下:
发生错误
代码如下:
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