C#参数的按值传递和按引用传递
一个方法可以包括 0 或多个参数。在方法前面括号中规定的参数列表称为形参,而传递进方法的参数称为实参。
C# 的默认方式是按值传递(pass by value),若传递对象是值类型,则按值传递之后,传递进方法的不过是值的副本而已,方法外部的对象不受影响。
按引用传递(pass by reference)之后,传递进方法的是值类型的地址,方法外部的对象会受影响。
实际上,按引用传递是按值传递的一种特殊情况(引用是地址,地址也是值)。
例如一个试图调换 a 和 b 值的 Swap 函数,如果其签名为 void Swap(int a, int b),则如果在某处调用该函数,调用完毕后,a 和 b 的值是不会变的。
因为,传入时会自动复制两个 int 副本,函数内部的任何操作和 a,b 都没有关系。
如果其签名是 void Swap(ref int a, ref int b),,则改为传入 a 和 b 的地址,不会发生复制,调用该函数将确实会调换 a 和 b 的值。
如果传递对象是引用类型,则无论是普通的传递还是加上 ref 和 out 关键字,都会更改方法外部的对象。
加不加 ref 和 out 关键字的区别在于是否可以把引用重新赋值给一个新的对象(即更改传入的地址本身)。
ref 和 out 的区别是,ref 需要事先赋值,而 out 必须在方法返回之前赋值。
另外,一个方法可以有多个由 ref 或 out 修饰的输入变量,这间接地令方法有了多个返回值。
如果两个方法仅仅在 ref 和 out 上有区别(相同名称,相同输岀,相同输入),则在编译器看来他们是相同的方法。
特殊情况:如果传递的对象是字符串,则其行为类似值类型(字符串的值不改变)。要 传递引用,必须要加 ref 关键字才可以。
例如 void Swap(string a, string b),调用完毕后,外 部字符串是不会被交换的。
为了验证引用类型的按引用传递,我们来看下面的代码:
在看完上面的例子之后,理解字符串的行为可能就容易了一些。看下面的代码:
但是,由于没有更改 s 本身引用的指向(令 s 等于它),所以 test1 实际上没有引用指向它,方法结束之后,它就变成了垃圾。
此时,外部的 s 仍然指向 test,所以,s 的值不发生变化。这和普通的引用类型按值传递不同,因为,普通的引用类型没有不变性,不会在操作时产生新的对象。
Change2 方法则返回一个值,所以打印岀的值发生了变化,s 现在指向一个新的值。此时,内存中又增加了一个字符串 test2。
Change3 方法传递引用,所以可以把字符串 s 赋值给一个新的对象 test3。这和普通的引用类型按引用传递相同。
C# 的默认方式是按值传递(pass by value),若传递对象是值类型,则按值传递之后,传递进方法的不过是值的副本而已,方法外部的对象不受影响。
按引用传递(pass by reference)之后,传递进方法的是值类型的地址,方法外部的对象会受影响。
实际上,按引用传递是按值传递的一种特殊情况(引用是地址,地址也是值)。
例如一个试图调换 a 和 b 值的 Swap 函数,如果其签名为 void Swap(int a, int b),则如果在某处调用该函数,调用完毕后,a 和 b 的值是不会变的。
因为,传入时会自动复制两个 int 副本,函数内部的任何操作和 a,b 都没有关系。
如果其签名是 void Swap(ref int a, ref int b),,则改为传入 a 和 b 的地址,不会发生复制,调用该函数将确实会调换 a 和 b 的值。
如果传递对象是引用类型,则无论是普通的传递还是加上 ref 和 out 关键字,都会更改方法外部的对象。
加不加 ref 和 out 关键字的区别在于是否可以把引用重新赋值给一个新的对象(即更改传入的地址本身)。
ref 和 out 的区别是,ref 需要事先赋值,而 out 必须在方法返回之前赋值。
另外,一个方法可以有多个由 ref 或 out 修饰的输入变量,这间接地令方法有了多个返回值。
如果两个方法仅仅在 ref 和 out 上有区别(相同名称,相同输岀,相同输入),则在编译器看来他们是相同的方法。
特殊情况:如果传递的对象是字符串,则其行为类似值类型(字符串的值不改变)。要 传递引用,必须要加 ref 关键字才可以。
例如 void Swap(string a, string b),调用完毕后,外 部字符串是不会被交换的。
为了验证引用类型的按引用传递,我们来看下面的代码:
public class AClass { public int a; public string b; public static void ChangeValue(AClass a) { a.a = 111; var b = new AClass(); b.a = 222; a = b; } public static void ChangeValueRef(ref AClass a) { a.a = 888; var b = new AClass(); b.a = 999; a = b; } }当我们调用时:
static void Main(string[] args) { var a = new AClass(); a.a = 1; AClass.ChangeValue(a); Console.WriteLine(a.a); AClass.ChangeValueRef(ref a); Console.WriteLine(a.a); Console.ReadKey(); }会输出 111 和 999。我们发现,按值传递时,引用类型栈上的地址不能改变;而按引用传递时,可以改变栈上的地址,指向一个新的堆上的对象。
在看完上面的例子之后,理解字符串的行为可能就容易了一些。看下面的代码:
static void Main(string[] args) { var s = "test"; Change(s); Console.WriteLine(s); Console.WriteLine(Change2(s)); ChangeRef(res s); COnsole.ReadKey(); } public static void Change(string s) { s += "1"; } public static void Change2(string s) { return s += "2"; } public static void ChangeRef(ref string s) { s += "3"; }当调用 Change 方法之后,字符串池中有两个字符串,分别为 test 和 test 1(大部分情况下,字符串操作每次都产生新的字符串)。
但是,由于没有更改 s 本身引用的指向(令 s 等于它),所以 test1 实际上没有引用指向它,方法结束之后,它就变成了垃圾。
此时,外部的 s 仍然指向 test,所以,s 的值不发生变化。这和普通的引用类型按值传递不同,因为,普通的引用类型没有不变性,不会在操作时产生新的对象。
Change2 方法则返回一个值,所以打印岀的值发生了变化,s 现在指向一个新的值。此时,内存中又增加了一个字符串 test2。
Change3 方法传递引用,所以可以把字符串 s 赋值给一个新的对象 test3。这和普通的引用类型按引用传递相同。
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算