Go基础之函数和方法讲解
1 自定义函数
1.1 函数定义
Go 语言函数定义格式如下:
func function_name( [parameter list] ) [return_types] {
函数体
}
函数定义解析:
func
:函数由 func 开始声明function_name
:函数名称,参数列表和返回值类型构成了函数签名。parameter list
:参数列表,参数就像一个占位符,当函数被调用时,可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。return_types
:返回类型,函数返回一列值。return_types
是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。函数体
:函数定义的代码集合。
1.2 函数调用与返回多值
1.2.1 返回类型
Go 函数可以返回多个值,例如:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Google", "Runoob")
fmt.Println(a, b)
}
以上实例执行结果为:
Runoob Google
1.2.2 命名返回值
在 Go 中,函数返回值
可以同时指定类型
和返回参数
的名字。这种方式叫做 命名返回值
,它提供了一种方便的方法来明确返回值的含义,并可以在函数体中直接操作这些命名返回值,无需显式声明和返回它们。
如果指定了返回值的具体名字
,则可以认为是在函数体的第一行声明了该名字的变量
。
在函数定义时,如果返回值不仅指定类型,还给出了名字,Go 会为这些返回值在函数体内隐式声明变量
。函数结束时,返回的值会自动从这些命名变量中获取
。
注意
:所有定义了返回值的函数
必须显式地使用 return
语句返回值,即使返回值是命名的
,即:虽然在命名返回值的函数中return
语句不会显示的返回任何值,但是不可以省略 return
关键字
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error() // 直接操作返回值 `errorMsg`
return
} else {
return varDividee / varDivider, "" // 直接赋值给命名返回值 `result`
}
}
命名返回值的作用:
隐式声明变量
: 在函数体中,可以直接使用 result 和 errorMsg 变量,而无需手动声明
自动返回
: 使用return
语句时,如果没有指定具体的返回值,Go 会返回当前命名返回变量的值。
对比未命名返回值,主要区别:
- 未命名返回值需要在
return
时显式列出所有返回值。 - 命名返回值提供了更清晰的语义和更简洁的 return 语句。
注意
:
- 如果在函数中使用了
命名返回值
,但未对其赋值,Go 会自动为命名返回值提供零值
- 命名返回值过多时,可能会降低代码的简洁性和可读性,因此适度使用。
- 命名返回值主要适用于返回值数量少、意义明确的场景。
1.3 函数参数
1.3.1 值传递
值传递
是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 100
var b int = 200
fmt.Printf("交换前 a 的值为 : %d\n", a )
fmt.Printf("交换前 b 的值为 : %d\n", b )
/* 通过调用函数来交换值 */
swap(a, b)
fmt.Printf("交换后 a 的值 : %d\n", a )
fmt.Printf("交换后 b 的值 : %d\n", b )
}
/* 定义相互交换值的函数 */
func swap(x, y int) int {
var temp int
temp = x /* 保存 x 的值 */
x = y /* 将 y 值赋给 x */
y = temp /* 将 temp 值赋给 y*/
return temp;
}
结果:
交换前 a 的值为 : 100
交换前 b 的值为 : 200
交换后 a 的值 : 100
交换后 b 的值 : 200
1.3.2 引用传递
引用传递
是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
package main
import "fmt"
func main() {
/* 定义局部变量 */
var a int = 100
var b int= 200
fmt.Printf("交换前,a 的值 : %d\n", a )
fmt.Printf("交换前,b 的值 : %d\n", b )
/* 调用 swap() 函数
* &a 指向 a 指针,a 变量的地址
* &b 指向 b 指针,b 变量的地址
*/
swap(&a, &b)
fmt.Printf("交换后,a 的值 : %d\n", a )
fmt.Printf("交换后,b 的值 : %d\n", b )
}
func swap(x *int, y *int) {
var temp int
temp = *x /* 保存 x 地址上的值 */
*x = *y /* 将 y 值赋给 x */
*y = temp /* 将 temp 值赋给 y */
}
结果:
交换前,a 的值 : 100
交换前,b 的值 : 200
交换后,a 的值 : 200
交换后,b 的值 : 100
1.3.3 不定参数
可以通过在参数类型前加上省略号(...
)来定义不定参数。不定参数在函数内部表现为一个切片
。
package main
import "fmt"
func sum(numbers ...int) int {
total := 0
for _, number := range numbers {
total += number
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3)) // 输出: 6
fmt.Println(sum(4, 5, 6, 7)) // 输出: 22
}
注意
:不能把切片作为可变参数传递给函数,如下将报错:cannot use nums (type []int) as type int in argument to find
package main
import (
"fmt"
)
func find(num int, nums ...int) {
fmt.Printf("type of nums is %T\n", nums)
found := false
for i, v := range nums {
if v == num {
fmt.Println(num, "found at index", i, "in", nums)
found = true
}
}
if !found {
fmt.Println(num, "not found in ", nums)
}
fmt.Printf("\n")
}
func main() {
nums := []int{89, 90, 95}
find(89, nums)
}
将nums
作为一个可变参数传递给find
函数。这些可变参数将转换为int类型的切片
。在这种情况下,nums
已经是一个int类型切片
,编译器尝试使用nums
创建一个新的[]int切片
,像这样:find(89, []int{ nums})
,因为nums
是一个 []int
类型 而不是 int
类型。类型根本就不相同,所以会失败。
那么有没有办法将切片传递给变参函数?答案是肯定的。
有一个语法糖可用于将切片传递给变参函数。必须为切片添加后缀...
,把切片展开,这样则可以将切片直接传递给函数,而不会创建新切片。
在上面的程序中,如果将第 find(89, nums)替换为 find(89, nums...),程序将成功编译
1.4 函数变量&回调
1.4.1 函数变量
Go 语言可以很灵活的创建函数,并作为另外一个函数的实参。以下实例中在定义的函数中初始化一个变量,该函数仅仅是为了使用内置函数 math.sqrt()
,实例为:
package main
import (
"fmt"
"math"
)
func main(){
/* 声明函数变量 */
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}
/* 使用函数 */
fmt.Println(getSquareRoot(9))
}
1.4.2 函数回调
package main
import "fmt"
// 声明一个函数类型
type cb func(int) int
func main() {
testCallBack(1, callBack)
testCallBack(2, func(x int) int {
fmt.Printf("我是回调,x:%d\n", x)
return x
})
}
func testCallBack(x int, f cb) {
f(x)
}
func callBack(x int) int {
fmt.Printf("我是回调,x:%d\n", x)
return x
}
1.5 匿名函数
匿名函数,可作为闭包。匿名函数
是一个内联
语句或表达式。匿名函数
的优越性在于可以直接使用函数内的变量,不必申明。
匿名函数
是一种没有函数名
的函数,通常用于在函数内部定义函数,或者作为函数参数进行传递。
package main
import "fmt"
func getSequence() func() int {
i:=0
return func() int {
i+=1
return i
}
}
func main(){
/* nextNumber 为一个函数,函数 i 为 0 */
nextNumber := getSequence()
/* 调用 nextNumber 函数,i 变量自增 1 并返回 */
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())
/* 创建新的函数 nextNumber1,并查看结果 */
nextNumber1 := getSequence()
fmt.Println(nextNumber1())
fmt.Println(nextNumber1())
}
带参数的匿名函数
package main
import "fmt"
func main() {
add_func := add(1,2)
fmt.Println(add_func(1,1))
fmt.Println(add_func(0,0))
fmt.Println(add_func(2,2))
}
// 闭包使用方法
func add(x1, x2 int) func(x3 int,x4 int)(int,int,int) {
i := 0
return func(x3 int,x4 int) (int,int,int){
i++
return i,x1+x2,x3+x4
}
}
1.6 defer函数
1.6.1 定义
defer
是 Go 中的一个关键字,用于延迟函数调用
,它会将指定的函数推迟到包含它的函数返回
时才执行。无论函数是正常返回还是遇到 panic
退出,defer
注册的调用都会执行,因此常用于资源释放
、清理操作
等场景。
执行顺序:defer
的调用按照先进后出
(LIFO
,栈
)顺序执行。
核心特点,延迟调用:
defer
会在包含它的函数返回时才执行,即使函数中有多个 return。- 遇到
panic
(抛出异常),defer
的调用也会在panic
传播前执行。
defer
语句的执行时机:
defer
语句会在函数执行完所有代码
后,但在函数返回值发送给调用者之前
执行。- 换句话说,
defer
语句是在函数的return
语句执行后、实际返回函数值给调用者之前执行的。
defer
中的参数求值:
defer
中的参数会在defer 声明时
立即求值,而不是等到defer 执行时
才求值。
1.6.2 使用方式
func main() {
fmt.Println("Start")
defer fmt.Println("Defer 1")
defer fmt.Println("Defer 2")
fmt.Println("End")
}
输出:
Start
End
Defer 2
Defer 1
mathematica
1.6.3 参数求值
被 defer
调用的函数,其参数在 defer
声明时就会被求值,而不是等到执行时才求值。
示例:
func main() {
x := 5
defer fmt.Println("x =", x)
x = 10
}
输出:
x = 5
defer 注册的是 fmt.Println("x =", 5)。
Go 中的 defer
是在函数的 return
语句执行后,但在实际返回值发送给调用者之前执行的。然而,defer
的参数在声明时就会被计算,而不是等到 defer
执行时才计算。这个特性会影响返回值的行为,特别是在 defer
中对参数做修改时
package main
import "fmt"
func test() int {
a := 5
defer func() int {
a++ // 修改 a 的值 ,但此处a 还是5 ,所以下面Defer是6
fmt.Println("Defer:", a)
return a // 这个 return 语句不会影响 test() 函数的返回值
}()
return a++ // 返回 a 的当前值(5),然后 a 自增
}
func main() {
fmt.Println("Return Value:", test()) // 输出返回值
}
结果:
Defer: 6
Return Value: 5
但是 命名返回函数会修改
package main
import "fmt"
func test() (result int) {
result = 5
defer func() {
result++ // 修改 result 的值
fmt.Println("Defer result:", result)
}()
return result // 返回 result(初始值是 5)
}
func main() {
fmt.Println("Return Value:", test()) // 输出返回值
}
1.6.4 LIFO 执行顺序
多个 defer
按照后注册的先执行。
1.6.4.1 资源清理
用于关闭文件、解锁互斥锁、关闭数据库连接等。
示例:
func main() {
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close() // 确保函数结束时关闭文件
// 读取文件内容
buf := make([]byte, 100)
file.Read(buf)
fmt.Println(string(buf))
}
1.6.4.2 互斥锁解锁
var mu sync.Mutex
func criticalSection() {
mu.Lock()
defer mu.Unlock() // 保证函数结束时解锁
// 临界区代码
fmt.Println("In critical section")
}
1.6.4.3 捕获 panic 并恢复
使用 defer 和 recover 捕获和处理 panic,防止程序崩溃。
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
fmt.Println("Start")
panic("Something went wrong!") // 触发 panic
fmt.Println("End") // 不会被执行
}
输出:
Start
Recovered from panic: Something went wrong!
1.6.4.4 函数退出日志
func trace(s string) {
fmt.Println("Enter:", s)
defer fmt.Println("Exit:", s)
}
func main() {
trace("main")
fmt.Println("Doing work in main")
}
输出:
Enter: main
Doing work in main
Exit: main
1.7 方法
方法就是一个包含了接受者
的函数,接受者可以是命名类型
或者结构体类型
的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:
func (variable_name variable_data_type) function_name() [return_type]{
/* 函数体*/
}
下面定义一个结构体类型和该类型的一个方法:
package main
import (
"fmt"
)
/* 定义结构体 */
type Circle struct {
radius float64
}
func main() {
var c1 Circle
c1.radius = 10.00
fmt.Println("圆的面积 = ", c1.getArea())
}
//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}
// 注意如果想要更改成功c的值,这里需要传指针
func (c *Circle) changeRadius(radius float64) {
c.radius = radius
}
方法的接受者可以是命名类型
或 结构体类型
下的值或指针,一共四种情况:
package main
import (
"fmt"
"math"
)
// 定义圆形结构体
type Circle struct {
Radius float64
}
// 定义命名类型
type MyFloat float64
// 使用结构体值作为接收者
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
// 使用结构体指针作为接收者
func (c *Circle) AreaPointer() float64 {
return math.Pi * c.Radius * c.Radius
}
// 使用命名类型值作为接收者
func (f MyFloat) Square() float64 {
return f * f
}
// 使用命名类型指针作为接收者
func (f *MyFloat) Cube() float64 {
return *f * *f * *f
}
func main() {
var c Circle
c.Radius = 10.00
// 调用圆形的Area方法
fmt.Println("Area of circle:", c.Area())
// 调用圆形的AreaPointer方法
fmt.Println("Area of circle (pointer):", c.AreaPointer())
var f MyFloat = 2.0
// 调用MyFloat的Square方法
fmt.Println("Square of MyFloat:", f.Square())
// 调用MyFloat的Cube方法
fmt.Println("Cube of MyFloat:", f.Cube())
}
传递指针时自动转,一个指针接收器的方法既可以接受值接收器 也可以接受指针接收器,一个值接收器的方法既可以接受值接收器 也可以接受指针接收器
// 传递指针
func (s *Student) chushi(name string, age int) {
s.Name = name
s.Age = age
}
func main() {
var stu Student = Student{
Name: "lisi",
Age: 20,
}
// (&stu).chushi("zhangsan",18) 指针自动转化
stu.chushi("zhangsan",18)
fmt.Println(stu)
}
2 内置函数
2.1 初始化函数
在 Go 中,每个 .go 文件
都可以有一个 init
函数,并且一个文件中可以定义多个 init
函数,但是它只被初始化一次
。这些 init
函数有特定的用途和执行顺序。
2.1.1 简介
init 函数的特点:
- 无参数、无返回值:
func init() {
// 初始化代码
}
- 自动执行:
init
函数会在程序运行时自动执行,无需显式调用。 - 初始化逻辑:
常用于初始化包级变量、设置运行环境、或者执行一些启动时的准备工作。 - 一个文件可以有多个
init
函数:
同一个文件中可以定义多个 init 函数,甚至分布在不同的.go
文件中。
init 函数的执行顺序:
- 按包的依赖顺序执行:
如果包 A 依赖包 B,则会先初始化包 B,再初始化包 A。 - 同一包中按文件顺序执行:
Go 编译器会按照文件名的字典顺序
,逐个初始化每个文件。 - 同一文件中按代码顺序执行:
在一个文件中,多个init
函数会按代码中出现的顺序依次执行。 - main 包的特殊性:
程序总是先执行所有依赖包的init
函数,然后再执行main
包的init
,最后是 main 函数。
2.1.2 示例
包中多个文件的 init 执行顺序
mypackage/
|- file1.go
|- file2.go
main.go
file1.go:
package mypackage
import "fmt"
func init() {
fmt.Println("Init in file1")
}
func File1Func() {
fmt.Println("Function in file1")
}
file2.go:
package mypackage
import "fmt"
func init() {
fmt.Println("Init in file2")
}
func File2Func() {
fmt.Println("Function in file2")
}
main.go:
package main
import (
"fmt"
"mypackage"
)
func init() {
fmt.Println("Init in main")
}
func main() {
fmt.Println("Main function")
mypackage.File1Func()
mypackage.File2Func()
}
它通常用于初始化包级变量、依赖设置和其他启动前的准备工作。
2.2 输入输出函数
2.2.1 输入函数
输入函数:
Scan
函数:
Scan
从标准输入中读取数据,并将数据用空白分割并解析后存入 a 提供的变量中(换行符会被当作空白处理),变量必须以指针传入。
当读到EOF
或所有变量都填写完毕则停止扫描。返回成功解析的参数数量。
func Scan(a ...interface{}) (n int, err error)
Scanln
函数:
Scanln
和Scan
类似,只不过遇到换行符
就停止扫描。
func Scanln(a ...interface{}) (n int, err error)
Scanf
函数:
Scanf
从标准输入
中读取数据,,返回成功解析的参数数量,并根据格式字符串format
对数据进行解析,将解析结果存入参数 a 所提供的变量中,变量必须以指针
传入。
输入端的换行符必须和format
中的换行符相对应(如果格式字符串中有换行符,则输入端必须输入相应的换行符)。
占位符%c
总是匹配下一个字符,包括空白,比如空格符、制表符、换行符。
func Scanf(format string, a ...interface{}) (n int, err error)
其他读入函数:
- 从io 中读取数据:
func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
- 从 str 中读取数据。
func Sscan(str string, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)
示例:
// 对于 Scan 而言,回车视为空白
func main() {
a, b, c := "", 0, false
fmt.Scan(&a, &b, &c)
fmt.Println(a, b, c)
// 在终端执行后,输入 abc 1 回车 true 回车
// 结果 abc 1 true
}
// 对于 Scanln 而言,回车结束扫描
func main() {
a, b, c := "", 0, false
fmt.Scanln(&a, &b, &c)
fmt.Println(a, b, c)
// 在终端执行后,输入 abc 1 true 回车
// 结果 abc 1 true
}
// 格式字符串可以指定宽度
func main() {
a, b, c := "", 0, false
fmt.Scanf("%4s%d%t", &a, &b, &c)
fmt.Println(a, b, c)
// 在终端执行后,输入 1234567true 回车
// 结果 1234 567 true
}
2.2.2 输出函数
2.2.2.1 常见输出
Print
函数
将参数列表 a 中的各个参数转换为字符串并写入到标准输出中。
非字符串参数之间会添加空格,返回写入的字节数。
func Print(a ...interface{}) (n int, err error)
Println
函数
类似Print
,只不过最后会添加一个换行符。
所有参数之间会添加空格,返回写入的字节数。
func Println(a ...interface{}) (n int, err error)
Printf
函数
将参数列表 a 填写到格式字符串format
的占位符中。
填写后的结果写入到标准输出中,返回写入的字节数。
func Printf(format string, a ...interface{}) (n int, err error)
其他输出函数:
- 从
io
中输出数据:
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
- 从 str 中输出数据:
func Sprint(a ...interface{}) string
func Sprintln(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string
2.2.2.2 Printf函数格式
Printf
字符串格式化符号:
格 式 | 描 述 |
---|---|
%v | 按值的本来值输出 |
%+v | 在 %v 基础上,对结构体字段名和值进行展开 |
%#v | 输出 Go 语言语法格式的值 |
%T | 输出 Go 语言语法格式的类型和值 |
%% | 输出 % 本体 |
%b | 整型以二进制方式显示 |
%o | 整型以八进制方式显示 |
%d | 整型以十进制方式显示 |
%x | 整型以十六进制方式显示 |
%X | 整型以十六进制、字母大写方式显示 |
%U | Unicode 字符 |
%f | 浮点数 |
%p | 指针,十六进制方式显示 |
对齐方式,通过在格式化字符串中使用宽度和对齐参数,可以控制生成的字符串的对齐方式。
常用的对齐参数有:
%s
:字符串格式,可以使用以下对齐参数:%s
:默认对齐方式,左对齐。%10s
:指定宽度为 10 的右对齐。%-10s
:指定宽度为 10 的左对齐。
%d
:整数格式,可以使用以下对齐参数:%d
:默认对齐方式,右对齐。%10d
:指定宽度为 10 的右对齐。%-10d
:指定宽度为 10 的左对齐。
%f
:浮点数格式,可以使用以下对齐参数:%f
:默认对齐方式,右对齐。%10f
:指定宽度为 10 的右对齐。%-10f
:指定宽度为 10 的左对齐。
示例
package main
import (
"fmt"
"os"
)
type point struct {
x, y int
}
func main() {
p := point{1, 2}
fmt.Printf("%v\n", p)
fmt.Printf("%+v\n", p)
fmt.Printf("%#v\n", p)
fmt.Printf("%T\n", p)
fmt.Printf("%t\n", true)
fmt.Printf("%d\n", 123)
fmt.Printf("%b\n", 14)
fmt.Printf("%c\n", 33)
fmt.Printf("%x\n", 456)
fmt.Printf("%f\n", 78.9)
fmt.Printf("%e\n", 123400000.0)
fmt.Printf("%E\n", 123400000.0)
fmt.Printf("%s\n", "\"string\"")
fmt.Printf("%q\n", "\"string\"")
fmt.Printf("%x\n", "hex this")
fmt.Printf("%p\n", &p)
fmt.Printf("|%6d|%6d|\n", 12, 345)
fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45)
fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45)
fmt.Printf("|%6s|%6s|\n", "foo", "b")
fmt.Printf("|%-6s|%-6s|\n", "foo", "b")
s := fmt.Sprintf("a %s", "string")
fmt.Println(s)
fmt.Fprintf(os.Stderr, "an %s\n", "error")
}
输出结果为:
{1 2}
{x:1 y:2}
main.point{x:1, y:2}
main.point
true
123
1110
!
1c8
78.900000
1.234000e+08
1.234000E+08
"string"
"\"string\""
6865782074686973
0xc0000b4010
| 12| 345|
| 1.20| 3.45|
|1.20 |3.45 |
| foo| b|
|foo |b |
a string
an error
2.3 测试函数
在 Go 语言中,测试函数(Test Functions
)是通过 Go 的内置测试框架 testing
来进行单元测试和集成测试的。Go
提供了一些简洁的工具来创建、运行和管理测试,以下是对 Go 测试函数的详细讲解:
2.3.1 测试文件结构
Go 测试文件
通常以 _test.go
作为后缀名,并且测试函数必须放在相同的包(package)内。
假设有一个 Go 文件 math.go
,它包含一个简单的加法函数:
package math
func Add(a, b int) int {
return a + b
}
可以创建一个测试文件 math_test.go
来测试 Add 函数:
package math
import "testing"
func TestAdd(t *testing.T) {
result := Add(1, 2)
if result != 3 {
t.Errorf("Add(1, 2) = %d; want 3", result)
}
}
2.3.2 测试函数的命名
测试函数的命名必须以 Test
开头,后面跟着要测试的功能名称或方法名称。测试函数的签名必须是:func TestXxx(t *testing.T)
其中 Xxx
是测试的功能或方法名。
func TestAdd(t *testing.T) {
// 测试代码
}
2.3.3 testing.T 类型
Test
函数接收一个指向 testing.T
类型的指针。T 类型提供了多个方法来帮助我们报告错误和测试结果:
t.Errorf(format string, args ...)
:如果测试失败,使用这个方法报告错误,格式和 fmt.Printf 相似。t.Fatal(args ...)
:如果测试失败,停止执行并报告错误。t.Log(args ...)
:在测试中打印日志信息,默认情况下日志不会显示,只有在使用 -v 标志时才会显示。t.Skip(args ...)
:跳过当前测试。
示例:
func TestAdd(t *testing.T) {
result := Add(1, 2)
if result != 3 {
t.Errorf("Add(1, 2) = %d; want 3", result)
}
}
2.3.4 运行测试
要运行测试,可以使用 go test
命令。Go 会自动查找所有以 _test.go
结尾的文件,并运行其中的所有测试函数。
go test
会自动寻找当前目录下所有以 Test 开头的函数并运行。
默认情况下,测试会在无输出模式下运行,只有失败时才显示错误信息。
其他常用的 go test 命令
go test -v
:显示详细输出,包括每个测试函数的执行情况。
go test -run TestAdd
:只运行名为 TestAdd 的测试函数。
go test -bench .
:运行基准测试(benchmark)。
go test -cover
:报告代码覆盖率。
2.3.5 基准测试(Benchmark Tests)
Go 也支持基准测试,用于测量代码性能。基准测试函数的命名规则是 BenchmarkXxx
,例如:
package math
import "testing"
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
// 如果在这里做测试用的数据的生成,不想把这个时间加进去,可以在生成数据之后reset一下时间
b.ResetTimer()
}
其中的 b.N
: 运行的次数,Go 会自动决定 b.N
的值,以便进行足够多次的性能测量。
要运行基准测试:
go test -bench .
2.3.6 命令行测试
go test -coverprofile=c.out
生成文件go tool cover -html=c.out
页面观看覆盖率
注意
:test
文件要和逻辑代码在同一个包中 不然显示不出来go test -bench . -cpuprofile=cpu.out
生成二进制文件
用pprof
查看这个二进制文件go tool pprof cpu.out
进入pprof环境- go语言的文档生成用
go doc 变量
在页面中看:go doc -http :8080