传值还是传引用—— java和C函数参数传递解读

晨曦之光 发布于 2012/03/09 12:11
阅读 2K+
收藏 0

关于Pass-By-refrence 和Pass-By-value 的争论始终活跃在论坛一线。 

今天就来揭露其中的来龙去脉, 给自己和初学者来个彻底剖析。

 

首先来看, 为什么有"值"和"引用"的提法.

 

什么是值(Value), 直接操作的东西, 就是值。如我有一个包裹, 不用通过中间手段, 我拿起来就可以操作。

相对于引用(refrence), 是指间接的东西。比如我有一个包裹的传单, 当然要通过这个传单上的包裹地址,才能找到包裹,进而去操作。

reference, 就是中介的意思,指引你去找到你需要的对象(值). reference除了帮你找到你需要的对象之外,没有其它作用。

 

在Java中, 没有“对象(值)”,之说,你看到的,都是对象的reference,并且都是在堆中创建(基本类型除外), 比如:

Object obj = new Object(); //创建一个对象(在堆中), 并将obj引用指向这个对象。

不像C++,除了上述方法之外,还可以这样创建:

Object obj(); //创建一个对象(在栈中), obj是一个实实在在的对象

因此,java中,明确表示,java中,只有引用的概念。

 

 

现在来看参数传递的原理:

 

定义一个函数 void fuction(A a)

这里A可以指代任何变量, 指针,值或者是其它。

 

调用时:

A b;

fuction(b);

发生了什么事情?

首先b将自己复制一份b1,并将b1作为形参,给fuction的入口值a.

所有的函数传递务必都是这样,但是,当形参如果reference和value的时候, 我们会有一些混淆:

1. 当为value传递的时候, 实参会复制一份, 比如用值的传了一个包裹到fuction中, 这个时候,包裹被复制, 所以包裹不是原来的包裹,此时,改写包裹内容, 对原来的包裹没有任何作用了!

 

2. 当为reference传递的时候,如前所述,此时传递的是带有包裹地址的传单, 这个时候传单复制了一份, 相当于复印件。 我们的操作,都是通过复印件传单的地址,去找到包裹,然后操作。复印件上的地址,和原来的地址,肯定是一样的,所以,我们找的,依然是原来的包裹对象,改写包裹,当然会对原来的包裹产生影响。

 

所以,函数传参, 都是值,引用值或者对象值 。如果想修改对象,传引用进去,这样方便找到对象然后去修改。

 

 

为什么要复制一份呢! 原因是每个函数是一个栈结构,栈空间管理变量资源,所以就有复制一说。

鉴于复制的原因,所以,传参数的时候,还是传引用比较好,大的对象会非常消耗栈空间的。

 

 

前些天论坛有个有趣的例子: 抄摘过来:

 

public static void main(String[] args)
    {
        StringBuffer  str1 = new StringBuffer("hello");
        test(str1);
        System.out.println("main  : " + str1);
    }
 public static void test(StringBuffer str)
    {
        StringBuffer tempStr = new StringBuffer(); 
        System.out.println("first : " + str);
        str = str.append(" world");
        System.out.println("second: " + str);
        str = tempStr;
        System.out.println("third : " + str);
    }

 

结果是这样的:

 

结果为:
first : hello
second: hello world
third :
main  : hello world

 

把我的回复粘贴一把:

 

Java codepublicstaticvoid main(String[] args)
    {
        StringBuffer  str1=new StringBuffer("hello");
        test(str1);
        System.out.println("main  :"+ str1);
    }
    //传入的是一个StringBuffer对象的指针(java中喜欢称为引用)
    //实际是: 将new StringBuffer("hello");的指针值,复制一份, 传递给str
    publicstaticvoid test(StringBuffer str) 
    {
        //到这里,注意,你拿的是new StringBuffer("hello")指针值的副本!叫做str
        StringBuffer tempStr=new StringBuffer();
        System.out.println("first :"+ str);
        //通过str指针,先原来的对象并改变, 所以,你能改变原对象的值
        str= str.append(" world");
        System.out.println("second:"+ str);
        //这里将副本改写,操作的不是对象,是副本的值,当然对原对象不起作用!
        str= tempStr;
        System.out.println("third :"+ str);
    }

 

举一个例子:

有人从邮局给你送了一个包裹(对象new StringBuffer("hello")), 然后通过一个凭据(StringBuffer  str1)写明包裹所在的邮局地址(包裹的引用地址),同时将这个凭据复印了一下(StringBuffer str),  , 让你操作一下(test函数),撕开包裹,取出邮件。

重点讲一下test函数,即你拿到凭据复印件str之后,怎么操作的,

你通过str到地址邮局中找到了包裹, 打开它,在里面塞了一些废纸,
然后,你自己又弄了一个新包裹, 在里面塞了一只烟灰缸。这个时候,你把地址的复印单改成了新包裹的地址。

 

于是最后结果: 你原来的包裹, 里面有废纸,

 

新的包裹,里面有烟灰缸;

 

但很糟糕的是, 你私自改了包裹的复印单(生活中,复印单一般是不允许更改的,更改就无效),把它改成你新包裹的地址,

 

但你没有返回这个改了的复印单,你把它遗忘了, 所以处理完包裹之后,带有烟灰缸的包裹地址,你找不到了!

 

最后,你只能拥有原来的包裹,因为原来的包裹地址(包裹的地址的原件)邮局还为你保留着呢!

 

顺便提及,这里如果想得到带有烟灰缸的新包裹, 给一个返回值就OK了; 但似乎这并不是一个好的解决方案。

 

为了防止改写复印件, c++传递指针的时候, 用了关键词Constant, java则用了final,

 

异曲同工.


原文链接:http://blog.csdn.net/ostrichmyself/article/details/4630976
加载中
0
_MrLiu
_MrLiu

描述的很生动!很风趣  懂了~

0
中山野鬼
中山野鬼
哈。。应该是C++吧。不是C。
返回顶部
顶部