C#中值传递和引用传递

    一. 传递的概念

    传递是指在调用方法时,用实际参数为方法的形式参数赋值。方法的形式参数,按照参数类型划分有两种,分为是值类型和引用类型。

    二. 数据类型

    数据类型是计算机科学核心概念之一。在C#中,数据类型分两大类,值类型和引用类型。

    写程序,首先面对的一个概念是变量。什么是变量?

    变量的定义:变量是指在程序运行过程中,其值可以改变的量。

    变量三要素,分别是变量的名称(name)、变量的类型(type)、变量的值(value)。其中,变量的名称,也可以理解为变量的地址,变量在内存单元意义上的地址,变量名其实就是内存单元的名称。

    变量名称决定了存储变量的内存地址;变量类型(数据类型)决定了存储变量的内存空间大小,同时也决定了变量的值的上限和下限。

    为什么要提变量三要素呢?因为值类型和引用类型的区别,就要从变量的三要素说起。

    值类型和引用类型的区别有两点:1.数据的存储方式;2.数据的传递方式(复制方式)。

    可以简单的认为,对于一个值类型变量,只存储变量三要素中的值。所以,值类型变量在进行数据传递时,只传递三要素中的值;对于一个引用类型的变量,它存储的是变量三要素中变量的地址,同时,在进行数据传递时,传递的也是变量的地址。

    怎么理解这段话呢?我们来个程序示例,在程序中感受下这种区别。

(1)新建一个Window窗体应用程序项目,项目名称“值传递和引用传递”。为项目添加一个类文件,保存为General.cs。

         在类文件中输入如下代码:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Text;
 4 
 5 namespace 值传递和引用传递
 6 {
 7     class General
 8     {
 9         public string Name { get; set; } //姓名
10         public int Age { get; set; } //年龄
11     }
12 }

 (2)在项目默认创建的窗体Form1中添加一个按钮button1,将其Text属性改为“确定”,在窗体的代码文件中分别添加两个方法ChangeGeneralInfoFirst和ChangeGeneralInfoSecond,并创建button1的事件处理过程,然后输入以下代码:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel;
 4 using System.Data;
 5 using System.Drawing;
 6 using System.Text;
 7 using System.Windows.Forms;
 8 
 9 namespace 值传递和引用传递
10 {
11     public partial class Form1 : Form
12     {
13         public Form1()
14         {
15             InitializeComponent();
16         }
17         //自定义方法
18         void ChangeGeneralInfoFirst(string name, int age)
19         {
20             name = "徐达";
21             age = 41;
22         }
23         //自定义方法
24         void ChangeGeneralInfoSecond(General general)
25         {
26             general.Name = "霍去病";
27             general.Age = 19;
28         } 
29         //按钮button1的事件处理过程
30         private void button1_Click(object sender, EventArgs e)
31         {
32             General newGeneral = new General();
33             newGeneral.Name = "卫青";
34             newGeneral.Age = 38;
35 
36             string testStr = "";
37             testStr = string.Format("调用前,结果为:{0},{1}\r\n",newGeneral.Name,newGeneral.Age);
38 
39             ChangeGeneralInfoFirst(newGeneral.Name, newGeneral.Age);//传递值
40             testStr += string.Format("值传递调用后,结果为:{0},{1}\r\n",newGeneral.Name,newGeneral.Age);
41 
42             ChangeGeneralInfoSecond(newGeneral);//传递对象(引用传递)
43             testStr += string.Format("引用传递调用后,结果为:{0},{1}\r\n",newGeneral.Name,newGeneral.Age);
44 
45             MessageBox.Show(testStr,"消息",MessageBoxButtons.OK,MessageBoxIcon.Asterisk);
46         }        
47     }
48 }

代码说明:

(1)ChangeGeneralInfoFirst(string name,int age)方法中包含两个值类型形式参数,在调用该方法时,如果将General类的对象newGeneral的属性值作为实际参数传递给该方法对应位置的形式参数,为形式参数赋值。调用完该方法后,方法体中代码对形式参数的值进行了改变,并不改变General类的对象newGeneral原来的属性值。

(2)ChangeGeneralInfoSecond(General general)方法中的参数是引用类型,在调用该方法时,如果将General类的对象newGeneral作为实际参数传递(赋值)给该方法的形式参数general,此时newGeneral和general都指向同一个对象。在方法体内如果通过形式参数修改了对象的属性值,那么在方法执行完毕后实际参数general的属性值也会发生改变。

(3)在button1的事件处理过程中,首先创建了General对象,并对Name和Age属性进行了赋值,然后将调用ChangeGeneralInfoFirst和ChangeGeneralSecond方法前后的属性值进行比较。从而得出结论:

如果是值传递,不影响实际参数的值;如果是引用传递,在方法中如果改变了对象的属性,那么实际参数指向的对象的属性也将改变。

“ 值类型用作方法参数时,用实际参数给方法的形式参数赋值时,只是将实际参数的值赋值给形式参数,方法中形式参数的值修改了,实际参数的值,并不改变;

引用类型用作方法参数时,用实际参数给方法的形式参数赋值时,是将实际参数的地址赋值给形式参数,这样一来,方法中形式参数的值修改了,同时也把实际参数的值一并修改。

我们来看一下程序运行结果,如下图所示:

 特别注意:

引用类型作为方法的形式参数时,

1.如果方法体内的代码是【修改变量本身】时,调用方法时,结果类似值传递,即不会改变传递前的变量的值;

2.如果方法体内的代码是【修改变量的属性或字段】时,调用方法时,才是引用传递,会影响传递前的变量的值。

3.方法的形式参数使用了ref后,才是真正的引用传递。无论方法体内的代码是【修改变量本身】还是【修改变量的属性或字段】,都会影响传递前的变量的值。

关于第2点,上述程序示例中的ChangeGeneralInfoSecond(General general)方法,方法体就是在【修改变量的属性或字段】。

关于第1点,关于方法体【修改变量本身】,我们看下面这个程序示例;

 

 1 using System;
 2 using System.Collections.Generic;
 3 using System.ComponentModel;
 4 using System.Data;
 5 using System.Drawing;
 6 using System.Text;
 7 using System.Windows.Forms;
 8 
 9 namespace 值传递和引用传递
10 {
11     public partial class Form1 : Form
12     {
13         public Form1()
14         {
15             InitializeComponent();
16         }
17         /*
18          * 引用类型用作形式参数
19          * 方法中修改是变量本身,即对象本身时
20          * 方法的执行结果,类似值传递,即不会改变方法执行前变量的值
21          * */
22         void SwapObject(General first,General second)
23         {
24             General temp = first;
25             first = second;
26             second = temp;
27         }
28 
29         private void button2_Click(object sender, EventArgs e)
30         {
31             General one = new General();
32             General two = new General();
33 
34             one.Name = "张无忌";
35             one.Age = 25;
36 
37             two.Name = "常遇春";
38             two.Age = 30;
39 
40             string str = "";
41             str = string.Format("SwapObject交换前,结果为:\r\none.Name ={0},one.Age ={1}\r\ntwo.Name ={2},two.Age ={3}\r\n", one.Name, one.Age, two.Name, two.Age);
42             SwapObject(one,two);
43             str += string.Format("SwapObject交换后,结果为:\r\none.Name ={0},one.Age ={1}\r\ntwo.Name ={2},two.Age ={3}\r\n", one.Name, one.Age, two.Name, two.Age);
44 
45             MessageBox.Show(str, "消息",MessageBoxButtons.OK,MessageBoxIcon.Asterisk);
46         }
47     }
48 }

 

程序执行结果为:

为了验证第3点,我们将上述代码的第22行和第42行修改为:

 1 void SwapObject(ref General first,ref General second)
 2 
 3 SwapObject(ref one,ref two);

    三.数据类型的划分

    上面例子提到,方法ChangGeneralInfoFirst(string name,int age)的形式参数是值类型,方法ChangeGeneralInfoSecond(General general)的形式参数是引用类型。那么问,我们如何区分一个给定的形式参数,是属于值类型呢还是引用类型呢?

    关于值类型和引用类型的划分,具体见下图:

   

 由上表可以得知,string类型是引用类型,但是,上面方法ChangGeneralInfoFirst(string name,int age)的形式参数是值类型。是不是有些矛盾?string类型到底是引用类型还是值类型?

看下面这段代码:

 1            //值类型
 2             int a = 1;
 3             int b = a;
 4             a = 2;
 5             string str = "";
 6             str= string.Format("a={0}\r\nb={1}\r\n",a,b);
 7             //引用类型
 8             string str1 = "ab";
 9             string str2 = str1;
10             str1 = "abc";
11             str += string.Format("str1={0}\r\nstr2={1}\r\n",str1,str2);
12 
13             MessageBox.Show(str,"消息",MessageBoxButtons.OK,MessageBoxIcon.Asterisk);

执行结果:

从运行结果可以看出,str2并没有随着st1的改变而改变。如果string是引用类型,str1和st2应该是指向同一个内存单元,如果st1内容发生变化,st2也应该跟着变化。由此例看,string更像值类型。但是MSDN上明确指出,string类型是引用类型。这是为何呢?究其原因,是因为string类型的对象是不可变的,包括长度和其中的任何字符都是不可以改变的。

string类型的不变性

    string对象称为不可变的(只读),因为一旦创建了该对象,就不能修改该对象的值。有时候看起来似乎改了,实际上string经过了特殊处理,每次改变值时都会建立一个新的string对象,变量会指向这个比较新的对象,而原来的还是指向原来的对象,所以不会改变。这也是string效率低下的原因。如果经常改变string的值则应该使用StringBuilder而不使用string。

在上述例子中,str1 = "ab",这时在内存中将"ab"存下来,如果在创建字符串对象st2,其值也等于"ab",即str2 = str1,则并非再重新分配内存空间,而是将之前保存"ab"的地址赋给str2。而当str1 = "abc",str1的值发生改变时,这时检查内存,发现不存在"abc"的字符串,则重新分配内存空间,存储"abc",并将其地址付给str1,而str2依旧指向保存字符串"ab"的内存地址。上述例子的结果,可以印证这段解释。

结论

string类型是引用类型,只是编译器对其做了特殊处理。

本文参考文献:

1.《C#程序设计基础与应用》,严健武 严耿超 李彬 主编,2019年7月第1版;

2.C#中值传递和引用传递是什么?

3.string类型到底是值类型还是引用类型?

posted @ 2022-10-12 18:29  美人她爹  阅读(1001)  评论(0编辑  收藏  举报