<Go>3 函数、包和错误处理
本文最后更新于:2023年6月27日 上午
3 函数、包和错误处理
3.1 包
go 以包的形式管理文件和项目目录结构。Go 中的每个文件都属于一个包。
#包的作用:
- 区分相同名字的函数、变量等标识符
- 程序文件繁多时,可以很好地管理项目
- 控制函数、变量等访问范围(作用域)
#语法:
package 包名 // 文件打包
import "包的路径" // 引入包
#使用细节:
-
文件打包时,让该包对应文件夹名。包名与文件夹名一致,且为小写字母
-
当文件要使用其他包的函数或变量时,要先引入对应的包
一次性引入多个包时,也能这样做
import ( "包名1" "包名2" )
-
package 指令必须存在于文件第一行,之后才是 import 命令
-
import 包时,编译器自动从 $GOPATH 的 src 目录下开始引入。
-
访问其他包的函数、变量时,语法是:
包名.标识符
-
如果包名较长,也能为其取别名。但取别名后,原本的包名就不再使用了
import 别名 "包名"
-
在同一包下,不能有相同的函数名。否则会导致重复定义
-
main 包是唯一的。如果要编译成一个可执行文件,就需要将该包声明为 main。
*****>go build [-o bin/my.exe] ****/****/****/main
[ ]
中是可选内容。加入的场合,在那个指定位置生成指定名称的可执行文件。否则在 $GOPATH 目录下生成默认名称的可执行文件。
3.2 函数
为完成某一功能的程序指令的集合,称为函数。程序不能没有函数,就像西方不能失去耶路撒冷。
在 Go 中,函数分为:自定义函数、系统函数
#基本语法:
func 函数名 (形参列表) (返回值列表) {
执行语句
return 返回值列表
}
可以没有返回值。这个场合,也能没有 return 语句
只有一个返回值(且未命名)时,那个返回值列表的括号也能省略
#注意事项:
-
接收函数返回值时,如果希望忽略某个返回值,可以使用
_
接收那个希望忽略的值 -
参数列表和返回值列表的数据类型可以是值类型,也能是引用类型
-
函数命名遵循标识符命名规范。
-
函数中的变量是局部的,函数外不生效
-
基本数据类型与数组的传参方法默认是值传递。在函数内修改不会影响原来的值
可以传入变量的地址,并在函数内以指针形式操作变量。这样从效果上看类似引用传递
不管值传递还是引用传递,传递给函数的都是变量的副本。但值传递传递值的拷贝,引用传递传递那个地址的拷贝。
-
Go 函数不支持重载
-
Go 中的函数也是一种数据类型。
可以把函数赋值给一个变量。这样,该变量就是一个函数类型的变量了。
Go 中函数是数据类型,因此也能作为形参,并调用
func main() { a := act fmt.Printf("%T\n", a) // func(int, int) int fmt.Println(met(act)) // 6 } func act(a, b int) int { return a + b } func met(f func(int, int) int) int { return f(1, 5) }
-
为简化数据类型定义,Go 支持自定义数据类型。语法如下:
type 自定义数据类型 数据类型
可以理解为给数据类型起了一个别名
type myfunc func(int, int) int // 此时 myfunc 等价于 func(int, int) int var a myfunc = act fmt.Printf("%T\n", a) // main.myfunc
虽然起了别名,但 Go 认为是两个不同类型
type myInt int var i int = 1000 var n myInt = 10 fmt.Printf("%T\n", i) fmt.Printf("%T\n", n) /* n = i */ // 无法这样赋值,因为 Go 认为 myInt 不是 int n = myInt(i) // 需要进行类型转换
-
支持对函数返回值命名
func act(a int, b int) (int, sum int, sub int) { sum = a + b sub = a - b if (sum > 20) { return } else { return 1, 1, 1 } } func main() { fmt.Println(act(10, 1)) // 1 1 1 fmt.Println(act(100, 1)) // 0 101 99 }
对返回值命名时,那些命名的返回值后方的所有返回值也要一并命名。
只要有对返回值的命名,就能使用单独的 return 语句。届时将所有命名的返回值返回,未命名的返回值返回那个类型的零值
即使对返回值进行了命名,也能指定返回值。指定返回值时必须指定全部返回值
-
Go 支持可变参数
func met(args ...int) (sum int) { for _, value := range args { sum += value } return sum }
args 本质是一个 slice 切片。通过 args[index] 访问各个值
-
函数内部声明 / 定义的变量称为 局部变量。其作用域仅限于函数内部。如果变量在代码块中,其作用域即限于该代码块。
函数外部声明 / 定义的变量称为 全局变量。作用域在整个包都有效。那个首字母大写的场合,其作用域在整个程序中有效。
3.2.1 init 函数
每个源文件都能包含一个 init 函数。该函数会在 main 函数执行前,被 Go 调用。
通常可以在 init 函数中完成初始化工作。
func init() {
...
}
#注意事项:
-
如果一个文件包含 全局变量定义、init 函数 和 main 函数,其执行流程是:
全局变量定义 -> init 函数 -> main 函数
-
init 函数最主要的作用是进行初始化工作
-
引入文件也有 init 函数时,执行流程如下:
被引入的全局变量定义 -> 被引入的 init 函数 -> 全局变量定义 -> init 函数 -> main 函数
3.2.2 匿名函数
Go 支持匿名函数。如果我们希望某个函数只使用一次,可以使用匿名函数。
匿名函数也能多次调用。
#使用方式:
-
将匿名函数赋给变量。通过变量来调用
f := func(n1 int, n2 int) int { return n1 + n2 } i := f(4, 100)
-
定义匿名函数时直接调用
i := func(n1 int, n2 int) int { return n1 + n2 }(4, 7)
这个调用可以理解成
i := f(4, 100)
中的f
标识符直接替换为函数对象。因为没有命名,所以只能调用一次
-
将匿名函数赋给全局变量,则其成为全局匿名函数
3.2.3 闭包
闭包就是一个函数和其相关的引用环境组成的整体(实体)
func act() func(int) int {
i := 0
return func(a int) (ret int) {
ret = i
i += a
return
}
}
上述函数返回一个匿名函数,该匿名函数用到函数外的 i。
该匿名函数与 i 是一个整体,即构成一个闭包。
反复调用闭包对象时,i 只初始化一次,故而每次调用都会造成累计:
f := act()
fmt.Println(f(100)) // 0
fmt.Println(f(100)) // 100
fmt.Println(f(100)) // 200
fmt.Println(act()(100)) // 0
fmt.Println(act()(100)) // 0
fmt.Println(act()(100)) // 0
3.2.4 defer 关键字
在函数中,程序员经常需要创建资源(如:数据库连接、文件句柄、锁等),为在函数执行完毕后,及时释放资源,Go 的设计者提供 defer(延时机制)
f := func(a int) {
defer fmt.Println(a, "(defer1)")
a += 10
defer fmt.Println(a, "(defer2)")
a *= 10
fmt.Println(a)
}
f(0)
此时,系统依次输出 100 、10(defer2)、0(defer1)
#注意事项:
-
执行函数时,如果遇到 defer 语句,系统不会执行该语句,而是会将其压入栈中。
函数执行结束后,再从该栈中依次取出语句(先入后出)并执行。
-
让 defer 语句入栈时,也会将相关数值一并拷贝入栈
-
defer 最主要的价值是,函数执行完毕后,可以及时释放资源
3.3 常用函数
3.3.1 字符串函数
-
len(v) int
:返回传入的 v 的长度。传入字符串时,返回那个字节数量。传入数组时,返回那个元素数量
传入数组指针时,返回 *v 元素数量。传入切片、映射时,返回元素数量
传入通道时,返回通道缓存中队列未读取的元素数量
-
r := []rune(string)
:将字符串转为 rune 切片这样,能解决中文字符乱码问题
b := []byte(string)
:将字符转为 byte 切片string([]rune) string
:将 rune 切片转为字符串string([]byte) string
:将 byte 切片转为字符串
以下函数被包含在 strconv 包内:
-
strconv.Atoi(string)(int, error)
:将字符串转成整数strconv.Itoa(int) string
:将字符串转成整数 -
strconv.FormatInt(int, int) string
:将指定 int 转为指定进制的字符串第二个参数可以是 2、8、16
以下函数被包含在 strings 包内:
-
strings.Contains(string, string) bool
:查找子串是否在指定字符串中 -
strings.Count(string, string) int
:统计字符串中有多少指定的子串 -
strings.EqualFold(string, string) bool
:不区分大小写地比较字符串是否相等 -
strings.Index(string, string) int
:返回子串第一次出现的 index 值。没有则返回 -1strings.LastIndex(string, string) int
:返回子串最后一次出现的 index 值 -
strings.Replace(string, string, string, int) string
:将指定子串替换为另一个子串可以指定要替换的数量。-1 的场合全部替换
所有的替换的场合,原字符串不变,返回那个替换后的新字符串
-
strings.ToLower(string) string
:转为全部小写英文字母strings.ToUpper(string) string
:转为全部大写英文字母 -
strings.Split(string, string) string[]
:按照指定字符对字符串进行分割 -
strings.TrimSpace(string) string
:去掉字符串前后的空格strings.Trim(string, string) string
:去掉字符串前后的所有的指定字符那些指定字符指的是第二个字符串参数中,包含的任意字符
fmt.Print(strings.Trim("1321oAo12", "123")) // 输出 oAo
strings.TrimLeft(string, string) string
:仅去掉字符串左边的指定字符strings.TrimRight(string, string) string
-
strings.HasPrefix(string, string) bool
:字符串是否以某子串开头strings.HasSuffix(string, string) bool
:字符串是否以某子串结尾
3.3.2 时间和日期函数
以下函数被包含在 time 包内:
-
time.Time
:Time 类型。代表时间time.Now() Time
:获取一个当前时间的 Time 对象 -
获取 Time 对象的某项时间参数
t := time.Now()
t.Day() int
:获取日数t.Month() time.Month
:获取月份。利用类型转换才能得到 int 类型的月份t.Year() int
:获取年份t.Hour() int
、t.Minute() int
、t.Second() int
:获取时、分、秒t.Format(string) string
:按照字符串格式将格式化该时间。那个格式字符串各项可以自由组合,但时间是固定的,不能变更。即必须是:2006 年 01 月 02 日 15 时 04 分 05 秒s := t.Format("2006/01/02 15:04:05") // 格式字符串必须是该时间 s2 := t.Format("2006-01-02") s3 := t.Format("15:04:05")
这个时间据说是开发者夹带私货。会开发的就是任性哦~
t.Unix() int
:获取指定时间戳t.UnixNano() int64
:获取指定纳秒时间戳 -
time.Hour
:时间常量,代表 1 小时。要获取 10 小时可以使用
10 * time.Hour
。可以使用加法、减法。但不能使用除法time.Minute
、time.Second
:分钟、秒time.Millisecond
:毫秒。1000 毫秒 == 1 秒time.Microsecond
:微秒。1000 微秒 == 1 毫秒time.Nanosecond
:纳秒。1000 纳秒 == 1 微秒以上这些时间常量的类型是 time.Duration
-
time.sleep(time.Duration)
:休眠指定时间
3.4 内置函数
Go 的设计者为了编程方便,提供了一些函数。这些函数能直接使用。即为内置函数。
-
len(v) int
:返回传入的 v 的长度。取决于 v 的类型cap(v) int
:返回 v 的容量。取决于 v 的类型 -
new(Type) *Type
:使用 new 来分配内存。返回值为一个指向该类型新分配的零值的指针 -
make(Type, IntegerType) Type
:分配并初始化一个类型为切片、映射或通道的对象 -
recover() error
:捕获一个异常
☆
3.5 错误处理
在默认情况下,程序发生错误(panic)后就会退出(崩溃)
如果希望发生错误后,可以捕获错误并进行处理,使程序得以继续执行,还能进行提示,就需要错误处理
Go 语言追求简洁优雅。Go 通过 defer、panic、recover 进行错误处理:
Go 可以抛出 panic 异常,并在 defer 中通过 recover 捕获该异常,然后进行处理
defer func() {
err := recover()
if err != nil {
fmt.Println("Error -", err)
}
}()
i := 10
n := 0
fmt.Println(i / n) // 这里出现一处除以 0 的错误
以上代码输出:Error -runtime error: integer divide by zero
加入错误处理后,程序不会轻易崩溃。如果加入预警代码,就能增强代码健壮性
#自定义错误:
Go 语言支持自定义错误。
errors.New(string) error
:传入错误说明,返回一个 error 对象panic(interface{})
:输出错误信息,并退出程序
defer func() {
fmt.Println(recover(), "!!!")
}()
panic(errors.New("???"))
输出:??? !!!