函數是構建 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 參數
}
}
命令行中運行結果爲: