• C#值参数和引用参数


    一、值参数

    未用ref或out修饰符声明的参数为值参数。

    使用值参数,通过将实参的值复制到形参的方式,把数据传递到方法。方法被调用时,系统做如下操作。

    • 在栈中为形参分配空间。
    • 复制实参到形参。

    值参数的实参不一定是变量。它可以是任何能计算成相应数据类型的表达式。

    看一个例子:

    float func1(float val)    //声明方法
    {
      float j=2.6F;
      float k=5.1F;
      ....
    }
    

    下面来调用方法

    float fValue1=func1(k);        //实参是float类型的变量
    
    float fValue2=func1((k+j)/3);  //实参可以计算成float表达式
    

    在把变量作用于实参之前,变量必须赋值(除非是out参数)。对于引用类型,变量可以被设置为一个实际的引用或null。

    下面的代码展示了一个名为MyMethod的方法,它有两个参数,一个是MyClass型变量和一个int。

     1  class MyClass
     2     {
     3         public int Val = 20;
     4     }
     5     class Program
     6     {
     7 
     8         static void MyMethod(MyClass f1, int f2)
     9         {
    10             f1.Val = f1.Val + 5;
    11             f2 = f2 + 5;
    12             Console.WriteLine("f1.Val: {0}, f2: {1}", f1.Val, f2);
    13         }
    14         static void Main(string[] args)
    15         {
    16             MyClass a1 = new MyClass();
    17             int a2 = 10;
    18 
    19             MyMethod(a1, a2);
    20 
    21             Console.WriteLine("f1.Val: {0}, f2: {1}", a1.Val, a2);
    22          }
    23     }

    我们用图来表示实参和形参在方法执行的不同阶段的值。

    • 在方法被调用前,用作实参的a2已经在栈里了。
    • 在方法开始前,系统在栈中为形参分配空间,并从实参复制值。 
    • 因为a1是引用类型,所以引用被复制,结果实参和形参都引用堆中的同一对象。
    • 因为a2是值类型,所以值被复制,产生了一个独立的数据项。
    • 在方法的结尾,f2和对象f1的字段都被加上了5。
    • 方法执行后,形参从栈中弹出。
    • a2,值类型,它的值不受方法行为的影响。
    • a1,引用类型,但它的值被方法的行为改变了。

    二、引用参数

    使用引用参数时,必须在方法的申明和调用中都使用关键字ref修饰符。

    实参必须是变量,在用作实参前必须被赋值。如果是引用类型的变量,可以赋值为一个引用或者null值。

    下面的代码阐明了引用参数的声明和调用的语法:

      void MyMethod(ref int val)  //方法声明包含ref修饰符
      {
         //your code
       }
      int y = 1;
      MyMethod(ref y);   //方法调用
    
      MyMethod(ref 3+5);  //错误,形参必须是变量

    在第一小节的内容中我们知道,对于值参数,系统在栈上为形参分配内存,相反对于引用参数:

    • 不会为形参在栈上分配内存。
    • 实际情况是,形参的参数名将作为实参变量的别名,指向相同的内存位置

    由于形参名和实参名的行为,就好象指向相同的内存位置,所以在方法的执行过程中,对形参作的任何改变,在方法完成后依然有效(表现在实参变量上)。

    在方法的声明和调用上都使用关键字ref.

    下面的代码再次展示了方法MyMethod,但这一次参数是引用参数而不是值参数。

     1  class MyClass
     2     {
     3         public int Val = 20;
     4     }
     5     class Program
     6     {
     7 
     8         static void MyMethod(ref  MyClass f1,ref int f2)
     9         {
    10             f1.Val = f1.Val + 5;
    11             f2 = f2 + 5;
    12             Console.WriteLine("f1.Val: {0}, f2: {1}", f1.Val, f2);
    13         }
    14         static void Main(string[] args)
    15         {
    16             MyClass a1 = new MyClass();
    17             int a2 = 10;
    18 
    19             MyMethod(ref a1, ref a2);
    20 
    21             Console.WriteLine("f1.Val: {0}, f2: {1}", a1.Val, a2);
    22 
    23         }
    24     }

    同样,还是用图来阐明方法执行的不同阶段实参和形参的值。

    • 在方法被调用前,用作实参的a1,a2已经在栈里了。
    • 在方法的开始,形参名被设置为实参的别名。变量a1和f1引用相同的内存位置,a2和f2引用相同的内存位置。
    • 在方法的结束位置,f2和对象f1的字段都被加上了5。
    • 方法执行之后,形参的名称已经失效,但是值类型a2和引用类型a1所指向的对象的值都被方法内的行为改变了。

    三、引用类型作为值参数和引用参数

     对于一个引用类型对象,不管是将其作为值参数传递还是作为引用参数传递,我们都可以在方法成员内部修改它的成员。不过,我们并没有在方法内部设置形参本身。

     下面我们就来看看在方法内部设置形参本身时会发生什么。

    1、将引用类型对象作为值参数传递

     1  class MyClass
     2     {
     3         public int Val = 20;
     4     }
     5     class Program
     6     {
     7 
     8         static void RefAsParameter(MyClass f1)
     9         {
    10             f1.Val = 50;
    11             Console.WriteLine("After member assignment:   {0}", f1.Val);
    12             f1 = new MyClass();
    13             Console.WriteLine("After new object creation: {0}", f1.Val);
    14         }
    15         static void Main(string[] args)
    16         {
    17 
    18             MyClass a1 = new MyClass();
    19             Console.WriteLine("Before method  call:       {0}", a1.Val);
    20             RefAsParameter(a1);
    21             Console.WriteLine("After method  call:        {0}", a1.Val);
    22         }
    23     }

    这段代码的输出如下:

    Before method  call:       20
    After member assignment:   50
    After new object creation: 20
    After method  call:        50

    同样,还是用图来阐明以下几点。

    • 在方法开始时,实参和形参都指向堆中相同的对象。
    • 在为对象的成员赋值之后,他们仍指向堆中相同的对象。
    • 当方法分配新的对象并赋值给形参时,方法外部的实参仍指向原始对象,而形参指向的是新对象。
    • 在方法调用之后,实参指向原始对象,形参和新对象都会消失。

    2、将引用类型对象作为引用参数传递

    除了在方法声明和方法调用时使用ref关键字之外,与上面的代码完全一样。

     1   class MyClass
     2     {
     3         public int Val = 20;
     4     }
     5     class Program
     6     {
     7 
     8         static void RefAsParameter(ref MyClass f1)
     9         {
    10             f1.Val = 50;
    11             Console.WriteLine("After member assignment:   {0}", f1.Val);
    12             f1 = new MyClass();
    13             Console.WriteLine("After new object creation: {0}", f1.Val);
    14         }
    15         static void Main(string[] args)
    16         {
    17 
    18             MyClass a1 = new MyClass();
    19             Console.WriteLine("Before method  call:       {0}", a1.Val);
    20             RefAsParameter(ref a1);
    21             Console.WriteLine("After method  call:        {0}", a1.Val);
    22         }
    23     }

    这段代码的输出如下:

    Before method  call:       20
    After member assignment:   50
    After new object creation: 20
    After method  call:        20

    我们开始说过,引用参数的行为就是将实参作为形参的别名。

    • 在方法开始时,实参和形参都指向堆中相同的对象。
    • 在为对象的成员赋值之后,他们仍指向堆中相同的对象。
    • 当方法分配新的对象并赋值给形参时,形参和实参都指向新对象。
    • 在方法调用之后,实参指向方法内创建的新对象

    四、写在最后

    这些都是老生常谈的问题,为什么还要写?

    一是因为今天看书看到了与此相关的内容,回去翻了翻书,然后记录下来

    二是供自己以后查阅,毕竟看博客比翻书来的快。

    最后,祝大家周末愉快,玩的开心。

  • 相关阅读:
    iOS-MVC设计模式
    LoadRunner中文转码
    LoadRunner中Base64编码解码
    jmeter持续集成化(一)---jmeter+Ant+DOS构建执行脚本
    LoadRunner MD5加密
    Jmeter元件--BeanShell Timer
    ftp上传下载
    Shell编程实例
    Oracle数据库sqlplus操作
    oracle执行计划分析
  • 原文地址:https://www.cnblogs.com/liuyoung/p/7819052.html
Copyright © 2020-2023  润新知