Golang函數(shù)

函數(shù)

Golang函數(shù)特點

  • 無需聲明原型
  • 支持多返回值
  • 不定參數(shù)傳參 也就是函數(shù)的參數(shù)個數(shù)不是固定的 但是后面的類型是固定的
  • 支持命名返回參數(shù)
  • 支持匿名函數(shù)和閉包
  • 函數(shù)也是一種類型 可以賦值給一個變量
  • 不支持 嵌套 一個包不能有2個名字一樣的函數(shù)
  • 不支持重載
  • 不支持默認參數(shù)

函數(shù)聲明

函數(shù)聲明包含一個函數(shù)名裁蚁,參數(shù)列表钝鸽,返回值列表和函數(shù)體磅摹,如果函數(shù)沒有返回值米酬,則返回列表可以省略瞬浓。函數(shù)從第一條語句開始執(zhí)行旋讹,直到執(zhí)行return語句或者執(zhí)行函數(shù)的最后一條語句序厉。

func test(str,x string,y int)(int,string){
 //相同的2個類型 可以省略type合并稱一個
 //(int,string)返回參數(shù)
}
//函數(shù)是第一類對象 可以作為參數(shù)傳遞 
  • 參數(shù)

    函數(shù)定義時指出邻辉,函數(shù)定義時有參數(shù)溪王,該變量可被稱為函數(shù)的形參,形參就像定義在函數(shù)的局部變量值骇。但是當(dāng)調(diào)用函數(shù)的時候莹菱,傳遞過來的變量就是函數(shù)的實參,函數(shù)可以通過2種方式傳遞參數(shù)

    1. 值傳遞:在 在調(diào)用函數(shù)的時候?qū)嶋H參數(shù)復(fù)制一份傳遞到函數(shù)中吱瘩,這樣在函數(shù)中如果對參數(shù)修改道伟,不會影響到實際參數(shù)
    func swap(x ,y int)int{
    
    }
    
    1. 引用傳遞: 引用傳遞是指在調(diào)用函數(shù)時把實際參數(shù)的地址傳遞到函數(shù)中,那么函數(shù)中對參數(shù)進行修改就是對實際參數(shù)修改使碾,會影響到實際參數(shù)蜜徽。

      func swap(x,y *int){//傳入指針
       var temp int //創(chuàng)建一個臨時變量
          temp =*x  //x的地址的值賦值給temp臨時變量
          *x=*y //y的地址的值給x 
          *y=temp   //temp的地址給到y(tǒng)
      }
      

      在默認情況下Go語言使用的是值傳遞,也就是在調(diào)用過程中不會影響到實際參數(shù)

      無論是值傳遞票摇,還是引用傳遞拘鞋,傳遞給函數(shù)的都是變量的副本,不過兄朋,值傳遞是值得拷貝掐禁。引用傳遞是地址得拷貝怜械,一般來說,地址拷貝更加高效傅事。而值拷貝取決于對象得大小缕允,對象越大 性能越低

      map\slice\array\chan\指針\interface 默認都是以引用的方式傳遞

    Golang可變參數(shù)本質(zhì)上就是slice。只能有一個蹭越,且必須是最后一個障本。在參數(shù)賦值的時候不用一個個的賦值∠炀椋可以直接傳遞一個數(shù)組或者切片驾霜,注意最后一個參數(shù)要加上args...int

  • 返回值

    "_"標(biāo)識符,用來忽略函數(shù)的某個返回值买置。Go的返回值可以被命名粪糙,并且像在函數(shù)開頭聲明的變量那樣使用。返回值的名稱應(yīng)當(dāng)具有一定的意義忿项,可以作為文檔使用蓉冈。沒有參數(shù)的return語句返回各個變量的當(dāng)前值。這種用法被稱作"裸返回"轩触。直接返回語句僅應(yīng)用在像下面這樣的短函數(shù)中寞酿,在長的函數(shù)中它們會影響代碼的可讀性。

    //直接返回sum
    func add(x, y int) (sum int) {
    sum = x + y
     return
    }
    func main() {
     sum := add(10, 11)
     fmt.Println(sum)//21
    }
    //命名返回參數(shù)允許defer延遲調(diào)用通過閉包讀取和修改
    //直接返回sum
    
    //直接返回sum
    func add(x, y int) (sum int) {
     defer func() {
         fmt.Println("加100")
         sum += 100
     }()
     sum = x + y
     return
    }
    func main() {
     sum := add(10, 11)
    fmt.Println(sum) //加100 121
    //顯示return返回前脱柱,會先修改命名返回參數(shù)
    }
    

匿名函數(shù)

匿名函數(shù)是指不需要定義函數(shù)名的一種函數(shù)實現(xiàn)方式伐弹。在GO里面,函數(shù)可以像普通變量一樣被傳遞或使用榨为,Go語言支持隨時在代碼里定義匿名函數(shù)惨好。匿名函數(shù)由一個不帶函數(shù)聲明和函數(shù)體組成。匿名函數(shù)的優(yōu)越性可以直接使用函數(shù)內(nèi)的變量不必聲明随闺。

func main() {
  var sum = func() int {
      return 100 * 100
  >   }
  fmt.Println(sum())//10000
}
//Golang匿名函數(shù)可賦值給變量昧狮,作為結(jié)構(gòu)字段,或者在channel里傳送

func main() {
  //創(chuàng)建一個函數(shù)變量
  fun := func() {
      fmt.Println("111")
  }
  fun() //111
  //fun 數(shù)組 創(chuàng)建多個函數(shù) 函數(shù)的格式為接受一個參數(shù)返回一個int類型
  funs := [](func(x int) int){
      func(x int) int {
          return x + 1
      },
      func(x int) int {
          return x + 2
      },
  }
  fmt.Println(funs[0](100)) //101

  //作為結(jié)構(gòu)體的一個field
  d := struct {
      fn func(x int) int
  }{ //創(chuàng)建一個函數(shù)變量
      fn: func(x int) int { //函數(shù)變量賦值
          return 1
      },
  }
  fmt.Println(d.fn(1)) //1

  //channel
  c := make(chan func() string, 2)
  c <- func() string {
      return "hello world"
  }
  fmt.Println((<-c)())//hello world

}

閉包

閉包可以理解為一種保存函數(shù)狀態(tài)的方法板壮,當(dāng)我們調(diào)用一個函數(shù),或者執(zhí)行操作合住,或者返回結(jié)果绰精,函數(shù)運行結(jié)束之后,隨即消亡透葛,因為函數(shù)一般都是在堆上笨使,當(dāng)系統(tǒng)檢測當(dāng)前內(nèi)存空間沒有被引用就會回收

閉包的作用就是保存函數(shù)的運行狀態(tài) 避免函數(shù)被回收。

  • 訪問所在的作用域
  • 函數(shù)嵌套
  • 在所在作用域外被調(diào)用
func bb() func(i int) int {
 var s int = 0
   fmt.Println("ccccc")
 fmt.Println("ccccc")
 b := func(i int) int {
     s = s + 1
     fmt.Printf("地址%#v", &s)
     return s
 >   }
 >   return b
 > }
 > func main() {
 >   a := bb()
 >   b := bb()
 >   fmt.Println(a(1))
 >   fmt.Println(a(1))
 >   fmt.Println(b(1))
 >   fmt.Println(b(1))
 > }
 > //ccccc
 > //ccccc
 > //ccccc
 > //ccccc
 > //地址(*int)(0xc00000a0a8)1
 > //地址(*int)(0xc00000a0a8)2
 > //地址(*int)(0xc00000a0c0)1
 > //地址(*int)(0xc00000a0c0)2
 > //可以看到a和b變量的調(diào)用 s被存在了不同的內(nèi)存當(dāng)中
 > //當(dāng)采用a調(diào)用的時候 s的變量被保存到了0xc00000a0a8當(dāng)中
 > //當(dāng)采用b調(diào)用的時候 s變量被保存到了0xc00000a0c0 中 因為這是在調(diào)用bb()方法的時候 都申請了一塊內(nèi)存
 > 
 > //把s定義成全局變量
 > var s int = 0
 > func bb() func(i int) int {
 >   b := func(i int) int {
 >       s = s + 1
 >       fmt.Printf("地址%#v", &s)
 >       return s
 >   }
 >   return b
 > }
 > func main() {
 >   a := bb()
 >   b := bb()
 >   fmt.Println(a(1))
 >   fmt.Println(a(1))
 >   fmt.Println(b(1))
 >   fmt.Println(b(1))
 > }
 > //ccccc
 > //ccccc
 > //ccccc
 > //ccccc
 > //地址(*int)(0x85dc68)1
 > //地址(*int)(0x85dc68)2
 > //地址(*int)(0x85dc68)3
 > //地址(*int)(0x85dc68)4
 > //可以看到當(dāng)s提升為全局變量 申請的一塊地址 用于a和b調(diào)用的時候 是共享的內(nèi)存變量
 > ```
 >
 > 通過以上案例我們可以得出在a:=bb()的時候執(zhí)行了bb函數(shù)僚害,實際上這里的指向是直接指向的bb()函數(shù)的內(nèi)部子函數(shù)硫椰。
 >
 > 當(dāng)我們調(diào)用a()的時候 直接執(zhí)行的也是內(nèi)部的子函數(shù)。
 >
 > 通過局部變量和全局變量可以發(fā)現(xiàn),申請的內(nèi)存方式是不同的

遞歸函數(shù)

遞歸就是在運行的過程中調(diào)用自己靶草。一個函數(shù)調(diào)用自身叫做遞歸函數(shù)

構(gòu)成遞歸函數(shù)得條件:

子問題必須與原始問題為同樣的事蹄胰,且更為簡單

不能無限制調(diào)用必須有個出口,化簡為分遞歸狀態(tài)處理

func factorial(i int) int {
  if i <= 1 {
      return 1
  }
  return i * factorial(i-1)
}
func main() {
  fmt.Println(factorial(7))//5040
}

延遲調(diào)用(defer)

defer特性:
1.關(guān)鍵字defer用于注冊延遲調(diào)用
2. 這些調(diào)用直到return前才會被執(zhí)行奕翔。因此可以用來做資源清理
3.多個defer裕寨,按照先進后出的方式執(zhí)行
4.defer語句中的變量,在defer聲明時就決定了
defer用途:
1.關(guān)閉文件句柄
2.鎖資源釋放
3.數(shù)據(jù)庫連接釋放

func main() {

 for i := 0; i < 10; i++ {
     //defer是先進后出的   defer 調(diào)用的函數(shù)參數(shù)的值 defer 被定義時就確定了.
     //defer fmt.Print(strconv.Itoa(i) + "\t") //9   8   7   6   5   4   3   2   1   0
     //defer 碰上閉包 
     defer func() {
         fmt.Println(&i) //defer func內(nèi)部所使用的變量的值需要在這個函數(shù)運行時才確定
         //也就是講這在閉包用到的時候這個變量已經(jīng)變成了10,所以輸出全都是10
     }()
 }
}


defer 與return

//defer 與return
func foo() (i int) {

 i = 0
 defer func() {
     fmt.Println(i)
 }()
 //在具名返回函數(shù)中派继,執(zhí)行return 2的時候已經(jīng)將i的值賦值為2了宾袜。所以輸出的時候結(jié)果為2不是0
 return 2
}

func main() {
 foo()
}

defer nil報錯

//defer nil函數(shù)報錯

func test() {
 //聲明的時候未被調(diào)用
 var run func() = nil
 //被調(diào)用 報錯
 defer run()
 fmt.Println("runs")
}

func main() {
 //捕捉異常
 defer func() {
     if err := recover(); err != nil {
         fmt.Println("錯誤日志")
         fmt.Println(err)
     }
 }()
 //調(diào)用 runtime error: invalid memory address or nil pointer dereference
 //main從上到下開始執(zhí)行。defer 延遲執(zhí)行
 //執(zhí)行test方法驾窟。test方法調(diào)用的時候,出test的時候進行報錯庆猫。 當(dāng)test調(diào)用完成的時候 defer run才會被調(diào)用
 test()
}

在錯誤的地方使用defer

func do() error {
 res, err := http.Get("http://www.google.com")
 defer res.Body.Close()
 if err != nil {
     return err
 }
 return nil
}

func main() {
 do()
 //panic: runtime error: invalid memory address or nil pointer dereference
 //因為google無法訪問因為網(wǎng)絡(luò)原因 defer 直接使用了res變量 res為nill未判斷
//可以在defer 的時候加上一層判斷解決該錯誤
if res!=nil{
   defer res.Body.Close()
}
}

不檢查錯誤

//對于f.Close()可能會返回一個錯誤,但是這個錯誤會被我們忽略掉
func do() error {
 f, err := os.Open("book.txt")
 if err != nil {
     return err
 }

 if f != nil {
     defer f.Close()
 }


 return nil
}

func main() {
 do()
}
// 改進
if f!=nill{
 defer func(){
     if err:=f.Close();err!=nill{
         //code
     }
 }
}

釋放相同的資源

func do() error {
f, err := os.Open("book.txt")
if err != nil {
   return err
}
if f != nil {
   defer func() {
       if err := f.Close(); err != nil {
           fmt.Printf("defer close book.txt err %v\n", err)
       }
   }()
}

f, err = os.Open("another-book.txt")
if err != nil {
   return err
}
if f != nil {
   defer func() {
       if err := f.Close(); err != nil {
           fmt.Printf("defer close another-book.txt err %v\n", err)
       }
   }()
}

return nil
}

func main() {
do()//輸出結(jié)果: defer close book.txt err close ./another-book.txt: file already closed
//當(dāng)延遲函數(shù)執(zhí)行時候绅络,只有最后一個變量會被用到月培,因此f會成為最后那個資源。而且2個資源都將這一個資源作為關(guān)閉對象
}
//解決方案
//可以把f當(dāng)作參數(shù)傳遞進去
defer func(f io.Closer){

}(f)

異常處理

Golang沒有結(jié)構(gòu)化異常昨稼,使用panic拋出異常节视,recover捕獲錯誤。

異常的使用場景很簡單:Go可以拋出一個panic異常假栓,然后在defer中通過recover捕獲這個異常寻行。然后正常處理

panic:
  1.內(nèi)置函數(shù)
  2.加入函數(shù)F中書寫了panic語句 ,會終止其后要執(zhí)行得代碼匾荆,在panic所在函數(shù)F內(nèi)如果存在要執(zhí)行得defer函數(shù)列表拌蜘。按照defer先進后出執(zhí)行
  3.返回函數(shù)F得調(diào)用者G,在G中牙丽,調(diào)用函數(shù)F語句之后得代碼不會執(zhí)行简卧,加入函數(shù)G在存在要執(zhí)行得defer先進后廚執(zhí)行
  4.直到goroutine整個退出,并報告錯誤
recover:
  1.內(nèi)置函數(shù)
  2.用來控制一個goroutine得panicking行為烤芦,捕獲panic举娩,從而影響應(yīng)用得行為
  3.一般的調(diào)用建議 
      1.在defer函數(shù)中,通過recever來終止一個goroutine的panicking過程构罗,從而恢復(fù)正常代碼的執(zhí)行
      2.可以獲取通過panic傳遞的error
注:
  1.利用recover處理panic指令铜涉,defer必須放在panic之前定義,另外recover只有在defer調(diào)用的函數(shù)中才有效遂唧,否則當(dāng)panic芙代,receover無法捕捉到panic,無法防止panic擴散
  2.recover處理異常后盖彭,邏輯并不會恢復(fù)到panic那個點去纹烹,函數(shù)跑到defer之后的那個點
  3.多個defer會形成defer棧页滚,后定義的defer語句最先被調(diào)用。

延遲調(diào)用

//延遲調(diào)用中引發(fā)錯誤铺呵,可被后續(xù)異常調(diào)用捕獲裹驰,但僅最后一個錯誤可被捕獲
func test() {
  defer func() {
      fmt.Println("最后調(diào)用")
      fmt.Println(recover())
  }()

  defer func() {
      fmt.Println("第一次調(diào)用")
      panic("defer panic")
  }()

  panic("test panic")
}

func main() {
  test()
}
//捕獲函數(shù)recover 只有在延遲調(diào)用內(nèi)直接調(diào)用才會終止錯誤,否則總是返回nill陪蜻。任何未捕獲的錯誤都會沿調(diào)用堆棧向外傳遞
package main



import (
"fmt"
)


func test() {
  defer func() {
      fmt.Println(recover()) //有效
  }()
  defer recover()              //無效邦马!
  defer fmt.Println(recover()) //無效!
  defer func() {
      func() {
          println("defer inner")
          recover() //無效宴卖!
      }()
  }()

  panic("test panic")
}

func main() {
  test()
}


Go實現(xiàn)類似try catch的異常處理

func Try(fn func(), handler func(interface{})) {
  defer func() {
      if err := recover(); err != nil {
          handler(err)
      }
  }()
  fn()
}
func main() {
  Try(func() {
      fmt.Println("測試")
  }, func(err interface{}) {

  })
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末滋将,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子症昏,更是在濱河造成了極大的恐慌随闽,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肝谭,死亡現(xiàn)場離奇詭異掘宪,居然都是意外死亡,警方通過查閱死者的電腦和手機攘烛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門魏滚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人坟漱,你說我怎么就攤上這事鼠次。” “怎么了芋齿?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵腥寇,是天一觀的道長。 經(jīng)常有香客問我觅捆,道長赦役,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任栅炒,我火速辦了婚禮掂摔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赢赊。我一直安慰自己棒呛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布域携。 她就那樣靜靜地躺著,像睡著了一般鱼喉。 火紅的嫁衣襯著肌膚如雪秀鞭。 梳的紋絲不亂的頭發(fā)上趋观,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音锋边,去河邊找鬼皱坛。 笑死,一個胖子當(dāng)著我的面吹牛豆巨,可吹牛的內(nèi)容都是我干的剩辟。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼往扔,長吁一口氣:“原來是場噩夢啊……” “哼贩猎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起萍膛,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤吭服,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蝗罗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體艇棕,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年串塑,在試婚紗的時候發(fā)現(xiàn)自己被綠了沼琉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡桩匪,死狀恐怖打瘪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吸祟,我是刑警寧澤瑟慈,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站屋匕,受9級特大地震影響葛碧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜过吻,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一进泼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纤虽,春花似錦乳绕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至杰刽,卻和暖如春菠发,著一層夾襖步出監(jiān)牢的瞬間王滤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工滓鸠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留雁乡,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓糜俗,卻偏偏與公主長得像踱稍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悠抹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容