***回答
一、所有程序设计语言都有关于过程的概念,但是在这些过程如何获取它们的参数方面,不同的语言之间有所不同。使用哪一种传递机制决定了调用代码序列如何处理参数。下面是就我个人的理解对四种参数传递机制的简单分析与比较。
1)值调用/按值传递
2)引用调用/按址传递
3)名调用/换名调用
4)复制恢复
1>值调用/按值传递(call by value):
值调用中,会对实在参数求值(如果是表达式)或者拷贝(如果它是变量)。这些值被放在属于被调用过程的相应形式参数的内存位置上。效果是,被调用过程所做的所有有关形式参数(在过程定义中使用的参数)的计算都局限于这个过程,相应的实在参数(在调用过程时使用的参数)本身不会被改变。
在按传值调用时,过程的形式参数取得的是实际参数的值。在这种情况下,形式参数实际上是过程中的局部变量,其值的改变不会导致调用点所传送实际参数的值发生改变,也就是说数据的传送是单向的。
java:按值传递在每次传递参数时,把参数的原始数据拷贝一份,把新拷贝出来的数值传递到方法内部,在方法内部修改时,修改的是拷贝出来的值,而原始值不发生改变。
使用这种传递方式,参数的原始值不发生改变。
2>引用调用/按址传递(call by address)
在引用调用中,实在参数的地址作为相应的形式参数的值被传递给被调用者。在被调用者的代码中使用形式参数时,实现方法是沿着这个指针找到调用者指明的内存位置。因此,改变心事参数看起来就像是改变了实在参数一样。
在引用调用时,过程的形式参数取得的是实际参数所在单元的地址。在过程中,对该形式参数的引用相当于对实际参数所在的存储单元的地址引用。任何改变形式参数值得操作会反映在该存储单元中,也就是反映在该存储单元中,也就是反映在实际参数中,因此,数据的传送是双向的。
java:按址传递指每次传递参数时,把参数在内存中的存储地址传递到方法内部,在方法内部通过存储地址改变对应存储区域的内容。由于内存中固定地址的值只有一个,所以当方法内部修改了参数的值之后,参数原始的值也会发生变化。
使用这种传递方式,在方法内部修改参数的同时,参数原始的值也发生改变。
3>名调用/换名调用
要求被调用者的运行方式好像是用实在参数以字面方式替换了被调用者代码中的形式参数一样。这么做就好像形式参数是一个代表了实在参数的宏。当实在参数是一个表达式而不是一个变量时,会发生一些和直觉不符的问题。
严格讲,名调用并不能算作真正的过程调用和参数传递。名调用被早期的程序设计语言Algol60使用(我第一次听说)。这种调用方式告诉你,眼见未必为实!
4>复制恢复
该机制下,参数在被子程序调用时只是对实在参数的拷贝进行操作,实在参数不发生改变,这是复制后操作。子程序操作完成后进行恢复,形式参数的值恢复到实在参数,即将实在参数的结果改为形式参数的最终值。
下面是一个简单的例子:
主程序: A:=2;B:=3;
P(A+B,A,A);
PRINT A;
子程序:P(x,y,z){
y=y+1;
Z=x+5;
}
四种传递机制的结果:
1>值传递
主:
A
2
B
3
A+B
5
子:
x
5
y
3
z
7
<!--EndFragment-->
2>址传递
主:
A
8
B
3
A+B
5
子:
x
(A+B)
y
A
z
A
3>复制恢复
主:
A
7
B
3
A+B
5
子:
x
5
y
3
z
7
4>名
主:
A
11
B
3
A+B
5
子:
A=A+1=3;
A=A+B+5
二、下面着重对比一下java中的参数传递机制:
java中,参数传递一般有两种方法:按值传递和按址传递。每种数据类型的传递方式都是固定的。
1、按值传递的数据类型有:八种基本数据类型和String(事实上,String是按址传递的,但string对象和其他对象不同的是,string对象是不能被改变的,内容改变就会产生新的对象)
2、按址传递的数据类型:除String以外的所有复合数据类型,包括数组类和接口。
下面是一个简单的java示例:
Java代码
public class Test0 {
/**参数传递机制测试
* @param args
*/
public static void main(String[] args) {
int m=10;
int a[]={100,200,300};
System.out.println("调用前: m:"+m+" a[1]:"+a[1]);
test(m,a);
System.out.println("调用后: m:"+m+" a[1]:"+a[1]);
}
private static void test(int m, int[] a) {
m=20;
a[1]=2000;
}
}
凡事无绝对,参数传递机制也并非呆板固定的,下面两个有趣的例子说明这一点:
Java代码
public class Test1 {
/**参数传递测试
* @param args
*/
public static void main(String[] args) {
//1、按值传递的类型通过返回值修改参数的值
int m=10;
System.out.println("m修改前:"+m);
//通过修改以后的参数n(test1的形参)的返回值,来为变量m赋值,从而达到修正参数的目的。
m=test1(m);//手动赋值
System.out.println("m修改后:"+m);
//2、按址传递通过重新生成变量避免修改参数的值:
int a[]={1,2,3};
System.out.println("a[1]修改前:"+a[1]);
//通过在方法内部新创建一个数组,并且把传入数组的每个参数的值都付给新创建的数组,
//从而实现复制数组内容,然后在修改复制后数组中的值时,原来的参数内容就不发生改变了。
test2(a);
System.out.println("a[1]修改后:"+a[1]);
}
private static int test1(int n) {
n=20;
return n;
}
private static void test2(int[] m) {
int n[]=new int[m.length];
for(int i=0;i<m.length;i++){
n[i]=m[i];
}
n[1]=100;
}
}