Chinaunix首页 | 论坛 | 博客
  • 博客访问: 268421
  • 博文数量: 88
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 840
  • 用 户 组: 普通用户
  • 注册时间: 2014-04-20 21:13
文章分类

全部博文(88)

文章存档

2022年(1)

2017年(1)

2016年(2)

2015年(1)

2014年(83)

分类: Java

2014-04-20 22:40:41

C、C++和Java想必大部分人都认为自己或多或少懂得点。但要提起“最简单”的交换问题,恐怕有些人就不能那么快的答得很好了。
直接交换当然很容易。但是专门写一个函数做交换,在C、C++、Java中情况各自如何呢?

首先说一下结论,在C、C++中,可以通过传指针和传引用的方式来交换值。在java中,传值给函数参数只能是拷贝,所以用函数是不能交换值的。

先总结一下C/C++中指针传递和引用传递的问题

指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(这里是在说实参指针本身的地址值不会变)

 

而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址(int &a的形式)。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
 

引用传递和指针传递是不同的,虽然它们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将影响不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针,或者指针引用。

为了进一步加深大家对指针和引用的区别,下面我从编译的角度来阐述它们之间的区别:

程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。

最后,总结一下指针和引用的相同点和不同点:

★相同点:

●都是地址的概念;

指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。

★不同点:

●指针是一个实体,而引用仅是个别名;

●引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;

●引用没有const,指针有const,const的指针不可变;(具体指没有int& const a这种形式,而const int& a是有     的,  前者指引用本身即别名不可以改变,这是当然的,所以不需要这种形式,后者指引用所指的值不可以改变)

●引用不能为空,指针可以为空;

●“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;

●指针和引用的自增(++)运算意义不一样;

●引用是类型安全的,而指针不是 (引用比指针多了类型检查



一、引用的概念

引用引入了对象的一个同义词。定义引用的表示方法与定义指针相似,只是用&代替了*。
例如: Point pt1(10,10);
Point &pt2=pt1; 定义了pt2为pt1的引用。通过这样的定义,pt1和pt2表示同一对象。
需要特别强调的是引用并不产生对象的副本,仅仅是对象的同义词。因此,当下面的语句执行后:
pt1.offset(2,2);
pt1和pt2都具有(12,12)的值。
引用必须在定义时马上被初始化,因为它必须是某个东西的同义词。你不能先定义一个引用后才
初始化它。例如下面语句是非法的:
Point &pt3;
pt3=pt1;
那么既然引用只是某个东西的同义词,它有什么用途呢?
下面讨论引用的两个主要用途:作为函数参数以及从函数中返回左值。

二、引用参数

1、传递可变参数
传统的c中,函数在调用时参数是通过值来传递的,这就是说函数的参数不具备返回值的能力。
所以在传统的c中,如果需要函数的参数具有返回值的能力,往往是通过指针来实现的。比如,实现
两整数变量值交换的c程序如下:

点击(此处)折叠或打开

  1. void swapint(int *a,int *b)
  2. {
  3. int temp;
  4. temp=*a;
  5. a=*b;
  6. *b=temp;
  7. }

使用引用机制后,以上程序的c++版本为:

点击(此处)折叠或打开

  1. void swapint(int &a,int &b)
  2. {
  3. int temp;
  4. temp=a;
  5. a=b;
  6. b=temp;
  7. }

调用该函数的c++方法为:swapint(x,y); c++自动把x,y的地址作为参数传递给swapint函数。  

  无论你传值还是传指针,函数都会生成一个临时变量,  

  但传引用时,不会生成临时变量,  
   
  当你传值时,只可以引用值而不可以改变值,但传值引用时,可以改变值, 
  当你传指针时,只可以改变指针所指的内容,不可以改变指针本身,但传指针引用时,既可以改变指针所指的内容,又可以改变指针本身,  
   
  但传引用主要是它不生成临时变量,不进行返回值copy等,速度快。  

  数据结构的二叉排序树中有典型应用..

注:简单一点可以这么想,如果不用引用的话,被传递的参数本身是不能被修改的,  
  即使你传递的是指针,也只能修改指针指向的内容,不能修改指针本身。  
  如果要修改当前被传递的参数的话,要么再加一级指针,要么用引用。



在java中,参数传递是通过传值(对于对象类型,传递的是引用的拷贝),所以常规的交换实现不了两个对象的交换,下面是测试代码:
Java代码  

点击(此处)折叠或打开

  1. import org.apache.commons.beanutils.BeanUtils; 
  2. public class Employee {
  3.     // Properties
  4.     private int id;
  5.     private String name

  6.     // Constructors
  7.     public Employee() {
  8.     }
  9.        
  10.     public Employee(int id, String name) {
  11.         this.id = id;
  12.         this.name = name;
  13.     }
  14.        
  15.     // get、set methods
  16.     public int getId() {
  17.         return id;
  18.     }
  19.     
  20.     public void setId(int id) {
  21.         this.id = id;
  22.     }
  23.   
  24.     public String getName() {
  25.         return name;
  26.     }
  27.   
  28.     public void setName(String name) {
  29.         this.name = name;
  30.     }
  31.        
  32.     // 无效的交换
  33.     public static void swap(Employee x, Employee y) {
  34.         Employee temp = x;
  35.         x = y;
  36.         y = temp;
  37.     }
  38.        
  39.     // 使用 BeanUtils 方法的有效交换
  40.     public static void swapByBeanUtils(Employee x, Employee y) {
  41.         try {
  42.             // clone x to temp
  43.             Employee temp = (Employee)BeanUtils.cloneBean(x);
  44.             // copyProperties from y to x
  45.             BeanUtils.copyProperties(x, y);
  46.             // copyProperties from temp to y
  47.             BeanUtils.copyProperties(y, temp);
  48.             // then, the Properties values of x,y has been swaped.
  49.         } catch (Exception e) {
  50.             e.printStackTrace();
  51.         }
  52.     }
  53.        
  54.     // 有人提出使用交换数组等集合里的元素 来实现交换,这样也是无效的
  55.     public static void swapByArray(Object[] a, int i, int j) {
  56.         Object t = a[i];
  57.         a[i] = a[j];
  58.         a[j] = t;
  59.     }
  60.        
  61.     public static void main(String[] args) {
  62.         Employee a = new Employee(1, "Bob");
  63.         Employee b = new Employee(2, "Jack");
  64.            
  65.         // 直接方法调用交换 -- NO
  66. // swap(a, b);
  67.            
  68.         // 利用BeanUtils交换 -- YES
  69. // swapByBeanUtils(a, b);
  70.            
  71.         // 利用 交换数据 思想 -- NO
  72. // Object[] o = {a, b};
  73. // swapByArray(o, 0, 1);
  74.            
  75.         // 直接交换 -- YES
  76. // Employee temp = a;
  77. // a = b;
  78. // b = temp;
  79.            
  80.         System.out.println(a);
  81.         System.out.println(b);
  82.     }
  83.        
  84.     // method for print
  85.     public String toString() {
  86.         StringBuilder sb = new StringBuilder();
  87.         sb.append("id: ").append(id).append(" name: ").append(name);
  88.         return sb.toString();
  89.     }
  90. }
  91. import org.apache.commons.beanutils.BeanUtils;
  92. public class Employee {
  93. // Properties
  94. private int id;
  95. private String name;

  96. // Constructors
  97. public Employee() {
  98. }

  99. public Employee(int id, String name) {
  100. this.id = id;
  101. this.name = name;
  102. }

  103. // get、set methods
  104. public int getId() {
  105. return id;
  106. }
  107. public void setId(int id) {
  108. this.id = id;
  109. }
  110. public String getName() {
  111. return name;
  112. }
  113. public void setName(String name) {
  114. this.name = name;
  115. }

  116. // 无效的交换
  117. public static void swap(Employee x, Employee y) {
  118. Employee temp = x;
  119. x = y;
  120. y = temp;
  121. }

  122. // 使用 BeanUtils 方法的有效交换
  123. public static void swapByBeanUtils(Employee x, Employee y) {
  124. try {
  125. // clone x to temp
  126. Employee temp = (Employee)BeanUtils.cloneBean(x);
  127. // copyProperties from y to x
  128. BeanUtils.copyProperties(x, y);
  129. // copyProperties from temp to y
  130. BeanUtils.copyProperties(y, temp);
  131. // then, the Properties values of x,y has been swaped.
  132. } catch (Exception e) {
  133. e.printStackTrace();
  134. }
  135. }

  136. // 有人提出使用交换数组等集合里的元素 来实现交换,这样也是无效的
  137. public static void swapByArray(Object[] a, int i, int j) {
  138.    Object t = a[i];
  139.    a[i] = a[j];
  140.    a[j] = t;
  141. }

  142. public static void main(String[] args) {
  143. Employee a = new Employee(1, "Bob");
  144. Employee b = new Employee(2, "Jack");

  145. // 直接方法调用交换 -- NO
  146. //     swap(a, b);

  147. // 利用BeanUtils交换 -- YES
  148. //     swapByBeanUtils(a, b);

  149. // 利用 交换数据 思想 -- NO
  150. //     Object[] o = {a, b};
  151. //     swapByArray(o, 0, 1);

  152.       // 直接交换 -- YES
  153. //     Employee temp = a;
  154. //     a = b;
  155. //     b = temp;

  156. System.out.println(a);
  157. System.out.println(b);
  158. }

  159. // method for print
  160. public String toString() {
  161. StringBuilder sb = new StringBuilder();
  162. sb.append("id: ").append(id).append(" name: ").append(name);
  163. return sb.toString();
  164. }
  165. }
 2. 结论
在java中,对象引用是通过值来传递的,方法不能修改参数指向新的对象。
 
注:需加载 BeanUtils包


附加思考:java中,用函数交换两个对象可以交换吗?
传参数时,是按引用传递的,但传递的是引用的值有点拗口,大家品味下。结论是依然无法交换。参考:关于Java交换两个对象的问题

阅读(1110) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

7大爷2014-04-21 11:07:54

不错不错!