在讲C#参数传递之前,我们先简单讨论下 c#中值类型和引用类型的定义以及区别,有助于我们更好的理解参数传递。
我们从内存的角度来简单讨论下值类型和引用类型的区别。我们都知道值类型存储在栈上,引用类型分别在栈和托管堆上。如下图:
我们通过例子来看下 值类型和引用类型存储结构不同有哪些区别:
定义一个类 (引用类型)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 public class Student 2 { 3 public int Age { get; set; } 4 5 public void Say() 6 { 7 Console.WriteLine("我的年龄是:"+Age); 8 } 9 }
定义一个结构(值类型)
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 public struct StructStudent 2 { 3 public int Age { get; set; } 4 5 public void Say() 6 { 7 Console.WriteLine("我的年龄是:" + Age); 8 } 9 }
在控制台输出定义如下代码:
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 static void Main(string[] args) 2 { 3 Student stu1 = new Student { Age=20}; 4 Student stu2 = new Student(); 5 stu2 = stu1; 6 stu2.Age = 30; 7 stu1.Say(); 8 9 stu2.Say(); 10 }
输出结果:
我们将类换成结构,在看看结果如何
定义代码:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
输出结果:
我们会发现同样的代码 但是结果不一样,其实 每个变量 都有其堆栈,不同的变量不能共用一个堆栈地址。直接上图
由图 我们可以看出 对于值类型 stu2的age属性更改不会影响 stu1 对于引用类型 stu1 和stu2 在栈上的地址是不同的。stu1=stu2 。这是2个相等的变量 所以他们在托管堆上的引用地址是一样的。修改age 会对stu1 stu2都造成影响。所以 2种情况下输出结果会不一样。
明白这些 我们就很容易理解参数传递了。
关于参数传递 分按值传递 和按 引用传递
按值传递 分 值类型参数按值快递 和 引用类型参数按值传递
按引用传递分 值类型参数按引用传递 和引用类型参数按引用传递
在讲解参数传递之前 我们还要明白一个概念 形参和实参。
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
形参和实参的类型 个数 顺序 必须要完全匹配。
1、值类型参数按值传递:
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 static void Main(string[] args) 2 { 3 int sum = 10; 4 Add(sum); 5 Console.WriteLine(sum); 6 } 7 8 public static void Add(int i) 9 { 10 i++; 11 }
输出 结果:
我们可以看出sum的值 并没有改变还是10. 我们通过图表来说明为什么会这样。我们知道值类型都是存储在栈上。
2、引用类型参数按值传递:
定义一个引用类型:
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 public class Student 2 { 3 public int Age { get; set; } 4 }
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
1 static void Main(string[] args) 2 { 3 Student stu = new Student { Age =20}; 4 Add(stu); 5 Console.WriteLine(stu.Age); 6 } 7 8 public static void Add(Student stu) 9 { 10 stu.Age++; 11 }
输出结果:
可以看出结果改变了 输出是21.
为什么会是这样呢,直接上图:
3、值类型按引用传递
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
输出结果:
可以看出 这次sum的 值改变了 不是 10 而是11. 这就是ref 关键字的神奇作用。ref在参数传递中到底起了什么作用呢,我们可以理解为ref 仅仅是一个地址。在文章前面我们提到过, 每个变量都有其堆栈,不同的变量不能共用一个堆栈地址。所以形参和实参在栈上的地址是不一样的。ref所起的作用我们可以简单的理解为他会记住实参的地址,当形参的值改变后,他会把值映射回 之前的实参地址。
4、引用类型按引用传递
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
在使用ref 的时候 调用方法一定要加上ref关键字 ,Add(int i) 和Add(ref int i)是构成方法的重载的。
在参数传递中 我们都使用ref 作为示例,那么out关键字 和ref 有什么区别呢。ref和out本质基本一样。唯一的区别就是一个是侧重于修改,一个侧重于输出。
static void Main(string[] args) { int sum = 10; Add(ref sum); Console.WriteLine(sum); } public static void Add(ref int i) { i++; }
这段代码中 我们使用ref 之前 先给 sum 赋了初始值,然后修改sum的值。如果我们使用out 可以先不给sum赋值。在方法内部赋值。
params 关键字 是数组类型的参数。可以指定在参数数目可变处采用参数的方法参数,在方法声明中的 params 关键字之后不允许任何其他参数,并且在方法声明中只允许一个 params 关键字。也就是说方法中如果有多个参数只允许有一个params参数,而且要放在方法的最后一个参数。
参见 MSDN示例:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
string 类型我们都知道是我们最常用的一种引用类型,那么sting类型的参数传递会是什么样的呢,我们可以自己试验一下,会发现string是很特殊的一个引用类型。后续我会继续讲解关于string类型。 不正之处 欢迎指正!
-
葛子木
Posted @ 2012-06-25 11:22
收藏了 -
程序老炮儿清道夫
Posted @ 2012-06-25 11:24
不错,写得很细,赞一个 -
JerremyZhang
Posted @ 2012-06-25 11:29
不错,mark下!
赋值不赋值其实没所谓的吧,因为在方法内部你必须要赋值的,如上面楼主例子,即使在外面赋值,也会被内部的赋值覆盖掉的嘛 -
vito qi
Posted @ 2012-06-25 11:34
@ JohnZhang
是的,我这里主要是想说明ref 和out的区别 -
Jaryleely
Posted @ 2012-06-25 12:35
good job! -
alab
Posted @ 2012-06-26 23:32
@ vito qi
一个形象的说法是ref有进有出,out只出不进 -
alab
Posted @ 2012-06-26 23:34
值类型不一定分配在栈上,比如,是一个类中定义的值类型,还是分配在堆上, 作为局部变量(函数内)和参数才会分配在栈上 -
vito qi
Posted @ 2012-06-27 10:04
@ alab
对的,我这个地方主要是讲参数传递。 -
雄风哥
Posted @ 2015-01-08 14:08
醍醐灌顶! -
一别经年
Posted @ 2016-08-31 09:23
由浅入深,层层推进!图文并茂,好文!