函数是构建 Go 程序的主要成分。其定义如下形式:
func (recvarg type) funcname(arg int) (ret1[, ret2] int) { return 0[,0] }
关键字 func
用于定义一个函数;recvarg
用于指定此函数可用于的类型,即该类型的方法。funcname
为函数名;arg
为函数参数,函数为值传递;ret
为函数命名返回值,也可以只有类型而不命名,可以有 0 个、1 个或者多个返回值;剩下的为函数体,左花括号必须在同一行,不能新起一行。
func identity(in int) int {
return in
}
注意: Go 不允许函数嵌套,然而可以利用匿名函数实现它。Go 的函数可以任意安排定义顺序,编译器会在执行前扫描每个文件,因此 Go 中不需要函数原型。函数可以递归调用。
1、作用域
在 Go 中,定义在函数外的变量是全局的,那些定义在函数内部的变量,对于函数来说是局部的。如果命名覆盖 — 一个局部变量与一个全局变量有相同的名字 — 在函数执行的时候,局部变量将覆盖全局变量。
2、多值返回
Go 的函数和方法可以返回多个值。多值返回避免了传递指针模拟引用参数来返回值。当有多个返回值时需要使用圆括号将返回值括起来。
// 返回数组当前位置的值和下一个位置
func nextInt(b []byte, i int) (int, int) {
x := 0
// 假设所有的都是数字
for ; i < len(b) ; i++ {
x = x*10 + int(b[i])-'0'
}
return x, i
}
3、命名返回值
Go 函数的返回值或者结果参数可以指定一个名字(名字不是强制的),并且像原始的变量那样使用,就像输入参数那样。如果对其命名,在函数开始时,它们会用其类型的零值初始化;如果函数在不加参数的情况下执行了 return
语句,结果参数的当前值会作为返回值返回。
// 计算斐波那契数列
func Factorial(x int) (result int) {
if x == 0 {
result = 1
} else {
result = x * Factorial(x - 1)
}
return
}
4、匿名函数
匿名函数即没有函数名的函数,只能放在函数中,可以实现函数嵌套定义的功能。
func f(n int) int {
sum := 0
func(x int) {
for i := 0; i < x; i++ {
sum += i
}
}(n) // 匿名函数,圆括号必须,也可以没有参数
return sum
}
5、延迟代码执行
关键字 defer
可以延迟函数的执行。调用函数时,在函数前加 defer
关键字,可以延迟该函数执行,在 defer 后指定的函数会在函数退出前调用。
func ReadWrite() bool {
file.Open("file")
defer file.Close() // close() 函数加入延迟列表
// 做一些工作
if failureX {
return false // 若此处退出,Close() 现在自动调用
}
if failureY {
return false // 这里也是
}
return true
}
可以将多个函数放入“延迟列表”中,延迟的函数是按照后进先出( LIFO)的顺序执行,因此,下面函数模拟反序输出:
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
// 输出为:4 3 2 1 0
利用 defer 甚至可以修改返回值。
func f() (ret int) { // ret 初始化为零
defer func() {
ret++ // ret 增加为 1
}() // 匿名函数,括号必须的
return 0 // 返回的是 1 而不是 0!先执行 return
}
6、变参
接受变参的函数是有着不定数量的参数的。其函数定义形式如下:
func funcname(arg ... type) { }
arg ... type
告诉 Go 这个函数接受不定数量的参数。注意,在函数体中,变量 arg
是一个type
类型的 slice,可以使用 range
遍历,也可以将其作为实参全部或者部分传递给调用函数。
for _, n := range arg {
fmt.Println(n)
}
func myfunc(arg ... int) {
myfunc2(arg...) // 按原样传递
myfunc2(arg[:2]...) //传递部分
}
如果不指定变参的类型,默认是空的接口 interface{}
。
7、函数作为值
就像其他在 Go 中的其他东西一样,函数也是值而已。它们可以像下面这样赋值给变量:
func main() {
a := func() { // 定义一个匿名函数,并且赋值给 a
fmt.Println("Hello")
} //这里没有 ()
a() //调用函数
}
函数作为值也可以用于其他一些地方,如 map
var xs = map[int]func() int{
1: func() int { return 10 },
2: func() int { return 20 },
3: func() int { return 30 }, ← 必须有逗号
/* ... */
}
8、回调函数
函数也可以作为另一个函数的参数,即回调函数。回调函数需要调用函数的形参格式和被调用函数原型相同。
type testInt func(int) bool // 声明了一个函数类型
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
// 声明的函数类型在这个地方当做了一个参数
// 也可以将第二个形参写成 f func(int) bool
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
//主函数
func main(){
slice := []int {1, 2, 3, 4, 5, 7}
odd := filter(slice, isOdd) // 函数当做值来传递了
even := filter(slice, isEven) // 函数当做值来传递了
/*...*/
}
9、命令行参数
关于命令行参数的几个概念:
- 命令行参数(或参数):是指运行程序时提供的参数。
- 已定义命令行参数:是指程序中通过
flag.Xxx
等这种形式定义了的参数。输入参数时需要-flag
形式。 - 非 flag(non-flag)命令行参数(或保留的命令行参数):不符合
-flag
形式的参数。-
、--
和--flag
都属于 non-flag 参数。
来自命令行的参数在程序中通过字符串 slice os.Args
获取,导入包 os 即可。 flag 包有着精巧的接口,提供了解析命令行参数标识的方法。
其中 os.Args[0]
为执行的程序名,os.Args[1]
~ os.Args[n-1]
是具体的参数。
比如在命令行中运行 test.exe 1 2 3
,则 os.Args[0]
~ os.Args[3]
的值分别为 "test.exe"
"1"
"2"
"3"
。
import (
"fmt"
"os"
)
func main() int{
arg_num := len(os.Args) // 计算参数个数
fmt.Printf("the num of input is %d\n",arg_num)
fmt.Printf("they are :\n")
for i := 0 ; i < arg_num ;i++{
fmt.Println(os.Args[i]) // 打印命令行参数,参数都字符串
}
}
10、flag 包
flag 包实现了对命令行的解析。
定义 flag 通常使用 flag.Xxx()
形式,其中 Xxx
是对应类型的首字母大写形式,可以是 Int、String等;返回一个相应类型的指针,如:
package main
/**flagtest.go**/
import (
"flag"
"fmt"
)
func main() {
// 定义 -H 参数
shelp := flag.Int("H", 1234, "help message for flagname")
flag.Parse() // 解析参数,必须有
fmt.Println(*shelp)
}
假设在命令行中运行带有此参数的程序 flagtest.go
,用如下形式:
go run flagtest.go -H 666
则输出为 666。
如果不带参数,
go run flagtest.go
则输出默认值 1234。
若格式错误,即以如下形式运行,
go run flagtest.go -H
则会输入提示字符串:help message for flagname。
flag 语法
命令行 flag 的语法有如下三种形式:
-flag // 只支持 bool 类型
-flag=x // 等号前后没有空格
-flag x // 只支持非 bool 类型
int 类型可以是十进制、十六进制、八进制甚至是负数;bool 类型可以是 1, 0, t, f, true, false, TRUE, FALSE, True, False。对于 bool 类型,如果不是采用 -flag=x
的形式指定确切的值, 如果命令行中有该选项,无论默认值是 true 还是 false,结果都是 true,否则,如果无此选项,结果为默认值。
/**booltest.go**/
func main() {
// 定义 -B 参数,类型为 bool
fbool := flag.Bool("B", true, "without is false")
flag.Parse() // 解析参数,必须有
fmt.Println(*fbool)
}
在命令行中以如下形式运行输出为 false。
go run booltest.go -B=false
以如下形式运行输出为 true。
go run booltest.go -B
// 非 bool 类型以此格式为错误格式,会输出提示
go run booltest.go -B=true
参数解析
在使用 flag 参数前,必须调用 flag.Parse()
对参数进行解析。
Parse()
只解析已定义的命令行参数,即(-flag 参数),当遇到第一个 non-flag 参数时停止解析。单独的 -
和 --
都不是 flag 参数。因此,flag 参数必须出现在命令行 non-flag 参数之前,否则无法解析。
当命令行中对 flag 参数指定值时,则参数将采用指定的值;如果不指定 flag 参数或者参数不是处在 non-falg 之前,则采用默认值;如果参数格式错误,将无法解析而输出提示信息,即 flag.Xxx
函数的第三个参数。
/** parse.go **/
func main(){
af := flag.Int("I", 2, "int")
bf := flag.String("S", "hello", "string")
cf := flag.Bool("b", true, "bool")
flag.Parse()
fmt.Printf("%v, %s, %v\n", *af,*bf,*cf)
}
若命令行没有指定已定义参数,或者 flag 参数出现在 non-falg 参数之后(此时会被当做 non-falg 参数),即输入为:
go run parse.go
或者 go run parse.go befor -I 3
输出默认值: 2, hello, true
。
若正确输入,则 flag 参数会采用指定值:
go run parse.go -I 3 -S world -b
或者 go run parse.go -I 3 -b -S world
也可以在 flag 参数后加上 non-flag 参数,即
go run parse.go -I 3 last
则输出都是:3, world, true
。
上面实例可以看出,只要 flag 参数格式正确,位置前后顺序没有要求。即 -S world -b
和 -b -S world
结果都一样。
Arg(i int) 和 Args()、NArg()、NFlag()
Arg(i int)
和 Args()
这两个方法是获取 non-flag 参数的;NArg()
获得 non-flag 个数;NFlag()
获得命令行中被设置了的 flag 参数的个数。
flag.Args()
和 os.Args()
区别是:os.Args()
包括可执行文件名和所有的参数(flag 参数 和 non-falg 参数),即索引 0 为可执行文件名, 而 flag.Args()
只包括 non-flag 参数,且索引 0 为命令行中第一个non-flag 参数。
/** flagtest.go **/
func main(){
_ = flag.Int("I", 2, "int") // 定义三个 flag 参数
_ = flag.String("S", "hello", "string")
_ = flag.Bool("B", true, "bool")
flag.Parse()
for _, val := range os.Args{ // os.Args 获取所有的命令行参数
fmt.Printf("%s ", val)
}
fmt.Printf("\n")
fmt.Println(flag.NFlag()) // NFlag() 获取命令行中设置了的 flag 参数个数
for _, val := range flag.Args(){ // 使用 flag.Ags() 获取 non-flag 参数
fmt.Printf("%v ", val)
}
fmt.Printf("\n")
for i := 0; i < flag.NArg(); i++{ // 计算 non-flag 参数个数
fmt.Printf("%v ", flag.Arg(i)) // 使用索引获取 non-flag 参数
}
}
命令行中运行结果为:
版权声明:本文为博主原创文章,未经博主允许不得转载。
有疑问加站长微信联系(非本文作者)