Go關鍵字--func
友情推廣
func
func關鍵字用來定義函式,函式是golang中非常重要的一塊。定義一個函式的語法格式是:
func fnctionName(arg1 dataType,arg2 dataType)(dataType,dataType){
fmt.Println("func body")
return "a","b"
}
函式多返回值是golang的特性之一,在宣告返回值時,可以建立帶變數名的返回值,語法如下:
func functionName(arg1 dataType,arg2 dataType)(retName dataType){ fmt.Println("func body") retName = "hello world" return }
上邊的retName就是函式返回值的變數名,在函式的body內,可以直接給retName這個返回值變數賦值,效果等同於return retName
函式呼叫方法:
如果是同一個包中的函式,直接使用函式名加上括號即可呼叫,如果函式需要引數,則在括號內傳入引數即可。如下程式碼所示:
package main
import (
"fmt"
)
func functionName(str string) {
fmt.Println(str)
}
func main() {
functionName("hello world")
}
如果要引用另一個包中的函式,首先使用import匯入想要引用的包,然後以包名為字首訪問包中可匯出函式。上面示例程式碼中Println函式在fmt包中,在呼叫Println函式時,首先匯入了fmt包,然後通過fmt為字首,呼叫Println函式。
函式的幾個特點
- 引數
- 返回值
1.函式引數
golang函式支援兩種形式的引數,第一是:固定個數引數,第二是可變個數引數。固定個數引數,就是函式接收固定個數的引數,如建立一個2個引數且沒有返回值的函式
// 第一種寫法
func World(str1, str2 string) {
fmt.Println(str1, str2)
}
// 第二種寫法
func World(str1 string, str2 string) {
fmt.Println(str1, str2)
}
當引數型別相同時,可以簡寫成第一種寫法。如果引數型別不同,只能採用第二種寫法,依次指定每一個引數的型別。
上邊介紹了固定引數的函式,下邊介紹可變引數函式。可變引數只能是函式的最後一個引數
func functionName(arg ...dataType){
fmt.Println("func body")
}
下邊來一段示例程式碼,詳細的介紹函式可變引數情形:
package main
import (
"fmt"
)
func VarParameter(str1 string, num int, other ...string) {
fmt.Println("第一個引數:", str1)
fmt.Println("第二個引數:", num)
fmt.Println("可變引數:", other, "引數型別是:", reflect.ValueOf(other).Kind())
for index, val := range other {
fmt.Println("可變引數第", index, "個值是:", val)
}
}
func main() {
VarParameter("var", 100, "a", "b", "c")
}
輸出資訊:
第一個引數: var
第二個引數: 100
可變引數: [a b c] 引數型別是:slice
可變引數第 0 個值是: a
可變引數第 1 個值是: b
可變引數第 2 個值是: c
other是一個可變引數,那麼在golang中,可變引數是一個什麼型別呢?答案是:slice。獲取可變引數中的值,只需要按照slice型別變數的操作方法即可。
如果可變引數不是函式最後一個引數,那麼在編譯時會提示如下錯誤資訊:
can only use ... with final parameter in list
如果在建立函式時,可變引數存在多種資料型別,則可以將可變引數型別設定成interface{},示例程式碼如下:
package main
import (
"fmt"
"reflect"
)
func VarParameter(str1 string, num int, other ...interface{}) {
fmt.Println("第一個引數:", str1)
fmt.Println("第二個引數:", num)
fmt.Println("可變引數:", other, "引數型別是:", reflect.ValueOf(other).Kind())
for index, val := range other {
fmt.Println("可變引數第", index, "個值是:", val, ",引數型別是:", reflect.ValueOf(val).Kind())
}
}
func main() {
VarParameter("var", 100, 1, "b", 3.234)
}
輸出資訊是:
第一個引數: var
第二個引數: 100
可變引數: [1 b 3.234] 引數型別是: slice
可變引數第 0 個值是: 1 ,引數型別是: int
可變引數第 1 個值是: b ,引數型別是: string
可變引數第 2 個值是: 3.234 ,引數型別是: float64
引數傳遞時,是值傳遞,還是指標傳遞呢?
當變數被當做引數傳入呼叫函式時,是值傳遞,也稱變數的一個拷貝傳遞。如果傳遞過來的值是指標,就相當於把變數的地址作為引數傳遞到函式內,那麼在函式內對這個指標所指向的內容進行修改,將會改變這個變數的值。如下邊示例程式碼:
package main
import (
"fmt"
)
func PtrTest(str *string) {
*str = "world"
}
func main() {
var str = "hello"
PtrTest(&str)
fmt.Println("str value is:", str)
}
輸出資訊是:
str value is: world
從上邊的輸出資訊可知,str變數地址當做引數傳入函式後,在函式中對地址所指向內容進行了修改,導致了變數str值發生了變化。
這個過程能否說明函式呼叫傳遞的是指標,而不是變數的拷貝呢?下邊通過另一個例子來進行說明:
package main
import (
"fmt"
)
var workd = "hello wolrd"
func PtrTest(str *string) {
str = &workd
}
func main() {
var str = "hello"
PtrTest(&str)
fmt.Println("str value is:", str)
}
輸出資訊是:
str value is: hello
上邊示例中,str變數地址被作為引數傳入到了函式PtrTest中,在函式中對引數進行重新賦值,將world變數地址賦值給了引數,函式呼叫結束後,重新列印變數str值,發現值沒有被修改。所以,在函式呼叫中,變數被拷貝了一份傳入函式,函式呼叫結束後,拷貝的值被丟棄。如果拷貝的是變數的地址,那麼在函式內,其實是通過修改這個地址所指向記憶體中內容,從而達到修改變數值的目的,但是函式內並不能修改這個變數的地址,也就是str變數雖然將地址當做引數傳入到PtrTest函式中,PtrTest函式中雖然對這個地址進行了修改,但是在函式呼叫結束後,拷貝傳遞進去並被修改的引數被丟棄,str變數地址未發生變化。
2.多返回值
golang中一個函式可以有多個返回值,多返回值函式定義如下:
package main
import (
"fmt"
)
func functionName(str string) (string, int) {
return "hello" + str, 200
}
func main() {
msg, status := functionName("hello world")
fmt.Println("msg is:", msg)
fmt.Println("status is :", status)
}
輸出資訊是:
msg is: hellohello world
status is : 200
golang語言允許給返回值設定變數名,寫法如下:
package main
import (
"fmt"
)
func functionName(str string) (msg string, status int) {
msg = "hello " + str
status = 200
return
}
func main() {
msg, status := functionName("hello world")
fmt.Println("msg is:", msg)
fmt.Println("status is :", status)
}
給返回值設定變數名後,在函式體內,可以直接使用返回值變數,無需在函式體內定義。
函式型別變數
golang可以定義函式型別的變數,函式型別的變數可以被呼叫。定義函式型別變數示例程式碼:
package main
import (
"fmt"
"reflect"
)
func main() {
var f = func(str string) {
fmt.Println("hello", str)
}
fmt.Println("型別是:", reflect.ValueOf(f).Kind())
f("func type")
}
輸出資訊是:
型別是: func
hello func type
函式型別變數也可以當做引數傳遞給另一個函式,然後在另一個函式中執行,示例程式碼如下:
package main
import (
"fmt"
"reflect"
)
func exec(f func(str string)) {
f("func type")
}
func main() {
var f = func(str string) {
fmt.Println("hello", str)
}
fmt.Println("型別是:", reflect.ValueOf(f).Kind())
exec(f)
}
輸出資訊是:
型別是: func
hello func type
exec函式接收一個函式型別的引數,那麼是不是隻要是函式,就可以被當做引數傳入到exec中嗎?答案是:不行。 從exec函式的引數列表可知,exec接收一個函式型別引數,這個函式型別引數接收一個字串型別的引數。
匿名函式與函式閉包
匿名函式,就是定義函式的時候沒有給函式取名字。定義函式型別變數,其實就是匿名函式的一種形式。而匿名函式又是閉包的一種形式,閉包示例如下:
package main
import (
"fmt"
)
func main() {
var str = "hello"
var num = 200
// 閉包,沒有函式名也可稱為匿名函式
func() {
fmt.Println(str, num)
}()
}
輸出資訊是:
hello 200
巢狀層級稍微複雜一些的閉包示例如下:
package main
import (
"fmt"
)
func main() {
var num = 200
var p = func() func() {
var s = 100
return func() {
fmt.Println(s + num)
}
}()
p()
num = 400
p()
}
輸出結果是:
300
500
閉包在程式設計中有著諸多的價值,閉包中可以直接使用外部的變數,閉包內的變數又可以不被閉包外訪問,保證閉包內變數的安全性。