.NET基础拾遗(1)类型语法基础和内存管理基础1

一、基础类型和语法

1.1 .NET中所有类型的基类是什么?

  在.NET中所有的内建类型都继承自System.Object类型

1.2 值类型和引用类型的区别?

  在.NET中的类型分为值类型和引用类型,它们各有特点,其共同点是都继承自System.Object,但最明显的区分标准却是是否继承自System.ValueType(System.ValueType继承自System.Object),也就是说所有继承自System.ValueType的类型是值类型,而其他类型都是引用类型。常用的值类型包括:结构、枚举、整数型、浮点型、布尔型等等;而在C#中所有以class关键字定义的类型都是引用类型。

PS:严格来讲,System.Object作为所有内建类型的基类,本身并没有值类型和引用类型之分。但是System.Object的对象,具有引用类型的特点。这也是值类型在某些场合需要装箱和拆箱操作的原因。

  (1)赋值时的区别

  这是值类型与引用类型最显著的一个区别:值类型的变量直接将获得一个真实的数据副本,而对引用类型的赋值仅仅是把对象的引用赋给变量,这样就可能导致多个变量引用到一个对象实例上

  (2)内存分配的区别

  引用类型的对象将会在堆上分配内存,而值类型的对象则会在堆栈上分配内存。堆栈空间相对有限,但是运行效率却比堆高很多。

  (3)继承结构的区别

  由于所有的值类型都有一个共同的基类System.ValueType,因此值类型具有了一些引用类型所不具有的共同性质,比较重要的一点就是值类型的比较方法:Equals。所有的值类型已经实现了内容的比较(而不再是引用地址的比较),而引用类型没有重写Equals方法还是采用引用比较。

1.3 装箱和拆箱的原理?

  (1)装箱:CLR需要做额外的工作把堆栈上的值类型移动到堆上,这个操作就被称为装箱。

  (2)拆箱:装箱操作的反操作,把堆中的对象复制到堆栈中,并且返回其值。

  装箱和拆箱都意味着堆和堆栈空间的一系列操作,毫无疑问,这些操作的性能代价是很大的,尤其对于堆上空间的操作,速度相对于堆栈的操作慢得多并且可能引发垃圾回收,这些都将大规模地影响系统的性能。因此,我们应该避免任何没有必要的装箱和拆箱操作

  如何避免呢,首先分析装箱和拆箱经常发生的场合:

  ①值类型的格式化输出

  ②System.Object类型的容器

  对于第①种情况,我们可以通过下面的改动示例来避免:

    int i = 10;
    Console.WriteLine("The value is {0}", i.ToString());

  对于第②种情况,则可以使用泛型技术来避免使用针对System.Object类型的容器,有效避免大规模地使用装箱和拆箱:

    ArrayList arrList = new ArrayList();
    arrList.Add(0);
    arrList.Add("1");
    // 使用泛型数据结构代替ArrayList
    List<int> intList = new List<int>();
    intList.Add(1);
    intList.Add(2);

1.4 struct和class的区别,struct适用于哪些场合?

  首先,struct(结构)是值类型,而class(类)是引用类型,所有的结构对象都分配在堆栈上,而所有的类对象都分配在堆上。

  其次,struct与class相比,不具备继承的特性,struct虽然可以重写定义在System.Object中的虚方法,但不能定义新的虚方法和抽象方法。

  最后,struct不能有无参数的构造方法(class默认就有),也不能为成员变量定义初始值。

    public struct A
    {
        public int a = 1; // 这里不能编译通过
    }

  结构对象在构造时必须被初始化为0,构造一个全0的对象是指在内存中为对象分配一个合适的空间,并且把该控件置为0。

  如何使用struct or class?当一个类型仅仅是原始数据的集合,而不需要复杂的操作时,就应该设计为struct,否则就应该设计为一个class

1.5 C#中方法的参数传递有哪几种方式?

  (1)ref关键字:引用传递参数,需要在传递前初始化;(ref 要求参数在传入前被初始化)

  (2)out关键字:引用传递参数,需要在返回前初始化;(out 要求参数在方法返回前被初始化)

  ref和out这两个关键字的功能极其类似,都用来说明该参数以引用方式进行传递。大家都知道,.NET的类型分为引用类型和值类型,当一个方法参数是引用类型时,传递的本质就是对象的引用。所以,这两个关键字的作用都发生在值类型上

  (3)params关键字:允许方法在定义时不确定参数的数量,这种形式非常类似数组参数,但形式更加简洁易懂。

  But,params关键字的使用也有一定局限:当一个方法申明了一个params参数后,就不允许在其后面再有任何其他参数

  例如下面一段代码,定义了两个完全相等的方法:NotParams和UseParams,使用由params修饰参数的方法时,可以直接把所有变量集合传入而无须先申明一个数组对象。

    class Program
    {
        static void Main(string[] args)
        {
            // params
            string s = "I am a string";
            int i = 10;
            double f = 2.3;
            object[] par = new object[3] { s, i, f };
            // not use params
            NotParams(par);
            // use params
            UseParams(s, i, f);

            Console.ReadKey();
        }

        // Not use params
        public static void NotParams(object[] par)
        {
            foreach (var obj in par)
            {
                Console.WriteLine(obj);
            }
        }

        // Use params
        public static void UseParams(params object[] par)
        {
            foreach (var obj in par)
            {
                Console.WriteLine(obj);
            }
        }
    }

1.6 浅复制和深复制的区别?

  (1)浅复制:复制一个对象的时候,仅仅复制原始对象中所有的非静态类型成员和所有的引用类型成员的引用。(新对象和原对象将共享所有引用类型成员的实际对象)

  (2)深复制:复制一个对象的时候,不仅复制所有非静态类型成员,还要复制所有引用类型成员的实际对象

  下图展示了浅复制和深复制的区别:

  在.NET中,基类System.Object已经为所有类型都实现了浅复制,类型所要做的就是公开一个复制的接口,而通常的,这个接口会由ICloneable接口来实现。ICloneable只包含一个方法Clone,该方法既可以被实现为浅复制也可以被实现为深复制,具体如何取舍则根据具体类型的需求决定。此外,在Sys-tem.Object基类中,有一个保护的MemeberwiseClone()方法,它便用于进行浅度复制。所以,对于引用类型,要想实现浅度复制时,只需要调用这个方法就可以了:

    public object Clone()
    {
        return MemberwiseClone();
    }

  下面的代码展示了一个使用ICloneable接口提供深复制的简单示例:

    public class DeepCopy : ICloneable
    {
        public int i = 0;
        public A a = new A();

        public object Clone()
        {
            // 实现深复制-方式1:依次赋值和实例化
            DeepCopy newObj = new DeepCopy();
            newObj.a = new A();
            newObj.a.message = this.a.message;
            newObj.i = this.i;

            return newObj;
        }

        public new object MemberwiseClone()
        {
            // 实现浅复制
            return base.MemberwiseClone();
        }

        public override string ToString()
        {
            string result = string.Format("I的值为{0},A为{1}", this.i.ToString(), this.a.message);
            return result;
        }
    }

    public class A
    {
        public string message = "我是原始A";
    }

    public class Program
    {
        static void Main(string[] args)
        {
            DeepCopy dc = new DeepCopy();
            dc.i = 10;
            dc.a = new A();

            DeepCopy deepClone = dc.Clone() as DeepCopy;
            DeepCopy shadowClone = dc.MemberwiseClone() as DeepCopy;

            // 深复制的目标对象将拥有自己的引用类型成员对象
            deepClone.a.message = "我是深复制的A";
            Console.WriteLine(dc);
            Console.WriteLine(deepClone);
            Console.WriteLine();
            // 浅复制的目标对象将和原始对象共享引用类型成员对象
            shadowClone.a.message = "我是浅复制的A";
            Console.WriteLine(dc);
            Console.WriteLine(shadowClone);

            Console.ReadKey();
        }
    }

  其执行结果如下图所示,可以清楚地看到对深复制对象的属性的赋值不会影响原始对象,而浅复制则相反。

  从上面的代码中可以看到,在深复制的实现中,如果每个对象都要这样去进行深度复制就太麻烦了,可以利用序列化/反序列化来对对象进行深度复制:先把对象序列化(Serialize)到内存中,然后再进行反序列化,通过这种方式来进行对象的深度复制:

    [Serializable]
    public class DeepCopy : ICloneable
    {
        ......

        public object Clone()
        {
            // 实现深复制-方式1:依次赋值和实例化
            //DeepCopy newObj = new DeepCopy();
            //newObj.a = new A();
            //newObj.a.message = this.a.message;
            //newObj.i = this.i;

            //return newObj;
            // 实现深复制-方式2:序列化/反序列化
            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            bf.Serialize(ms, this);
            ms.Position = 0;
            return bf.Deserialize(ms);
        }

        ......
    }
    [Serializable]
    public class A
    {
        public string message = "我是原始A";
    }

PS:一般可被继承的类型应该避免实现ICloneable接口,因为这样做将强制所有的子类型都需要实现ICloneable接口,否则将使类型的深复制不能覆盖子类的新成员。

时间: 2024-08-07 17:52:06

.NET基础拾遗(1)类型语法基础和内存管理基础1的相关文章

计算机操作系统学习笔记_7_内存管理 --内存管理基础

h2.western { font-family: "Liberation Sans",sans-serif; font-size: 16pt; }h2.cjk { font-family: "微软雅黑"; font-size: 16pt; }h2.ctl { font-family: "AR PL UMing CN"; font-size: 16pt; }h1 { margin-bottom: 0.21cm; }h1.western { fon

Redis数据库操作、持久化详解及内存管理基础概览

Redis数据库操作.持久化详解及内存管理基础概览 前言 ? 上篇主要介绍了redis的编译安装流程以及redis常用的两个命令工具,本文主要讲述redis的数据库相关操作:增删改查等等,以及简述理解redis持久化原理和配置操作. Redis数据库操作 Redis 数据库常用命令(一般大小写均可)--比较简单,直接介绍语法,自行尝试验证当然这些只是冰山一角,例如不同的数据类型对应的操作都不一样,有研究兴趣的可以深入了解.单个数据库命令: set--写入数据 语法:set key value g

蓝懿-打飞机图片,内存管理基础 刘国斌老师

一 图片 1背景图片 因为是两张相同图片不断重复出现  开始时瞬间加载 用for(int i=0,i<2,i++)   开timer实现动作 2飞行效果 是两张图片出现一个位置 长时间变换加载 开timer int count; count++: if(count%2=0){ self.image=[uiimage imagenamed:@"1"]} else{ self.image=[uiimage imagenamed:@"2"]} 3爆炸效果 多张图片按

.NET基础拾遗(1)类型语法基础和内存管理基础2

二.内存管理和垃圾回收 2.1 .NET中栈和堆的差异? 每一个.NET应用程序最终都会运行在一个OS进程中,假设这个OS的传统的32位系统,那么每个.NET应用程序都可以拥有一个4GB的虚拟内存..NET会在这个4GB的虚拟内存块中开辟三块内存作为 堆栈.托管堆 以及 非托管堆. (1).NET中的堆栈 堆栈用来存储值类型的对象和引用类型对象的引用(地址),其分配的是一块连续的地址,在.NET应用程序中,堆栈上的地址从高位向低位分配内存,.NET只需要保存一个指针指向下一个未分配内存的内存地址

.NET基础拾遗(1)类型语法基础和内存管理基础

一.基础类型和语法 1.1 .NET中所有类型的基类是什么? 在.NET中所有的内建类型都继承自System.Object类型.在C#中,不需要显示地定义类型继承自System.Object,编译器将自动地自动地为类型添加上这个继承申明,以下两行代码的作用完全一致: public class A { } public class A : System.Object { } 1.2 值类型和引用类型的区别? 在.NET中的类型分为值类型和引用类型,它们各有特点,其共同点是都继承自System.Ob

类型语法基础和内存管理基础

一:基础类型和语法 1.1 .net中所有类型的基类是什么: 在.net中所有的内建类型都继承System.Object类型.C#中编译器自动添加继承申明 public class A{} public class A:System.Object{} 1.2 值类型和引用类型的区别 .net中分值类型和引用类型,都继承自System.Object,区分标准是否继承System.ValueType(继承自System.Object), 继承自System.VauleType的类型的是值类型(整型,

Objective-C基础笔记(3)OC的内存管理

Objective-C的内存基本管理 在OC中每个变量都保存着引用计数器,当这个对象的引用计数器为0的时候该对象会被回收.当使用alloc.new或者copy创建一个对象的时候,对象的引用计数器被置为1. 给对象发送一条retain消息,可以使引用计数器+1. 给对象发送一条release消息,可以使引用计数器-1. 当OC被销毁的时候会发送一条dealloc消息(不要直接调用,由系统调用),可以重写dealloc方法,在该方法中释放相关资源. 可以给对象发送retainCount消息获取对象的

Objective--C内存管理基础

2016-08-01 15:36:30 OC中的内存管理: Objective--C增加了垃圾回收机制,作为初学者,需要清楚内存的管理,什么时候该申请内存,什么时候该释放内存,养成良好的编程习惯,开发无内存泄漏的应用程序.对于自己开发的应用程序,自己管理内存,当程序需要空间,则手动分配空间,当空间或其他资源不再需要时,手动回收. OC中的alloc+init为开辟空间,OC中的release是释放空间的一个标示. 2.自动释放池 自动释放池是OC的一种内存自动回收机制,可以将一些临时变量通过自动

Swift 值类型和引用类型的内存管理

1.内存分配 1.1 值类型的内存分配 在 Swift 中定长的值类型都是保存在栈上的,操作时不会涉及堆上的内存.变长的值类型(字符串.集合类型是可变长度的值类型)会分配堆内存. 这相当于一个 "福利",意味着你可以使用值类型更快速的完成一个方法的执行. 值类型的实例只会保存其内部的存储属性,并且通过 "=" 赋值的实例彼此的存储是独立的. 值类型的赋值是拷贝的,对于定长的值类型来说,由于所需的内存空间是固定的,所以这种拷贝的开销是在常数时间内完成的. struct