Hike News
Hike News

Go初識-day8-細談函數

聲明語法

1
2
3
func 函數名 (參數列表)(返回值列表){
函數主體
}
  • 參數列表可以為空但一定要有()
  • 返回值列表也可以是空的,多個返回值需加()

example 1

1
2
3
func add(){

}
  • 返回值與參數皆為空

example 2

1
2
3
func add(a int, b int){

}
  • 多個參數,但無返回值

example 3

1
2
3
func add(a int, b int)int{

}
  • 多個參數,返回一個返回值,返回值的()省略

example 4

1
2
3
func add(a int, b int)(int,int){

}
  • 多個參數,返回多個返回值,返回值須加上()

example 5

1
2
3
func add(a, b int)(int,int){

}
  • 多個同類型參數可一起聲明,返回多個返回值,返回值須加上()

錯誤範例

1
2
3
4
func add()
{

}
  • 函數聲明必須與{在同一行,否則無法編譯

函數的特點

  • 函數不支持重載,一個包中不能有兩個名字一樣的函數
  • 一個函數可以賦值給變量,因此函數也可以是變量
  • 函數是一等公民,函數也可以是一種類型
  • 支持匿名函數 (沒有名字的函數)
    • 聲明時就可直接調用
    • 賦值給一變量,使用變量調用
  • 支持多返回值

函數可以賦值給變量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func add(a,b int)int{
return a + b
}

func main(){
c := add //[1]
fmt.Println("add func address =",add)
fmt.Println("c address =",c)

sum := c(10,20) //[2]
fmt.Println("sum is",sum)
}

[1] add函數賦值給一個變量,c變量為一指針指向函數的地址

  • 其等同於:
    1
    2
    var c func(int,int)int
    c = add

[2] c變量可以直接當作函數調用

result

1
2
3
add func address = 0x48c950  
c address = 0x48c950
sum is 30

從結果可以知道add函數跟c變量指向的是同一個內存地址


函數亦可為類型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

type add_func func(int,int) int //[1]

func add(a,b int) int {
return a + b
}

func sub(a,b int)int {
return a-b
}

func operator(op add_func, a int, b int) int { //[2]
return op(a,b)
}

func operator_2(op func(int,int)int, a,b int)int { //[3]
return op(a,b)
}

func main(){
c := add
sum := operator(c,100,200) //[4]
sub := operator_2(sub,200,100)
fmt.Println("sum =",sum)
fmt.Println("sub =",sub)
}

[1] type關鍵字可以用來定義一種自訂義的類型,其中add_func為類型名
[2] operator函數接受自訂義為add_func的函數類型
[3] 如果不使用type定義自訂義類型的話,直接於形式參數後面接上自訂義函數類型也是允許的
[4] c變量指向的add函數符合自訂義的add_func這個函數類型

  • 須注意傳入的參數必須符合自訂義的函數類型格式,否則會造成類型不匹配而無法編譯

result

1
2
sum = 300
sub = 100


匿名函數

沒有名字的函數

聲明時可直接調用

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main(){
func(a, b int){
fmt.Println("a =",a)
fmt.Println("b =",b)
fmt.Println("a + b=",a+b)
return
}(10,20) //[1]
}

[1] 在函數寫完之後直接在}右邊加()並填入參數調用

result

1
2
3
a = 10
b = 20
a + b= 30


使用變量調用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main(){
sum := func(a, b int){ //[1]
fmt.Println("a =",a)
fmt.Println("b =",b)
fmt.Println("a + b=",a+b)
return
}

sum(10,20) //[2]
}

[1] 在聲明變量時定義一個匿名函數
[2] 變量(),於()中填入參數並調用匿名函數

result

1
2
3
a = 10
b = 20
a + b= 30


函數參數傳遞方式

  • 值傳遞

    • 基本的數據類型皆為值傳遞
  • 引用傳遞

    • 常見的引用傳遞類型為指針(pointer)、map,slice,chan,interface類型
  • 無論是值傳遞,還是引用傳遞,傳遞給函數的都是變量的副本,不過值傳遞是值的拷貝;引用傳遞是地址的拷貝,一般來說,地址的拷貝更為高效(最大為8 bytes),而值拷貝取決於拷貝的物件大小,物件要是越大,性能會越低


命名返回值的名字

在定義函數返回值的類型時,可將返回值對應的名字一起定義

example 1

1
2
3
4
func add(a,b int)(c int){
c = a + b
return
}

example 2

1
2
3
4
5
func calc (a,b int)(sum int, avg int) {
sum = a + b
avg = (a + b) / 2
return
}

_ 忽略返回值

_標示符,用來忽略返回值

1
2
3
4
5
6
7
8
9
func calc (a,b int) (sum int, avg int) {
sum = a + b
avg = (a + b) / 2
return
}

func main(){
sum , _ := calc(100,200)
}


可變參數

如同最經常使用的fmt.Println()函數
調用函數時,我們可以傳入多個參數,也可以不傳入參數

example 1

不傳入參數多個參數

1
2
3
func function_1(arg ...int) {

}

example 2

必須至少傳入一個參數或傳入多個參數

1
2
3
func function_1(a int, arg ...int){

}

example 3

必須至少傳入兩個參數或傳入多個參數

1
2
3
func function_1(a,b int, arg ...int){

}

tips

  1. 其中arg參數名可自訂義
  2. arg是一個切片(slice)
  3. 可通過arg[index]依次訪問所有參數
  4. 可通過len(arg)來判斷傳遞參數的個數

defer

特性

  • 當函數返回時,會執行defer語句,因此可以用來做資源清理

  • 多個defer語句,按先進後出的方式執行
    • 會先執行第二次聲明的defer語句,在執行第一次執行的defer語句
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      package main

      import "fmt"

      func main(){
      var i int = 0
      defer fmt.Println(i)
      defer fmt.Println("second_defer")

      i = 10
      fmt.Println(i)
      return
      }

result

1
2
3
10
second_defer
0

  • 先聲明的defer語句被壓至stack中,取值時從最晚聲明的defer語句開始取,所以”second defer”會先被執行

  • defer語句中的變量,在defer聲明時就決定了
1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main(){
i := 0
defer fmt.Println(i) //[1]
i++
fmt.Println(i)
return
}

[1] 即使i++,但i在defer聲明時就確定i之值,當函數返回時打印出來的i仍為0
result

1
2
1
0


用途

關閉文件句柄

1
2
3
4
5
6
func read(){
file := open(filename)
defer file.close()

//文件操作語句
}

鎖資源的解放

1
2
3
4
5
6
func read(){
mc.Lock()
defer mc.Unlock()

//其他操作
}

數據庫連接的釋放

1
2
3
4
5
6
func read(){
conn := openDatabase()
defer conn.close()

//數據庫操作
}