👨‍💻簡介

在 Go 語言中,函數(Function)是一個強大且重要的概念,就像食譜一樣,告訴你應該如何處理食材,最後得到一道美味的料理。經過哪些程序讓程式更有組織性和可讀性。函數可幫助你將程式碼區塊組織成可重複使用的元件,進而執行特定的任務。本文要帶你一探究竟,深入了解 Golang 裡的函數有哪些不同的方面。從基本的概念開始,一路講到更高級的技巧,我們會告訴你怎麼樣用函數來處理各種不同的情況。

函數基本概念

函數在 Golang 中是一組程式語句的組合,用於執行特定的任務。它們接受輸入(參數)並返回輸出(返回值),從高層次來看,就像是黑盒子,你提供輸入,它交付結果。在每個 Golang 程式中,最常見的函數是 main(),它是程式的入口點。

函數名稱與命名規則

在 Golang 中,函數名稱的命名規則如下:

  • 名稱必須以字母開始,後面可以是任意數量的字母和數字。
  • 函數名稱不能以數字開頭。
  • 函數名稱不能包含空格。
  • 如果函數名稱以大寫字母開頭,則可以被其他套件引用。如果以小寫字母開頭,則不能被其他套件引用,但可以在同一套件內部使用。
  • 如果名稱由多個單詞組成,從第二個單詞開始的每個單詞首字母應大寫,例如 empNameEmpAddress 等。
  • 函數名稱區分大小寫,即 carCarCAR 是三個不同的變數。

創建函數

在 Golang 中,創建函數的基本語法如下:

func 函數名稱(參數1 型別1, 參數2 型別2, ...) 返回型別 {
    // 函數程式碼
    return 返回值
}

首先,用 func 關鍵字開始函數宣告,然後是你想要給函數的名稱,一對括號 (),然後是一個包含函數程式碼的區塊。

以下是一個簡單的範例,它不接受任何參數,也不返回任何值。

package main

import "fmt"

// SimpleFunction 印出一個訊息
func SimpleFunction() {
	fmt.Println("Hello World")
}

func main() {
	SimpleFunction() // 呼叫 SimpleFunction 函數
}

接受參數的函數

如果要將資訊傳遞給函數,可以通過參數來實現。參數就像變數一樣。

在函數名稱後面的括號內指定參數。你可以添加任意多的參數,只需用逗號分隔。

以下是一個帶有兩個整數參數的範例。當呼叫 add() 函數時,我們傳遞了兩個整數值(例如 20 和 30)。

package main

import "fmt"

// 接受參數的函數
func add(x int, y int) {
	total := 0
	total = x + y
	fmt.Println(total)
}

func main() {
	// 傳遞參數
	add(20, 30)
}

在 Golang 中,函數名稱首字母大寫的話,可以被其他套件引用(export)使用。如果函數名稱首字母小寫,則不能被其他套件引用,但在同一套件內部可以呼叫該函數。

返回值的函數

如果要在函數中返回值,可以在函數中指定返回類型。以下是一個計算兩個整數之和並返回結果的範例:

package main

import "fmt"

// 函數返回 int 型別的值
func add(x int, y int) int {
	total := x + y
	return total
}

func main() {
	// 接收返回值並存入變數
	sum := add(20, 30)
	fmt.Println(sum)
}

命名返回值的函數

在 Golang 中,函數的返回值可以命名。這意味著你可以在函數中指定返回值的名稱,並在函數內部直接使用這些名稱。

以下是一個計算矩形面積和周長的範例:

package main

import "fmt"

func rectangle(l int, b int) (area int) {
	parameter := 2 * (l + b)
	fmt.Println("周長:", parameter)

	area = l * b
	return// 省略返回值變數,使用命名的返回
}

func main() {
	fmt.Println("面積:", rectangle(20, 30))
}

返回多個值的函數

函數還可以返回多個值,這在處理多個相關的值時非常方便。以下是一個返回矩形面積和周長的範例:

package main

import "fmt"

// rectangle 是一個計算矩形面積和周長的函數,返回兩個值
func rectangle(length int, breadth int) (area int, perimeter int) {
    perimeter = 2 * (length + breadth)
    area = length * breadth
    return // 省略返回值變數,使用命名的返回
}

func main() {
    a, p := rectangle(10, 5) // 呼叫 rectangle 函數並接收返回值
    fmt.Println("面積:", a)
    fmt.Println("周長:", p)
}

將位址傳遞給函數進行值更新

在 Golang 中,你還可以將變數的位址傳遞給函數,並且在函數內部透過解引用的方式來修改變數的值。以下範例將展示這種通過位址更新變數值的情境:

package main

import "fmt"

func update(a *int, t *string) {
	*a = *a + 5      // 透過解引用位址修改變數值
	*t = *t + " Doe" // 透過解引用位址修改變數值
	return
}

func main() {
	var age = 20
	var text = "John"

	fmt.Println("修改前:", text, age)

	update(&age, &text)

	fmt.Println("修改後:", text, age)
}

匿名函數:更靈活的處理

在 Golang 中,還可以使用匿名函數。匿名函數是一種無需命名識別符即可宣告的函數。匿名函數可以接受輸入並返回輸出,就像普通函數一樣。以下是一個將函數指派給變數並使用匿名函數:

package main

import "fmt"

var (
	area = func(l int, b int) int {
		return l * b
	}
)

func main() {
	fmt.Println(area(20, 30))
}

你還可以將參數傳遞給匿名函數:

package main

import "fmt"

func main() {
	func(l int, b int) {
		fmt.Println(l * b)
	}(20, 30)
}

匿名函數也可以接受參數並返回值:

package main

import "fmt"

func main() {
	fmt.Printf(
		"100 (°F) = %.2f (°C)\n",
		func(f float64) float64 {
			return (f - 32.0) * (5.0 / 9.0)
		}(100),
	)
}

閉包:捕獲外部變數

在 Golang 中,閉包是匿名函數的一種特殊情形。閉包是可以存取在函數外部定義的變數的匿名函數。

以下是一個使用閉包的範例,其中匿名函數可以訪問在函數外部定義的變數:

package main

import "fmt"

func main() {
	l := 20
	b := 30

	func() {
		var area int
		area = l * b
		fmt.Println(area)
	}()
}

另一個範例是,在函數內的迴圈迭代過程中,匿名函數可以訪問變數的值:

package main

import "fmt"

func main() {
	for i := 10.0; i < 100; i += 10.0 {
		rad := func() float64 {
			return i * 39.370
		}()
		fmt.Printf("%.2f 公尺 = %.2f 英吋\n", i, rad)
	}
}

高階函數:函數作為參數和返回值

在 Golang 中,函數可以作為參數傳遞給其他函數,也可以作為函數的返回值。這種功能稱為高階函數(Higher-Order Functions),能夠提供更靈活且模組化的程式設計方式。以下範例演示如何創建一個高階函數,將一個函數套用到切片的每個元素:

package main

import (
    "fmt"
    "strings"
)

// mapString 將 f 函數套用到每個字串元素並返回新的切片
func mapString(arr []string, f func(string) string) []string {
    result := make([]string, len(arr))
    for i, v := range arr {
        result[i] = f(v)
    }
    return result
}

func main() {
    words := []string{"apple", "banana", "cherry"}
    toUpper := func(s string) string {
        return strings.ToUpper(s)
    }
    upperWords := mapString(words, toUpper)
    fmt.Println(upperWords) // 輸出:[APPLE BANANA CHERRY]
}

函數作為參數傳遞給其他函數

以下是一個將函數作為參數傳遞給高階函數的範例:

package main

import "fmt"

func sum(x, y int) int {
	return x + y
}

func partialSum(x int) func(int) int {
	return func(y int) int {
		return sum(x, y)
	}
}

func main() {
	partial := partialSum(3)
	fmt.Println(partial(7))
}

將函數作為結果返回的高階函數

你還可以將函數作為結果返回,這被稱為返回函數的高階函數。以下是一個範例,該範例返回一個函數,該函數在其內部使用嵌套的匿名函數:

package main

import "fmt"

func squareSum(x int) func(int) func(int) int {
	return func(y int) func(int) int {
		return func(z int) int {
			return x*x + y*y + z*z
		}
	}
}

func main() {
	resultFunc := squareSum(5)    // 返回一個函數
	subResultFunc := resultFunc(6) // 返回一個嵌套函數
	finalResult := subResultFunc(7) // 呼叫最內層的函數獲取結果
	fmt.Println(finalResult)       // 印出最終計算結果
	fmt.Println(squareSum(5)(6)(7))
}

自定義函數類型

在 Golang 中,你可以使用 type 關鍵字定義自己的函數類型。這就像是為函數創建了一個別名,使得該函數類型能夠被用作變數的型別。自定義函數類型能夠讓你更好地組織程式碼,以及在特定情境下使代碼更具表達性。

以下是一個使用自定義函數類型的範例,我們將創建一個函數類型 First,並進一步使用 Second 函數類型。這些自定義的函數類型可以用於創建一個執行特定計算的函數:

package main

import "fmt"

// 定義 First 函數類型
type First func(int) int

// 定義 Second 函數類型
type Second func(int) First

// squareSum 返回一個 Second 函數
func squareSum(x int) Second {
	return func(y int) First {
		return func(z int) int {
			return x*x + y*y + z*z
		}
	}
}

func main() {
	// 使用自定義函數類型的範例
	result := squareSum(5)(6)(7)
	fmt.Println("計算結果:", result) // 輸出:110
}

函數的特性、限制與使用場景

函數是程式設計中的重要組成部分,它們能夠將程式碼模組化、重複使用,使程式更具可讀性和可維護性。然而,函數也有其特性、限制以及適用的使用場景,讓我們來一起深入了解。

函數的特性

  1. 模組化: 函數能夠將程式碼分成小的模組,使得程式邏輯更加清晰,也更容易進行測試和修改。

  2. 重複使用: 定義一次函數後,你可以在程式中多次調用它,避免了重複編寫相同的程式碼。

  3. 參數和返回值: 函數可以接受參數,這些參數提供了執行函數所需的資訊。同時,函數可以返回值,使得結果能夠被其他程式碼使用。

  4. 封裝: 函數能夠封裝實現細節,隱藏程式碼的複雜性,使得外部使用者只需關心函數的功能。

  5. 遞迴: 函數可以呼叫自己,這種稱為遞迴的特性在處理某些問題時非常有用。

函數的限制

  1. 可讀性: 過多的函數調用可能導致程式難以閱讀和理解,因此需要適當的拆分和命名函數。

  2. 效能考慮: 過於頻繁的函數調用可能會影響程式的效能,因為每次函數呼叫都會有一定的開銷。

  3. 記憶體使用: 函數的呼叫需要保留堆疊帧等相關資訊,這可能會佔用一些記憶體空間。

  4. 可變性: 在某些程式語言中,函數可能會更改其外部範疇的變數,這可能導致程式難以理解和調試。

函數的使用場景

  1. 程式模組化: 將相關的程式碼封裝為函數,可以使程式更具結構,更容易維護。

  2. 代碼重用: 如果你在多個地方需要執行相同或類似的操作,可以將這些操作封裝成函數,方便重複使用。

  3. 抽象化: 函數可以將複雜的實現細節隱藏起來,只暴露必要的介面,使外部程式碼更易於理解。

  4. 遞迴問題: 某些問題的解決方案適合使用遞迴,例如數學中的遞迴定義和分治算法。

  5. 事件處理: 在事件驅動的程式中,函數可以用於處理不同的事件類型,使程式具有彈性。

  6. 資料轉換: 函數可以用於將一種資料類型轉換為另一種,例如日期格式轉換、單位轉換等。

  7. 處理業務邏輯: 在軟體應用中,函數可以用於實現業務邏輯,如處理訂單、用戶驗證等。

  8. 演算法實現: 函數是實現演算法的基本元素,能夠將複雜的演算法分解成可管理的部分。

函數的常見用法

1. 計算數值的平方和

package main

import "fmt"

func squareSum(numbers ...int) int {
    sum := 0
    for _, num := range numbers {
        sum += num * num
    }
    return sum
}

func main() {
    result := squareSum(2, 3, 4)
    fmt.Println("平方和:", result)
}

2. 找出切片中的最大值

package main

import (
    "fmt"
    "math"
)

func findMax(numbers ...float64) float64 {
    max := math.Inf(-1)
    for _, num := range numbers {
        if num > max {
            max = num
        }
    }
    return max
}

func main() {
    result := findMax(2.3, 5.7, 1.2, 9.8)
    fmt.Println("最大值:", result)
}

3. 檢查字串是否為回文

package main

import (
    "fmt"
    "strings"
)

func isPalindrome(s string) bool {
    s = strings.ToLower(s)
    for i := 0; i < len(s)/2; i++ {
        if s[i] != s[len(s)-1-i] {
            return false
        }
    }
    return true
}

func main() {
    result := isPalindrome("racecar")
    fmt.Println("是否為回文:", result)
}

4. 計算 Fibonacci 數列

package main

import "fmt"

func fibonacci(n int) int {
    if n <= 0 {
        return 0
    } else if n == 1 {
        return 1
    }
    return fibonacci(n-1) + fibonacci(n-2)
}

func main() {
    result := fibonacci(6)
    fmt.Println("第 6 個 Fibonacci 數:", result)
}

5. 判斷是否為質數

package main

import "fmt"

func isPrime(n int) bool {
    if n <= 1 {
        return false
    }
    for i := 2; i*i <= n; i++ {
        if n%i == 0 {
            return false
        }
    }
    return true
}

func main() {
    result := isPrime(17)
    fmt.Println("17 是否為質數:", result)
}

小結

函數是 Golang 程式設計的基石之一,讓你能夠組織程式碼、提高重用性,並以更模組化的方式處理不同情境的需求。無論是創建簡單的函數、使用閉包捕獲變數,還是運用高階函數進行模組化設計,函數都在 Golang 中扮演著關鍵角色。

📚Reference