首頁 > 軟體

Go語言中的函數詳解

2022-07-12 18:00:16

1.函數的宣告定義

//func關鍵字

//getStudent函數名

//(id int, classId int) 參數列

//(name string,age int) 返回值列表

func getStudent(id int, classId int)(name string,age int) {

   //函數體

  if id==1&&classId==1{
 
     name = "BigOrange"

     age = 26

   }

  //返回值
  
  return name, age // 支援多重返回值

}

有意思的是Go語言的返回值可以有多個,並且放在了參數列後面,而C#等都是在函數名之前,也沒有關鍵字。

2.函數的呼叫

import "fmt"

//呼叫fmt包中的Println方法。

fmt.Println("Name:", std.Name, "Age:",std.Age)

3.函數編寫的原則

很好奇為什麼沒有public private等關鍵字,那函數怎麼才能定義為公用和私有呢?

Go語言有這樣的規則:小寫字母開頭的函數只在本包內可見,大寫字母開頭的函數才

能被其他包使用。這個規則也適用於型別和變數的可見性。

4.不定引數問題

不定引數是指函數傳入的引數個數為不定數量。

func myfunc(args ...int) {
     for _, arg := range args {
     fmt.Println(arg)
  }
}

函數myfunc()接受不定數量的引數,這些引數的型別全部是int

※形如...type格式的型別只能作為函數的引數型別存在,並且必須是最後一個引數。

它是一個語法糖(syntactic sugar),即這種語法對語言的功能並沒有影響,但是更方便程式設計師使用。

從內部實現機理上來說,型別...type本質上是一個陣列切片,也就是[]type,這也是為

什麼上面的引數args可以用for迴圈來獲得每個傳入的引數。

不定引數的傳遞

myfunc3(args ...int)

對應上面的這個函數,傳遞引數可以為下面兩種

// 按原樣傳遞
myfunc3(args...)
// 傳遞片段,實際上任意的int slice都可以傳進去
myfunc3(args[1:]...)

任意型別的不定引數

可以看到 fmt.Println()方法接受了不定引數,但它是 ...interface{}

用interface{}傳遞任意型別資料是Go語言的慣例用法。

5.多返回值

Go語言函數可以返回多個返回值

如果呼叫方呼叫了一個具有多返回值的方法,但是卻不想關心其中的某個返回值,可以簡單

地用一個下劃線“_”來跳過這個返回值

6.匿名函數

Go語言支援隨時在程式碼裡定義匿名函數。

匿名函數由一個不帶函數名的函數宣告和函數體組成

func(a, b int, z float64) bool {
     return a*b <int(z)
}

匿名函數可以直接賦值給一個變數或者直接執行:(有點像js哈)

f := func(x, y int) int {
     return x + y
}
func(ch chan int) {
    ch <- ACK
} (reply_chan) // 花括號後直接跟參數列表示函數呼叫

7.閉包

package main
import (
   "fmt"
)
func main() {
   var j int = 5
   a := func()(func()) {
      var i int = 10
      return func() {
         fmt.Printf("i, j: %d, %dn", i, j)
      }
   }()
   a()
   j *= 2
   a()
}

結果:

i, j: 10, 5
i, j: 10, 10

分析:

1---"...func()(func()) {....."

表明此匿名函數返回值的型別是func(), 即此匿名函數返回一個函數指標(此處參照一下c 的概念);

2---"...returnfunc() {

fmt.Printf("i, j: %d, %dn", i, j)

}..."

表明返回的函數指標指向一個列印i, j: %d, %dn的函數;

3---"...a := func()(func()) {

...

}()..."

末尾的括號表明匿名函數被呼叫,並將返回的函數指標賦給變數a ;

綜合來看:

"...a := func()(func()) {

var i int = 10

return func() {

fmt.Printf("i, j: %d, %dn", i, j)

}

}()..."

此程式碼片段的意思"等價於"

a := func() {

fmt.Printf("i, j: %d, %dn", i, j)

}

至於為何要用匿名函數如此的轉一圈,是因為要參照閉包的概念,此概念省略不表,多寫點程式碼試試就能體會了。

補充:傳值和傳參照

1.type 定義一個型別,有兩種方式

  • ①配合struct,建立一個新的結構,類似C#裡面的Class
  • ②配合既存的型別(int64...),建立一個新的型別,類似C++裡面的typedef

2.Struct的如果不進行賦值,會使用0值進行初始化。

3.type使用既存型別定義新結構,和既存型別不是同一個型別,不能進行轉換,例如

package main

type Integer int64

func main() {
    var srcInt Integer 
    srcInt = int64(1000)
}

結果:

4.方法和函數的區別

方法能給使用者定義的型別新增新的行為。方法實際上也是函數,只是在宣告時,在關鍵字func 和方法名之間增加了一個引數

例如:

這是函數,它的呼叫就是直接在使用的時候 傳入引數獲取返回值就行

func getStudentName(student Student)(name string) {
    //返回值
    return student.Name 
}

這是方法

package main

import (
    "fmt"
)

type Student struct
{   
    Name string
    Age int
}

//Student類的方法 使用值接收者實現了一個方法
func (student Student) getStudentName()(string){
    return student.Name
}

//Student類的方法 使用指標接收者實現了一個方法
func (student *Student) changeStudentName(name string){
    student.Name = name
    fmt.Println("方法執行之後name",student.Name)

}

//Student類的方法 使用指標接收者實現了一個方法
func (student Student) changeStudentNameByValue(name string){
    student.Name = name
    fmt.Println("方法執行之後name",student.Name)
}

func main() {
    bigOrange:=Student{
        Name:"BigOrange",Age:18,
    }

    bigApple:=Student{
        Name:"BigApple",Age:20,
    }

    //使用函數獲取學生名稱
    name1 := getStudentName(bigOrange)
    name2 := getStudentName(bigApple)
    
    fmt.Println("========通過傳地址ChangeName之後==========")
    fmt.Println("方法執行之前name",name1)
    bigOrange.changeStudentName("BigBanana")
    name1 = bigOrange.getStudentName()
    fmt.Println("方法返回之後Name",name1)

    fmt.Println("========通過傳值ChangeName之後===========")
    fmt.Println("方法執行之前name",name2)
    bigApple.changeStudentNameByValue("BigPear")
    name2 = bigApple.getStudentName()
    fmt.Println("方法返回之後Name",name2)
}

結果:

========通過傳地址ChangeName之後==========
方法執行之前name BigOrange
方法執行之後name BigBanana
方法返回之後Name BigBanana
========通過傳值ChangeName之後===========
方法執行之前name BigApple
方法執行之後name BigPear
方法返回之後Name BigApple

上面的例子中

分別使用了函數和方法進行獲取學生姓名

分別使用了傳值和傳地址的方式對學生名稱進行修正

綜上

  • ① Go語言中的方法,相比於函數,多了一個接收者引數
  • ② Go 語言裡有兩種型別的接收者:值接收者和指標接收者

方法如果使用 值接收者,那麼呼叫者可以是值接收者型別、也可以是它的指標型別,請看下面的例子(補充上例):

fmt.Println("========使用指標來呼叫值型別宣告的接收者方法===========")
bigGrape := &Student{ Name:"bigGrape",Age:22} //取地址!!!!
name3 := bigGrape.getStudentName();
fmt.Println("========通過傳值ChangeName之後===========")
fmt.Println("方法執行之前name",name3)
bigGrape.changeStudentNameByValue("BigXXXX")
name3 = bigGrape.getStudentName()
fmt.Println("方法返回之後Name",name3)

結果:

========使用指標來呼叫值型別宣告的接收者方法===========
name bigGrape
========通過傳值ChangeName之後===========
方法執行之前name bigGrape
方法執行之後name BigXXXX
方法返回之後Name bigGrape

如上程式碼 使用了&獲取地址,所以bigGrape是一個student型別的指標。下面的程式碼和上例一樣,直接使用了【變數.方法】的方式呼叫方法,結果也和值型別呼叫的一樣。

為什麼?

【Go 在程式碼背後的執行動作】

name4:=(*bigGrape).getStudentName()

Go 編譯器為了支援這種方法呼叫背後做的事情。【指標被解除參照為值】,這樣就符合了值接收者的要求。再強調一次,getStudentName 操作的是一個副本,只不過這次操作的是從bigGrape指標指向的值的副本。

同理還記得上面的,這句程式碼嗎?對bigOrange這個變數修改了名稱

bigOrange.changeStudentName("BigBanana")

bigOrange和bigApple明顯是值型別,但是 changeStudentName 接收者是一個指標型別,為什麼能呼叫,也是基於Go程式碼背後的執行動作,將值型別取了地址。

(&bigOrange).changeStudentName("BigOrange")

所以對於Go語言呼叫方法的型別,使用值或者指標都是可以的,不用拘泥於型別。

到此這篇關於Go語言函數的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com