深入理解go的函數參數傳遞

2021-03-06 利志分享

首先我們要有一個理解:go的函數參數傳遞都是值傳遞,為什麼說是傳值呢?因為go的函數傳遞都是複製了一份傳遞到參數中。下面我們看一個例子:

package main
import "fmt"
func main() { a := 1 b := "xx" c := []int{1} d := map[string]string{"xx": "xxx"} e := Tmp{ Name: "xxx", } f := func() string { return "xxx" } g := new(Tmp) fmt.Printf("原來的:a:%p b:%p c:%p d:%p e:%p f:%p g:%p\n", &a, &b, &c, &d, &e, &f, &g) testParamFunc(a, b, c, d, e, f, g)}
func testParamFunc(a int, b string, c []int, d map[string]string, e Tmp, f func() string, g *Tmp) { fmt.Printf("函數內的:a:%p b:%p c:%p d:%p e:%p f:%p g:%p\n", &a, &b, &c, &d, &e, &f, &g)}
type Tmp struct { Name string}

執行結果如下:

原來的:a:0xc00000a0b0 b:0xc00003c1f0 c:0xc000004480 d:0xc000006028 e:0xc00003c200 f:0xc000006030 g:0xc000006038函數內的:a:0xc00000a0e0 b:0xc00003c220 c:0xc0000044c0 d:0xc000006048 e:0xc00003c230 f:0xc000006050 g:0xc000006058

上面的例子我們看到了不管是什麼類型傳過來,函數都給複製了一份傳遞到函數中,函數的參數地址都變化了,參數地址指向的內容是原來的值。

所謂值傳遞:指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數。但是其實go裡面有些類型會影響到實際參數,下面我們對不同的類型來不同的講解。

關於字符串和整形的參數傳遞例子:

package main
import "fmt"
func main() { a := 1 bTmp := 2 b := &bTmp c := "xx" dTmp := "kk" d := &dTmp e := 3 fmt.Printf("a:%d b:%d c:%s d:%s e:%d\n", a, *b, c, *d, e) testFunc(a, b, c, d, &e) fmt.Printf("a:%d b:%d c:%s d:%s e:%d\n", a, *b, c, *d, e)}
func testFunc(a int, b *int, c string, d *string, eParam *int) { a = 11 tmpInt := 22 b = &tmpInt c = "qqq" tmpStr := "ooo" d = &tmpStr *eParam = 33}

執行結果如下:

a:1 b:2 c:xx d:kk e:3a:1 b:2 c:xx d:kk e:33

字符串和整形傳遞函數裡面是無法修改實參的,但是為什麼e我們看那個值變化了呢?*eParam = 33這行代碼其實不是對e直接賦值,是對eParam的地址進行重新賦值了,本來eParam傳過來就是上面變量e的地址,然後在函數裡面取*eParam就是變量e。

關於struct參數傳遞

package main
import "fmt"
func main() { a := Tmp{ Name: "xxx", } b := &Tmp{ Name: "xxx", } fmt.Printf("a:%v b:%v\n", a, *b) testFunc2(a, b) fmt.Printf("a:%v b:%v\n", a, *b)}
func testFunc2(a Tmp, b *Tmp) { a.Name = "ddd" b.Name = "ggg"
}
type Tmp struct { Name string}

執行結果如下:

a:{xxx} b:{xxx}a:{xxx} b:{ggg}

struct的參數傳遞,如果是本身傳遞,參數內無法修改實參,但是如果是傳struct地址,通過地址也能取參數的屬性,這樣是可以修改實參,所以我們看到b被修改了。

關於slice,map,chan三種數據結構,我們也先看下例子

package main
import "fmt"
func main() { a := []int{1, 2, 3, 6} b := []int{1, 2, 3, 6} c := map[string]string{"xxx": "vvv"} d := make(chan int, 1) fmt.Printf("a:%v b:%v c:%v d:%d\n", a, b, c, len(d)) testFunc3(a, b, c, d) fmt.Printf("a:%v b:%v c:%v d:%d\n", a, b, c, len(d))}func testFunc3(a, b []int, c map[string]string, d chan int) { a = append(a, 100) b[3] = 100 c["iiii"] = "sss" d <- 1}

結果如下:

a:[1 2 3 6] b:[1 2 3 6] c:map[xxx:vvv] d:0a:[1 2 3 6] b:[1 2 3 100] c:map[iiii:sss xxx:vvv] d:1

結果是不是沒想到呢?a通過append是沒法直接修改實參,但是b呢通過直接操作某一個屬性是可以修改的。因為a是slice,用append的話,slice會擴容,會copy一個slice,原來的地址(即臨時參數)會指向新的slice,但是這個臨時參數又和原來的傳進來的變量地址不一樣,所以沒法修改實參。但是b呢,能修改是因為makeslice返回的是一個指針,我們通過修改屬性其實修改的是b變量的指向底層數組指針屬性,所以能修改。但是c呢,為什麼直接傳過來就直接修改了呢?這個得看看源碼了,翻了一下makemap看看,makemap返回值是返回一個指針,其實就是map的地址,我們在函數內操作直接就修改了實參。關於d呢,原因和c是一樣的,makechan會返回一個指針,函數內操作其實就是直接對傳過來的變量的屬性進行更改了,所以函數內操作直接修改了實參。

下面我們過一下這個創建的函數的源碼,以上的實例分析希望對大家有幫助,有問題隨時交流。

創建slice

func makeslice(et *_type, len, cap int) unsafe.Pointer {  .  return mallocgc(mem, et, true)}

創建map

func makemap(t *maptype, hint int, h *hmap) *hmap {  .  return h}

創建chan

func makechan(t *chantype, size int) *hchan {  .  return c}

相關焦點

  • Python函數的參數傳遞
    在定義函數時,如果有些參數存在默認值,即部分參數不一定需要調用程序輸入,可以在定義函數時直接為這些參數指定默認值。調用時,這些參數被當作元組類型傳遞到函數中,參見下面例子:def leju(a, *b):print(type(b))for n in b:a += nreturn aprint(leju(1,2,3,4,5))輸出結果:leju()函數定義了可變參數b,調用leju()函數時輸入的(2,
  • 傳遞函數、頻率響應函數以及參數識別詳解
    你能給我解釋傳遞函數,FRF和參數識別嗎?當然了 , 沒問題。一開始曲線擬合可能看起來像是黑魔法,但是我想做些簡單的類比,來幫助你了解它實際上相當簡單,並且利用這個例子我將表明它確實非常簡單。關於系統傳遞函數和頻響函數(FRF),上次我們討論過一些相關知識。
  • 每天都在調用函數,Go 中函數調用的原理你知道嗎?
    函數是 Go 語言中的一等公民,理解和掌握函數的調用過程是深入學習 Golang 時無法跳過的步驟,這裡會介紹 Go 語言中函數調用的過程和實現原理並與 C 語言中函數執行的過程進行對比,同時對參數傳遞的原理進行剖析,讓讀者能夠清楚地知道 Go 在函數的執行過程中究竟都做了哪些工作。
  • Python入門基礎中怎麼定義函數,函數調用,函數傳遞參數
    語法Python 定義函數使用 def 關鍵字,一般格式如下:函數調用定義一個函數,給了函數一個名稱,指定了函數裡包含的參數,和代碼塊結構。函數調用格式:函數名()參數傳遞在 python 中,類型屬於對象,變量是沒有類型的。參數可以看鑰匙,函數是門,多個參數就是多把 鑰匙,開門需要鑰匙。
  • js中對函數的深入理解(下)
    在js函數中,有一個特殊的對象this,this引用的就是函數執行的環境對象,當在全局作用域中的時候,this指向的對象就是window;這裡getName函數的作用域是window,當我們直接調用該函數的時候,this指向的是window對象
  • Go有GC就不需要掌握內存堆棧知識了嗎?Go 堆棧的理解
    堆棧跟蹤下面討論堆棧跟蹤信息以及如何在堆棧中識別函數所傳遞的參數。Example 函數在第 9 行聲明,它有三個參數,一個字符串 slice,一個字符串和一個整數。, 0x425c0, 0x5, 0xa)上面列舉了 Example 函數的聲明,調用以及傳遞給它的值的信息。
  • 深入剖析 defer 原理篇 —— 函數調用的原理?
    地址空間函數棧幀棧幀的劃定函數調用函數返回舉個例子總結本篇文章是深入剖析 golang 的 defer 的基礎知識準備,如果要完全理解 defer ,避免踩坑,這個章節的基礎知識必不可少我們先複習一個最基礎的知識 —— 函數調用。這個對理解 defer 在函數裡的行為必不可少。那麼,當你看到一個函數調用的語句你能回憶起多少知識點呢?
  • 深入理解 Go 語言 defer
    defer 估計是每個 Gopher 每天寫代碼都會寫,那麼你是不是真正的理解了 defer 呢?不妨看一下下面這個代碼片段,這個是我之前給 UC 那邊一個 team 做 Golang 培訓的時候想的例子。
  • python入門第九課:函數的定義及多種參數傳遞方式
    5、函數名後括號中的book是函數的參數,可以用來傳遞數據,本例就是用來傳遞各種書籍名,這個參數可以是零個或者多個。6、print('我最喜歡的書是:'+book)是這個函數的唯一一行代碼,當然代碼可以有很多行,只需要在縮進體內就行。
  • Python使用ctypes模塊調用DLL函數之複數數組的參數傳遞
    在Python語言中,可以使用ctypes模塊調用其它如C++語言編寫的動態連結庫DLL文件中的函數,前面多篇文章中已經講了傳遞數值/指針/字符串參數、傳遞結構體參數、傳遞普通數組類型的例子,大家可以回看一下,這樣可以更好的理解本次要講的內容。
  • VBA代碼解決方案第124講:VBA程序執行過程中,參數是如何傳遞的
    大家好,我們今日繼續講解VBA代碼解決方案的第124講內容:VBA程序執行過程中,參數的傳遞形式。在代碼的編寫過程中,為了深入的理解代碼,有時候要對一些理論的知識必須要做必要的深入了解。我將在這節和下階內容中著重講解程序在運行時參數是如何傳遞的。理論的成分多些,但還是希望朋友們能好好掌握。
  • Python使用ctypes模塊調用DLL函數之傳遞結構體參數
    上篇已經講了傳遞數值、指針、字符串參數的例子,詳細細節請參考:這次講一下在Python中使用ctypes模塊調用DLL中的庫函數傳遞結構體參數的情況。同樣,作業系統環境是win7 64位,Python使用的版本是python2.7.14,函數約定的調用方式為C調用(cdecl)方式。
  • Go 中的函數是一等公民,這到底在說什麼?
    大意是說,在程式語言中,所謂一等公民,是指支持所有操作的實體, 這些操作通常包括作為參數傳遞,從函數返回,修改並分配給變量等。比如 int 類型,它支持作為參數傳遞,可以從函數返回,也可以賦值給變量,因此它是一等公民。
  • Python每天一分鐘:如何把元組/字典作為參數傳遞給函數(附代碼)
    學習程式語言的讀者對於函數參數的傳遞應該都不陌生,目前業內已經有多種高級語言允許定義個數可變的參數,這樣的好處是可以在調用函數時傳入任意多個參數。python可變參數當然,Python 也不例外,那麼如何在定義函數時支持使用可變參數呢?
  • python入門第十課:函數任意個參數傳遞、返回值,函數模塊化方法
    本教程使用的課本是《Python編程:從入門到實踐》,作者:[美] Eric Matthes本節繼續介紹函數的一些特性,包括返回值,任意個參數傳遞,列表作為函數參數等。一、函數的返回值函數的一個很重要的功能是有返回值,函數使用return語句將結果返回給主函數。
  • python使用ctypes模塊調用DLL函數之傳遞數值、指針與字符串參數
    由於DLL中函數中傳遞的參數類型比較多樣化,擬打算分三次講解這部分內容,這次先講傳遞數值、指針與字符串參數的情況,後面再分兩次講解傳遞結構體、數值數組等類型的情況。,其函數聲明如下:該函數有兩個int類型的輸入參數數x和y,返回的兩個數的和。
  • 函數的參數
    位置參數位置參數就是一個定義。雖然說這個定義目前我還是比較模糊,不太能理解,但是可以通過一個例子來例舉:❝def a(x):return x*x❞對於a(x)函數而言,參數x是位置參數。默認參數可以理解為,將函數的位置參數中的某一個設為默認值。如上,將n設為默認值2。❝def a(x,n=2):❞則此時print(a(3))時,不會報錯,而是輸出print(a(3,2))。最終輸出:9。
  • Python函數作為函數的參數來使用
    在前面討論閉包概念的時候,我們了解了函數名稱可以賦值給其它變量,被賦值的變量稱為函數的引用,當該變量被使用時,就會執行變量所引用的函數。上面的代碼定義了add函數,用於求兩數的和,需要傳入a和b兩個參數。
  • JS中的參數傳遞詳解
    JS語法中的傳遞參數,對於初學者是一個非常重要的概念。很多小夥伴在學習「值傳遞」和「引用傳遞」時,會有不少煩惱。今天我們就來通過各種姿勢全方位剖析JS中的值傳遞。 本文章將會用10分鐘時間無死角的解析JS的傳參方式,希望能對您有所幫助。
  • Python函數參數的使用方法
    在《初識Python函數編程與代碼的可復用性》一文中,我們已經了解了如何定義一個函數和調用函數。但對函數參數的傳遞沒有詳細討論,本文主要討論函數參數的傳遞。函數中的參數起到了傳遞數據的作用,函數調用者可以通過函數參數把函數內部需要的數據從外部傳遞過去。