<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go:為什么你應(yīng)當避免使用指針

          共 2099字,需瀏覽 5分鐘

           ·

          2020-09-07 09:47


          via:

          https://medium.com/better-programming/why-you-should-avoid-pointers-in-go-36724365a2a7

          作者:Dirk Hoekstra

          四哥水平有限,如有翻譯或理解錯誤,煩請幫忙指出,感謝!

          別被作者的這個標題誤導了,其實閱讀完全文,發(fā)現(xiàn)作者并不是排斥使用指針,而是應(yīng)選擇適當?shù)膱鼍叭ナ褂弥羔?/strong>。關(guān)于指針的基礎(chǔ)知識,可以閱讀公號之前發(fā)的文章?指針。

          原文如下:


          什么是指針

          為了覆蓋基礎(chǔ)知識,我們先講解什么是指針。

          看下面 CoffeeMachine 的例子,CoffeeMachine 結(jié)構(gòu)體中保存咖啡豆的數(shù)量。

          為了創(chuàng)建一臺“咖啡機”,我需要使用 NewCoffeeMachine() 函數(shù)。

          這里我創(chuàng)建了一個新的結(jié)構(gòu)體,使用 & 操作符返回結(jié)構(gòu)體的引用。

          type?CoffeeMachine?struct?{
          ????NumberOfCoffeeBeans?int
          }

          func?NewCoffeeMachine()?*CoffeeMachine?{
          ????return?&CoffeeMachine{}
          }

          當我將 CoffeeMachine 結(jié)構(gòu)體的引用傳遞給其他函數(shù)時,在這些函數(shù)里可以改變結(jié)構(gòu)體的底層數(shù)據(jù)。

          例如,我可以創(chuàng)建 SetNumberOfCoffeeBeans() 函數(shù),可以像下面這樣在函數(shù)內(nèi)部改變 CoffeeMachine 結(jié)構(gòu)體的值:

          package?main

          import?"fmt"

          type?CoffeeMachine?struct?{
          ????NumberOfCoffeeBeans?int
          }

          func?NewCoffeeMachine()?*CoffeeMachine?{
          ????return?&CoffeeMachine{}
          }

          func?(cm?*CoffeeMachine)?SetNumberOfCoffeeBeans(n?int)?{
          ????cm.NumberOfCoffeeBeans?=?n
          }

          func?main()?{
          ????cm?:=?NewCoffeeMachine()
          ????cm.SetNumberOfCoffeeBeans(100)

          ????fmt.Printf("The?coffee?machine?has?%d?beans\n",?cm.NumberOfCoffeeBeans)
          }

          因為 SetNumberOfCoffeeBeans() 函數(shù)的指針接收者指向 CoffeeMachine() 結(jié)構(gòu)體的底層結(jié)構(gòu),所以在函數(shù)內(nèi)部可以直接改變結(jié)構(gòu)體字段的值。

          因此,當我運行此程序時,顯示機器中確實有 100 個咖啡豆!

          go?run?main.go
          The?coffee?machine?has?100?beans

          不使用指針解決這個問題

          我們可以使用非指針方式實現(xiàn)同樣的“咖啡機”

          func?NewCoffeeMachine()?CoffeeMachine?{
          ????return?CoffeeMachine{}
          }

          func?(cm?CoffeeMachine)?SetNumberOfCoffeeBeans(n?int)?CoffeeMachine?{
          ????cm.NumberOfCoffeeBeans?=?n
          ????return?cm
          }

          func?main()?{
          ????cm?:=?NewCoffeeMachine()
          ????cm?=?cm.SetNumberOfCoffeeBeans(100)

          ????fmt.Printf("The?coffee?machine?has?%d?beans\n",?cm.NumberOfCoffeeBeans)
          }

          現(xiàn)在主要不同的是 SetNumberOfCoffeeBeans() 函數(shù)接收的是 CoffeeMachine 結(jié)構(gòu)體的副本,正因為這樣,需要返回更新之后的 CoffeeMachine 結(jié)構(gòu)體。

          輸出結(jié)構(gòu)如下:

          go?run?main.go
          The?coffee?machine?has?100?beans

          性能

          好的,到這里你可能會在想:“是不是傳值始終都會比傳指針效率低”。

          現(xiàn)在我們來做個實用性的測試,比較下傳指針和傳值的效率。

          我修改了 CoffeeMachine 結(jié)構(gòu)體,加入了兩個字段 UID 和 Description。

          type?CoffeeMachine?struct?{
          ????UID?string
          ????Description?string
          ????NumberOfCoffeeBeans?int
          }

          下一步,我使用指針方式給結(jié)構(gòu)體賦值,循環(huán) 100000 次,測量需要消耗多長時間。

          func?main()?{
          ????cm?:=?NewCoffeeMachine()

          ????start?:=?time.Now()
          ????for?i?:=?0;?i<100000;?i++?{
          ????????cm.SetUID(fmt.Sprintf("random-generated-uid-%d",?i))
          ????????cm.SetNumberOfCoffeeBeans(i)
          ????????cm.SetDescription(fmt.Sprintf("This?is?the?best?coffee?machine?that?is?around!?This?is?version?%d",?i))
          ????}

          ????elapsed?:=?time.Since(start)
          ????fmt.Printf("It?took?%s\n",?elapsed)
          }

          同樣的,我們再次使用傳值的方式實現(xiàn)上面的賦值操作。

          func?main()?{
          ????cm?:=?NewCoffeeMachine()

          ????start?:=?time.Now()
          ????for?i?:=?0;?i<100000;?i++?{
          ????????cm?=?cm.SetUID(fmt.Sprintf("random-generated-uid-%d",?i))
          ????????cm?=?cm.SetNumberOfCoffeeBeans(i)
          ????????cm?=?cm.SetDescription(fmt.Sprintf("This?is?the?best?coffee?machine?that?is?around!?This?is?version?%d",?i))
          ????}

          ????elapsed?:=?time.Since(start)
          ????fmt.Printf("It?took?%s\n",?elapsed)
          }

          分別執(zhí)行這兩段程序,發(fā)現(xiàn)消耗的時間差不多:

          With?pointers?result:?????32ms
          Without?pointers?result:?31ms

          我上面舉例子使用的結(jié)構(gòu)體比較小,如果需要拷貝的結(jié)構(gòu)體很大,則性能差距會更大。

          “意外之喜”

          所以,使用指針的缺點是什么?

          當你在函數(shù)之間傳指針時,你不知道是否會改變指針指向的值。

          這增加了代碼庫的復(fù)雜性,并且隨著代碼的增長,很容易就會出現(xiàn)錯誤,因為調(diào)用堆棧深處的某個地方改變了指針指向的值。

          最近,在我的項目里遇到了一個“搜索商品”的函數(shù):

          func?SearchProducts(criteria?*SearchCriteria)?[]Product?{
          ????//?Searches?for?products?here
          }

          在這個函數(shù)里,我不希望 SearchCriteria 被改變。但是,事實證明,在函數(shù)某個地方已經(jīng)將 SearchCriteria 的值改變了。

          在我看來,盡可能使用不可變的參數(shù)(即值而不是指針)是一種更好的做法,并且可以避免此類bug。

          指針的 Nil 值

          使用指針的時候,我們都需要考慮指針可能為 nil 的情況。程序員在使用指針之前不會被明確地強制檢查指針是否為 nil 的情況,因此在代碼里很容易出現(xiàn)這種人為錯誤。

          一起來思考下面這個例子:

          package?main

          import?"fmt"

          type?Product?struct?{
          ????Price?string
          }

          func?GetProduct(productUid?string)?*Product?{
          ????//?Code?that?retrieves?a?product?or?nil?if?not?found.
          ????//?Let's?simulate?a?"not?found"?scenario.
          ????return?nil
          }

          func?main()?{
          ????product?:=?GetProduct("corona-face-mask")
          ????fmt.Println("The?Corona?Face?mask?is?currently?%d?euro's",?product.Price)
          }

          在這個例子中,函數(shù) GetProduct() 返回一個 nil 值,但是我們沒有強制檢查返回值是否為 nil,所以運行這代代碼會報錯 nil pointer:

          panic:?runtime?error:?invalid?memory?address?or?nil?pointer?dereference
          [signal?SIGSEGV:?segmentation?violation?code=0x1?addr=0x8?pc=0x10994f3]
          goroutine?1?[running]:
          main.main()
          ?main.go:17?+0x23
          exit?status?2

          解決這個問題更優(yōu)雅的做法是,如果商品沒有找到就返回空結(jié)構(gòu)體和錯誤信息,想下面這樣:

          package?main

          import?(
          ????"fmt"
          ????"errors"
          )

          type?Product?struct?{
          ????Price?string
          }

          func?GetProduct(productUid?string)?(Product,?error)?{
          ????//?Code?that?retrieves?a?product?or?nil?if?not?found.
          ????//?Let's?simulate?a?"not?found"?scenario.
          ????return?Product{},?errors.New("Product?not?found")
          }

          func?main()?{
          ????product,?err?:=?GetProduct("corona-face-mask")
          ????if?err?!=?nil?{
          ????????fmt.Println("Error,?product?not?found")
          ????}?else?{
          ????????fmt.Println("The?Corona?Face?mask?is?currently?%d?euro's",?product.Price)
          ????}
          }

          像上面那樣,判斷返回值是否為 nil,絕對可以確保不會發(fā)生 nil pointer 錯誤。

          什么時候使用指針

          好吧,使用指針并不總是壞事,下面這兩種情況你應(yīng)當使用指針

          當你確實需要修改參數(shù)的時候

          舉個例子,下面的代碼片段,通過指針的方式可以直接在函數(shù) setName() 里面修改 User 結(jié)構(gòu)體的 Name 字段。

          type?User?struct?{
          ????Name?string
          }

          func?(user?*User)?setName(name?string)?{
          ????user.Name?=?name????
          }

          func?main()?{
          ????user?:=?&User{}
          ????user.setName("John")
          }

          當使用單例的時候

          有時候,當需要在全局保存唯一一個實例時,使用指針就很重要,這樣就能確保內(nèi)存中的數(shù)據(jù)不會發(fā)生多次拷貝(拷貝是需要消耗性能的)。

          總結(jié)

          不要在項目里面瘋狂地使用指針,而是要考慮何時以及如何更好地使用指針。

          如果你遵循上面的建議,大概率你就不會再次遇到 nil pointer dereference 的錯誤!




          推薦閱讀



          學習交流 Go 語言,掃碼回復(fù)「進群」即可


          站長 polarisxu

          自己的原創(chuàng)文章

          不限于 Go 技術(shù)

          職場和創(chuàng)業(yè)經(jīng)驗


          Go語言中文網(wǎng)

          每天為你

          分享 Go 知識

          Go愛好者值得關(guān)注



          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  东方a在线 | 亚洲国产一级一区 | 九九九九精品在线 | 国产一级做a爰片在线看免费 | www.污污污在线观看 |