什么是类型(Type)

  • 又名数据类型(Data Type)

    • A data type is a homogeneous collection of values,effectively presented,equipped with a set of operations which manipulate these values
    • 是数据在内存中存储时的”型号”
    • 小内存容纳大尺寸数据会丢失精确度、发生错误
    • 大内存容纳小尺寸数据会导致浪费
    • 编程语言的数据类型与数学中的数据类型不完全相同,如int x = 3/4;(结果为0),而在数学中为0.75
    • 程序不运行时,存储在硬盘中,运行时,加载至内存中。打开软件,即将程序从硬盘加载至内存中,所以程序打开的快慢,与硬盘和内存都有关
  • 强类型语言与弱类型语言的比较

    • C语言示例:if条件

      1. int _tmain(int argc,_TCHAR* argv[])
      2. {
      3. int x = 100;
      4. //以下可以编译通过,并打印It's OK!
      5. //因为编译器会把x=200当成一个非0值,即为true
      6. if(x = 200)
      7. {
      8. printf("It's OK!");
      9. }
      10. //以下编译会报错,需改成"200 == x"
      11. if(200 = x)
      12. {
      13. printf("It's OK!");
      14. }
      15. return 0;
      16. }
    • javascript语言示例:动态类型

      1. <script>
      2. function ButtonClicked(){
      3. var myVar = 100;
      4. myVar = "Mr.Okay!";
      5. alert(myVar);
      6. }
      7. </script>
    • C#语言对弱类型/动态类型的模仿

      1. static void Main(string[] args)
      2. {
      3. dynamic myVar = 100;
      4. Console.WriteLine(myVar);
      5. myVar = "Mr.Okay!";
      6. Console.WriteLine(myVar);
      7. }

类型在C#语言中的作用

  • 一个C#类型中所包含的信息有
    • 存储此类型变量所需的内存空间大小
    • 此类型的值可表示的最大、最小值范围
      • 数据大小关系:1GB(千兆)=1024MB,1MB(兆)=1024KB,1KB(千字节)=1024B,1B(字节)=8bit(比特位)

image.png
image.png

  • 此类型所包含的成员(如方法、属性、事件等)
  • 此类型由何基类派生而来。示例:利用反射动态地调用一个类型的属性和方法 ```csharp using System.Windows.Forms

namespace TypeSample { class Program { static void Main(string[] args) { Type myType = typeof(Form); Console.WriteLine(myType.BaseType.FullName);

        PropertyInfo[] pInfos = myType.GetProperties();
        MethodInfo[] mInfos = myType.GetMethods();
        foreach(var p in pInfos)
        {
            Console.WriteLine(p.Name);
        }
        foreach(var m in mInfos)
        {
            Console.WriteLine(m.Name);
        }
    }
}

}


   - 程序运行的时候,此类型的变量在分配在内存的什么位置
      - stack(栈)简介:给方法调用使用,比较小(1-2MB,2MB=2*1024*1024=2,097,152byte)
      - stack overflow:循环的递归会导致溢出,或在栈上切过大的内存,如下:
```csharp
namespace StackOverflow
{
    class Program
    {
        static void Main(string[] args)
        {
            //循环的递归
            BadGuy bg = new BadGuy();
            bg.BadMethod();

            //在栈上切过大的内存
            //使用unsafe代码,需在build中允许不安全代码
            unsafe
            {
                int* p = stackalloc int[9999999];
            }
        }
    }

    class BadGuy
    {
        public void BadMethod()
        {
                int x = 100;
            this.BadMethod();
        }
    }
}
  - Heap(堆)简介:存储对象,比较大(几个GB)
  - 使用performance monitor查看进程的堆内存使用量,可以检查程序哪个操作占用较多内存
  - 关于内存泄露(没有垃圾回收),C#有垃圾回收(GC)机制,C++需手动回收
  • 此类型所允许的操作(运算),比如double x = 3/4;结果为0而非0.75,因为3和4都为int类型,除法运算后值为0,最后赋值给double类型的x变量

C#语言的类型系统

C#的五大数据类型

  • 类(Classes):如Window,Form,Console,String
  • 结构体(Structures):如Int32,Int64,Single,Double。(C#将常用的数据类型吸收为关键字了,int==Int32,long==Int64)
  • 枚举(Enumerations):如HorizontalAlignment,Visibility。比如Form类型的引用变量,在表示窗口尺寸状态时,分为最大、最小、正常三种,这时就通过FormWindowState枚举类型来设置,如下:

image.png
image.png

  • 接口(Interfaces)
  • 委托(Delegates)

C#类型的派生谱系

image.png

变量、对象与内存

什么是变量

  • 表面上来看(从C#代码的上下文行文上来看),变量的用途是存储数据
  • 实际上,变量表示了存储位置,并且每个变量都有一个类型,以决定什么样的值能够存入变量
  • 变量一共有7种
    • 静态变量,实例变量(成员变量/字段),数组元素,值参数,引用参数,输出形参,局部变量
  • 狭义的变量指局部变量,因为其他种类的变量都有自己的约定名称
    • 简单地讲。局部变量就是方法体(函数体)里申明的变量
  • 变量的申明
    • 有效的修饰符组合opt +类型 +变量名 +初始化器opt
  • 变量 = 以变量名所对应的内存地址为起点、以其数据类型所要求的存储空间为长度的一块内存区域
  • 各变量在代码中的示例如下:

    namespace TypeInCsharp
    {
    class Program
    {
       static void Main(string[] args)
       {
           Student.Amount = 3;//静态字段直接由类调用
           Student stu = new Student();
           stu.Age = 10;//字段由对象调用
           int[] arr = new int[100];//100为数组元素
           arr[0] = 10;
           int x;//x为局部变量,申明变量x
       }
    }
    
    class Student
    {
       public Static int Amount;//静态字段
       public int Age;//字段
       //a和b为值参数,如果a由ref修饰,则为引用参数。
       //如果b由out修饰,则为输出形参
       public double Add(double a,double b)
       {
           double resule = a + b;//resule为局部变量
           return resule;
       }
    }
    }
    

值类型的变量

  • 以byte/sbyte/short/ushort为例:

    static void Main(string[] args)
    {
    //byte为8个比特位,一个字节,100的二进制为01100100
    //由于一部分内存被系统和其他程序占用,内存在10000006地址开始有空闲,于是b的内存地址为10000006,值为01100100存在内存中
    byte b = 100;
    
    //有符号值的存储,拿最高位作为符号,0为正,1为负。负数的二进制数为正数按位取反再加1
    //-100的二进制为10011100,于是sb的内存地址为10000007,值为10011100存在内存中
    sbyte sb = -100;
    
    //ushort为16个比特位,两个字节,1000的二进制为0000‭0011 11101000‬
    //us的内存地址为以100000008为开始地址,以100000009为结束地址,值为0000‭0011 11101000‬存在内存中
    ushort us = 1000;
    }
    

内存分析如下:
计算机内存最小单位为bit位,一个比特位只能存储一个二进制的数字(0或1),由于比特位存储能力有限,因此计算机科学将8个bit位组成为一个字节(byte),计算机内存以字节为基本单元进行存储数据和读取数据,而且计算机为每个字节都准备了唯一的编号,内存地址即为字节在计算机科学中的编号。
image.png

  • 值类型没有实例,所谓的”实例”与变量合二为一

引用类型的变量与实例

  • 引用类型变量与实例的关系:引用类型变量里存储的数据是对象的内存地址

    namespace TypeInCsharp
    {
    class Program
    {
       static void Main(string[] args)
       {
           //引用类型变量stu,在栈内存中直接分4个字节大小的内存
           //stu的内存地址为以10000006为开始地址,以10000009为结束地址,存储的值为实例在堆内存中的地址值00000001 00110001 00101101 00000001‬
           Student stu;
    
           //在堆内存中找到空闲内存,地址为20000001(二进制值为00000001 00110001 00101101 00000001‬),存储实例中的值,ID为uint类型,4个字节,Score为ushort类型,2个字节,一共需要6个字节的空间,初始都为0
           stu = new Student();
    
           //引用类型变量stu2,在栈内存中寻找4个字节的空闲内存
           //stu2的内存地址为以10000015为开始地址,以10000018为结束地址,存储的值和stu存储的值相同
           Student stu2;
           stu2=stu
       }
    }
    
    class Student
    {
       uint ID;
       ushort Score;
    }
    }
    

内存分析如下:image.png

局部变量是在stack(栈)上分配内存,实例变量(字段)会随着实例在Hrap(堆)上分配内存

变量的默认值

  • 实例变量(字段)有默认值,如上例为0
  • 局部变量无默认值,为了避免不安全代码,必须赋初始值,否者会编译不通过

    常量

  • 值不可改变

  • 第一次申明赋值后,不可再次赋值:const x = 1;后续不可x = 2对x值进行改变;

装箱与拆箱(Boxing/Unboxing)

  • 装箱,如下示例:

    static void Main(string[] args)
    {
    int x = 100;//值类型变量
    object obj;//引用类型变量
    obj = x;//装箱
    }
    

    对上述代码进行分析,obj=x,由于x指向的内存地址,对应内存存储的值为100的二进制数,即01100100。如果obj指向的内存地址,对应内存存储的值也为01100100(obj为引用类型变量,存储的值为实例在堆内存的地址),而这个地址01100100有可能为操作系统中的值,此时会报错。
    所以如果引用类型所引用的值不是堆上的实例,而是一个栈上的值类型时,它会先把值类型的值复制,在堆上找一个空闲可存储的空间,将其变为一个对象,然后把对象的地址转化为二进制,存储到obj变量所对应的内存空间里。
    以上过程为装箱,装箱需要从栈上往堆上搬东西,会损耗性能。

  • 拆箱,如下示例:

    static void Main(string[] args)
    {
    int x = 100;//值类型变量
    object obj;//引用类型变量
    obj = x;//装箱
    int y = int(obj);//拆箱
    }
    

    int y = int(obj),表示将obj在堆上存储的值搬到y指向的内存地址所对应栈内存上,拆箱需要从堆上往栈上搬东西,也会会损耗性能。