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)

Functionpack1 包裏面的一個函數,括號裏的是被調用函數的實參(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 節)來實現相似的功能。使用這些技術將導致代碼更爲複雜、性能更爲低下,所以在非常注意性能的的場合,最好是爲每一個類型單獨創建一個函數,而且代碼可讀性更強。

鏈接

results matching ""

    No results matching ""