1. 程式人生 > >Go關鍵字--func

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

閉包在程式設計中有著諸多的價值,閉包中可以直接使用外部的變數,閉包內的變數又可以不被閉包外訪問,保證閉包內變數的安全性。