一、程序结构

  命名空间
       ↓
类、接口、结构体
       ↓
函数、属性、索引器、运算符重载等(类、接口、结构体)
       ↓
条件分支、循环

上层语句块:类、结构体
中层语句块:函数
底层的语句块: 条件分支 循环等

上层语句块中是成员变量;中、底层语句块中是临时变量。

二、变量的生命周期

在中底层申明的临时变量(函数、条件分支、循环语句块等)
语句块执行结束 ,没有被记录的对象将被回收或变成垃圾。
值类型:被系统自动回收
引用类型:栈上用于存地址的房间被系统自动回收,堆中具体内容变成垃圾,待下次GC回收

想要不被回收或者不变垃圾,必须将其记录下来。
 在更高层级记录或者使用静态全局变量记录。

三、结构体中的值和引用

结构体本身是值类型

前提:该结构体没有做为其它类的成员

在结构体中的值,栈中存储值具体的内容;在结构体中的引用,堆中存储引用具体的内容。

引用类型始终存储在堆中

栈(Stack)                            堆(Heap)
+----------------+                    +------------------+
| p (PersonStruct)|                    | "张三" (string对象)|
| +------------+ |                    +------------------+
| | Id = 100   | |                    ^
| | Name = ref----+--------------------+
| | Scores = ref----+------------------+
| +------------+ |                    | 
+----------------+                    v
                                      +------------------+
                                      | [100,95,88] (数组)|
                                      +------------------+

栈中存储的是值类型的数据和引用类型的地址 。这些地址指向了堆中对应位置 通过这些位置找到实际数据,堆上存的是引用类型的实际值。

四、类中的值和引用

类本身是引用类型。在类中的值,堆中存储具体的值,在类中的引用,堆中存储具体的值

栈                          堆
┌──────┐    ┌─────────────────────────────────────────────┐
│ p    │───▶│ Person对象 (堆)                              │
└──────┘    │ ┌─────┐ ┌──────┐ ┌────────┐                │
            │ │ Age │ │ Name │ │ Scores │                │
            │ │ 25  │ │ 引用 │ │ 引用   │                │
            │ └─────┘ └──│───┘ └───│────┘                │
            └─────────────┼─────────┼─────────────────────┘
                          │         │
                          ▼         ▼
            ┌─────────────┴───┐   ┌─┴─────────────────┐
            │ "张三" (string)  │   │ int[] 数组         │
            └─────────────────┘   │ ┌────┬────┬────┐  │
                                  │ │ 90 │ 95 │ 88 │  │
                                  │ └────┴────┴────┘  │
                                  └───────────────────┘

五、数组中的存储规则

数组本身是引用类型 值类型数组,堆中房间存具体内容

引用类型数组,堆中房间存地址

六、结构体继承接口

能用接口的地方都能用结构体

interface ITest
{
    int Value
    {
        get;
        set;
    }
}

struct TestStruct : ITest
{
    int value;
    public int Value 
    {
        get
        {
            return value;
        }
        set
        {
            this.value = value;
        }
    
    }
}

TestStruct obj1 = new TestStruct();
obj1.Value = 1;
Console.WriteLine(obj1.Value);
TestStruct obj2 = obj1;
obj2.Value = 2;
Console.WriteLine(obj1.Value);
Console.WriteLine(obj2.Value);

ITest iObj1 = obj1;//装箱  value 1
ITest iObj2 = iObj1;
iObj2.Value = 99;
Console.WriteLine(iObj1.Value);
Console.WriteLine(iObj2.Value);

TestStruct obj3 = (TestStruct)iObj1;//拆箱

结构体本身值类型,变量独立,修改不受影响

ITest iObj1 = obj1;
由于 ITest 是引用类型,而 obj1 是值类型,这里发生装箱(Boxing):

在堆上分配一个新对象,将 obj1 的数据复制过去。iObj1 指向这个堆对象(不再是栈上的 obj1)。

ITest iObj2 = iObj1;
引用类型赋值,iObj2 和 iObj1 指向同一个装箱后的堆对象

iObj2.Value = 99; 修改的是堆上的对象,因此通过 iObj1 和 iObj2 访问到的都是 99

输出:9999

七、详细分析

值类型

  1. a 是一个局部变量,类型是 int(值类型)。
    在 .NET / C# 中,局部值类型变量通常分配在(Stack)上。所以 a 占据的 4 个字节在栈上。

  2. 1 是一个字面量(常量),它不会被单独分配一块内存。
    编译器将 1 直接编码到 IL 指令中(如 ldc.i4.1),运行时作为立即数使用,不需要额外的存储位置。
    当执行 int a = 1; 时,CPU 直接将数值 1 写入 a 所在的栈位置。

  3. 从逻辑上,你可以理解为“a 在栈上,它的值是 1(也在栈上)”,但这并不是说有一个独立的 1 对象在栈上;而是说 a 所在的那块栈内存中存放的数值就是 1

更多推荐