Go函數和方法


 一、函數

    函數是基本的代碼塊,用於執行一個任務。

    go語言至少有個main()函數

    1)函數定義

func function_name( [parameter list] ) [return_types] {
   函數體
}

      func:聲明這是一個函數
      function_name:函數名稱,函數名和參數列表一起構成了函數簽名
      parameter list:參數列表,注意類型在變量名之后
      return_types:返回類型,不是必須的,當沒有返回值時,可以不指定返回類型,也可以返回多個值,如(string,string)
      函數體:函數定義的代碼集合

    

     常用函數用法:

// 函數多參無返回值
func func_name(a,b int, c string){}
// 函數無參無返回值
func func_name(){}
// 單個返回值
func func_name(s string) string{}
// 多個返回值
func func_name (s string) (string,int){}
// 命名返回參數
func func_name(s string) (result string){
    ...
    result=1
    return
}
// 可變參數,可變參數只能做為函數參數存在,並且是最后一個參數,本質上是slice
func func_name(s string,args ...int){}

// 匿名函數,調用:f(1,2)
f := func(x,y int) int {
    return x + y
}

     注意:

      ①:Go函數不支持重載

      ②:一個包中不能有兩個名字一樣的函數

      ③:當兩個或多個連續的函數命名參數是同一類型,則除了最后一個類型之外,其他的類型可以省略

      ④:函數可以返回任意數量的返回值

      ⑤:使用關鍵字 func 定義函數,左大括號依舊不能另起一行

 

  

     2)函數參數

      函數如果使用參數,該變量可稱為函數的形參。

      形參就像定義在函數體內的局部變量。

      調用函數,傳遞過來的變量就是函數的實參,可以通過兩種方式來傳遞參數:

傳遞類型 描述
值傳遞 值傳遞是指在調用函數時將實際參數復制一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數。
引用傳遞 引用傳遞是指在調用函數時將實際參數的地址傳遞到函數中,那么在函數中對參數所進行的修改,將影響到實際參數。

      默認情況下,Go 語言使用的是值傳遞,即在調用過程中不會影響到實際參數

      注意:

        ①:無論是值傳遞,還是引用傳遞,傳遞給函數的都是變量的副本,不過,值傳遞是值的拷貝。引用傳遞是地址的拷貝,一般來說,地址拷貝更為高效。而值拷貝取決於拷貝的對象大小,對象越大,則性能越低

        ②:map、slice、chan、指針、interface默認以引用的方式傳遞

 

      可變參數:

        Golang 可變參數本質上就是 slice。只能有一個,且必須是最后一個。

         在參數賦值時可以不用用一個一個的賦值,可以直接傳遞一個數組或者切片,特別注意的是在參數后加上“…”即可

      任意類型的不定參數:

        就是函數的參數和每個參數的類型都不是固定的

        用空接口:interface{}傳遞任意類型數據是Go語言的慣例用法,而且interface{}是類型安全的

 

 

    3)遞歸函數

      遞歸,就是在運行的過程中調用自己

      Go語言支持遞歸,注意在使用遞歸時,要設置退出條件,避免陷入死循環

func recursion() {
   recursion() /* 函數調用自身 */
}

func main() {
   recursion()
}

      構成遞歸需具備的條件

        ①:子問題須與原始問題為同樣的事,且更為簡單

        ②: 不能無限制地調用本身,須有個出口,化簡為非遞歸狀況處理

 

 

    4)defer

      Go語言的 defer 語句會將其后面跟隨的語句進行延遲處理

      在 defer 歸屬的函數即將返回時,將延遲處理的語句按 defer 的逆序進行執行,也就是說,先被 defer 的語句最后被執行,最后被 defer 的語句,最先被執行。

      關鍵字 defer 的用法類似於面向對象編程語言 Java 和 C# 的 finally 語句塊,它一般用於釋放某些已分配的資源,典型的例子就是對一個互斥解鎖,或者關閉一個文件

      defer關鍵字特性:

        ①:關鍵字 defer 用於注冊延遲調用

        ②:這些defer調用直到所在函數 return 前才被執行。因此,可以用來做資源清理

        ③:多個defer語句,defer 所在的函數返回后,將按照后進先出的順序執行 defer 保存的延遲調用函數,也就是說,后定義的defer函數先執行

        ④:defer 延遲調用函數可以讀取並分配給返回函數的命名返回值

        ⑤:defer語句中的變量,在defer聲明時就決定了

 

      defer用途:

        ①:關閉文件句柄

        ②:鎖資源釋放

        ③:數據庫連接釋放

        ④:panic捕獲

 

 

    5)init函數和mian函數

      ①:init函數

        go語言中init函數用於包(package)的初始化,該函數是go語言的一個重要特性

        init函數是用於程序執行前做包的初始化的函數,比如初始化包里的變量等

        每個包可以擁有多個init函數,包的每個源文件也可以擁有多個init函數

        對同一個go文件的init()調用順序是從上到下的。

        對同一個包中不同文件是按文件名字符串比較“從小到大”順序調用各文件中的init()函數

        不同包的init函數按照包導入的依賴關系決定該初始化函數的執行順序

        init函數不能被其他函數調用,而是在main函數執行之前,自動被調用

      ②:main函數

        Go語言程序的默認入口函數(主函數)

    func main(){
        //函數體
    }

 

    補充:go包初始化

      ①:包的初始化是按照包在程序中導入的順序來進行,依賴順序優先,每次初始化一個包。如果包b導入了包a,那么a在b之前就已經完成初始化。runtime需要解析包依賴關系,沒有依賴的包最先初始化

      ②:初始化過程是自下而上的,main包最后初始化。所以在main函數開始執行之前,所有的包已經初始化完畢

      ③:包的初始化從初始化包級別的變量開始,這些變量按照聲明順序初始化,依賴順序優先

      ④:如果包由多個.go文件組成,go工具在調用編譯器前會將這些.go文件進行排序,那初始化會安裝編譯器收到的文件順序進行

      ⑤:init函數在一個 package 中的所有全局變量都初始化完成之后, 才開始運行。init函數只會運行一次, 即使被 import 了很多次

        同一個 package 或源文件中, 可以有很多個init函數,同一個 Package 下的多個源文件中都有init函數也可以

        同一個源文件中, 寫在更靠近文件上面的 init 函數更早運行

        同一個 package 中, 文件名排序靠前的文件中的 init 函數更早運行

        建議將init 函數就放在源文件的最上面

        如果一個 package 只有一個init函數, 那盡量放在和 package 同名的源文件里

 

    6)函數表達式

      使用函數表達式實現三目運算

    // 函數表達式,實現三目運算
    // 格式:func() returnType {...}()
    i := 1
    j := 2
    k := func() int {
        if i > j {
            return i
        }
        return j
    }()
    fmt.Println(k)

 

      帶參數的函數表達式:

    h := func(a, b int) int {
        if a > b {
            return a
        }
        return b
    }(i, j)

 

 

    7)匿名函數

      匿名函數是指不需要定義函數名的一種函數實現方式,匿名函數由一個不帶函數名的函數聲明和函數體組成。

      匿名函數的優越性在於可以直接使用函數內的變量,不必聲明

      在Go里面,函數可以像普通變量一樣被傳遞或使用。Golang匿名函數可賦值給變量,做為結構字段,或者在 channel 里傳送。

      如:

    getSqrt := func(a float64) float64 {
        return math.Sqrt(a)
    }
    fmt.Println(getSqrt(4))

      上面先定義了一個名為getSqrt 的變量,初始化該變量時和之前的變量初始化有些不同,使用了func,func是定義函數的,可是這個函數和上面說的函數最大不同就是沒有函數名,也就是匿名函數。這里將一個函數當做一個變量一樣的操作

      

 

    8)閉包

      什么是閉包?閉包就是能夠讀取其他函數內部變量的函數。

      通常只有函數內部的子函數才能讀取局部變量,所以閉包可以理解成“定義在一個函數內部的函數“。在本質上,閉包是將函數內部和函數外部連接起來的橋梁

       Go語言支持閉包,通過匿名函數的方式,如:

// 創建函數a,返回另外一個函數。該函數的目的是在閉包中遞增i的變量
func a() func() int {
    i := 0
    b := func() int {
        i++
        fmt.Println(i)
        return i
    }
    return b
}

func main() {
    c := a()
    c() // 1
    c() // 2
    c() // 3

    a() //不會輸出i
}

 

 

    9) new和make函數

      在Go語言中對於引用類型的變量,我們在使用的時候不僅要聲明它,還要為它分配內存空間,否則我們的值就沒辦法存儲。而對於值類型的聲明不需要分配內存空間,是因為它們在聲明的時候已經默認分配好了內存空間。要分配內存,就引出來今天的new和make。 Go語言中new和make是內建的兩個函數,主要用來分配內存

      

      ①:new

        new函數的簽名:  func new(Type) *Type

        Type表示參數類型,*type表示類型指針,new函數返回一個指向該類型內存地址的指針

        new函數不太常用,使用new函數得到的是一個類型的指針,並且該指針對應的值為該類型的零值。

        如var a *int只是聲明了一個指針變量a但是沒有初始化,指針作為引用類型需要初始化后才會擁有內存空間,才可以給它賦值。

        應該按照如下方式使用內置的new函數對a進行初始化之后就可以正常對其賦值了:

func main() {
    var a *int
    a = new(int)
    *a = 10
    fmt.Println(*a)
}

      new引發panic異常:

//定義一個結構體,age字段為指針
type Student struct {
    age *int
}

//獲取結構體對象指針
func getStudent() *Student {
    s := new(Student) // panic,因為new只會為結構體Student申請一片內存空間,不會為結構體中的指針age申請內存空間
    return s
}

 

      ②:make

        make函數的簽名:func make(t Type, size ...IntegerType) Type

        make也是用於分配內存的,區別於new,它只用於slice、map以及channel的內存創建,而且它返回的類型就是這三個類型本身,而不是他們的指針類型,因為這三種類型就是引用類型,所以就沒必要返回他們的指針了。

        make函數是無可替代的,我們在使用slice、map以及channel的時候,都需要使用make進行初始化,然后才可以對它們進行操作

 

      new函數和make函數的區別

        相同點:

          底層都是通過mallocgc申請內存

        不同點:

          ①:make 返回值是”引用類型“,new 返回值是指針類型

          ②:make僅用於初始化 slice,map 和 chan;new 可用於初始化任意類型(new並不常用) 

        

 

      

 

 

 二、方法

    Go 語言中同時有函數和方法。一個方法就是一個包含了接收者的函數。

    方法可以將類型和方法封裝在一起,實現強耦合。

    接收者可以是命名類型或者結構體類型的一個值或者是一個指針。所有給定類型的方法屬於該類型的方法集

    Go語言中的方法時一種作用於特定類型變量的函數。這種特定類型變量叫做接收者,接收者的概念類似於Java語言中的this,和Python語言中的self。只不過Go語言中需要將this顯式的聲明出來。

 

    方法的定義格式如下:

    func (接收者變量 接收者類型) 方法名(參數列表) (返回類型) {
       方法體
    }

    方法示例:

// 定義結構體
type User struct {
    name    string
    gender  string
    address string
    age     int
}

// 接收User類型的方法
// 值類型的接收者
func (user User) getName() string {
    return user.name
}

// 指針類型的接收者
func (user *User) setName(name string) {
    user.name = name
}

func main() {
    user := &User{
        name:    "yangyongjie",
        age:     27,
        gender:  "male",
        address: "nanjing",
    }
    name := user.getName() // 該方法只能User結構體類型的變量或指針才能調用
    fmt.Println(name)      // yangyongjie

    user1 := &User{}
    user1.setName("yyj")
    fmt.Println(user1.name) // yyj

}

 

    值類型的接收者和值類型的接收者方法的區別:

      指針類型的接收者由一個結構體的指針組成,由於指針的特性,調用方法時修改接收者指針的任意成員變量,在方法結束后,修改都是有效的。這種方式十分接近於Java語言中的this,和Python語言中的self

      當方法作用於值類型的接收者時,Go語言會在代碼運行時將接收者的值復制一份。在值類型接收者的方法中可以獲取接收者的成員值,但是修改操作只是針對副本,無法修改接收者變量本身。

    如:

import "fmt"

// 定義結構體
type User struct {
    name    string
    gender  string
    address string
    age     int
}

// 值類型的接收者
func (user User) setAddress(address string) {
    user.address = address
}

// 指針類型的接收者
func (user *User) setName(name string) {
    user.name = name
}

func main() {
    user := &User{
        name:    "yangyongjie",
        age:     27,
        gender:  "male",
        address: "nanjing",
    }
    
    // 接收值類型的方法,user變量本身值沒有被修改
    user.setAddress("beijing")
    fmt.Println(user.address) // nanjing

    // 接收指針類型的方法,user變量本身值沒有被修改
    user.setName("yyj")
    fmt.Println(user.name) // yyj

}

 

      什么時候應該使用指針類型接收者?

        ①:需要修改接收者中的值

        ②:接收者是拷貝代價比較大的大對象

        ③:保證一致性,如果有某個方法使用了指針接收者,那么其他的方法也應該使用指針接收者

 

  結構體變量和結構體指針的理解:

    結構體指針指向的是結構體變量的內存地址

    結構體變量是結構體類型的變量本身的值

 

  Go單元測試

import (
	"testing"
)

func TestSendTalos(t *testing.T) {
	
}

 

END.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM