C# 传值和传引用 ( ref out in )
引用类型的变量不直接包含其数据;它包含的是对其数据的引用。当通过值传递引用类型的参数时,有可能更改引用所指向的数据,如某类成员的值(更改属性的值),但是无法更改引用本身的值;也就是说,不能使用相同的引用为新类分配内存(比如在被调用的方法中通过new来分配新的内存空间)并使之在块外(调用方法中,比如Main方法中)保持。若要这样做,应使用引用传递方式(注意:引用传递方式和引用类型是不同的概念)——用 ref 或 out 关键字传递参数(参数类型可以是值类型也可以是引用类型)。为了简单起见,下面的示例使用 ref。
通过值传递引用类型
下面的示例演示通过值向 Change 方法传递引用类型的参数 arr。由于该参数是对 arr 的引用,所以有可能更改数组元素的值。但是,尝试将参数重新分配到不同的内存位置时,该操作仅在方法内有效,并不影响原始变量 arr。
class PassingRefByVal
{
static void Change(int[] pArray)
{
pArray[] = ; // 这里赋值 会改变 原来pArray[0]的值
pArray = new int[] { -, -, -, -, - }; // 这里的赋值,只在该方法内有效
System.Console.WriteLine("在Change方法里面,arr第一个元素的值是:{0}", pArray[]); pArray[] = ; // 这里赋值 不会改变 原来pArray[1]的值,只是改变这里新创建的数组的第一个元素的值
System.Console.WriteLine("在Change方法里面,arr第二个元素的值是:{0}", pArray[]);
} static void Main()
{
int[] arr = { , , };
System.Console.WriteLine("在Main方法里面,调用Change方法前,arr数组中第一个元素的值是:{0}", arr[]);
System.Console.WriteLine("在Main方法里面,调用Change方法前,arr数组中第二个元素的值是:{0}", arr[]); Change(arr);
System.Console.WriteLine("在Main方法里面,调用Change方法后,第一个元素的值是:{0}", arr[]);
System.Console.WriteLine("在Main方法里面,调用Change方法后,第二个元素的值是:{0}", arr[]); System.Console.ReadLine();
}
} /* 输出:
在Main方法中,调用Change方法之前,pArray中第一个元素的值是:1
在Main方法中,调用Change方法之前,pArray中第二个元素的值是:4
在Change方法中,pArray中第一个元素的值是:-3
在Change方法中,pArray中第二个元素的值是:666
在Main方法中,调用Change方法后,pArray中第一个元素的值是:888
在Main方法中,调用Change方法后,pArray中第二个元素的值是:4
*/
在上面的示例中,数组 arr 为引用类型,在未使用 ref 参数的情况下传递给方法。在此情况下,将向方法传递 指向 arr 的引用的一个副本 。输出显示方法有可能更改数组元素的内容,在这种情况下,从 1 改为 888。但是,在 Change 方法内使用 new 运算符来分配新的内存空间,将使变量 pArray 引用新的数组。因此,这之后的任何更改都不会影响原始数组 arr(它是在 Main 内创建的)。实际上,本示例中创建了两个数组,一个在 Main 内,一个在 Change 方法内。
下面的示例与前面的示例相比,除了在方法标头和调用方法的地方添加了 ref 关键字之外,其他的都一样。在方法中做的更改会影响到原始的变量。
class PassingRefByRef
{
static void Change(ref int[] pArray)
{
// 下面的更改都会影响到变量的原始值
pArray[] = ;
pArray = new int[] { -, -, -, -, - };
System.Console.WriteLine("在Change方法中,pArray中第一个元素的值是:{0}", pArray[]);
} static void Main()
{
int[] arr = { , , };
System.Console.WriteLine("在Main方法中,调用Change方法之前,pArray中第一个元素的值是:{0}", arr[]); Change(ref arr);
System.Console.WriteLine("在Main方法中,调用Change方法后,pArray中第一个元素的值是:{0}", arr[]); System.Console.ReadLine();
}
}
/* Output:
在Main方法中,调用Change方法之前,pArray中第一个元素的值是: 1
在Change方法中,pArray中第一个元素的值是: -3
在Main方法中,调用Change方法后,pArray中第一个元素的值是: -3
*/
方法内发生的所有更改都影响 Main 中的原始数组。实际上,使用 new 运算符对原始数组对象进行了重新分配内存的操作。因此,调用 Change 方法后,对 arr 的任何引用都将指向 Change 方法中创建的新的数组。
交换字符串是传递引用类型参数的很好的示例。
1. 通过值传递:worker 和 manager 两个字符串在 Main 中进行初始化,并作为一般参数传递给静态方法 swap。这两个字符串在 swap 方法内进行了交,但在 Main 内没有进行交换。
class SwapEmployees
{
// 传值
static void swap(string e1, string e2)
{
string temp = e1;
e1 = e2;
e2 = temp;
System.Console.WriteLine("在swap方法里:{0} {1}", e1, e2);
} static void Main()
{
string worker = "John";
string manager = "Smith";
System.Console.WriteLine("在Main方法里,调用swap方法之前:{0} {1}", worker, manager); swap(worker, manager); // 通过引用传递方式,来传递字符串变量
System.Console.WriteLine("在Main方法里,调用swap方法之后:{0} {1}", worker, manager); System.Console.ReadLine();
}
} /* Output:
在Main方法里,调用swap方法之前:John Smith
在swap方法里:Smith John
在Main方法里,调用swap方法之后:John Smith
*/
通过值传递时,对应的内存变化如图一所示:
图一:通过值传递引用类型参数时的内存变化
2. 通过引用传递:worker 和 manager 两个字符串在 Main 中进行初始化,并作为参数(带 ref 关键字)传递给静态方法 swap。这两个字符串在该方法内和 Main 内均进行了交换。
class SwapEmployees
{
// 传引用
static void swap(ref string e1, ref string e2)
{
string temp = e1;
e1 = e2;
e2 = temp;
System.Console.WriteLine("在swap方法里:{0} {1}", e1, e2);
} static void Main()
{
string worker = "John";
string manager = "Smith";
System.Console.WriteLine("在Main方法里,调用swap方法之前:{0} {1}", worker, manager); swap(ref worker, ref manager); // 通过引用传递方式,来传递字符串变量
System.Console.WriteLine("在Main方法里,调用swap方法之后:{0} {1}", worker, manager); System.Console.ReadLine();
}
} /* Output:
在Main方法里,调用swap方法之前:John Smith
在swap方法里:Smith John
在Main方法里,调用swap方法之后:Smith John
*/
通过引用传递时,对应的内存变化如图二所示:
图二:通过引用传递引用类型参数时的内存变化
总结:
本示例中,需要通过传递引用的方式来传递参数,才能影响调用程序中的对象。如果把方法声明和方法调用中的 ref 关键字都去掉,那么调用方法后,原对象不会被重新分配内存。不难发现,上面的两种方式与值类型和引用类型的存储方式密切相关。关于值类型和引用类型的存储方式的区别,可以参考文章:http://www.xuexila.com/baikezhishi/536966.html
//摘录其中一段
与 C++ 不同,Java 自动管理栈和堆,程序员不能直接地设置栈或堆。
Java 的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过 new, newarray, anewarray, multianewarray 等指令建立,它们不需要程序代码来显式的释放。堆由垃圾回收来负责,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java 的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。
栈的优势是,存取速度比堆要快,仅次于寄存器,栈的数据还可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和对象句柄。
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
int a = 3;
int b = 3;
编译器先处理 int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理 int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了 a 与 b 同时均指向3的情况。这时,如果再令 a = 4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令 a 指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况 a 的修改并不会影响到 b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量 。
官方文档:
C# 传值和传引用 ( ref out in )的更多相关文章
- [UE4]传值与传引用
值传递是圆形图标 设置引用需要使用Set by ref函数 对象在蓝图中都是以引用传递 对象,不需要额外设置参数类型是传值还是传引用. 结构体在蓝图中默认是按值传递 也可以手动设置结构体参数为按引用类 ...
- java集合中的传值和传引用
在学习java集合过程中发现了传值和传引用的区别: 我们来看下面两句话 ●java集合就像一种容器,我们可以把多个对象(实际上是对象的引用),丢进该容器.(来自疯狂java讲义) ●当使用Iterat ...
- Go语言的传值与传引用
Go语言里的传值与传引用大致与C语言中一致,但有2个特例,map和channel默认传引用,也就是说可以直接修改传入的参数,其他的情况如果不用指针的话,传入的都是参数的副本,在函数中修改不会改变调用者 ...
- java中的传值与传引用
java函数中的传值和传引用问题一直是个比较“邪门”的问题,其实java函数中的参数都是传递值的,所不同的是对于基本数据类型传递的是参数的一份拷贝,对于类类型传递的是该类参数的引用的拷贝,当在函数体中 ...
- python函数传参是传值还是传引用?
首先还是应该科普下函数参数传递机制,传值和传引用是什么意思? 函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题.基本的参数传递机制有两种:值传递和引用传 ...
- java 函数形参传值和传引用的区别
java方法中传值和传引用的问题是个基本问题,但是也有很多人一时弄不清. (一)基本数据类型:传值,方法不会改变实参的值. public class TestFun { public static v ...
- JavaScript系列----数据类型以及传值和传引用
1.简单数据类型 在JavaScript中简单数据类型分为5种.分别为 Undefined, Null,Boolean,Number,String. Undefined类型Undefined类型只有一 ...
- Go 参数传递是传值还是传引用
什么是传值(值传递)? 传值的意思是:函数传递的总是原来这个东西的一个副本.一个副拷贝.比如我们传递一个 int 类型的参数,传递 的其实这个参数的一个副本:传递一个指针类型的参数,其实传递的是这个指 ...
- C++ 参数传值 与 传引用
参数传值 在 C++ 中,函数参数的传递有两种方式:传值和传引用.在函数的形参不是引用的情况下,参数传递方式是传值的.传引用的方式要求函数的形参是引用.“传值”是指,函数的形参是实参的一个拷贝,在函数 ...
随机推荐
- Linux下汇编语言学习笔记4 ---
这是17年暑假学习Linux汇编语言的笔记记录,参考书目为清华大学出版社 Jeff Duntemann著 梁晓辉译<汇编语言基于Linux环境>的书,喜欢看原版书的同学可以看<Ass ...
- 基于jQuery的图片加载loading效果插件
基于jQuery的图片加载loading效果插件 图片loading的效果是网页中比较常见的,尤其是对大图片,loading效果让用户能够明白图片加载的过程. 实现思路也是比较简单的: $.fn.Lo ...
- 【编程大系】Java资源汇总
1.学习资料: 1)Spring Boot 那些事:https://www.w3cschool.cn/springboot/ 对应的 gitHub代码: https://github.com/Jeff ...
- oracle链接不上的问题
使用plSql连接数据库看看,登录提示如下:ORA-12514:TNS:监听程序当前无法识别连接描述符中请求的服务. 查了许久的baidu也没有解决问题的方法.想起来看看oracle的服务是否开启,O ...
- 通过ambari安装hadoop集群
转载:http://www.cnblogs.com/cenyuhai/p/3295635.html 整个过程走完,问题不大,不过有一个事情要注意的是就算创建数据库的,使用localhost会报错,要使 ...
- PHP关于文件与文件夹(1) 写入文件 文件权限 三、锁定文件
一.文件权限 总之中的一个切都是为了保证文件夹的安全,保证文件夹的安全比保证文件的安全更重要. 二.写入文件 file_put_contents($file,$data); //假设没有的话会创建. ...
- Ubuntu下Zabbix安装及使用问题
1.configure: error: MySQL library not found MySQL library not found root@kallen:~# apt-get install l ...
- $.extent()的理解
$.extend()主要是用来扩展插件的,所谓的插件就是封装好的函数或者方法,可以直接调用. $.extend()与$.fn.extend()(或者写成$.prototype.extend()或者jq ...
- Mahout贝叶斯算法拓展篇3---分类无标签数据
代码測试环境:Hadoop2.4+Mahout1.0 前面博客:mahout贝叶斯算法开发思路(拓展篇)1和mahout贝叶斯算法开发思路(拓展篇)2 分析了Mahout中贝叶斯算法针对数值型数据的处 ...
- ACdream区域赛指导赛之手速赛系列(5) 题解
A - Problem A Time Limit: 2000/1000MS (Java/Others) Memory Limit: 128000/64000KB (Java/Others) Submi ...