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#基础之参数(二) 数组参数、可选参数与命名参数

 

 

    


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM