C#基础之参数(一) 值参数、引用参数与输出参数

  要学会各种参数,重点是理解参数参数传递的内存原理。理解了内存,各种参数使用的作用、效果等便可以自己分析出来,而不用死记硬背。  

  1.按值传递参数(值参数)

    值参数是最常见的一种参数,也很好判断。在方法声明与调用时,参数前不加ref或out关键字的参数,便是按值传递。
    值参数是将实参变量在“栈”中存储的值复制一份副本,将副本传递给方法的形参。
    按值传递参数又分为两种不同情况:传递值类型,与传递引用类型
    (注意这里的值类型、引用类型是数据类型,而值参数、引用参数是参数传递方式。二者不是同一概念,不可混淆。)

    (1)传递值类型参数

    示例:

 1     class Program
 2     {
 3         static void Main(String[] args)
 4         {
 5             int a = 4;
 6             int b = 5;
 7             Add(a, b);
 8             Console.WriteLine($"a={a},b={b}");
 9         }
10         public static void Add(int x, int y)
11         {
12             x += 1;
13             y += 1;
14         }
15     }

    输出为:

a=4,b=5

    内存原理:在Add方法中对形参x与y的操作,并不会对实参a与b的值产生影响,因为在Add方法中操作的是a与b的副本。

    (2)传递引用类型参数 

    示例1:

 1     class Program
 2     {
 3         static void Main(String[] args)
 4         {
 5             Animal a = new Animal() { Age = 1 };
 6             Add(a);
 7             Console.WriteLine($"a.Age={a.Age}");
 8         }
 9         public static void Add(Animal animal)
10         {
11             animal.Age += 1;
12         }
13     }
14     class Animal
15     {
16         public int Age { get; set; }
17     }    

    输出为:

a.Age=2

    内存原理:

    在Add方法中形参animal拿到了实参a在栈中的副本,副本中存储着对象的地址。所以,animal与a指向了同一个对象。
       这时操作的不是副本,而是副本所指的对象,即操作a所指对象。

    示例2:

    

 1     class Program
 2     {
 3         static void Main(String[] args)
 4         {
 5             Animal a = new Animal() { Age = 1 };
 6             Add(a);
 7             Console.WriteLine($"a.Age={a.Age}");
 8         }
 9         public static void Add(Animal animal)
10         {
11             animal = new Animal() { Age = 10 };
12             animal.Age += 1;
13         }
14     }
15     class Animal
16     {
17         public int Age { get; set; }
18     }

    输出为:

a.Age=1

    内存原理:

    与示例1不同的是,在示例2中Add方法中改变了animal所指向的对象,指向一个new的Animal对象,此时a仍指向原对象,这时再对animal进行操作,
       便不再会影响实参a所指的对象。

  2.按引用传递参数(引用参数)

     形参与实参之前,都要加ref关键字。
     需要注意的是,实参在使用之前必须赋值,否则编译器会报错。(如下图)
     此时不再复制实参在栈中的副本,而是将实参在栈中的地址传给形参,也就是实参与形参共用栈中的值。此时在方法中对形参所做的任何操作,都会影响实参。
     按引用传递也分为值类型和引用类型两种情况。

    (1)传递值类型参数

    示例:

    

 1     class Program
 2     {
 3         static void Main(String[] args)
 4         {
 5             int a = 4;
 6             int b = 5;
 7             Add(ref a, ref b);
 8             Console.WriteLine($"a={a},b={b}");
 9         }
10         public static void Add(ref int x, ref int y)
11         {
12             x += 1;
13             y += 1;
14         }
15     }

    输出为:

a=5,b=6

    内存原理:

    由于a与x,b与y指向了栈中相同的地址,在Add方法中对形参x与y的操作,会影响实参a与b的值。

    (2)传递引用类型参数

    示例1:

 1     class Program
 2     {
 3         static void Main(String[] args)
 4         {
 5             Animal a = new Animal() { Age = 1 };
 6             Add(ref a);
 7             Console.WriteLine($"a.Age={a.Age}");
 8         }
 9         public static void Add(ref Animal animal)
10         {
11             animal.Age += 1;
12         }
13     }
14     class Animal
15     {
16         public int Age { get; set; }
17     }

    输出为:

a.Age=2

    内存原理:

    这时得到的效果,与按值传递参数是一样的,但内存原理却不相同。

    这时Add方法中形参animal指向的不再是实参a在栈中的副本的地址,而是与a指向了栈中相同的位置。

    示例2:

 1     class Program
 2     {
 3         static void Main(String[] args)
 4         {
 5             Animal a = new Animal() { Age = 1 };
 6             Add(ref a);
 7             Console.WriteLine($"a.Age={a.Age}");
 8         }
 9         public static void Add(ref Animal animal)
10         {
11             animal = new Animal() { Age = 10 };
12             animal.Age += 1;
13         }
14     }
15     class Animal
16     {
17         public int Age { get; set; }
18     }

    输出为:

a.Age=11

    内存原理:

     这个示例便可以看出与按值传递时示例2的不同。

     Add方法中改变了animal所指向的对象,指向一个new的Animal对象,此时a所指向的对象也随之改变。这是由于形参animal
     与实参a指向的是栈中相同的地址(这个地址中存储的是对象在堆中的地址)。当栈中存储的堆地址改变时,animal与a会同时指向新的对象。

  3.输出参数

    用法:
     (1)形参与实参之前,都要加out关键字。
     (2)输出参数主要是用于函数需要有多个返回值时,作为返回值使用。
    与引用变量相同的是:
     (1)输出参数也不复制实参在栈中的副本,而是将实参在栈中的地址传给形参。在这点上,输出参数与引用参数相同。
     (2)实参必须是可以赋值的变量,而不能是常亮。
    与引用参数不同的是:
     (1)实参在使用之前不必赋值。
     (2)事实上,在使用之前对实参的赋值没有任何意义,因为在调用方法的最开始,便会将其值抹去,使其成为未赋值的变量。
     (3)在方法返回之前,必须对out参数进行赋值。
    由以上特点所决定的是,输出参数无法向方法中传递信息,其唯一作用便是,当一个参数需要返回多个值时,作为返回值返回。

    示例1:

 1     class Program
 2     {
 3         static void Main(String[] args)
 4         {
 5             int a;
 6             Add(1, 2, out a);
 7             Console.WriteLine(a);
 8         }
 9         public static bool Add(int x, int y, out int result)
10         {
11             result = x + y;
12             return true;
13         }
14     }

    输出为:

3

    引用类型的输出参数同理,请自行编写示例代码。

 请继续阅读参数学习的下篇:C#基础之参数(二) 数组参数、可选参数与命名参数

    

原文地址:https://www.cnblogs.com/vsSure/p/7847204.html