问题引出:


编写一个简单的交换值的小程序,如果我们只是简单地定义一个交换函数接收两个数,在函数内部定义一个中间变量完成交换。那么当我们把a,b两个实参传给这个函数时,往往得不到预期的结果。这是为什么呢?

在C语言和C#中:


在C语言中,如果我们运行下列代码:

#include<stdio.h>  

void swap(int,int);  

void main(){  

  int a=;  

  int b=;  

 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=;  

  int b=;  

 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=;  

  intb=;  

 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+;
d=d+;
}
int main(){ int array[]={,,,,,};
Reverse(array,,);
for(int i=;i<;++i){
cout<<array[i];
}
cout<<endl;
int a=,b=;
Add(a,b);
cout<<a<<","<<b;
cout<<endl;
return ;
}

运行结果:

例2(代码来自于参考博客):

#include<iostream>
using namespace std;
void mySwap(int *p1,int *p2);
int main(){ int a=;
int b=; int *pa=&a;
int *pb=&b; if(a<b){ mySwap1(a,b);
//mySwap2(pa,pb);
//mySwap3(pa,pb);
//mySwap4(a,b);
} return ;
} /*
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

java方法中,传参是传值还是传址问题(对比C语言、C#和C++)的更多相关文章

  1. python中给函数传参是传值还是传引用

    首先还是应该科普下函数参数传递机制,传值和传引用是什么意思? 函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题.基本的参数传递机制有两种:值传递和引用传 ...

  2. Python中的传参是传值还是传址?

    传值:在C++中,传值就是把一个参数的值给这个函数,其中的更改不会影响原来的值. 传址:即传引用,直接把这个参数的内存地址传递进去,直接去这个内存地址上进行修改. 但是这些在Python中都没有,Py ...

  3. python函数传参是传值还是传引用?

    首先还是应该科普下函数参数传递机制,传值和传引用是什么意思? 函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题.基本的参数传递机制有两种:值传递和引用传 ...

  4. python传参是传值还是传引用

    在此之前先来看看变量和对象的关系:Python 中一切皆为对象,数字是对象,列表是对象,函数也是对象,任何东西都是对象.而变量是对象的一个引用(又称为名字或者标签),对象的操作都是通过引用来完成的.例 ...

  5. 065 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 带参无返回值方法

    065 01 Android 零基础入门 01 Java基础语法 08 Java方法 03 带参无返回值方法 本文知识点:带参无返回值方法 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...

  6. 066 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 带参有返回值方法

    066 01 Android 零基础入门 01 Java基础语法 08 Java方法 04 带参有返回值方法 本文知识点:带参有返回值方法 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...

  7. 064 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 无参带返回值方法

    064 01 Android 零基础入门 01 Java基础语法 08 Java方法 02 无参带返回值方法 本文知识点:无参带返回值方法 说明:因为时间紧张,本人写博客过程中只是对知识点的关键步骤进 ...

  8. 063 01 Android 零基础入门 01 Java基础语法 08 Java方法 01 无参无返回值方法

    063 01 Android 零基础入门 01 Java基础语法 08 Java方法 01 无参无返回值方法 本文知识点:无参无返回值方法 无参无返回值方法 案例 为什么使用方法?--方便复杂问题调用 ...

  9. java 对象的this使用 java方法中参数传递特性 方法的递归

    一.this关键字,使用的情形,以及如何使用. 1.使用的情形 类中的方法体中使用this  --初始化该对象 类的构造器中使用this --引用,调用该方法的对象 2.不写this,调用 只要方法或 ...

随机推荐

  1. ubuntu双网卡配置,实现内网外网同时访问!

    我们假定内网IP为:10.35.0.58,内网网关为:10.35.0.254:外网IP为222.76.250.4,外网网关为:222.76.250.1.其中局域名网需要连接:10.35.0.X,10. ...

  2. Express中间件的意思 next()的方法

    一.什么是express?Express是一个简洁.灵活的noode.jsWeb应用开发框架,它提供一系列强大的特性,帮助你创建各种Web和移动设备应用.Express项目的底层由许多的中间件在协同工 ...

  3. Hadoop面试题

    1.把数据仓库从传统关系数据库转到hadoop有什么优势? 原关系存储方式昂贵 空间有限 hadoop支持结构化(例如 RDBMS),非结构化(例如 images,PDF,docs )和半结构化(例如 ...

  4. HttpSessionListener的用法

    Session创建事件发生在每次一个新的session创建的时候,类似地Session失效事件发生在每次一个Session失效的时候. 这个接口也只包含两个方法,分别对应于Session的创建和失效: ...

  5. springboot 与 shiro 整合 (简洁版)

    前言: 网上有很多springboot 与 shiro 整合的资料,有些确实写得很好, 对学习shiro和springboot 都有很大的帮助. 有些朋友比较省事, 直接转发或者复制粘贴.但是没有经过 ...

  6. python练习一—文本转化渲染为html

    想学习python已经很久了,以前使用ArcGIS的时候学习过一些简单的python语法,用来进行一些简单的GIS数据处理,但是后来并没有用到工作中也就荒废了,后来断断续续看过一些,最近想学习一门新的 ...

  7. linux 命令 — 文件相关

    使用文件相关命令 dd 用来生成任意大小的文件 dd if=/dev/zero of=junk.data bs=1m count=1 生成一个1m大小的文件,里面全部使用0填充 if: 指定输入文件, ...

  8. 【原创】USART异步模式配置

        特性: (1)USART只能一位一位地发送和接受数据,在起始位期间,TX端处于低电平:当闲置时,TX端为高. (2)发送和接受由一共用的波特率发生器驱动,当发送器和接收器的使能位分别置位时,分 ...

  9. 依赖倒置原则(DIP)

    什么是依赖倒置呢?简单地讲就是将依赖关系倒置为依赖接口,具体概念如下: 1.上层模块不应该依赖于下层模块,它们共同依赖于一个抽象(父类不能依赖子类,它们都要依赖于抽象类) 2.抽象不能依赖于具体,具体 ...

  10. Mycat - 实现数据库的读写分离与高可用

    前言 开心一刻 上语文课,不小心睡着了,坐在边上的同桌突然叫醒了我,并小声说道:“读课文第三段”.我立马起身大声读了起来.正在黑板写字的老师吓了一跳,老师郁闷的看着我,问道:“同学有什么问题吗?”,我 ...