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 值传递与引用传递
基本数据类型和数组默认是值传递,在函数内部修改,不影响原有的值
希望函数函数内部修改,能影响到原有的变量,那就采用引用传递,将变量的地址
&
传给函数,函数内部以指针
形式操作
:star: 不论是值传递还是引用传递,其实给函数的都是变量副本,不同的是,值传递的是值得拷贝副本,而引用传递则是地址的拷贝,通常地址拷贝因为数据量小,故效率更高.而值拷贝则是数据量越大,效率越差
值传递类型 : 所有
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
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的点退出后,程序继续执行后面的流程