java方法中,傳參是傳值還是傳址問題,java
問題引出:
編寫一個簡單的交換值的小程式,如果我們只是簡單地定義一個交換函數接收兩個數,在函數內部定義一個中間變數完成交換。那麼當我們把a,b兩個實參傳給這個函數時,往往得不到預期的結果。這是為什麼呢?
在C語言和C#中:
在C語言中,如果我們運行下列代碼:
#include<stdio.h> void swap(int,int); void main(){ int a=5; int b=7; printf("a=%d b=%d\n",a,b);//a=5 b=7 swap(a,b); printf("a=%d b=%d\n",a,b);//a=5 b=7 } void swap(int c,int d){ int temp; temp=c; c=d; d=temp; }
以及在C#中運行下列代碼並試著傳進去a,b:
static void swap(int c, int d) //這個函數將以傳值的方式運行 { int temp; temp=c; c=d; d=temp; }
得到的結果都不是我們預期的那樣實現a和b的數值交換。
這就是因為在C和C#中存在傳值和傳址兩個不同的概念。
以上代碼實現的是“傳值”的概念。也就是說,在程式運行時,實參將自己的值複製給形參,而形參也是分配記憶體空間的。因此在這個值傳遞的過程過後,實參和形參實際上就指向了兩個不同的儲存空間,不存在關聯關係了。在我們所謂的執行交換值的函數中,交換的實際上是形參而已(不是a,b而是c,d),並且形參作為局部變數的生命週期也就是只在函數內部,函數運行結束,形參也就不複存在了。故代碼達不到我們交換值的目的。
對於這個問題的解決,C語言和C#分別提供的解決方案之一就是址傳遞
在C語言中,我們可以很簡單地利用別名就可以完成兩個數的交換:
#include<stdio.h> void swap(int &,int &); void main(){ int a=5; int b=7; printf("a=%d b=%d\n",a,b);//a=5 b=7 swap(a,b); printf("a=%d b=%d\n",a,b);//a=7 b=5 } void swap(int &a,int &b){ //此處利用取地址符,傳入地址
int temp; temp=a; a=b; b=temp; }
在C#中,提供了專門的關鍵字ref可以表示是傳值還是傳地址。
static void swap(ref int i, ref int t) // 這個函數將會以傳址的方式進行運算. { int temp; temp=a; a=b; b=temp; }
以上兩段代碼實現的就是傳址的方式。可以實現我們的預期代碼實現功能。
在C語言中還可以使用操作指標的方式(但是屬於值傳遞)達到預期
#include<stdio.h> void swap(int *,int *); void main(){ inta=5; intb=7; printf("a=%d b=%d\n",a,b);//a=5 b=7 swap(&a,&b);//此處傳出a,b的地址,利用取地址符 printf("a=%d b=%d\n",a,b);//a=7 b=5 } void swap(int *a,int *b){//傳入地址,就可以完成兩個說 inttemp; temp=*a; *a=*b; *b=temp; }
無論是C語言中的別名,還是C#中的關鍵字ref。傳遞的都是實參的地址,也就是說程式運行時,實參將自己的地址傳遞給函數。這時的實參和形參所指向的地址是一致的,也就是說是一個記憶體空間上的對象,那麼在函數運行期間對形參的操作也就順理成章的改變了實參的值。
那麼對於java呢?
先貼個例子:
package com.test;public class Test1 { public static void Reverse(int R[],int l,int r){ int i,j; int temp; for(i=l,j=r;i<j;++i,--j){ temp=R[i]; R[i]=R[j]; R[j]=temp; } } public static void Add(int a,int b){ a=a+100; b=b+1000; } public static void main(String[] args) { int[] array=new int[]{1,2,3,4,5,6}; Reverse(array,0,5); for(int i=0;i<array.length;++i) System.out.print(array[i]);
System.out.println(); System.out.println("——————————————"); int i=1,j=2; Add(i,j); System.out.println(i+"####"+j); }}
輸出結果:
654321——————————————1####2
由於Java中程式員無法直接操作指標,對於傳值還是傳地址很模糊,但我們知道java中變數分為兩類,一類是基本變數,一類是引用。(數組屬於引用)
其實當我們把實參a,b傳進方法後,就相當於把a,b的值分別傳給了方法用於接收a,b的局部變數,那麼不管是傳進去引用還是值,實參a,b的值都不會被改變,因為操作的時函數中接收a,b傳遞的值的局部變數,函數執行完畢,這兩個局部變數也就不存在了。
那為什麼傳進去引用可以修改a,b的值呢,這是不違反上面說的原理了嗎?
其實,Java中無論傳值還是傳引用都是傳的參數的副本,即將實參進行複製。副本只在函數內部有效。而當傳引用時,傳進去的是實參自己的地址的複製版,地址被複製了,但是指向的值的指向沒變(即對於“地址”這個東西有實參和形參不指向同一儲存空間這回事,但是對於“地址指向的值”只有一個),同時地址無法被改變但是地址指向的值可以被改變。所以可以說java中都是在傳值。
在C++中:
在C++中,允許操作指標。也存在著明確的值傳遞和址傳遞。(其實下面兩條在概念上對於所有這幾種語言來說是普適的)
值傳遞(按值傳遞): 1.實參值傳遞給相應形參 2.實參地址傳遞給相應形參 比如:數組、指標。(類似Java)
址傳遞(引用傳遞): 使用別名,共用儲存空間(直接存取) 形參為引用參數時,才為按址傳遞,此時對應實參一般為一個變數。
強調:除了使用別名外,即使是使用指標也是值傳遞。即在函數的形參列表中,只要存在形參儲存空間的開闢就屬於值傳遞。
貼兩個例子:
例1:
#include<iostream>using namespace std;void Reverse(int R[],int l,int r){ int i,j; int temp; for(i=l,j=r;i<j;++i,--j){ temp=R[i]; R[i]=R[j]; R[j]=temp; }}void Add(int c,int d){c=c+100;d=d+1000;}int main(){ int array[]={1,2,3,4,5,6}; Reverse(array,0,5); for(int i=0;i<6;++i){ cout<<array[i]; } cout<<endl; int a=1,b=2; Add(a,b); cout<<a<<","<<b; cout<<endl; return 0;}
運行結果:
例2(代碼來自於參考部落格):
#include<iostream> using namespace std; void mySwap(int *p1,int *p2); int main(){ int a=12; int b=44; int *pa=&a; int *pb=&b; if(a<b){ mySwap1(a,b); //mySwap2(pa,pb); //mySwap3(pa,pb); //mySwap4(a,b); } return 0; } /* int類型作為形參--值傳遞:形參a ,b 也要分配記憶體空間,實參的值複製給形參 */ void mySwap1(int a,int b){ int temp; temp=a; a=b; b=temp; } /* int * 類型作為形參--值傳遞:形參p1,p2 也要分配記憶體空間,實參的值複製給形參(地址) */ void mySwap2(int *p1,int *p2){ //改變形參指標的指向,不會影響到實參 int *temp; temp=p1; p1=p2; p2=temp; } /* int * 類型作為形參--值傳遞:形參p1,p2 也要分配記憶體空間,實參的值複製給形參(地址) */ void mySwap3(int *p1,int *p2){ //改變形參指標指向的記憶體空間值,也就改變了實參指標指向的記憶體空間值 int temp2; temp2=*p1; *p1=*p2; *p2=temp2; } /* 參考型別作為形參--址傳遞:形參a,b是不分配記憶體空間的,形參是實參的“別名” */ void mySwap4(int &a,int &b){ int temp; temp=a; a=b; b=temp;
總結一下:
C語言、C#、C++中有明確的傳值和傳址方式。程式員可以主動區分。
在C語言和C++中,除了使用別名外,其他都屬於值傳遞。
在Java中,由於不能直接操作指標,只存在值傳遞,但是當傳遞的值是引用時,那麼方法中得到的是引用的地址的複製版,地址被複製了,但是指向的值的指向沒變(即對於“地址”這個東西有實參和形參不指向同一儲存空間這回事,但是對於“地址指向的值”只有一個),同時地址無法被改變但是地址指向的值可以被改變。所以可以說java中都是在傳值。
那麼,回到最初的問題上面來,我們要怎麼做才能實現兩個數值的交換呢?
小編的答案是操作地址,無論是值傳遞還是址傳遞,只要操作的是地址,那麼形參和實參指向的就是同一個儲存空間,也就是說形參的改變可以帶來實參的改變。
那麼,請各位想一想怎樣實現Java中基本變數的數值交換呢?小編還沒想好,歡迎大家賜教。
參考部落格:
http://blog.csdn.net/single0515/article/details/51584536
http://blog.csdn.net/cxc19890214/article/details/44617685
http://blog.csdn.net/u010126792/article/details/63771475