C#中的函數(三)參數傳遞及返回值


接前面二篇,繼續開始新的研究

 

前面忘了說什么是主調函數與被調函數

主調函數:執行調用其它函數語句所在的函數

被調函數:被其它函數所調用的函數

簡單說就是一個是發起調用者,另一個是被調用者

寫個小例子說明下,一看就懂

Main函數就是主調函數,test_A()這句語句所在的函數就是主調函數

tset_A就是被調函數, 它是被主調函數Main中的語句test_A()進行調用的

 重歸正題

參數傳遞分為2類

1.普通傳遞(形參數據類型前面沒有ref或者out關鍵字,傳遞的是變量中的數據)

2.引用傳遞(形參數據類型前面加上ref或者out關鍵字,傳遞的是變量在棧中的地址)

 

普通傳遞根據參數的數據類型分為普通傳遞值類型跟普通傳遞引用類型

普通傳遞值類型

小例子如下:

主調函數Main部份

1.定義二個變量a與b

2.主調函數里調用MyAdd,把變量a與b當作實參進行傳遞

被調函數MyAdd部份

1.把參數a與參數b進行相加,保存在臨時變量c中

2.返回前對a與b進行修改,然后返回變量c中的二個數相加的結果

最終返回到Main函數中,變量a與b的值沒有發生改變

總結下:

普通傳遞值類型,傳遞實參后被調用函數內部對它進行修改不會影響到主調函數中的變量

因為變量是值類型,變量中的數據是數值

普通傳遞引用類型

小例子如下:

主調函數Main部份

1.主調函數main中先實例化一個自定義的類TestClass,然后保存在引用類型變量temp中

2.調用被調函數test_A, 把變量temp當作實參進行傳遞

被調函數tset_A部份

1.把參數TestClass類型的對象temp中的MyName屬性修改為大白腿

最終返回到Main函數中,引用類型變量temp中的Myname字段發生改變

總結下:

普通傳遞引用類型,傳遞實參后被調用函數內部對它進行修改會影響到主調函數中的變量

因為變量是引用類型,變量中的數據是引用地址,引用地址是對象在托管堆中的內存地址

當外部對引用類型變量進行修改時,相當於直接對托管堆中的數據進行了修改.

 

引用傳遞(形參數據類型前面加上ref或者out關鍵字,傳遞的是變量在棧中的地址)

引用傳遞的主要目的是對參數進行修改,然后讓外部數據進行同步更新,返回多個數據的作用

寫到這估計又有人有疑問了,直接在函數前面定義返回值用來接收返回值不行嗎?

一來麻煩,因為如果需要對返回值進行接收,同步更新變量,那接收返回值還得進行賦值操作

二來返回值只能返回一個數據,如果函數有多個形參,需要對多個參數進修改,讓外部數據進行同步更新,

起到返回多個數據的目的,那就只能使用引用傳遞的方式。

引用傳遞根據參數的數據類型分為引用傳遞值類型跟引用傳遞引用類型

根據前面說的普通傳遞,那引用類型的變量需要引用傳遞嗎?大多數情況下不需要,因為前面說過,

引用類型的實參,傳遞的是引用地址,其它地方對它進行修改,會直接對堆中數據進行更改

真正常用的是多個值類型的實參,才需要使用引用傳遞的方式

 

寫了一大堆引用傳遞,還是寫個值類型的引用傳遞來看下效果

引用傳遞值類型 (out)

小例子如下:

主調函數Main部份

1.定義四個變量a,b,addNum,maxNum

2.調用Test函數,前二個實參a,b只是用來計算的,並不需要返回數據或者修改數據,所以不使用引用傳遞.

后二個參數是用來接收返回的二個數相加的結果,所以需要使用引用傳遞參數,這里使用的是out方式

被調函數

1. 對二個數進行相加,然后根據傳遞過來的addNum在主函數main中的變量地址,對這個地址寫入新的數據

2. 對二個數進行比較,然后根據傳遞過來的maxNum在主函數main中的變量地址,對這個地址寫入新的數據

從這里就能看出使用引用傳遞的好處了

 

順帶驗證Test函數中addNum下到底是不是主函數main中的變量地址呢?

在Test中的addNum = a + b; 下個斷點,添加臨視,看下addNum中是什么

從圖中看,addNum是值啊,不是地址…估計到這有人迷茫了.

其實這是vs特意隱藏了內部的細節,這里的值是是addNum在主函數main中的變量地址下的值,

並不是真正addNum下的數據…

為了證明這點,在主調函數的Test(a,b, out addNum, out maxNum); 這一行再下個斷點

F5運行調試,此時斷下來,我們轉到反匯編,好好分析下它是怎么傳遞參數的

這七行反匯編代碼對應的就是Test(a,b, out addNum, out maxNum);

第一句Lea eax,[ebp - 48h]    //ebp-48h 是變量addNum在棧中的地址,Lea是傳地址操作,相當於mov eax,ebp-0x48

第二句push eax   //看過前二篇的就知道這是因為參數多於2個,使用壓棧的方式傳遞參數,這里push的是addNum在棧中的地址

第三句Lea  eax,[ebp - 4ch]   //ebp-4ch 是變量maxNum在棧中的地址,Lea是傳地址操作,相當於mov eax,ebp-0x4c

第四句push eax //看過前二篇的就知道這是因為參數多於2個,使用壓棧的方式傳遞參數,這里push的是maxNum在棧中的地址

第五句mov ecx,dowrd ptr [ebp-40h] //這是傳遞的臨時變量a

第六句mov edx,dowrd ptr [ebp-44h] //這是傳遞的臨時變量b

第七句 call 00360c30   //通過間接調用的方式調用Test函數

這里可以單步執行,紀錄下ebp-0x48 與ebp-0x4c的值

addNum = 071CE950
maxNum = 071CE94C

進入到Test函數內部, 先單步執行完前二句,查看下堆棧中的情況

[Ebp] = 0x071CE928 = 當前ESP棧頂

[EBP + 4] = 執行完當前函數后的返回地址

[EBP + 8 ] = 傳遞過來的第四個實參maxNum

[EBP + 0xC] = 傳遞過來的第三個實參addNum

這里的參數順序是因為主調函數中是從左向右往堆棧中壓入參數三跟參數四,但是在堆棧中棧的增長是從內存地址的高位往

內存地址的低位進行增長的,每壓入一個數據,esp - 4開辟四個字節空間,然后esp指針指向esp-4的位置

后面的一些不需要關心了,轉回源代碼,直接在addNum = a + b; 這一行下個斷點

 

F5運行調試,轉到反匯編繼續看

這四行反匯編代碼對應的就是addNum = a + b;

第一行mov eax,dword ptr [ebp - 3ch]; //取得第一個實參a,賦值給寄存器eax

第二行add eax,dowrd ptr [ebp - 40h]; //取得第二個實參b,把寄存器eax與b進行相加,再賦值給eax

第三行mov edx,word ptr[ebp + 0ch]; //取得堆棧中的addNum的變量地址

第四行mov dword ptr [edx],eax  //把二個數相加的結果寫入到addNum的變量地址下

 

其實這里的引用傳遞的實參就是C++中的一個指針變量,不過C#沒有指針這玩意就沒法講

確定了引用傳遞參數是傳遞地址,繼續回來,使用ref來寫個小例子

 

引用傳遞值類型 (ref)

小例子如下:

 

總結下:

ref跟out都是引用傳遞參數, 傳遞的是實參在主調函數的變量地址,在被調函數中參數是個指針變量,

參數指向了主調函數中的變量地址, 對參數進行操作,

實際就是對指針所指向的主調函數中的變量地址進行了讀取或者寫入數據

ref 跟 out的區別

ref 傳遞的實參在主調函數部份必須對它進行賦值,調用函數時實參前面加上ref關鍵字

在被調函數部份可以直接使用實參,不需要在被調函數里進行賦值

out 傳遞的實參在主調函數部份不需要對它進行賦值,調用函數時實參前面上out關鍵字

在被調函數部份不能直接使用實參,需要在被調函數里先賦值,才能進行訪問

 

還有更復雜的引用傳遞引用類型,很少能用到,多余了,大多還是面試題會問.

 


免責聲明!

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



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