C#两种数据类型:值类型和引用类型

C#值参数和引用参数

首先大概了解一下什么是值类型和引用类型:
概念:值类型直接存储其值,而引用类型存储对其值的引用。

值类型:byte,short,int,long,float,double,decimal,char,bool 和 struct 统称为值类型。
引用类型:string 和 class统称为引用类型。
  • 值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
  • 引用类型当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
  • 值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。
  • 引用类型的对象总是在进程堆中分配(动态分配)。

一、值参数
未用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

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

  • 在方法开始时,实参和形参都指向堆中相同的对象。
  • 在为对象的成员赋值之后,他们仍指向堆中相同的对象。
  • 当方法分配新的对象并赋值给形参时,形参和实参都指向新对象。
  • 在方法调用之后,实参指向方法内创建的新对象
    在这里插入图片描述
    转载自:

https://www.cnblogs.com/liuyoung/p/7819052.html
https://www.cnblogs.com/bakuhert/articles/5878086.html

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值