对于一些初学者(包括工做几年的人在内)来讲,有时候对于方法之间的参数传递的问题感受比较困惑的,由于以前在面试的过程也常常遇到参数传递的基础面试题,这样的面试题主要考察的开发人员基础是否扎实,对于C#中值类型和引用类型有没有深刻的一个理解——这个说的理解并非简单的对它们简单一个定义描述,而在于它们在内存中分布。因此本文章将带领你们深刻剖析下C#中参数传递的问题,并分享我本身的一个理解,只有你深刻理解了才能在不运行程序的状况就能够分析出参数传递的结果的。面试
对于C#中的参数传递,根据参数的类型能够分为四类:c#
值类型参数的按值传递ide
引用类型参数的按值传递spa
值类型参数的按引用传递指针
引用类型参数的按引用传递对象
然而在默认状况下,CLR方法中参数的传递都是按值传递的。为了帮助你们全面理解参数的传递,下面就这四种状况一一进行分析。内存
对于参数又分为:形参和实参,形参指的是被调用方法中的参数,实参指的是调用方法的参数,下面结合代码帮助你们理解形参和实参的概念:开发
class Program { static void Main(string[] args) { int addNum = 1; // addNum 就是实参, Add(addNum); } // addnum就是形参,也就是被调用方法中的参数 private static void Add(int addnum) { addnum = addnum + 1; Console.WriteLine(addnum); } }
对于值类型的按值传递,传递的是该值类型实例的一个拷贝,也就是形参此时接受到的是实参的一个副本,被调用方法操做是实参的一个拷贝,因此此时并不影响原来调用方法中的参数值,为了证实这点,看看下面的代码和运行结果就明白了:字符串
class Program { static void Main(string[] args) { // 1. 值类型按值传递状况 Console.WriteLine("按值传递的状况"); int addNum = 1; Add(addNum); Console.WriteLine(addNum); Console.Read(); } // 1. 值类型按值传递状况 private static void Add(int addnum) { addnum = addnum + 1; Console.WriteLine(addnum); }
运行结果为:编译器
从结果中能够看出addNum调用方法以后它的值并无改变,Add 方法的调用只是改变了addNum的副本addnum的值,因此addnum的值修改成2了。然而咱们的分析到这里并无结束,为了让你们深刻理解传递传递,咱们有必要知道为何值类型参数的按值传递不会修改实参的值,相信下面这张图能够解释你全部的疑惑:
当传递的参数是引用类型的时候,传递和操做的是指向对象的引用(看到这里,有些朋友会以为此时不是传递引用吗?怎么仍是按值传递了?对于这个疑惑,此时确实是按值传递,此时传递的对象的地址,传递地址自己也是传递这个地址的值,因此此时仍然是按值传递的),此时方法的操做就会改变原来的对象。对于这点可能看文字描述会比较难理解下面结合代码和分析图来帮助你们理解下:
class Program { static void Main(string[] args) { // 2. 引用类型按值传递状况 RefClass refClass = new RefClass(); AddRef(refClass); Console.WriteLine(refClass.addnum); } // 2. 引用类型按值传递状况 private static void AddRef(RefClass addnumRef) { addnumRef.addnum += 1; Console.WriteLine(addnumRef.addnum); } } class RefClass { public int addnum=1; }
运行结果为:
为何此时传递引用就会修改原来实参中的值呢?对于这点咱们仍是参数在内存中分布图来解释下:
对于String类型一样是引用类型,然而对于string类型的按值传递时,此时引用类型的按值传递却不会修改实参的值,可能不少朋友对于这点很困惑,下面具体看看下面的代码:
class Program { static void Main(string[] args) { // 3. String引用类型的按值传递的特殊状况 string str = "old string"; ChangeStr(str); Console.WriteLine(str); } // 3. String引用类型的按值传递的特殊状况 private static void ChangeStr(string oldStr) { oldStr = "New string"; Console.WriteLine(oldStr); } }
运行结果为:
对于为何原来的值没有被改变主要是由于string的“不变性”,因此在被调用方法中执行 oldStr="New string"代码时,此时并不会直接修改oldStr中的"old string"值为"New string",由于string类型是不变的,不可修改的,此时内存会从新分配一块内存,而后把这块内存中的值修改成 “New string”,而后把内存中地址赋值给oldStr变量,因此此时str仍然指向 "old string"字符,而oldStr却改变了指向,它最后指向了 "New string"字符串。因此运行结果才会像上面这样,下面内存分布图能够帮助你更形象地理解文字表述:
不论是值类型仍是引用类型,咱们均可以使用ref 或out关键字来实现参数的按引用传递,然而按引用进行传递的时候,须要注意下面两点:
方法的定义和方法调用都必须同时显式使用ref或out,不然会出现编译错误
CLR容许经过out 或ref参数来实现方法重载。如:
#region CLR 容许out或ref参数来实现方法重载 private static void Add(string str) { Console.WriteLine(str); } // 编译器会认为下面的方法是另外一个方法,从而实现方法重载 private static void Add(ref string str) { Console.WriteLine(str); } #endregion
按引用传递能够解决因为值传递时改变引用副本而不影响引用自己的问题,此时传递的是引用的引用(也就是地址的地址),而不是引用的拷贝(副本)。下面就具体看看按引用传递的代码:
class Program { static void Main(string[] args) { #region 按引用传递 Console.WriteLine("按引用传递的状况"); int num = 1; string refStr = "Old string"; ChangeByValue(ref num); Console.WriteLine(num); changeByRef(ref refStr); Console.WriteLine(refStr); #endregion Console.Read(); } #region 按引用传递 // 1. 值类型的按引用传递状况 private static void ChangeByValue(ref int numValue) { numValue = 10; Console.WriteLine(numValue); } // 2. 引用类型的按引用传递状况 private static void changeByRef(ref string numRef) { numRef = "new string"; Console.WriteLine(numRef); } #endregion }
运行结果为:
从运行结果能够看出,此时引用自己的值也被改变了,经过下面一张图来帮忙你们理解下按引用传递的方式:
到这里参数的传递全部内容就介绍完了。总之,对于按值传递,不论是值类型仍是引用类型的按值传递,都是传递实参的一个拷贝,只是值类型时,此时传递的是实参实例的一个拷贝(也就是值类型值的一个拷贝),而引用类型时,此时传递的实参引用的副本。对于按引用传递,传递的都是参数地址,也就是实例的指针。