引用类型的变量不直接包含其数据;它包含的是对其数据的引用。当通过值传递引用类型的参数时,有可能更改引用所指向的数据,如某类成员的值(更改属性的值),但是无法更改引用本身的值;也就是说,不能使用相同的引用为新类分配内存(比如在被调用的方法中通过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, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量 。

官方文档:

https://docs.microsoft.com/en-us/dotnet/csharp/reference-semantics-with-value-types?view=netcore-2.1#ref-struct-type

传递引用类型参数的两种方式(转自 MSDN)的更多相关文章

  1. 【前台 ajax】web项目前台传递数组给后台 两种方式

    项目使用maven    springMVC 有需求 将前台的数组   在ajax中 送给后台 方式1: 前台代码:[注意:ajax中的属性---traditional:true,  ] 如果Post ...

  2. Restful传递数组参数的两种方式

    第一种,直接传递数组 js直接传递数组 var data = ["123","456"];that.loadDictionarys(data).subscrib ...

  3. Json传递数据两种方式(json大全)

    1.Json传递数据两种方式(json大全)----------------------------字符串 var list1 = ["number","name&quo ...

  4. Eclipse项目中引用第三方jar包时将项目打包成jar文件的两种方式

    转载自:http://www.cnblogs.com/lanxuezaipiao/p/3291641.html 方案一:用Eclipse自带的Export功能 步骤1:准备主清单文件 “MANIFES ...

  5. JS实现把一个页面层数据传递到另一个页面的两种方式

    本博客整理了两种方式从一个页面层向另一个页面层传递参数. 一. 通过cookie方式 1. 传递cookie页面的html,此处命名为a.html 请输入用户名和密码: <input id=&q ...

  6. 在基于MVC的Web项目中使用Web API和直接连接两种方式混合式接入

    在我之前介绍的混合式开发框架中,其界面是基于Winform的实现方式,后台使用Web API.WCF服务以及直接连接数据库的几种方式混合式接入,在Web项目中我们也可以采用这种方式实现混合式的接入方式 ...

  7. 使用Fragment的两种方式:<fragment>与<FrameLayout>

    Android中使用Fragment的两种方式:<fragment>与<FrameLayout> 1.静态使用:自定义类,继承Fragment,在xml中使用<fragm ...

  8. jquery ajax提交表单数据的两种方式

    http://www.kwstu.com/ArticleView/kwstu_201331316441313 貌似AJAX越来越火了,作为一个WEB程序开发者要是不会这个感觉就要落伍,甚至有可能在求职 ...

  9. SqlServer2008 数据库同步的两种方式(Sql JOB)

    尊重原著作:本文转载自http://www.cnblogs.com/tyb1222/archive/2011/05/27/2060075.html 数据库同步是一种比较常用的功能.下面介绍的就是数据库 ...

随机推荐

  1. 拼接字符串去掉最后多余的串,JSON的遍历

    一.遍历json change_url: function(key, value){ condition[key] = value; var string_url = "?"; f ...

  2. java高薪之路__002_异常处理

    自定义异常 package learn.JavaBasics.Class; import java.util.Date; /** * 如何自定义一个异常类 * 1. 继承一个现有的异常类 * 2. 提 ...

  3. 【转载】协同过滤 & Spark机器学习实战

    因为协同过滤内容比较多,就新开一篇文章啦~~ 聚类和线性回归的实战,可以看:http://www.cnblogs.com/charlesblc/p/6159187.html 协同过滤实战,仍然参考:h ...

  4. hibernate延迟加载(get和load的区别)

    概要: 在hibernate中我们知道如果要从数据库中得到一个对象,通常有两种方式,一种是通过session.get()方法,另一种就是通过session.load()方法,然后其实这两种方法在获得一 ...

  5. Android之alertDialog、ProgressDialog

    一.alertDialog 置顶于所有控件之上的,可以屏蔽其他控件的交互能力.通过AlertDialog.Builder创建一个AlertDialog,并通过setTittle(),setMesseg ...

  6. Web1.0、Web2.0、Web3.0的主要区别

    Web1.0:以静态.单向阅读为主,网站内信息可以直接和其他网站信息进行交互,能通过第三方信息平台同时对多家网站信息进行整合使用. Web2.0:以分享为特征的实时网络,用户在互联网上拥有自己的数据, ...

  7. 003_kafka_主要配置

    1.broker配置 2.Producer主要配置 3.Consumer主要配置 出错提示信息: replication factor: 1 larger than available brokers ...

  8. (33)odoo中产品价格字段

    打开product.template 和 product.product 模型发现有很多关于价格描述的字段 product.template:        price        list_pri ...

  9. Mac 搭建 SVN 服务器环境

    Mac 搭建 SVN 服务器环境 一.创建svn repository svnadmin create /path/svn/pro //仓库位置,svn是svn的目录,pro是一个版本库的目录 PS: ...

  10. jquery中的cookie操作

    使用前在页面中引入下面的代码 /*! * jQuery Cookie Plugin v1.4.1 * https://github.com/carhartl/jquery-cookie * * Cop ...