文章目录
01.变参函数
- Go语言函数可变参数
- 在Go语言中,函数的参数可以支持指定任意的个数与数据类型,这就是Go语言函数的可变参数。
- 最典型的可变参数就是Printf()函数。
- Go语言虽然支持不定长变参,但是要注意不定长参数只能作为函数的最后一个参数,不能放在其他参数的前面。
- 语法格式:
func 函数名(固定参数列表,v ...T))(返回参数列表){
函数体
}
//可变参数一般放在函数参数列表的末尾,也可不存在固定参数列表
//"v...T"代表的其实就是变量v为T类型的切片, ** v和T之间为三个“...” **
-可变参数的本质:
- Go语言中函数的可变参数:必须是函数的最后一个参数;
- 这其实就是一个语法糖,效果类似于切片;
- 要在多个函数中传递可变参数,可在传递时添加“…”;
- 可变参数变量是一个包含所有参数的切片;
- 如果要将这个含有可变参数的变量传递给下一个可变参数函数,可以在传递时给可变参数变量后添加“…”,这样就可以将切片中的元素进行传递,而不是传递可变参数变量本身。
- Go语言函数可变参数,可以传入任意个数的参数
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
func main() {
fmt.Println(sum(1,2,3))
fmt.Println(sum(1,2,3,4,5,6,7))
}
//运行结果为:
6
28
- 这里定义了一个函数sum(),该函数的参数是可变参数,因此我们在main函数调用的时候,可以传入任意个数的参数,但所有的参数的类型必须都是int类型的。
package main
import "fmt"
func printStrs(words ...string){
for _, word := range words {
fmt.Print(word, " ")
}
fmt.Println()
}
func main() {
printStrs("Hello", "Golang")
printStrs("I", "Love", "Study")
}
//运行结果为:
Hello Golang
I Love Study
- 这里定义了一个函数printStrs(),该函数的参数是可变参数。因此我们在main函数调用的时候,可以传入任意个数的参数,但所有的参数的类型必须都是string类型的。
在Go语言中,函数的可变参数除了可以指定任意的个数,还可以支持任意的数据类型。
func funName(args ...interface{}){
函数体
}
- 上述片段是定义了一个名为 funName 的函数,参数是 args;
- 参数 args 的个数是不确定的,参数 args 的类型也是不确定的;
- 因此这里使用的数据类型是接口类型,即interface。
package main
import "fmt"
func haiPrint(args ...interface{}){
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "type is int")
case string:
fmt.Println(arg, "type is string")
case int64:
fmt.Println(arg, "type is int64")
case float64:
fmt.Println(arg, "type is float64")
default:
fmt.Println(arg, "type is unknown")
}
}
}
func main() {
haiPrint("Hello", "Golang", 3, 100.1)
}
//运行结果为:
Hello type is string
Golang type is string
3 type is int
100.1 type is float64
- 这里定义了一个函数haiPrint,该函数的参数是可变参数,参 参数的个数和参数类型都是不确定的,因此我们在main函数调用的时候,可以传入任意个数和类型的参数。
02.递归函数
1.递归函数的定义
递归函数:
- 递归本质:在运行的过程中自己调用自己
- 递归函数:在函数内部调用函数自身的函数
- 常见应用场景:数字阶乘、斐波那契数列等
语法格式:
func recursion() {
recursion() //函数调用自身
}
2.递归需要具备的条件
使用条件:
- 一个问题可以被拆分成多个子问题
- 拆分前的原问题与拆分后的子问题除了数据规模不同,但处理问题的思路是一样的
- 不能无限制的调用本身,子问题需要有退出递归状态的条件
3.递归函数的优缺点比较
优点:
- 定义简单,逻辑清晰;
- 理论上,所有的递归函数都可以用循环的方式实现,但循环的逻辑不如递归清晰。
缺点:
- 函数调用时通过栈(stack)这种数据结构实现的;
- 每当进入一个函数调用,栈就会加一层栈;
- 每当函数返回时,栈就会减少一层;
- 由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出,需要注意防止栈溢出。
实例:
1.利用递归函数实现阶乘计算,每一次都是乘以(当前数字-1)
package main
import "fmt"
func Factorial(n uint64)(result uint64) {
if (n > 0) {
result = n * Factorial(n-1)
return result
}
return 1
}
func main() {
var i int = 10
fmt.Printf("%d!=%d\n", i, Factorial(uint64(i)))
}
//运行结果为:
10!=3628800
2.利用递归函数实现斐波那契数列
斐波那契数列:
F(0)=0
F(1)=1
F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
package main
import "fmt"
func fibonacci(n int) int {
if n < 2 {
return n
}
return fibonacci(n-2) + fibonacci(n-1)
}
func main() {
var i int
for i = 0; i < 10; i++ {
fmt.Printf("%d\t", fibonacci(i))
}
}
//运行结果为:
0 1 1 2 3 5 8 13 21 34
03.defer语句
1.defer语句定义
defer语句:
- Go语言的 defer语句 会将其后面的语句进行延迟处理
- 在 defer 归属的函数即将返回时,将延迟处理的语句按 defe r的逆序进行执行
- 即先被 defer 的语句最后被执行,最后被 defer 的语句最先被执行
- 因为 defer 语句是在当前函数即将返回时被调用,所以 defer 常常被用来释放资源
语法格式:
defer 任意语句
示例:
- 1.当有多个 defer 行为被注册时,它们会以逆序执行**(类似于栈:后进先出)**
- 延迟调用是在 defer 所在函数结束时进行
package main
import "fmt"
func main() {
fmt.Println("defer begin")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("defer end")
}
//运行结果为:
defer begin
defer end
3
2
1
2.defer 的资源释放
- 在日常工作中处理业务或逻辑中涉及成对的操作是一件比较烦琐的事情,比如打开和关闭文件、接收请求和回复请求、加锁和解锁等;
- 在这些操作中,最容易忽略的就是在每个函数退出处正确的释放和关闭资源;
- 而 defer 语句正好是在函数退出时执行的语句,所以使用 defer 能非常方便地处理资源释放问题。
04.Test功能测试函数
- 测试的意义:
- 完善的测试体系能够提高开发的效率
- 当项目复杂的时候,想要保证尽可能的减少bug,有两种有效的方式分别是代码审核和测试
- Go语言中提供了testing 包来实现单元测试功能,可以进行自动化的单元测试,输出结果验证,并且可以测试性能
-测试的规则:
- 测试用例文件不会参与正常源码的编译,不会被包含到可执行文件中;
- 测试用例的文件名必须以 _test.go 结尾;
- 需要使用 import 导入 testing 包;
- 测试函数的名称要以 Test 或 Benchmark 开头,后面可以跟任意字母组成的字符串,但第一个字母必须大写,例如 TestAbc(),一个测试用例文件中可以包含多个测试函数;
- 单元测试则以(t *testing.T)作为参数,性能测试以(t *testing.B)作为参数;
- 测试用例文件使用 go test 命令来执行,源码中不需要main()函数作为入口,所有以_test.go 结尾的源码文件以 Test 开头的函数都会执行。
- go test 命令
会自动读取源码目录下名为 *_test.go 的文件,生成并运行测试用的可执行文件。 - 语法格式:
go test [-c] [-i] [build flags] [packages] [flags for test binary]
- -c :编译 go test 成为可执行的二进制文件,但是不运行
- -i :安装测试包依赖的 package ,但是不运行测试
- build flags :调用 go help build ,这些是编译运行过程中需要使用到的参数,一般设置为空
- packages :调用 go help packages ,这些是关于包的管理,一般设置为空
- flags for test binary :调用 go help testflag ,这些是 go test 过程中经常使用到的参数
- 测试语法:
import "testing"
func TestXxx( t *testing.T ){
//......
}
1.单元(功能)测试
- 单元就是认为规定的最小的被测功能模块
- 作用:对软件中的最小可测试单元进行检查和验证
- 单元测试是在软件开发过程中要进行的最低级别的测试活动
- 软件的独立单元将在与其他部分相隔离的情况下进行测试
示例:
//E:\go\demo\demo.go文件:
package demo
func GetArea(weight int, height int) int {
return weight * height
}
//E:\go\demo\demo_test.go文件:
package demo
import "testing"
func TestGetArea(t *testing.T) {
area := GetArea(40, 50)
if area != 2000 {
t.Error("测试失败")
}
}
//测试结果为:
E:\go\demo>go test -v
=== RUN TestGetArea
--- PASS: TestGetArea (0.00s)
PASS
ok gin/demo 0.810s
2.性能(压力)测试
- 性能测试是指:可以给出代码的性能数据,帮组测试者分析性能问题
- 性能测试也称基准测试,基准测试可以测试一段程序的运行性能及耗费 CPU 的程度
- Go语言中提供了基准测试框架,使用方法类似于单元测试
- 使用者无须准备高精度的计时器和各种分析工具,因为基准测试本身可以打印出非常标准的测试报告
示例:
//E:\go\demo\demo.go文件:
package demo
func GetArea(weight int, height int) int {
return weight * height
}
package demo
import "testing"
func BenchmarkGetArea(t *testing.B) {
for i := 0; i < t.N; i++ {
GetArea(40, 50)
}
}
//测试结果为:
E:\go\demo>go test -bench="."
goos: windows
goarch: amd64
pkg: gin/demo
cpu: Intel(R) Core(TM) i5-8300H CPU @ 2.30GHz
BenchmarkGetArea-8 1000000000
0.2634 ns/op
PASS
ok gin/demo 1.163s
3.覆盖率测试
- 能知道测试程序总共覆盖了多少业务代码
- 即是 demo_test.go 中测试了多少 demo.go 中的代码
- 最好的情况是覆盖测试结果为100%
示例:
//E:\go\demo\demo.go文件:
package demo
func GetArea(weight int, height int) int {
return weight * height
}
package demo
import "testing"
func TestGetArea(t *testing.T) {
area := GetArea(40, 50)
if area != 2000 {
t.Error("测试失败")
}
}
func BenchmarkGetArea(t *testing.B) {
for i := 0; i < t.N; i++ {
GetArea(40, 50)
}
}
//测试结果为:
E:\go\demo>go test -cover
PASS
coverage: 100.0% of statements
ok gin/demo 0.868s