GO語言————6.1 函式

FLy_鵬程萬里發表於2018-06-30

6.1 函式介紹

每一個程式都包含很多的函式:函式是基本的程式碼塊。

Go是編譯型語言,所以函式編寫的順序是無關緊要的;鑑於可讀性的需求,最好把 main() 函式寫在檔案的前面,其他函式按照一定邏輯順序進行編寫(例如函式被呼叫的順序)。

編寫多個函式的主要目的是將一個需要很多行程式碼的複雜問題分解為一系列簡單的任務(那就是函式)來解決。而且,同一個任務(函式)可以被呼叫多次,有助於程式碼重用。

(事實上,好的程式是非常注意DRY原則的,即不要重複你自己(Don't Repeat Yourself),意思是執行特定任務的程式碼只能在程式裡面出現一次。)

當函式執行到程式碼塊最後一行(} 之前)或者 return 語句的時候會退出,其中 return 語句可以帶有零個或多個引數;這些引數將作為返回值(參考 第 6.2 節)供呼叫者使用。簡單的 return 語句也可以用來結束 for 死迴圈,或者結束一個協程(goroutine)。

Go 裡面有三種型別的函式:

  • 普通的帶有名字的函式
  • 匿名函式或者lambda函式(參考 第 6.8 節
  • 方法(Methods,參考 第 10.6 節

除了main()、init()函式外,其它所有型別的函式都可以有引數與返回值。函式引數、返回值以及它們的型別被統稱為函式簽名。

作為提醒,提前介紹一個語法:

這樣是不正確的 Go 程式碼:

func g()
{
}

它必須是這樣的:

func g() {
}

函式被呼叫的基本格式如下:

pack1.Function(arg1, arg2, …, argn)

Function 是 pack1 包裡面的一個函式,括號裡的是被呼叫函式的實參(argument):這些值被傳遞給被呼叫函式的形參(parameter,參考 第 6.2 節)。函式被呼叫的時候,這些實參將被複制(簡單而言)然後傳遞給被呼叫函式。函式一般是在其他函式裡面被呼叫的,這個其他函式被稱為呼叫函式(calling function)。函式能多次呼叫其他函式,這些被呼叫函式按順序(簡單而言)執行,理論上,函式呼叫其他函式的次數是無窮的(直到函式呼叫棧被耗盡)。

一個簡單的函式呼叫其他函式的例子:

示例 6.1 greeting.go

package main

func main() {
    println("In main before calling greeting")
    greeting()
    println("In main after calling greeting")
}

func greeting() {
    println("In greeting: Hi!!!!!")
}

程式碼輸出:

In main before calling greeting
In greeting: Hi!!!!!
In main after calling greeting

函式可以將其他函式呼叫作為它的引數,只要這個被呼叫函式的返回值個數、返回值型別和返回值的順序與呼叫函式所需求的實參是一致的,例如:

假設 f1 需要 3 個引數 f1(a, b, c int),同時 f2 返回 3 個引數 f2(a, b int) (int, int, int),就可以這樣呼叫 f1:f1(f2(a, b))

函式過載(function overloading)指的是可以編寫多個同名函式,只要它們擁有不同的形參與/或者不同的返回值,在 Go 裡面函式過載是不被允許的。這將導致一個編譯錯誤:

funcName redeclared in this book, previous declaration at lineno

Go 語言不支援這項特性的主要原因是函式過載需要進行多餘的型別匹配影響效能;沒有過載意味著只是一個簡單的函式排程。所以你需要給不同的函式使用不同的名字,我們通常會根據函式的特徵對函式進行命名(參考 第 11.12.5 節)。

如果需要申明一個在外部定義的函式,你只需要給出函式名與函式簽名,不需要給出函式體:

func flushICache(begin, end uintptr) // implemented externally

函式也可以以申明的方式被使用,作為一個函式型別,就像:

type binOp func(int, int) int

在這裡,不需要函式體 {}

函式是一等值(first-class value):它們可以賦值給變數,就像 add := binOp 一樣。

這個變數知道自己指向的函式的簽名,所以給它賦一個具有不同簽名的函式值是不可能的。

函式值(functions value)之間可以相互比較:如果它們引用的是相同的函式或者都是 nil 的話,則認為它們是相同的函式。函式不能在其它函式裡面宣告(不能巢狀),不過我們可以通過使用匿名函式(參考 第 6.8 節)來破除這個限制。

目前 Go 沒有泛型(generic)的概念,也就是說它不支援那種支援多種型別的函式。不過在大部分情況下可以通過介面(interface),特別是空介面與型別選擇(type switch,參考 第 11.12 節)與/或者通過使用反射(reflection,參考 第 6.8 節)來實現相似的功能。使用這些技術將導致程式碼更為複雜、效能更為低下,所以在非常注意效能的的場合,最好是為每一個型別單獨建立一個函式,而且程式碼可讀性更強。


相關文章