C#菜鸟之旅------值类型,引用类型

       昨天在学习设计模式的时候,遇到了“原型模式”,在这个模型中有一个很关键的点就是:  传值和传址。针对这个问题我们小组又重新回到小杨视频中去学习了一番,貌似很清晰,但是回到设计模式之后每个人的意见也是大不相同。  

       所以针对值类型和引用类型这个问题,我展开了一系列的学习。下面是我的一些收获,分享给大家。

       在C#中的类型分为俩种:  值类型和址类型

 注:   结构体属于值类型

           所有的类都属于引用类型,包括接口。

值类型包括:数值类型,结构体,bool型,用户定义的结构体,枚举,可空类型。

引用类型包括:数组,用户定义的类、接口、委托,object,字符串,null类型,类。

值类型和引用类型的区别是什么: 

        值类型的实例都被分配到线程栈当中,所有的引用类型实例都被分配在托管堆上

性能:

        由于值类型实例的值是自己本身,而引用类型的实例的值是一个引用,所以如果将一个值类型的变量赋值给另一个值类型的

变量,会执行一次逐字段的复制,将引用类型的变量赋值给另一个引用类型的变量时,只需要复制内存地址。

例如下面的代码: 

    class SomRef
    {
        public int x;
    }

    struct SomeVal {
        public int x;
    }

    class Program
  {
        static void ValueTypeDemo()
       {
            SomRef r1 = new SomRef();//在堆上分配
            SomeVal v1 = new SomeVal();//在栈上分配
            r1.x = 5;//提领指针
            v1.x = 5;//在栈上修改
            SomRef r2 = r1;//只复制引用(指针)
            SomeVal v2 = v1;//在栈上分配并复制成员
        }
    }

再来一个例子:

   public class Person
   {
       public string Name { get; set; }// 定义个Name 属性
        public int Age { get; set; }//定义个Age 属性
    }
     
    public static class ReferenceAndValue
    {
        public static void Demonstration()
      {
         Person zerocool = new Person { Name = "ZeroCool", Age = 25 };
           Person anders = new Person { Name = "Anders", Age = 47 };
   
          int age = zerocool.Age;
          zerocool.Age = 22;
          Person guru = anders;// 这个用到了引用,引用了前面定义好的 类anders;
           anders.Name = "Anders  Hejlsberg";
         Console.WriteLine("zerocool's age:\t{0}", zerocool.Age);
           Console.WriteLine("age's value:\t{0}", age);
           Console.WriteLine("anders' name:\t{0}", anders.Name);
          Console.WriteLine("guru' name:\t{0}", guru.Name);
      }
   }

首先我们定义了一个  Person 类,毋庸置疑 这个类是引用类型

然后,我们声明了两个Person类的实例对象,zerocool和anders,前面提到过,这两个对象都被分配在堆上,而zerocool和anders本身其实只是对象所在内存区域的起始地址引用,换句话说就是指向这里的指针。

我们声明一个值类型变量age,直接在初始化时把zerocool的Age值赋给它,显然,age的值就是25了

我们声明age值类型变量,并将zerocool.Age赋给它,编译器在栈上分配了一块空间,然后把zerocool.Age的值填进去

但是引用类型就不一样了,我们在声明guy的时候把anders赋给它,前面说过,引用类型包含的是只想堆上数据区域地址的引用,其实就是把anders的引用也赋给guy了,因此这二者从此指向了同一块内存区域,既然是指向同一块区域,那么甭管谁动了里面的“奶酪”,另一个变现出来的结果也会跟着变

最终结果:

一个特殊的引用类型: String

昨天我们在讨论设计模式的时候,遇到了一个尖锐的问题,那就是String类型在网上说的也是一种引用类型,但是为什么它的结果和普通的引用类型不同呢?

因为它是一种特殊的引用类型。

string与引用类型在常见的操作上有一些区别。

例如字符串是不可改变的。修改其中一个字符串,就会创建一个全新的string对象,而另一个字符串不会发生任何变化

using System;

class StringExample
{

    public static int Main()
    {

           string s1 ="aaaa";

           string s2 = s1;

           Console.WriteLine("s1:" + s1);

           Console.WriteLine("s2:" + s2);

           s1 = "bbbb";

           Console.WriteLine("s1:" + s1);

           Console.WriteLine("s2:" + s2);

           return 0;
    }

}

输出结果:
s1: aaaa
s2: aaaa
s1: bbbb
s2: aaaa

改变s1的值对s2没有影响,这与引用类型的操作相反,当用"aaaa"初始化s1时,就在堆上分配了一个新的string对象。在初始化s2时,引用也指向这个对象,所以s2的值也是"aaaa",但是当改变s1的值时,并不会替换原来的值,堆上会为新值分配一个新的string对象

然后竟然还有人怀疑我,于是我画了一张图来解释这个现象:

之后有伙伴认为数组和字符串都是同样的效果,然后我们又通过一个代码来解决了一下:
 

static void Main(string[] args)
        {
            int[] s1 = new int[] { 1, 2, 3 };

            int[] s2 = s1;

            Console.WriteLine("s1:{0}{1}{2}", s1[0], s1[1], s1[2]);

            Console.WriteLine("s2:{0}{1}{2}", s2[0], s2[1], s2[2]);

            s1[1] = 4;

            Console.WriteLine("s1:{0}{1}{2}", s1[0], s1[1], s1[2]);

            Console.WriteLine("s2:{0}{1}{2}", s2[0], s2[1], s2[2]);

            Console.ReadKey();
        }


最终显示结果
s1:123
s2:123
s1:143
s2:143

显然结果是不一样的,所以这样我们就可以用一般的引用类型“数组”来衬托出特殊引用类型“string”,他们是不同的

注意:

static void StrChange(string str) 是值传递

static void StrChange(ref string str) 是引用传递

猜你喜欢

转载自blog.csdn.net/qq_30631063/article/details/86550140