• 《CLR Via C#》 学习心得之三 基元类型、引用类型和值类型


    1,何谓基元类型

    总所周知,c#中有简化的语法来操纵常用的数据类型,比如可以直接这样写:int a=0;string str="abc";等等,相比起System Int32 a=new System.Int32()来的简单很多吧。那么编译器直接支持的数据类型称为基元类型,基元类型直接映射到Framework类库中存在的类型。

    常见的有:

     
    C#基元类型 FCL类型 说明
    byte System.Byte 无符号8位值
    int System.Int32 有符号32位值
    string System.String 一个字符数组
    float System.Single IEEE32位浮点值
    bool System.Boolean 一个true/false值

    从另一个角度看,可以想象c#编译器自动假定在所有源代码文件中添加了以下using指令

    using byte=System.Byte;
    using int=System.int32;
    using string=System.String;
    using bool=System.Boolean;
    private static void PrimitiveDemo() {
    //初始化以下四个变量
    int a = new int();
    int b = 0;
    System.Int32 c = new System.Int32();
    Int32 d = 0;
    
    //四个变量全部包含值为0
    Console.WriteLine("a = {0}, b = {1}, c = {2}, d = {3}",
    new Object[] { a, b, c, d });
    
    //所有变量赋值为5
    a = b = c = d = 5;
    //全部显示为5
    Console.WriteLine("a = {0}, b = {1}, c = {2}, d = {3}",
    new Object[] { a, b, c, d });
    }

    2,checked和unchecked基元类型操作

    让c#编译器控制溢出的一个办法是使用/checked+编译器开关。如下代码:

    Byte b = 100;
    b = checked((Byte) (b + 200));//抛出OverflowException异常

    3,引用类型和值类型

    虽然FCL中的大多数类型都是引用类型,但程序用的最多还是值类型。引用类型总是从托管堆上分配的,c#的new操作符会返回对象的内存地址—也就是指向对象数据的内存地址。使用引用类型时,有以下几点:

    1,内存必须从托管堆上分配。

    2,堆上分配的每个对象都有一些额外的成员,这些成员必须初始化。

    3,对象中的其他字节总是为零。

    4,从托管堆上分配一个对象时,可能强制执行一次垃圾收集操作。

    如果所有类型都是引用类型,应用程序的性能将显著下降。所以“值类型”也适当的出现了,值类型的实例不受垃圾回收器的控制,因此,值类型的使用缓解了托管堆中的压力,并减少了一个应用程序在其生存期内需要进行的垃圾回收次数。常见的值类型有:System.Int32结构、System.Boolean结构、System.Decimal结构、枚举等。

    private static class ReferenceVsValue {
          // 引用类型(由于使用了'class')
          private class SomeRef { public Int32 x; }
          // 值类型 (由于使用了 'struct')
          private struct SomeVal { public Int32 x; }
          public static void Go() {
             SomeRef r1 = new SomeRef();   // 在堆上分配
             SomeVal v1 = new SomeVal();   // 在栈上分配
             r1.x = 5;                     // 提领指针
             v1.x = 5;                     // 在栈上修改
             Console.WriteLine(r1.x);      // 显示"5"
             Console.WriteLine(v1.x);      // 同样显示"5"
    
             SomeRef r2 = r1;              // 只复制引用(指针)
             SomeVal v2 = v1;              // 在栈上分配并复制成员
             r1.x = 8;                     // r1.x和r2.x都会改变
             v1.x = 9;                     // v1.x会改,v2.x不会改
             Console.WriteLine(r1.x);      // 显示"8"
             Console.WriteLine(r2.x);      // 显示"8"
             Console.WriteLine(v1.x);      // 显示"9"
             Console.WriteLine(v2.x);      // 显示"5"
          }
       }

    4,值类型的装箱和拆箱

    为了将一个值类型转换成一个引用类型,要使用一个名为装箱的机制。总结了几点:

    1,在托管堆中分配好内存。分配的内存是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外的成员(类型对象指针和同步块索引)需要的内存量。

    2,值类型的字段复制到新分配的堆内存。

    3,返回对象的地址。现在,这个地址是对一个对象的引用,值类型现在是一个引用类型。

    同理,拆箱,第一步是获取已装箱对象中的各个字段的地址,第二步是将这些字段的值从堆中复制到基于栈的值类型实例中。拆箱不是直接将装箱过程倒过来,拆箱的代价比装箱低得多。拆箱其实就是获取一个指针的过程,该指针指向包含在一个对象中的原始值类型(数据类型)。事实上,指针指向的是已装箱实例中的未装箱的部分。所以和装箱不同,拆箱不要求在内存中复制任何字节,知道这个重要区别之后,还应知道一个重点在于,往往会紧挨着拆箱操作发生一次字段的复制操作。

     private static class Boxing {
          public static void Go() {
             ArrayList a = new ArrayList();
             Point p;            //分配一个Point (not in the heap).
             for (Int32 i = 0; i < 10; i++) {
                p.x = p.y = i;   // 初始化值类型中的成员
                a.Add(p);        // 对值类型进行装箱,并将引用添加到ArrayList中
             }
          }
    
          // 声明了一个值类型.
          private struct Point { public Int32 x, y;  }
    
          public static void Main2() {
             Int32 x = 5;
             Object o = x;         
             Int16 y = (Int16)o;   //抛出InvalidCastException异常
          }
    
          public static void Main3() {
             Int32 x = 5;
             Object o = x;                 //对x进行装箱; o引用已装箱的对象
             Int16 y = (Int16)(Int32)o;    //先拆箱,然后进行转型
          }
    
          public static void Main4() {
             Point p;
             p.x = p.y = 1;
             Object o = p;   // 对p进行装箱; o引用已装箱的对象instance
             p = (Point)o;   //对o进行拆箱,将字段从已装箱的实例复制到栈变量中
          }
    
          public static void Main5() {
             Point p;
             p.x = p.y = 1;
             Object o = p; 
             p = (Point)o; 
             p.x = 2;       
             o = p;          
          }
    
          public static void Main6() {
             Int32 v = 5;            // 创建一个未装箱的值类型变量
             Object o = v;            // o引用一个已装箱的、包含值为5的int32
             v = 123;                 // 将未装箱的值修改为123
             Console.WriteLine(v + ", " + (Int32)o);    // 显示"123, 5"
          }
    
          public static void Main7() {
             Int32 v = 5;                // 创建一个未装箱的值类型变量
             Object o = v;               // o引用v的已装箱的版本
    
             v = 123;                    // 将未装箱的值类型修改成123
             Console.WriteLine(v);       // 显示"123"
    
             v = (Int32)o;               // 拆箱并将o复制到v
             Console.WriteLine(v);       // 显示"5"
          }
       }

    5,Dynamic基元类型

    c#是一种类型安全的编译语言。这意味着所有表达式都解析成某个类型的一个实例,在编译器生成的代码中,只会执行这个类型来说有效的操作。但是许多时候,程序员需要一些运行时才会知晓的信息,虽然说可以使用反射来解决,那是在纯C#语言写的应用程序,如果希望是其他语言写的组件,比如Ruby或者Python等,还有一些COM对像,可能是本地C或者C++实例,这时,c#编译器就允许将一个表达式的类型标记为Dynamic,还可以将一个表达式的结果放到一个变量中,并将变量的类型标记为Dynamic,然后可以用这个Dynamic表达式调用一个成员,比如字段、属性/索引器、方法、委托以及一元/二元/转换操作符。

    经过整理,对于更基础的知识理解更深刻点。

  • 相关阅读:
    C 实战练习题目45
    C 实战练习题目44
    C 实战练习题目43
    C 实战练习题目42 -auto定义变量
    C 实战练习题目41 -static定义静态变量
    如何0基础学习C/C++?
    2019-11-29-win10-uwp-关联文件
    2019-11-29-win10-UWP-Controls-by-function
    2019-11-29-WPF-高性能笔
    2019-11-29-WPF-使用-Win2d-渲染
  • 原文地址:https://www.cnblogs.com/cr330326/p/2736119.html
Copyright © 2020-2023  润新知