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++ 中,函数参数的传递有两种方式:传值和传引用.在函数的形参不是引用的情况下,参数传递方式是传值的.传引用的方式要求函数的形参是引用.“传值”是指,函数的形参是实参的一个拷贝,在函数 ...
随机推荐
- [bzoj1430]小猴打架_prufer序列
小猴打架 bzoj-1430 题目大意:题目链接. 注释:略. 想法: 我们发现打架的情况就是一棵树. 我们只需要把确定树的形态然后乘以$(n-1)!$表示生成这棵树时边的顺序. 一共$n$个节点我们 ...
- Ubuntu 16.04安装Memcached(单机)
Ubuntu 16.04安装Memcached,不过不仅限与Ubuntu,可以用CentOS等去安装,只不过测试时使用的是Ubuntu机器.Windows下不建议使用,本机调试可以使用,线上环境除了W ...
- Ubuntu 16.04安装TeamViewer
安装i386库: sudo apt-get install libc6:i386 libgcc1:i386 libasound2:i386 libexpat1:i386 libfontconfig1: ...
- Ubuntu16.04安装deb文件时提示:此软件来自第三方且可能包含非自由组件
解决方式: 1.在Ubuntu软件中心安装GDebi. 2.安装好之后,选择这个要安装的deb文件右键,打开方式选择GDebi,然后输入管理员密码等待安装,期间如果不行需要重试几次. 3.另外的方法, ...
- 在 IIS 中承载 WCF 服务
本主题概述了创建 Internet 信息服务 (IIS) 中承载的 Windows Communication Foundation (WCF) 服务所需的基本步骤. 本主题假设您熟悉 IIS 且了解 ...
- mybatis xml标签,批量插入
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-/ ...
- 我的arcgis培训照片8
来自:http://www.cioiot.com/successview-554-1.html
- linux shell学习一点点
问题,从shell command 交互式地由用户输入密码,但是输入的过程中不会显示用户输入的密码,起到类似于于html中input type=password的作用. #!/bin/bash rea ...
- x86CPU 实模式 保护模式 傻傻分不清楚? 基于Xv6-OS 分析CR0 寄存器
基于Xv6-OS 分析CR0 寄存器 之前一直认为晕乎乎的...啥?什么时候切换real model,怎么切换,为什么要切换? ------------------------------------ ...
- 深度学习-theano-windows -cuda-环境搭建
本文将具体介绍深度学习之cuda的环境搭建 工具:支持CUDA的显卡(安装cuda6.5),VS2013.Anaconda. 步骤: 1.安装cuda6.5 这个不具体介绍,网上有很多文章.注意选择你 ...