<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>

          揭秘!值傳遞與引用傳遞,傳的到底是什么?

          共 3220字,需瀏覽 7分鐘

           ·

          2020-12-22 19:50

          在網(wǎng)上看到過(guò)很多討論 Java、C++、Python 是值傳遞還是引用傳遞這類文章。

          所以這一篇呢就是想從原理講明白關(guān)于函數(shù)參數(shù)傳遞的幾種形式。

          參數(shù)傳遞無(wú)外乎就是傳值(pass by value),傳引用(pass by reference)或者說(shuō)是傳指針。

          傳值還是傳引用可能在 Java、Python 這種語(yǔ)言中常常會(huì)困擾一些初學(xué)者,但是如果你有 C/C++背景的話,那這個(gè)理解起來(lái)就是 so easy。

          今天我就從 C 語(yǔ)言出發(fā),一次性把 Java、Python 這些都給大家講明白,誰(shuí)讓我是指北呢哈哈

          不過(guò)呀,要想徹底搞懂這個(gè),需要了解兩個(gè)背景知識(shí):

          • 堆、棧
          • 函數(shù)調(diào)用棧

          一、堆、棧

          要注意,這“堆”和“棧”并不是數(shù)據(jù)結(jié)構(gòu)意義上的堆(Heap,一個(gè)可看成完全二叉樹(shù)的數(shù)組對(duì)象)和 棧(Stack,先進(jìn)后出的線性結(jié)構(gòu))。

          這里說(shuō)的堆棧是指內(nèi)存的兩種組織形式,堆是指動(dòng)態(tài)分配內(nèi)存的一塊區(qū)域,一般由程序員手動(dòng)分配,比如 Java 中的 new、C/C++ 中的 malloc 等,都是將創(chuàng)建的對(duì)象或者內(nèi)存塊放置在堆區(qū)。

          而棧是則是由編譯器自動(dòng)分配釋放(大概就是你申明一個(gè)變量就分配一塊相應(yīng)大小的內(nèi)存),用于存放函數(shù)的參數(shù)值,局部變量等。

          就拿 Java 來(lái)說(shuō)吧,基本類型(int、double、long這種)是直接將存儲(chǔ)在棧上的,而引用類型(類)則是值存儲(chǔ)在堆上,棧上只存儲(chǔ)一個(gè)對(duì)對(duì)象的引用。

          舉個(gè)栗子:

          int?age?=?22;
          String?name?=?new?String("shuaibei");

          這兩個(gè)變量存儲(chǔ)圖如下:

          如果,我們分別對(duì)agename變量賦值,會(huì)發(fā)生什么呢?

          age?=?18;
          name?=?new?String("xiaobei");

          如下圖:

          age 僅僅是將棧上的值修改為 18,而 name 由于是 String 引用類型,所以會(huì)重新創(chuàng)建一個(gè) String 對(duì)象,并且修改 name,讓其指向新的堆對(duì)象。(細(xì)心的話,你會(huì)發(fā)現(xiàn),圖中 name 執(zhí)行的地址我做了修改)

          然后,之前那個(gè)對(duì)象如果沒(méi)有其它變量引用的話,就會(huì)被垃圾回收器回收掉。

          這里也要注意一點(diǎn),我創(chuàng)建 String 的時(shí)候,使用的是 new,如果直接采用字符串賦值,比如:

          String?name?=?"shuaibei"

          那么是會(huì)放到 JVM 的常量池去,不會(huì)被回收掉,這是字符串兩種創(chuàng)建對(duì)象的區(qū)別,不過(guò)這里我們不關(guān)注。

          Java 中引用這東西,和 C/C++ 的指針就是一模一樣的嘛,只不過(guò) Java 做了語(yǔ)義層包裝和一些限制,讓你覺(jué)得這是一個(gè)引用,實(shí)際上就是指針。

          好,讓我繼續(xù)了解下函數(shù)調(diào)用棧。

          二、函數(shù)調(diào)用棧

          一個(gè)函數(shù)需要在內(nèi)存上存儲(chǔ)哪些信息呢?

          參數(shù)、局部變量,理論上這兩個(gè)就夠了,但是當(dāng)多個(gè)函數(shù)相互調(diào)用的時(shí)候,就還需要機(jī)制來(lái)保證它們順利的返回和恢復(fù)主調(diào)函數(shù)的棧結(jié)構(gòu)信息。

          那這部分就包括返回地址、ebp寄存器(基址指針寄存器,指向當(dāng)前堆棧底部) 以及其它需要保存的寄存器。

          所以一個(gè)完整的函數(shù)調(diào)用棧大概長(zhǎng)得像下面這個(gè)樣子:

          那,多個(gè)函數(shù)調(diào)用的時(shí)候呢?

          簡(jiǎn)單來(lái)說(shuō)就是疊羅漢,這是兩個(gè)函數(shù)棧:

          今天,我們不會(huì)去詳細(xì)了解函數(shù)調(diào)用過(guò)程ebpebp如何變化,返回地址又是如何起作用的。

          今天的任務(wù)就是搞明白參數(shù)傳遞,所以其它的都是非主線的知識(shí),忽略即可。

          順便插點(diǎn)題外話:

          學(xué)習(xí)新知識(shí)有時(shí)候需要刨根問(wèn)底,有時(shí)候卻需要及時(shí)回頭,尤其是計(jì)算機(jī),你要是一直刨根問(wèn)題,我能給你整到硅的提純?nèi)ィ@就是失去了學(xué)習(xí)的意義。

          最好的方式是,在一個(gè)恰到好處的地方建立一個(gè)抽象層,并且認(rèn)可這個(gè)抽象層提供的功能/接口,不去探究這一層下面是什么,怎么實(shí)現(xiàn)的。

          比如,學(xué)習(xí) HTTP,我就只需要認(rèn) TCP 提供穩(wěn)定、可靠傳輸就夠了,暫時(shí)就不需要去看 TCP 如何做到的。

          好了,繼續(xù)說(shuō)回函數(shù)傳參,舉個(gè)例子,下面這段代碼在main函數(shù)內(nèi)調(diào)用了func_a函數(shù)

          int?func_a(int?a,?int?*b)?{
          ?a?=?5;
          ?*b?=?5;
          };

          int?main(void)?{
          ?int?a?=?10;
          ??int?b?=?10;
          ??func_a(a,?&b);
          ??printf("a=%d,?b=%d\n",?a,?b);
          ??return?0;
          }

          //?輸出
          a=10,?b=5

          那么func_a(a, &b) 這個(gè)過(guò)程,在函數(shù)調(diào)用棧上究竟是怎么樣的呢?

          就像上圖所示,編譯器會(huì)生成一段函數(shù)調(diào)用代碼。

          main 函數(shù)內(nèi)變量 a 的值拷貝到 func_a 函數(shù)參數(shù) a 位置。

          將變量 b的地址,拷貝到 func_a 函數(shù)參數(shù) b 的位置。

          記住這張圖,這是函數(shù)參數(shù)傳遞的本質(zhì),沒(méi)有其它方式,just copy!

          copy 意味著是副本,也就是在子函數(shù)的參數(shù)永遠(yuǎn)是主調(diào)函數(shù)內(nèi)的副本。

          決定是值傳遞還是所謂的引用傳遞,在于你 copy 的到底是一個(gè)值,還是一個(gè)引用(的值)。

          其實(shí)引用也是值......不要覺(jué)得引用就是那種玄乎的東西。

          所以會(huì)有一種聲音說(shuō),是不存在所謂的引用傳遞的,一切傳引用的本質(zhì)還是傳值。

          也就是 pass pointer by value 或者 pass reference by value,哈哈哈有點(diǎn)意思。

          今天,我們不討論到底有沒(méi)有傳引用這個(gè)東西,這是一個(gè)個(gè)仁者見(jiàn)仁智者見(jiàn)智的問(wèn)題。

          我的目的呢,就是把參數(shù)傳遞這個(gè)過(guò)程給大家剖析下,至于到底是傳值還是傳引用,那就看大家怎么思考了。

          三、pass by value in java

          舉個(gè)最簡(jiǎn)單的例子來(lái)說(shuō)明下:

          public?class?HelloWorld?{
          ??
          ????public?static?void?ChangeRef(String?name)?{
          ????????name?=?new?String("xiaobei");
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????String?name?=?new?String("shuaibei");
          ???????ChangeRef(name);
          ????????System.out.println(name.equals("shuaibei"));

          ????}
          }

          上面,ChangeRef 函數(shù)實(shí)際上并沒(méi)有改變到 main 函數(shù)內(nèi)的 name 對(duì)象,看圖就明白了:

          根據(jù)我們前面所講,參數(shù)傳遞實(shí)際就是復(fù)制棧上的值本身,這里name的值就一串地址,所以ChangeRef接收到的也是這串地址,但是在ChangeRef函數(shù)內(nèi)將name的指向改成了一個(gè)新的 String 對(duì)象,但是這里不會(huì)對(duì)main函數(shù)中的 name 對(duì)象產(chǎn)生任何的影響。

          咦,不是說(shuō)引用類型都是引用傳遞嗎?為什么還不會(huì)對(duì)主調(diào)函數(shù)產(chǎn)生影響呢?

          我們都把引用的指向改變了,還能影響個(gè)啥,如果想通過(guò)引用傳遞修改外部傳進(jìn)來(lái)的值,一般是采用成員方法。

          假設(shè) String 類有一個(gè)方法叫做changeStr(String value),那么我們就可以在ChangeRef內(nèi)調(diào)用這個(gè)方法,修改name的值,

          并且會(huì)同步修改到main函數(shù)里的值。

          (其實(shí)這里最好的說(shuō)明方式是自己定義一個(gè)類,但是我懶了,就省掉了哈哈哈,相信聰明的你一定知道我在說(shuō)什么~)

          四、Python

          其實(shí)和Java 挺像的,但是 Python 有個(gè)特點(diǎn)就是所有變量本身只是一個(gè)引用,真正的類型信息都是和對(duì)象存儲(chǔ)在一起的。

          所以我打算后面單獨(dú)聊聊 Python 對(duì)象這個(gè)話題,然后把參數(shù)傳遞也放在那里了,今天就到這吧~





          0、離開(kāi)一線城市的程序員們后悔了嗎?這里有一些真實(shí)的打工人體驗(yàn)

          1、程序員小躍:三十而立,和大家一起「乘風(fēng)破浪」

          瀏覽 75
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  中文字幕无码综合 | 夜夜爽夜夜高潮夜夜爽 | 麻豆三级| 欧美乱伦视频小说 | 青青草免费在线观看 |