Go語言函式相關
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, %d\n", i, j)
}
}()
a()
j *= 2
a()
}
結果:
i, j: 10, 5 i, j: 10, 10
分析:
1---"...func()(func()) {....."
表明此匿名函式返回值的型別是func(), 即此匿名函式返回一個函式指標(此處引用一下c 的概念);
2---"...return func() {
fmt.Printf("i, j: %d, %d\n", i, j)
}..."
表明返回的函式指標指向一個列印 i, j: %d, %d\n 的函式;
3---"...a := func()(func()) {
...
}()..."
末尾的括號表明匿名函式被呼叫,並將返回的函式指標賦給變數a ;
綜合來看:
"...a := func()(func()) {
var i int = 10
return func() {
fmt.Printf("i, j: %d, %d\n", i, j)
}
}()..."
此程式碼片段的意思"等價於"
a := func() {
fmt.Printf("i, j: %d, %d\n", i, j)
}
至於為何要用匿名函式如此的轉一圈,是因為要引用閉包的概念,此概念省略不表,多寫點程式碼試試就能體會了。
補充:傳值和傳引用
2018/08/22 補充
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語言呼叫方法的型別,使用值或者指標都是可以的,不用拘泥於型別。