zoukankan      html  css  js  c++  java
  • C#引用类型与值类型浅析

    1引言

            对值类型和引用类型理解,是理解C#语言基础的重要主题之一。这里会介绍一下内容:

      • 1)值类型和引用类型的内存分配情况
      • 2)什么时候用值类型,什么时候用引用类型。
      • 3)装箱与拆箱

     1.1值类型与引用类型内存分配

          值类型:值类型实例要么分配在栈上,要么分配在堆上(此时以类型的字段存在托管堆上)。值类型包括简单类型、结构等。定义值类型用关键字Struct。(由于值类型均是密封的,意味着所有定义的值类型均继承字ValueType,且不能派生,但是可以实现接口),如果值类型实例分配在栈上,那么方法调用退出后则销毁;若位于GC堆上,则随其所在对象消亡而消亡,主要由GC控制。由于值类型本身包含值,通常分配在栈上,访问无需地址转换,分配空间自动释放,所以值类型的效率更高。

         引用类型:引用类型实例分配在托管堆上,而引用类型变量仅仅是持有对象在托管堆上的引用,等于是建立了实例和变量之间的关系。定义引用类型通常用关键字calss,interface,delegate.引用类型实例由GC控制回收。

         如下:在Main方法中定义了值类型变量a,引用类型变量stu   

        class Program
        {
            static void Main(string[] args)
            {
                Pointer a;
                a.X = 100;
                a.Y = 200;
                Console.WriteLine("鼠标当前位置为{0},{1}",a.X,a.Y);
    
                Student stu=new Student("Josh","Smith",100,Sex.Shemale);
                stu.Learn();
            }
        }
    
        /// <summary>
        /// 定义一个代表鼠标位置的pointer类
        /// </summary>
        public struct Pointer
        {
            /// <summary>
            /// 水平位置
            /// </summary>
            public  int X ;
    
            /// <summary>
            /// 垂直位置
            /// </summary>
            public int Y ;
        }
    
        /// <summary>
        /// 学生
        /// </summary>
        public class Student
        {
            //名称
            private string _lastName;
    
            //
            private string _firtName;
    
            //名子
            public string Name
            {
                get { return _firtName+ _lastName; }
            }
    
            private Sex _sex=Sex.UnKnow;
    
            /// <summary>
            /// 性别
            /// </summary>
            public Sex Sex
            {
                get { return _sex; }
            }
    
    
            /// <summary>
            /// 年龄
            /// </summary>
            public int Age { get; set; }
    
    
            /// <summary>
            /// 学习
            /// </summary>
            public void Learn()
            {
                Console.WriteLine("{0}学生学习",Name);
            }
    
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="argFirstName"></param>
            /// <param name="argLastName">名称</param>
            /// <param name="argAge">年龄</param>
            /// <param name="argSex">性别</param>
            public Student(string argFirstName, string argLastName, int argAge, Sex argSex)
            {
                _firtName= argFirstName;
                _lastName= argLastName;
                _sex = argSex;
                Age = argAge;
            }
        }
    
        /// <summary>
        /// 人的性别
        /// </summary>
        public enum Sex
        {
            /// <summary>
            /// 男性
            /// </summary>
            Man,
            
            /// <summary>
            /// 女性
            /// </summary>
            Women,
            
            /// <summary>
            /// 人妖
            /// </summary>
            Shemale,
       
            /// <summary>
            /// 未知
            /// </summary>
            UnKnow,
        }

    值类型变量a,和引用类型变量stu所引用的实例内存概况如下图

     

      1.2什么时候用值类型,什么时候用引用类型?

               引用类型有行为、有多态、有继承。可以发现只要用到struct的场合,基本都可以用class替代。那么为什么还要有值类型呢?答案显而易见是效率问题。我们知道引用类型分配在堆上,而堆上的每个对象都有额外的一些成员(同步索引块,类型对象指针(指向类型对象的方法表)),这些都要初始化;另外从托管堆上分配一个对象,可能强制执行一次垃圾回收操作。由上可见软件系统的所有类型全是引用类型,那么引用程序的性能会显著下降。而值类型通常分配在栈上,通常为较小的带有数值含义的数据结构,访问的时候不需要像访问对象那样通过引用类型变量查找,使用完也是自动释放,这样效率就会很高。

               通常定义类型的时候应该优先考虑值类型,因为它的效率更高。值类型一般在以下场合使用:

                    1类型本身数据量较小,且主要用于存储数据,表现出明显的数值含义。

                    2类型不需要从其他任何类型继承(接口除外),也不派生出任何其他类型

     相反,选用引用类型的时候会考虑其 1本身数据结构较大;2可能会扩展(即会派生)。目前个人知识有限,貌似也只能想到这两点。

    3装箱与拆箱

                  1概念:装箱与拆箱,就是值类型与引用类型的转换。装箱就是值类型数据转换为引用对象,可以将值类型视为对象来处理,通常转换为Object类型的或者该值类型实现的任何接口引用类型;拆箱就是引用类型转换为值类型,通常伴随着从堆中复制对象的数据字段的操作。注:只有被装过箱的对象才能被拆箱。

                 2先看一段代码

      static void Main(string[] args)
            {
                int x = 100;
                object o = x;//装箱
                int y = (int) o;//拆箱
            }

                装箱过程三步曲:

      • 内存分配:  在托管堆中分配内存空间,内存大小为待装箱值类型的大小加上其他额外的内存空间(主要为类型对象指针和同步索引块)
      • 实例拷贝:将值类型的字段值拷贝到新分配的内存中
      • 引用返回:将托管堆中的对象地址返回给新的引用类型变量 (即例子中的对象o)

               拆箱过程两部曲

      •  实例检查:检查对象是否为null,检查对象是否为给定值类型的装箱值,不满足检查条件抛异常。
      •  取数据字段地址:从实例中获取数据字段的地址(本例中为o中存储100至的地址)
      •  值拷贝:根据获取的数据字段地址将值拷贝到基于栈的值类型的实例中(本例中的y)

       总结:装箱拆箱会带来大量的性能损失,我们平时写程序的时候应尽可能用FCL提供的重载方法或者泛型。如大家经常用的StringBuilder 用来拼接大量的字符串 比用"+"性能要高的多。

     

  • 相关阅读:
    html5+css3中的background: -moz-linear-gradient 用法 (转载)
    CentOS 安装Apache服务
    Linux 笔记
    CURL 笔记
    Spring Application Context文件没有提示功能解决方法
    LeetCode 389. Find the Difference
    LeetCode 104. Maximum Depth of Binary Tree
    LeetCode 520. Detect Capital
    LeetCode 448. Find All Numbers Disappeared in an Array
    LeetCode 136. Single Number
  • 原文地址:https://www.cnblogs.com/gzrnet/p/3204321.html
Copyright © 2011-2022 走看看