CS2L4——值类型和引用类型

变量类型的复习

  • 基本数据类型
  • //1、有符号的整形变量 能存储一定范围内的正负数包括0的变量类型
    sbyte sb = 1; // -128~127
    int i = 1;  //  -21亿~21亿
    short s = 1;    //  -32768~32767
    long l = 1;    //  -九百万兆~九百万兆
    //2、无符号的整型变量 能存储一定范围内0和正数的变量类型
    byte b = 1;    //  0~255
    uint ui = 1;    //  0~42亿
    ushort us = 1;  //  0~65535
    ulong ul = 1;   //  0~18百万兆
    //3、浮点数(小数)
    float f = 0.1f;    // 数字后必须加f声明float,因为小数会被默认为double,从非0数算起为有效数字,7/8位有效数字,根据编译器不同有可能不同,超出部分会四舍五入
    double d = 0.1;    // 从非0数算起为有效数字,15~17位有效数字,超出部分会四舍五入
    decimal de = 0.1m;    // 数字后必须加m声明,因为小数会被默认为double,27~28位有效数字,不建议使用
    //4、特殊类型
    bool bo = true; //  true或者false
    char c = 'a';   //  需要加'',单个字符
    string st = "hello";    //  需要加"",字符串
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    * 复杂数据类型

    * ​`enum`​ 枚举
    * 数组(一维,二维,交错)
    * 结构体、类(未学习)

    # 值类型和引用类型

    在C\#中,所有的变量类型都会分为值类型和引用类型,分类如下:

    * 引用类型:`string`​,数组,类 `class`​(未学习)
    * 值类型:除 `string`​ 以外的所有基本数据类型,结构体 `struct`​(未学习)

    ## 值类型和引用类型在使用上的区别

    * 值类型

    ```c#
    //值类型
    int a = 10;
    //申明了一个b让其等于之前的a
    int b = a;
    Console.WriteLine("a={0},b={1}", a, b);
    //修改b
    b = 20;
    Console.WriteLine("修改了b和arr2[0]");
    Console.WriteLine("a={0},b={1}", a, b);

输出:

1
2
3
a=10,b=10
修改了b和arr2[0]
a=10,b=20

可见,值类型变量将自己的值赋值给另一个值类型变量时,修改新变量的值,原来的变量的值不被影响

  • 引用类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //引用类型
    int[] arr = new int[] { 1, 2, 3, 4 };
    //申明了一个arr2让其等于之前的arr
    int[] arr2 = arr;
    Console.WriteLine("arr={0},arr2={1}", arr[0], arr2[0]);
    //修改arr[0]
    arr2[0] = 5;
    Console.WriteLine("修改了b和arr2[0]");
    Console.WriteLine("arr={0},arr2={1}", arr[0], arr2[0]);

    输出:

    1
    2
    3
    arr=1,arr2=1
    修改了b和arr2[0]
    arr=5,arr2=5

    可见,引用类型变量将自己的值赋值给另一个引用类型变量时,修改新变量的值,原来的变量的值也会被修改

值类型和引用类型区别的原理

首先要知道,内存是划分为几块不同的空间的,其中就包括栈空间堆空间:

  • 栈空间 —— 系统分配,自动回收,小而快
  • 堆空间 —— 手动申请和释放,大而慢

值类型和引用类型 存储在内存的区域是不同的 存储方式是不同的

  • 值类型 存储在 栈空间 —— 系统分配,自动回收,小而快,值类型的值直接存储在栈空间内

    注意,引用类型变量内的值类型成员值不是存储在栈上的,例如数组内的数值,类(class​)内部的数字等等
    直接在函数内部创建的值类型变量才是存储在栈空间的,而创建(new​)引用类型变量时,其中的值类型成员则存储于堆上

  • 引用类型 存储在 堆空间 —— 手动申请和释放,大而慢,引用类型数据的地址存储在栈空间里,地址指向堆空间里的数据

因此,值类型和应用类型在内存内存储的模型大致如下:

image

因此,当我们修改值类型变量的值时,不会影响其他值类型变量的数据,因为它们分别存储于不同内存空间内

image

因此在 int b = a​ 时,相当于在栈空间内拷贝了一份 10​ 的数据,因此当 b = 20​ 时,会直接修改那个拷贝的 10​ 数据而不影响原数据

而当我们修改引用类型变量的值时,指向堆空间同一块内存空间的引用类型变量也会被影响,因为它们存储的地址是一致的

image

而在 int[] arr2 = arr​ 时,相当于在栈空间里拷贝了 int[] arr​ 的地址,
因此在 arr2[0] = 5​ 时,会修改该地址指向的堆空间里的数据,因此打印指向同一地址 arr[0]​ 值也可以看到修改

浅拷贝和深拷贝

因为引用类型存在如上文所述的特性,因此,拷贝引用类型变量就存在浅拷贝和深拷贝的区别

上边的引用类型变量赋值给新的引用类型变量就是浅拷贝,也就是只拷贝了指向具体数据的地址,而不重新复制一份具体数据
对浅拷贝得到的变量的值进行修改,原来的变量值也会改变,因为原变量和拷贝得到的变量指向同一块内存

深拷贝就是额外创建一个一模一样的变量,并单独开辟一块内存空间存储,
而拷贝的变量指向的内存空间和原变量指向的内存空间是不同的内存空间
因此,对深拷贝得到的变量的值进行修改,原来的变量值不会改变

类似于下面的代码就是深拷贝

1
2
3
4
5
6
7
8
9
int[] arr3 = { 1, 2, 3, 4 };
int[] arr4 = new int[4];
for (int i = 0; i < arr2.Length; i++)
{
arr4[i] = arr3[i];
}
Console.WriteLine("arr3 = {0}, arr4 = {1}", arr3[0], arr4[0]);
arr4[0] = 100;
Console.WriteLine("arr3 = {0}, arr4 = {1}", arr3[0], arr4[0]);

输出:

1
2
arr3 = 1, arr4 = 1
arr3 = 1, arr4 = 100