函数、包、错误处理

函数

概念和Java的一样。但是不同的点在于,go放入函数可以返回多个参数,这个是其他语言没有的;例如,求两个数的和和差

1
2
3
4
5
6
7
8
9
10
11
func main() {
a:=1
b:=2
sum,avg:=test(a,b)
fmt.Printf("%d和%d的和是%d \n",a,b,sum)
fmt.Printf("%d和%d的差是%d",a,b,avg)
}
func test(a int,b int)(int,int){
return a+b,a-b

}

如果希望不使用或者忽略变量,那么可以使用一个下划线来表示忽略,返回值只要写类型。使用return返回数据

如果你要编写一个可执行文件,编写一个可以运行看一下运行结果的话,那么那一个文件的包必须是main,函数必须是main()

函数的递归:求1到n的和

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
a:=test(5)
fmt.Println(a)

}
func test(a int)(int) {
sum:=0
if a>=1{
sum=a+test(a-1)
}
return sum;
}

函数的命名也是首字母大写代表public

函数使用细节讨论

  • 函数不可以修改函数外面的值,如果要修改可以使用指针修改数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    func main() {
    a:=1
    test(&a)
    fmt.Println(a)
    }
    func test(a *int){//传入指针修改数据
    b:=2
    *a=b;
    }
    //交换两个参数的值
    func main() {
    a:=1
    b:=2
    fmt.Println("交换前 \t",a,"\t",b)
    exchange(&a,&b)
    fmt.Println("交换后 \t",a,"\t",b)


    }
    func exchange(a *int,b *int){
    t:=0
    t=*a
    *a=*b
    *b=t
    }
  • go函数不支持函数的重载

  • 在go中函数也是一种数据类型,可以赋值给一个变量,通过该变量实现对函数的引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    func main() {
    a:=2
    b:=test(a)
    fmt.Printf("%T \n",test) //func(int) int
    fmt.Println(b)//2
    }
    func test(a int)(int){
    return a
    }

    func括号里面的是参数类型

    1
    2
    3
    4
    5
    6
    7
    8
    func main() {
    a:=test
    fmt.Println(a())//调用函数
    fmt.Printf("%T",a) //类型是func() string
    }
    func test() string {
    return "通过变量应用函数"
    }

    感觉是实际上是给函数取一个别名

  • 函数可以做另一个函数的形参使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    func main() {
    fmt.Println(test(sum,2,3))
    }
    func test(fun func(int, int) int, a int, b int) int {
    return fun(a,b)
    }
    func sum(a int, b int) int {
    return a + b
    }

    引用函数的时候,类型就是使用%T输出的那个类型,不要忘记写函数返回值类型

  • 为了简便数据类型的定义,可以自己定义数据类型,函数也可以

    type 名字 数据类型

    数据类型取别名

    1
    2
    3
    4
    5
    6
    type myint int

    func main() {
    var a myint = 3 //在原本写类型的地方写上我们的别名
    fmt.Println(a)
    }

    函数类型取别名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import "fmt"

    type myfun func(int, int) int

    func main() {
    fmt.Println(test(sum,1,2))
    }
    func test(fun myfun, a int, b int) int {
    return fun(a, b)//在表名函数类型的地方写上我们对函数类型取的别名
    }
    func sum(a int, b int) int {
    return a + b
    }

    取别名只是在使用函数作为参数的时候,相同的函数类型才可以替换

    给函数返回值取别名

    给返回值取别名以后,我们的不用写return 别名,直接写上返回值的表达式,他会自动返回

    1
    2
    3
    4
    func sum(a int, b int) (myint int) {
    myint=a+b
    return
    }

    但是值的注意的是,我们虽然给函数的返回值去了别名,但是我们输出他的函数类型的时候,依然是原样

    1
    2
    fmt.Printf("%T",sum)
    //结果 func(int, int) int
  • 函数支持可变长参数,使用args… 数据类型(int string float32 float64…) 表示,如果还有其他的参数,那么可变长参数要放在最后至于原因嘛,据我推测,如果你的可变长参数在前,你的后一个参数和你的可变长参数类型一样,那么系统就不知道,谁是谁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func main() {
    fmt.Println(test(1,1,2,3,4,5,6,7,8,9))
    }
    func test(a int,args... int) int{
    sum:=0
    for i:=0;i<len(args);i++{
    sum+=args[i]
    }
    return sum
    }
    • 说明args是切片,通过args[index]可以访问到各个值

init 初始化函数

初始化函数最主要的作用就是,完成一些 初始化工作,比如给变量赋值等,如果有引用的包,那么引用的包的初始化函数就比这个函数先

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//引用的包
package Demo2

import "fmt"
func init(){
fmt.Println("这是引用的包")
}

func Test(){
fmt.Println("我是谁")
}


package main

import "fmt"
import util "../Demo2"

func init(){
fmt.Println("函数初始化")
}

func main() {
util.Test()
}
//显示结果
这是引用的包
函数初始化
我是谁

匿名函数

就是只使用一次的函数,不用给名字,和java概念一样,像下面这个方式引用,那么他只能被调用一次

1
2
3
4
5
6
7
8
func main() {
a, b := func(a int, b int) (sum int,sub int) {
sum=a+b
sub=a-b
return
}(10, 20)
fmt.Println(a,b)
}

参数写在最后面,参数类型和函数返回值类型放在前面,使用和前面的一般函数一样,当我们把这个函数使用一个变量来接受,我们就可以使用这个函数来调用这个函数,实际上也算是给这个匿名函数取名

1
2
3
4
5
6
a:= func(a int, b int) (sum int,sub int) {
sum=a+b
sub=a-b
return
}//因为有一个变量来接受,所以我们这里不能传递参数
fmt.Println(a(100,50))

我们如果想要在函数的内部定义函数,不能直接定义,需要使用匿名函数

1
2
3
4
5
6
7
8
9
10
func main() {
test()

}
func test(){
a:=func (a int,b int){
fmt.Println(a+b)
}
a(1,2)
}

当然,如果我们把这个匿名函数赋值给一个全局变量那么他就是全局匿名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var(
a func(int,int)
)

func main() {
test()

}
func test(){
a=func (a int,b int){
fmt.Println(a+b)
}
a(1,2)
}

这个时候,有人要问了,这个有什么用啊,实际上我也不知道,以后看源码就明白了,现在先记着有这么个概念

闭包

image-20201207151909922

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
a := user()
a("美铝")
a("实际上")
a("喜欢你")
}

func user() func(string) {
name := "saxon"
return func(str string) {
fmt.Println(name + "喜欢" + str)
}
}

实际上,闭包就是函数的实现,用属性和操作(匿名函数)操作数据,实际上函数的参数就是匿名函数,匿名函数的返回值就是这个函数的返回值 而我们的函数里面的变量就相当于我们的属性,可以操作,并且如果一直是一个对象的话,那么他的属性就不会消失,就是同一个

==再次强调,我们的函数引用带()表示返回值,不带括号表示那个变量就是函数本身==

defer

当我们不想立马执行某一个语句,只想等所有的语句都结束了在执行,我们就可以使用defer这个关键字,他会把我们的带有defer的语句放到栈(先进后出)中,在所有的函数执行完了后,再返回俩执行这些压入栈的语句,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
test()
}
func test(){
n:=1
defer fmt.Println("第一句",n)
n++
defer fmt.Println("第二句",n)
n++
defer fmt.Println("第三句",n)
n++
defer fmt.Println("第四句",n)
}
//执行结果
第四句 4
第三句 3
第二句 2
第一句 1

值的我们关注的是,我们的语句虽然是后来执行但是,我们的值也在那个时候被拷贝到栈中,所以数据会被携带。一般可以用于资源的关闭,例如数据库操作完毕后,使用connection.close(),在Java的时候,我们需要使用try catch finally,在finally语句里面执行关闭操作,但是在go中,只要函数执行完了就可以关闭,不用担心在什么时候能关闭资源,或者忘记关闭资源

常见的strings函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
	str1 := "hello world"
str2 := "hello saxon"
str3 := []rune(str2)
isEqual := strings.EqualFold(str1, str2) //false 判断两个字符串是否相等,忽略大小写
pre := strings.HasPrefix(str1, "hello") //true 判断某个字符串是某一个字符串的前缀
suf := strings.HasSuffix(str1, "world") //false 判断某一个字符串是否是后缀
iscontain := strings.Contains(str1, "hello") //false 判断是否存在某一个字符串
iscontainsRune := strings.ContainsRune(str1, str3[0]) //使用ascii判断是否存在某一字符,只能判断一个字符可以是中文
isContainsAny := strings.ContainsAny(str1, "h") //判断字符串s是否包含字符串chars中的任一字符。只要有一个匹配就返回true
count := strings.Count(str1, "o") //判断字符串含有几个后面的字符串返回字符串s中有几个不重复的sep子串
index := strings.Index(str1, "d") //判断文字的第一次出现的下标,其中一个中文占用3个字节,len就是+3,从0开始,没有返回-1
indexbyte := strings.IndexByte(str1, 'd') //字符c在s中第一次出现的位置,不存在则返回-1。
indexrune := strings.IndexRune(str1, str3[0]) //unicode码值r在s中第一次出现的位置,不存在则返回-1
indexany := strings.IndexAny(str1, "llo") //字符串chars中的任一utf-8码值在s中第一次出现的位置,如果不存在或者chars为空字符串则返回-1
/**
关于下面这个函数的解析
IndexFunc->indexFunc 不是重载,而是对IndexFunc的具体实现
func IndexFunc(s string, f func(rune) bool) int {
return indexFunc(s, f, true)
}
func indexFunc(s string, f func(rune) bool, truth bool) int {
for i, r := range s {
if f(r) == truth {
return i
}
}
return -1
}
先遍历这个字符串,把每一次遍历的结果交给一个函数,那么当遇到条件符合的时候,返回此时的字符下标,
例如:我们写的函数是下面这个函数
func test(rune rune) bool {
if rune == 's' {
return true
} else {
return false
}
}
那么当字符串遍历结果符合我们设置的查询条件,返回true的时候,返回这个时候的下标,这个条件相当于是让我们自己写;你可以写任何关于这个字符的
判定条件,,比如当前遍历结果是某一个字符的时候,他们的编码值是多少的时候,但是好像前面都有相关的函数,所以先在此记一下,以后在慢慢用
*/
indexFunc := strings.IndexFunc(str1, test)
lastIndex := strings.LastIndex(str1, "s") //查询最后一次出现这个字符串的下标 和上面一样的有 LastIndexAny() LastIndexFunc()
title := strings.Title(str1) //首字母大写
tolower := strings.ToLower(str1) //字符串所有字母大写 类似的还有ToTitle() ToUpper() 全部大写
toup := strings.ToUpper(str1)
totitle := strings.ToTitle(str1)
repeat := strings.Repeat(str1, 2) //重复字符串
replace := strings.Replace(str1, "h", "qq", 1) //替换指定的字符串,最后一个参数是替换的个数
//TrimSpace 去掉空格
//TrimFunc 返回将s前后端所有满足f的unicode码值都去掉的字符串 和前面一样
//TrimLeft() TrimLeftFunc()去除前面的字符
//TrimPrefix 去除前缀
//TrimRight TrimRightFunc去除右边字符
//TrimSuffix 去除后缀
trim := strings.Trim(str1, "s") //去掉字符串最前面和最后面符合cutset的字符串
//FieldsFunc 分割
fields:=strings.Fields(str1)//按照空格分割字符串
// SplitN() 最后一个参数决定分几分
//SplitAfter() 从匹配到的字符串的后面开始分割 SplitAfterN()具体分成几份
spilts:=strings.Split(str1,"")//以sep为分割符用去掉s中出现的sep的方式进行分割,,分割后不在其中,分割字符串如果sep为空字符,Split会将s切分成每一个unicode码值一个字符串。
join:=strings.Join([]string{str1,str2},"hello world")//将字符串连接,之间使用sep来分割


func test(rune rune) bool {
if rune == 's' {
return true
} else {
return false
}
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
false
true
true
true
true
true
2
10
10
0
2
-1
-1
Hello World
hello world
HELLO WORLD
HELLO WORLD
hello worldhello world
qqello world
hello world
hello
world
h
e
l
l
o

w
o
r
l
d
hello worldhello worldhello saxon

Process finished with exit code 0

常见的时间日期处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//获得当前时间
timeNow := time.Now()
fmt.Println(timeNow)
start:=timeNow.UnixNano()//UnixNano:纳秒 Unix():秒
//可以通过now获得时分秒
year := timeNow.Year()
month := timeNow.Month()
day := timeNow.Day()
hour := timeNow.Hour()
minute := timeNow.Minute()
second := timeNow.Second()
//格式化时间
//第一种
fmt.Printf("%d年%d月%d日%d:%d:%d \n", year, month, day, hour, minute, second)
//第二种
Stime := fmt.Sprintf("%d年%d月%d日%d:%d:%d", year, month, day, hour, minute, second)
fmt.Println(Stime)
//第三种
//月份 1,01,Jan,January
//日  2,02,_2
//时  3,03,15,PM,pm,AM,am
//分  4,04
//秒  5,05
//年  06,2006
//时区 -07,-0700,Z0700,Z07:00,-07:00,MST
//周几 Mon,Monday
//要严格按照上面的代码书写,否则会出错
fmt.Println(timeNow.Format("2006-01-02 15:04:05"))
end:=time.Now().UnixNano()
fmt.Println("程序运行的时间是",(end-start)/1e9)

内置函数

就是可以直接使用的函数,例如前面用过的 len()

  1. len(),用来求某些类型数据的长度

  2. new(数据类型),用来声明变量,声明后就是指针,这个b存的是地址,那个地址才是真正的数据

    1
    2
    b:=new(int)
    fmt.Println(*b)
  3. make:用来分配内存,后面在解释

错误处理

这里介绍的就是关于go的异常捕获

由于为了简洁,所以go语言的设计者,没有使用Java里面的捕获异常机制,而是使用 **==defer+recover==**处理错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
test05()
}
func test05(){
defer func(){
err:=recover()
if err!=nil{
fmt.Println("程序运行过程中,发现错误")
}
}()
a:=5
b:=0
fmt.Println(a/b)
}

运行结果:

1
程序运行过程中,发现错误

这样的好处是,程序不会因为发送错误就退出,可以一直执行下去 和Java一样,我们自定义错误,让程序遇见我们自定义的错误的时候,也会停止运行而退出

自定义错误

1
2
3
4
5
6
7
8
9
10
11
12
func test05() {
err:=findError("error")
fmt.Println(err)
}

func findError(str string) (err error) {
if strings.EqualFold(str, "error") {
return errors.New("信息错误")
} else {
return nil
}
}

  • 访问其他包的函数使用 包名+函数名

  • 如果包名太长,可以使用别名,但是一旦使用别名的话,原来的包名就不可以使用了;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package main

    import (
    demo "../Demo2"
    )
    func main() {
    demo.Test()
    }

    import 引入的包的时候,位置是相对路径。相当于是一个类,引用里面的静态方法;