我是靠谱客的博主 含蓄季节,这篇文章主要介绍Go基础05:函数、包和错误处理,现在分享给大家,希望可以做个参考。

函数

为完成某一功能的程序指令(语句)的集合,称为函数。

函数定义

函数定义格式:

复制代码
1
2
3
4
func function_name( [parameter list] ) [return_types] { 函数体 }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main import "fmt" func test(n int) { n = n + 1 fmt.Println("test() n=", n) // 11 } func main() { n := 10 test(n) fmt.Println("main() n=", n) // 10 }

说明:

  1. 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写该函数可以被本包文件和其它包文件使用,类似 public,首字母小写,只能被本包文件使用,其它包文件不能使用,类似 privat。
  2. 在调用一个函数时,会给该函数分配一个新的空间,编译器会通过自身的处理让这个新的空间和其它的栈的空间区分开来。
  3. 在每个函数对应的栈中,数据空间是独立的,不会混淆。
  4. 当一个函数调用完毕(执行完毕)后,程序会销毁这个函数对应的栈空间。
  5. Go 函数不支持函数重载。

函数使用的注意事项和总结:

  1. 函数的形参列表可以是多个,返回值列表也可以是多个,使用_标识符,忽略返回值。

    复制代码
    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 main import "fmt" func getSum(n1 int, n2 int) int { sum := n1 + n2 return sum } // 函数支持返回多个值 func getSumAndSub(n1 int, n2 int) (int, int) { sum := n1 + n2 sub := n1 - n2 return sum, sub } func main() { sum := getSum(10, 20) fmt.Println(sum) // 30 // 调用多个值返回函数 sum1, sub1 := getSumAndSub(10, 20) fmt.Println(sum1, sub1) // 30 -10 // 忽略某个返回值 sum2, _ := getSumAndSub(10, 20) fmt.Println(sum2) // 30 }
  2. 形参列表和返回值列表的数据类型可以是值类型和引用类型。

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package main import "fmt" func test(n *int) *int { *n = *n + 1 return n } func main() { n := 10 res := test(&n) fmt.Println(*res) // 11 }
  3. 如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package main import "fmt" func test(n *int) { *n = *n + 1 } func main() { n := 10 test(&n) fmt.Println(n) // 11 }
  4. 在 Go 中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package main import "fmt" func getSum(n1 int, n2 int) int { return n1 + n2 } func main() { a := getSum // a的类型 fmt.Printf("%Tn", a) // func(int, int) int // getSum类型 fmt.Printf("%Tn", getSum) // func(int, int) int res := a(10, 20) fmt.Println(res) // 30 }
  5. 函数既然是一种数据类型,因此在 Go 中,函数可以作为形参,并且调用。

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package main import "fmt" func myFun(funVar func(int, int) int, num1 int, num2 int) int { return funVar(num1, num2) } func getSum(n1 int, n2 int) int { return n1 + n2 } func main() { res := myFun(getSum, 10, 20) fmt.Println(res) // 30 }
  6. 为了简化数据类型定义,Go 支持自定义数据类型基本语法:type 自定义数据类型名数据类型。

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package main import "fmt" type myType func(int, int) int func myFun(funVar myType, num1 int, num2 int) int { return funVar(num1, num2) } func getSum(n1 int, n2 int) int { return n1 + n2 } func main() { res := myFun(getSum, 10, 20) fmt.Println(res) // 30 }
  7. 支持对函数返回值命名。

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package main import "fmt" // 函数返回值命名 func getSumAndSub(n1 int, n2 int) (sum int, sub int) { sum = n1 + n2 sub = n1 - n2 return } func main() { sum1, sub1 := getSumAndSub(10, 20) fmt.Println(sum1, sub1) // 30 -10 }
  8. Go支持可变参数,如果一个函数的形参列表中有可变参数,则可变参数需要放在形参列表最后。

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package main import "fmt" func sum(n int, args ...int) int { sum := n for i := 0; i < len(args); i++ { sum += args[i] } return sum } func main() { sum := sum(1, 2, 3, 4, 5, 6, -1, -2) fmt.Println(sum) // 18 }

init 函数

每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也就是说 init 会在 main 函数前被调用。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
package main import "fmt" func init() { fmt.Println("init()...") } func main() { fmt.Println("main()...") }

匿名函数

Go 支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main import "fmt" func main() { // 方式一:在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次。 res1 := func(n1 int, n2 int) int { return n1 + n2 }(10, 20) fmt.Println(res1) // 30 // 方式二:将匿名函数赋给一个变量(函数变量),再通过该变量来调用匿名函数。 res2 := func(n1 int, n2 int) int { return n1 + n2 } fmt.Println(res2(10, 20)) // 30 }

全局匿名函数

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main import "fmt" var ( res = func(n1 int, n2 int) int { return n1 + n2 } ) func main() { fmt.Println(res(10, 20)) // 30 }

闭包

闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main import "fmt" func AddUpper() func(int) int { var n int = 10 return func(x int) int { n = n + x return n } } func main() { f := AddUpper() fmt.Println(f(1)) // 11 fmt.Println(f(2)) // 13 fmt.Println(f(3)) // 16 }

闭包说明:

  1. 闭包返回的是一个匿名函数,但是这个匿名函数引用到函数外的 n,因此这个匿名函数就和 n 形成一个整体,构成闭包。
  2. 可以理解闭包是类,函数是操作,n是字段。函数和它使用到 n 构成闭包。
  3. 当我们反复的调用 f 函数时,因为 n 是初始化一次,因此每调用一次就进行累计。
  4. 闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包。

闭包的最佳实践

  1. 编写一个函数 makeSuffix(suffix string) 可以接收一个文件后缀名(比如.jpg),并返回一个闭包
  2. 调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg),则返回文件名.jpg,如果已经有.jpg后缀,则返回原文件名。
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main import ( "fmt" "strings" ) func makeSuffix(suffix string) func(string2 string) string { return func(name string) string { if !strings.HasSuffix(name, suffix) { return name + suffix } return name } } func main() { f := makeSuffix(".jpg") fmt.Println(f("a")) // a.jpg fmt.Println(f("b.jpg")) // b.jpg }

函数的 defer

在函数中,程序员经常需要创建资源 (比如:数据库连接、文件句柄、锁等 ),为了在函数执行完毕后,及时的释放资源,Go 的设计者提供 defer(延时机制)。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main import ( "fmt" ) func sum(n1 int, n2 int) int { defer fmt.Println("ok1 n1=", n1) defer fmt.Println("ok2 n2=", n2) n1++ n2++ res := n1 + n2 defer fmt.Println("ok3 res=", res) return res } func main() { res := sum(10, 20) fmt.Println("res=", res) }

输出结果:

复制代码
1
2
3
4
5
ok3 res= 32 ok2 n2= 20 ok1 n1= 10 res= 32

defer 的注意事项和细节

  1. 当 Go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈中,然后继续执行函数下一个语句。
  2. 当函数执行完毕后,再从 defer 栈中,依次从栈顶取出语句执行(遵守栈先入后出的机制)。
  3. 在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈。

defer 的最佳实践

  1. 在 Go 编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源),可以执行 defer file.Close() defer connect.Close()。
  2. 在 defer 后,可以继续使用创建资源。
  3. 当函数完毕后,系统会依次从 defer 栈中,取出语句,关闭资源。
  4. 这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func test1() { // 关闭文件资源 file = openfile(文件名) defer file.close() // code... } func test2() { // 释放数据库资源 connect = openDatabase() defer connect.close() // code... }

Go 的每一个文件都是属于一个包的,也就是说 Go 是以包的形式来管理文件和项目目录结构的。

包的作用

  1. 区分相同名字的函数、变量等标识符。

  2. 当程序文件很多时,可以很好的管理项目。

  3. 控制函数、变量等访问范围,即作用域。

包的写法

  • 包名一般是小写的,使用一个简短且有意义的名称。
  • 包名一般要和所在的目录同名,也可以不同,包名中不能包含-等特殊符号。
  • 包一般使用域名作为目录名称,这样能保证包名的唯一性,比如 GitHub 项目的包一般会放到GOPATH/src/github.com/userName/projectName目录下。
  • 包名为 main 的包为应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件。
  • 一个文件夹下的所有源码文件只能属于同一个包,同样属于同一个包的源码文件不能放在多个文件夹下。

包的导入

单行导入

复制代码
1
2
3
import "包 1 的路径" import "包 2 的路径"

多行导入

复制代码
1
2
3
4
5
import ( "包 1 的路径" "包 2 的路径" )

自定义包

目录层次如下:

复制代码
1
2
3
4
5
6
7
. └── src └── main └── main.go └── mylib └── demo.go

demo.go

复制代码
1
2
3
4
5
6
7
8
package mylib import "fmt" func PrintStr() { fmt.Println("hello,world!") }

标准引用格式

复制代码
1
2
3
4
5
6
7
8
9
10
package main import ( "mylib" ) func main() { mylib.PrintStr() }

自定义别名引用格式

在导入包的时候,我们还可以为导入的包设置别名。

复制代码
1
2
3
4
5
6
7
8
9
10
package main import ( lib "mylib" ) func main() { lib.PrintStr() }

省略引用格式

这种格式相当于把 mylib包直接合并到当前程序中,在使用 mylib 包内的方法是可以不用加前缀mylib.,直接引用。

复制代码
1
2
3
4
5
6
7
8
9
10
package main import ( . "mylib" ) func main() { PrintStr() }

匿名引用格式

在引用某个包时,如果只是希望执行包初始化的 init 函数,而不使用包内部的数据时,可以使用匿名引用格式。

复制代码
1
2
3
4
5
6
7
8
9
10
package main import ( _ "mylib" ) func main() { }

错误处理

Go 语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种处理。

Go 中引入的处理方式为:defer,panic,recover

defer+recover

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main import "fmt" func test(a, b int) int { defer func() { err := recover() if err != nil { fmt.Println("err=", err) } }() res := a / b return res } func main() { n := test(10, 0) fmt.Println(n) }

自定义错误

Go程序中,也支持自定义错误,使用 errors.New 和 panic 内置函数。

  1. errors.New(“错误说明”) ,会返回一个 error 类型的值,表示一个错误.

  2. panic 内置函数 ,接收一个 interface{} 类型的值(也就是任何值了)作为参数。可以接收 error 类型的变量,输出错误信息,并退出程序。

复制代码
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
package main import ( "errors" ) func readFile(fileName string) (err error) { if fileName == "a" { return nil } else { return errors.New("读取文件错误...") } } func test() { err := readFile("b") if err != nil { // 输入错误,并终止程序 panic(err) } } func main() { test() }

最后

以上就是含蓄季节最近收集整理的关于Go基础05:函数、包和错误处理的全部内容,更多相关Go基础05内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(110)

评论列表共有 0 条评论

立即
投稿
返回
顶部