Golang与函数

mac2026-08-19  2

文章目录

1. 概述2. 声明函数2.1 函数基本形式2.2 值传递与引用传递 3. 函数变量4. 匿名函数4.1 定义形式14.2 定义形式24.3 定义形式34.4 匿名函数作为回调函数4.5 匿名函数示例 5. 闭包6. 函数的可变参数7.延迟调用defer7.1 defer的执行顺序7.2 defer 使用示例 8. 错误处理8.1 自定义错误8.2 异常的处理8.2.1 触发panic8.2.2 recover 捕获异常

1. 概述

函数是将实现单一或者相关联功能的代码组织起来,内部实现具有封闭性的代码集合,函数可以提高应用程序的模块性和功能代码的复用性.对大多数编程语言而言,函数是很重要的部分.

2. 声明函数

Go语言中函数声明用 func标识这是一个函数 后面跟着 函数名 参数列表 返回参数列表 函数主体内容

// 形式如下 func 函数名称(参数列表) (返回参数列表) { 函数执行体 }

对函数简单说明 :

函数名的命名和我们声明一个变量是一样的,由字母数字下划线组成.函数名不能以数字开头,同一个包中函数名不能重复

函数名尽量做到见名知意,函数名中字母的大小写不同,会被认为是不同的函数

一个包中的函数名首字母大写,表示该函数能被外部包访问到

//函数add 和 函数 Add 是两个不同的函数 func add(){} func Add(){}

参数列表 : 表示可以存在多个形式参数, 每个形式参数都是由参数变量名 和 参数数据类型 组成

func demo(x int ,y float64, s string){}

返回参数列表 : 可以是返回值的数据类型列表,也可以类似参数列表一样 返回参数变量名和 数据类型 的组成,函数声明了有返回值,那就必须用 return 语句 提供返回值列表

func demo1(x int ,y float64)(int,error){ ... ... return x+y error }

函数执行体 : 能复用的代码片段

2.1 函数基本形式

package main import ( "fmt" "math" ) // 基本函数格式写法 func hypot(x float64 ,y float64) (res float64){ res = math.Sqrt(x*x+y*y) // 因为返回值声明了变量res ,所以return 默认将res返回 return } // 如果参数列表中参数类型相同,可以简写 func swop(x,y int,s1,s2 string) (int,int,string,string){ x,y = y,x s1 ,s2 = s1 ,s2 // Go语言是支持多返回值得 // 以为返回值列表中没有声明返回变量,所以return要将返回的值写清楚 // 定义了多少返回值,就必须将它们都返回,否则编译报错`not enough arguments to return` return x,y,s1,s2 } func main(){ // 此处都是函数调用 fmt.Println(hypot(7,9)) fmt.Println(swop(10,99,"hello","golang")) }

go run main.go

11.40175425099138 99 10 hello golang

示例:

package main import "fmt" const( M = 60 H = M*60 D = H*24 ) func parseTime(s float64) (m ,h,d float64){ m = s/M h = s/H d = s/D return } func main(){ s := 1797979 m,h,d := parseTime(float64(s)) fmt.Printf("%d秒约等于%f分钟,%f小时,%f天",s,m,h,d) }

go run main.go

1797979秒约等于29966.316667分钟,499.438611小时,20.809942天

2.2 值传递与引用传递

基本数据类型和数组默认是值传递,在函数内部修改,不影响原有的值

希望函数函数内部修改,能影响到原有的变量,那就采用引用传递,将变量的地址 &传给函数,函数内部以指针 形式操作

⭐️ 不论是值传递还是引用传递,其实给函数的都是变量副本,不同的是,值传递的是值得拷贝副本,而引用传递则是地址的拷贝,通常地址拷贝因为数据量小,故效率更高.而值拷贝则是数据量越大,效率越差

值传递类型 : 所有int类型 , 所有 float类型 , bool类型 , 数组 , 结构体

引用类型 : 指针 , slice, map , chan , interface 等类型

值传递的示例:

package main import "fmt" //都是值传递,拷贝了一个副本数据 func passValue(n int, f float64, s string, arr [3]int) (int, float64, string,[3]int) { arr[1] = arr[1] * 100 return n * 2, f * f, s + "~~~~" ,arr } func main() { n := 10 f := 99.9 s := "hello" arr := [3]int{22, 33, 44} // 调用函数 n1, f1, s1 ,arr1:= passValue(n, f, s, arr) fmt.Println(n1, f1, s1, arr1) // 原来变量的值没有任何影响 fmt.Println(n, f, s, arr) }

go run main.go

20 9980.010000000002 hello~~~~ [22 3300 44] 10 99.9 hello [22 33 44]

引用传递示例 :

package main import "fmt" // 引用传递1 数据类型本身就时引用类型 func citePass(sli []string, m map[string]string) { sli[0] = "AAA" m["job"] = "programmer" } // 引用传递2 指针形式 func citePass2(n *int) { *n = *n +100 } func main() { //定义一个slice var sli []string = []string{"aa", "bb", "cc", "dd"} //定义一个map var person = make(map[string]string) person["name"] = "tom" person["addr"] = "Provence" fmt.Printf("sli的len = %d,cap = %d value = %v\n", len(sli), cap(sli), sli) fmt.Println(person) //执行函数citePass之后 citePass(sli, person) fmt.Printf("sli的len = %d,cap = %d value = %v\n", len(sli), cap(sli), sli) fmt.Println(person) n := 10 citePass2(&n) fmt.Println(n) }

go run main.go

sli的len = 4,cap = 4 value = [aa bb cc dd] map[addr:Provence name:tom] sli的len = 4,cap = 4 value = [AAA bb cc dd] map[addr:Provence job:programmer name:tom] 110

3. 函数变量

在Go语言中,函数像值一样拥有类型,可以赋值给变量,传递给函数,从函数中返回

package main import ( "fmt" "strings" ) func demo1(n int) int { return n * n } func demo2(s string) string{ return strings.ToUpper(s) } func main(){ // 定义一个函数变量 f := demo1 fmt.Printf("f type = %T\n",f) fmt.Println(f(9)) // 声明一个函数类型的变量 s var s func(string)string s = demo2 fmt.Printf("s type = %T\n s = %s",s,s("hello golang")) }

go run main.go

f type = func(int) int 81 s type = func(string) string s = HELLO GOLANG

函数作为一个数据类型在其他函数中作为参数的数据类型

package main import "fmt" func getSum(x, y int) int { return x + y } // demo1 的参数类型是func()类型 func demo1(f func(int,int)int, a int,b int) int { res := f(a,b) return a+res } func main() { // 定义一个函数类型 f := getSum // 函数类型作为实参传递 res := demo1(f,20,80) fmt.Println(res) }

go run main.go

120

函数作为返回类型

package main import "fmt" func getSum(x, y int) int { return x + y } // 函数作为返回类型 func demo2() (f func(int,int)int){ return getSum } func main() { rf := demo2() fmt.Printf("rf type = %T\n",rf) fmt.Println(rf(20,100)) }

go run main.go

rf type = func(int, int) int 120

4. 匿名函数

匿名函数就是没有函数名的函数,只有函数体,匿名函数具有函数的一般特性.可以被传递,赋值给变量等

4.1 定义形式1

定义匿名函数时直接使用

package main import "fmt" func main() { res := func(x,y int) int { return x+y }(90,10) fmt.Println(res) }

4.2 定义形式2

将匿名函数赋值给一个变量

package main import "fmt" func main() { sum := func(x,y int) int { return x+y } fmt.Printf("sum type is %T\n",sum) fmt.Println(sum(90,90)) }

4.3 定义形式3

定义成全局匿名函数

package main import "fmt" var func1 = func(x,y int) int { max :=0 if x>y{ max = x }else if x<y{ max = y } return max } func main() { fmt.Printf("func1 type is %T\n",func1) fmt.Println(func1(90,190)) }

4.4 匿名函数作为回调函数

package main import "fmt" func demo(s []int,f func(int)int) []int{ for k,v:=range s{ s[k] = f(v) } return s } func main() { res := demo([]int{1,2,3,4,5}, func(i int) int { return i*i }) fmt.Println(res) }

4.5 匿名函数示例

package main import ( "flag" "fmt" ) var skillString = flag.String("skill","","programmer have skill") func main(){ flag.Parse() var skill = map[string]func(){ "sing": func() { fmt.Println("会唱歌") }, "dance":func(){ fmt.Println("会跳舞") }, "rap": func() { fmt.Println("会rap") }, "code": func() { fmt.Println("会写code") }, } if f,ok := skill[*skillString];ok{ f() }else{ fmt.Println("这个真没有...") } }

5. 闭包

闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使己经离开了 自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量。因此,简 单的说 : 函数+引用环境=闭包

package main import "fmt" func counter(n int) func() int{ // 返回一个匿名函数,该匿名函数内引用了函数外的变量n,因此匿名函数和n构成了一个闭包 return func() int { n++ return n } } func main(){ f := counter(1) fmt.Printf("%p\n",f) fmt.Println(f()) fmt.Println(f()) fmt.Println(f()) fmt.Printf("%p\n",f) f1 := counter(10) fmt.Printf("%p\n",f1) fmt.Println(f1()) fmt.Println(f1()) fmt.Println(f1()) fmt.Printf("%p\n",f1) }

go run main.go

0x4932a0 2 3 4 0x4932a0 0x4932a0 11 12 13 0x4932a0

另外一个示例

package main import ( "fmt" "strings" ) func demo(s string) func(string) string { return func(n string) string { if !strings.HasSuffix(n, s) { return n + s } return n } } func main() { d := demo(".png") fmt.Println(d("aaa")) fmt.Println(d("bbb.png")) }

go run main.go

aaa.png bbb.png

6. 函数的可变参数

函数的参数列表没有固定的个数的参数

定义格式如下 :

func 函数名(固定参数列表,V ...T)(返回参数列表){ 函数执行体 } V表示可变参数变量,T表示参数的数据类型, 那么V的类型是 []T,数据类型为T的切片... 固定语法表示参数个数不确定可变参数一般放在参数列表最尾部

示例 1:

package main import ( "fmt" ) func demo1(s string, sliString ...string) { // 可变参数是数据类型为string的切片 fmt.Printf("%T\n",sliString) for _,v:= range sliString{ fmt.Println(s+" "+v) } } func demo2(s ...string) string{ res := "" for _,v:= range s{ res += v } return res } func main(){ demo1("hello","ok","nice","yes","good") fmt.Println(demo2("Let ","Us ","Go")) }

go run main.go

[]string hello ok hello nice hello yes hello good Let Us Go

示例2: 可变参数函数之间参数传递

package main import "fmt" func pri (s ...interface{}){ for _,v := range s{ fmt.Println(v) } } func dump(s ...interface{}){ pri(s...) } func main(){ dump("golang",2.0) }

示例3: (获取数据类型)

package main import ( "bytes" "fmt" ) func getType(s ...interface{}) string { var b bytes.Buffer for _, v := range s { str := fmt.Sprintf("%v", v) var typeS string switch v.(type) { case bool: typeS = "bool" case int: typeS = "int" case string: typeS = "string" case float64: typeS = "float64" case []string: typeS = "slice" default: typeS = "unknow" } b.WriteString("value : ") b.WriteString(str) b.WriteString(" , type : ") b.WriteString(typeS) b.WriteString("\n") } return b.String() } func main() { res := getType(90, "golang", true, 123.234, []string{"demo"}) fmt.Println(res) }

go run mian.go

value : 90 , type : int value : golang , type : string value : true , type : bool value : 123.234 , type : float64 value : [demo] , type : slice

7.延迟调用defer

Go语言中关键字 defer 表示延迟跟在其后面的语句的执行,被延迟的语句按照defer调用的顺序逆向执行,既:最后调用的defer语句,优先执行

defer 语句调用遵照先进后出的原则

defer延迟机制,通常讲是为了释放资源,一些常见的创建资源的操作 打开文件,连接数据库,枷锁 等在最后都需要关闭资源句柄,断开连接 释放锁 等操作,这些都可以交给defer来做

7.1 defer的执行顺序

package main import "fmt" func demo(s string) { fmt.Println(s) } func main(){ fmt.Println("program start") defer fmt.Println("one") defer demo("two") defer func() { fmt.Println("three") }() fmt.Println("program over") }

go run main.go

program start program over three two one

7.2 defer 使用示例

示例1 : 文件复制

从A文件复制到B文件,B文件不存在就创建

package main import ( "fmt" "io" "os" ) func copyFile(dstName, srcName string) (written int64, err error) { // open打开一个文件用于读取,读取成功,返回文件对象 src, err := os.Open(srcName) // open错误就直接返回错误 if err != nil { return } // defer操作关闭文件 defer src.Close() //openFile也是打开文件的函数,可以使用指定项(os.O_RDONLY 只读 os.O_CREATE 不存在就创建新文件 )和指定模式打开文件 dst, err := os.OpenFile(dstName, os.O_RDONLY|os.O_CREATE, 0644) if err != nil { return } // defer操作关闭文件 defer dst.Close() // io.copy()函数将src的数据拷贝到dst,直到src上到达EOF或者错误 return io.Copy(dst, src) } func main() { // 调用自定义函数copyFile // Args 保存用户命令行参数 []string 类型 copyFile(os.Args[1], os.Args[2]) fmt.Println("赋值完成~~") }

go run main.go "demo.txt" "english.log"

|_main.go |_demo.txt |_english.log

示例2 : 获取文件大小

package main import ( "fmt" "os" ) func getFileSize(filename string) int64{ f,err :=os.Open(filename) defer f.Close() if err != nil{ return 0 } info ,err := f.Stat() if err != nil{ return 0 } size := info.Size() return size } func main(){ // 获取文件的大小(byte) fmt.Println(getFileSize(os.Args[1])) }

8. 错误处理

错误处理是几乎所有编程语言都必须考虑的,Go语言中的错误就是可能出现错误的地方真就出现了错误,我们需要对这样的错误进行处理,这么看错误也是我我们业务代码的一部分,错误处理也将是开发中必须中的必要环节.正确的处理错误,能保障程序更加健壮.

Go语言中引入错误处理的标准模式 ,error 接口,是Go语言内建的接口类型

Go语言中错误处理有如下的特点

可能造成错误的函数,在返回值列表中应该返回一个错误接口 error 如果函数调用成功,错误接口返回的是nil函数在调用之后需要检查错误,如果有错误,需要进行错误处理 package main import ( "fmt" "os" ) func getFileSize(name string) (size int64, err error) { size = 0 f, err := os.Open(name) if err != nil { return size, err } info, err := f.Stat() if err != nil { return size, err } size = info.Size() return size, nil } func main() { // aa.log 文件不存在 s, e := getFileSize("aa.log") if e != nil { fmt.Println(e) } else { fmt.Println(s) } fmt.Println("go on ...") }

go run main.go

open aa.log: The system cannot find the file specified. go on ...

8.1 自定义错误

Go语言中支持自定义错误,使用 errors.New 和 panic 内置函数

errors.New("错误说明") 返回一个error类型的值,表示一个错误panic() 内置函数 可以接收任意类型的值,作为参数,输出错误信息,且退出程序 package main import ( "errors" "fmt" ) func readConfig(name string) (err error) { if name == "config.ini" { return nil } else { return errors.New("读取配置文件错误..") } } func test() { err := readConfig("config2.ini") if err != nil { // 输出错误信息,并退出程序 panic(err) } fmt.Println("test() go on ..") } func main() { test() fmt.Println("client process go on ...") }

go run main.go

panic: 读取配置文件错误.. goroutine 1 [running]: main.test() /Go/src/GoNote/chapter6/panic-operate/main.go:18 +0x79 main.main() /Go/src/GoNote/chapter6/panic-operate/main.go:23 +0x29

8.2 异常的处理

Go语言程序有些错误只能在运行时检查,像数组访问越界,空指针引用等,这些在运行会引起panic异常,一旦发生panic异常,程序就会中断执行,并输出日志,日志包括panic value和函数调用的堆栈跟踪信息

Go语言中处理异常的关键函数是 defer panic recover

简单描述是 : Go语言在运行的时候抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常的处理掉

8.2.1 触发panic

panic 触发一般有三种

手动触发主动触发延迟触发

示例 : 手动触发panic

package main import "fmt" func main(){ fmt.Println("program start ...") panic("手动触发panic") fmt.Println("program end ...") }

go run main.go

panic: 手动触发panic goroutine 1 [running]: main.main() E:/Go/src/GoNote/chapter6/demo2/main/main.go:7 +0x9d

示例 : 自动触发panic

package main import "fmt" func main() { defer fmt.Println("") var arr = [...]string{"zero", "one", "two", "three", "four"} length := len(arr) for i := 0; i <= length; i++ { fmt.Println(arr[i]) } }

go run main.go

zero one two three four panic: runtime error: index out of range goroutine 1 [running]: main.main() E:/Go/src/GoNote/chapter6/demo3/main/main.go:10 +0x177

示例 : panic 在defer之后执行

package main import ( "fmt" "time" ) func demo1() { defer func() { fmt.Println(time.Now().Format("2006-01-02 15:04:05")) }() } func main() { demo1() defer fmt.Println("first ...") defer fmt.Println("second ...") panic("really over") }

go run main.go

2019-08-24 16:25:06 second ... first ... panic: really over goroutine 1 [running]: main.main() E:/Go/src/GoNote/chapter6/demo4/main/main.go:18 +0xfd
8.2.2 recover 捕获异常

无论是运行时抛出的panic 还是其他形式的panic,我们都可以配合defer和recover实现异常的捕获和恢复,让程序继续运行下去

本质上Go语言中没有异常系统,使用panic和recover是模拟了其他编程语言中的应对异常的机制

package main import ( "fmt" "runtime" ) func divZero(){ num1 := 10 num2 := 0 res := num1 / num2 fmt.Println("res=", res) } func pointCite(){ var s *string *s = "hello world" } func Run(f func()){ defer func() { err := recover() switch err.(type) { case runtime.Error: fmt.Println("runtime error : ",err) default: fmt.Println(err) } }() f() } func main(){ fmt.Println("start===>") Run(divZero) fmt.Println("go on===>") Run(pointCite) fmt.Println("end===>") }

go run main.go

start===> runtime error : runtime error: integer divide by zero go on===> runtime error : runtime error: invalid memory address or nil pointer dereference end===>

panic 和 recover 的关系

有panic 没有recover 程序直接中断有panic 和recover捕获,程序不会中断,从报panic的点退出后,程序继续执行后面的流程
最新回复(0)