C#值类型和引用类型

一,值类型特性

1.C#的所有值类型均隐式派生自System.ValueType。 

2.每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。例如:int i = new int();等价于:int i = 0;

3.所有的值类型都是密封(seal)的,所以无法派生出新的值类型。
4.值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。

二,引用类型的特性:
1.C#的所有引用类型均隐式派生自System.object。

2.引用类型可以派生出新的类型。

3.引用类型可以包含null值。

4.引用类型变量的赋值只复制对对象的引用,而不复制对象本身。

5.引用类型的对象总是在进程堆中分配(动态分配)。

解释下内存下的堆和栈的区别(堆栈是两种数据结构):

1、栈:由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
2、堆:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。

 PS:

1,所有继承System.Value的类型都是值类型,其他类型都是引用类型。
System.Enuml类继承自System.Value但是它是引用类型

2,C#枚举类型都是值类型,System.Enum不是枚举类型,

3,System.Enum是一个抽象类(abstract class),所有枚举类型都直接继承自它,当然也同时继承了它的所有成员。那么System.Enum属于引用类型

4,枚举类型继承自一个引用类型后,却还是值类型!所有的值类型都是System.ValueType的后代,枚举类型也不例外,枚举类型直接继承自System.Enum,而System.Enum却又直接继承自System.ValueType的,所以,枚举类型也是System.ValueType的后代。

5,正确的说法应该是“值类型都是System.ValueType的后代”,但System.ValueType的后代不全是值类型,System.Enum就是唯一的特例!在System.ValueType的所有后代中,除了System.Enum之外其它都是值类型。

三,引用类型和值类型的例子:

 PS:将一个值类型的值赋值给另一个值类型:int a = 1; int b = a;这里的意思是将a的值拷贝给b,而如果定义一个还没初始化的引用类型时:A a = new A(); A a1; a1 = a;这里的意思a1和a同时指向于同一块内存,所以若a的值改变会影响a1的值

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace value
{
    class Program
    {
        static void Main(string[] args)
        {
            //引用类型输出
            //理解一:当两个引用类型(t,t1)同时引用同一个对象时,更改这个对象的值(A)时,两个引用类型同时变化
            Test t = new Test(100, 200);
            Test t1 = t;
            t.A = 200;
            Console.WriteLine("{0},{1}", t.A, t1.A);  //200 200

            //理解二:当两个引用类型(t3,t4)先同时引用同一个对象时,而当给t3引用一个新的对象,而t4依旧引用旧的对象,并没有受到t3的影响
            //则此时更改t3的A值不会影响到t4的A的值
            Test t3 = new Test(100, 200);
            Test t4 = t3;
            t3 = new Test(300, 600);
            t3.A = 200;
            Console.WriteLine("{0},{1}", t3.A, t4.A);  //200 100

            //值类型输出,理解两个结构体的值互补影响
            MyStruct s = new MyStruct(100, 200);
            MyStruct s1 = s;
            s.A = 200;
            Console.WriteLine("{0},{1}", s.A, s1.A); //200 100
            Console.ReadKey();
        }

        //引用类型
        public class Test
        {
            public int A;
            public int B;

            public Test(int a, int b)
            {
                this.A = a;
                this.B = b;
            }
        }

        //值类型
        public struct MyStruct
        {
            public int A;
            public int B;

            public MyStruct(int a, int b)
            {
                this.A = a;
                this.B = b;
            }
        }
    }
}

以上输出:

200,200 

200,100

 综上:

1,Test类,我们Test t =new Test();这样实例t对象,则已经在内存分配了块空间,然而如果是这样Test t1;这样定义,是还没有在内存为其分配空间

2,然而Test t1 = t;在这里有指的是将t1指向t所分配的内存空间,所以t和t1是两个对象的数据是一样的,则会出现上面程序调试的出一致的结果

3,那Test t =new Test();又怎么理解?

理解:

1》如果仅仅写Test t:表示创建了一个对象但是没有分配内存空间,实际上没有初始化

2》而Test t =new Test();表示创建了一个对象,并且调用这个类的构造函数初始化对象,Test()这个是构造函数,用来做初始化。

四,理解完值类型和引用类型,同时理解下装箱和拆箱

装箱:将值类型装换为引用类型

拆箱:将引用类型装换为值类型

然而在理解装箱和拆箱时会有下面几个容易理解错的知识点,上代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ObjectType
{
    class Program
    {
        static void Main(string[] args)
        {
            //一,当将值类型赋值给object,这里是拆箱和装箱
            object a = "a的初始值";
            object b = a;
            //不提示无法将类型Int隐式装换为string,这里是值类型装换为引用类型,即是拆箱和装箱的理解,不是用引用类型来理解
            //即是使用变量a和b都引用了("a的初始值")这个装箱的结果
            //而当a = 1;时的意思是:使用变量a引用了("a的修改值")这个装箱的结果,而b依旧是使用("a的初始值")这个装箱的结果没变化
            a = "a的修改值";
            Console.WriteLine(b);       //输出值为:a的初始值
            //Console.WriteLine(a.GetType());
            //Console.WriteLine(b.GetType());

            //二,当将引用类型赋值给object,并不属于拆箱装箱
            object a1 = new UsingType("a1的初始值"); //object 是内置引用类型之一。
            object b1 = a1;
            //重新赋值的意思是:a1引用了新的对象,但是因为b1变量还引用旧的对象,则旧的对象没有被GC销毁,所以b1的值不改变,而a1引用新的对象,值出现变化
            a1 = new UsingType("a1的修改值");
            Console.WriteLine(b1);    //输出值为:a的初始值


            //三,var 和 object 的区别
            //理解var是有类型的,就例如,当你定义了var a,机器判定了a是int型的,就会以int型的方式保存数据,和你直接定义 int 是一样的。
            var c = "c的初始值";
            object c1 = "c1的初始值"; 
            //c = 1;     //无法将类型Int隐式装换为string
            c1 = 1; //这里不提示错误
            var c2 = new UsingType("c2的初始值");
            object c3 = new UsingType("c3的初始值");
            Console.WriteLine(c);
            Console.WriteLine(c2.Str);
            //Console.WriteLine(c3.Str);   //这里的c3是用不了UsingType类中的Str属性

            Console.Read();
        }
    }
    public class UsingType {
        public UsingType(string str) {
            this.Str = str;
        }
        public override string ToString()
        {
            return this.Str.ToString();
        }
        public string Str { get; set; }

    }
}

五,拆箱装箱性能消耗的原因: 

1》在类型系统中,任何值类型和引用类型都可以和object类型进行转化,装箱转化 是指将一个值类型显式或者隐式的转化为一个object类型,或者是转化成一个被该值类型应用的接口类型,

2》将一个值类型装箱,就创建了一个object实 例,并且将这个值赋值给了object,object对象的数据位于堆中,在栈上有对该对象的引用,而被装箱的类型的值是被作为一个复制的文件赋给对象 的

3》所谓拆箱,就是装箱操作的反操作,复制堆中的对象至栈中,并且返回其值。

六,那堆和栈又怎么理解?

1》“栈”其实就是一种后入先出(LIFO)的数据结构。在我们.NET Framework里面,由CLR负责管理,我们程序员不用去担心垃圾回收的问题;每一个线程都有自己的专属的“栈”。

2》“堆”的存放就要零散一些,并且由 Garbage Collector(GC)执行管理,我们关注的垃圾回收部分,就是在“堆”上的垃圾回收;其次就是整个进程共用一个“堆”。

3》如果是引用类型里面存在存在类型的话,那这个引用类型的值类型该存堆还是栈?

结果是:引用类型中的值类型存在栈中,但是“栈”上有指向这个引用类型中的值类型变量的指针,所以引用类型中的值类型被使用完同时会自动回收

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DuiZhan
{
    class Program
    {
        static void Main(string[] args)
        {
            //实际上引用类型的中的值类型也是存在栈中,并且由一个位于“栈”上的指针引用,即是栈中存的值类型会被引用,在函数执行完毕后,“栈”同样会被清空。
            //而引用类型就需要被GC回收

            //例子一 引用同一对象的引用类型的值类型
            //当引用类型中存在值类型是,值类型的值存在堆还是栈呢?
            TestInt ti = new TestInt();
            ti.Val = 1;
            TestInt ti2 = ti;
            ti2.Val = 2;
            Console.WriteLine(ti.Val);  //输出2

            //例子二 引用不同对象的引用类型的值类型
            //因为重新初始化,重新分配内存空间,即使将ti值付给ti3,在更改ti3的值,却不会影响ti的值
            //原因ti和ti3并不是引用同一对象,
            TestInt ti3 = new TestInt(); 
            ti3 = ti;
            ti3.Val = 3;
            Console.WriteLine(ti.Val); //输出3
        }
    }

    //引用类型
    public class TestInt
    {
        public int Val;
    }
}

六,在程序运行中解析

1,任何一个操作的响应都在一个线程里面,每个线程都有自己的操作内存,叫线程栈,
2,线程栈随着线程的执行完毕,值都要被释放的,
3,值类型是存在线程栈里面的,而引用类型变量在栈里,可是引用类型的值存在堆里,就是变量的地址指向堆

七,解析

1,值类型出现在线程栈:每次调用都有线程栈,,用完自己就结束,变量 - 值类型 都会释放的
2,引用类型出现在堆里:全局就一个堆,空间有限,所以才需要垃圾回收
3,操作系统里面,内存是链式分配的,可能有碎片的
4,CLR的堆:连续分配(数组),空间有限,节约空间
5,每个应用程序的堆是固定大小的,不会增加

原文地址:https://www.cnblogs.com/May-day/p/6431301.html