C# 中的栈和堆

2018-09-21  本文已影响24人  源小白

本文是《C# 图解教程》的笔记。

程序运行时,它的数据必须存储在内存中。一个数据项需要多大的内存、存储在什么地方、以及如何存储都依赖于该数据项的类型。

运行中的程序使用两个内存区域来存储数据:栈和堆。

栈是一个内存数组,是一个LIFO(Last-In First-Out,后进先出)的数据结构。栈存储几种类型的数据:

系统管理所有的栈操作。作为程序员,你不需要显式地对它做任何事情。但了解栈的基本功能可以更好地了解程序在运行时正在做什么,并能更好地了解C#文档和著作。

image

堆是一块内存区域,在堆里可以分配大块的内存用于存储某类型的数据对象。与栈不同,堆里的内存能够以任意顺序存入和移除。图3-7展示了一个在堆里放了4项数据的程序。

image

虽然程序可以在堆里保存数据,但并不能显式地删除它们。CLR的自动GC(Garbage Collector,垃圾收集器)在判断出程序的代码将不会再访问某数据项时,自动清除无主的堆对象。我们因此可以不再操心这项使用其他编程语言时非常容易出错的工作了。

image

值类型和引用类型

数据项的类型定义了存储数据需要的内存大小及组成该类型的数据成员。类型还决定了对象在内存中的存储位置——栈或堆。

类型被分为两种:值类型和引用类型,这两种类型的对象在内存中的存储方式不同。

image

存储引用类型对象的成员

例如,假设有一个引用类型的实例,名称为MyType,它有两个成员:一个值类型成员和一个引用类型成员。它将如何存储呢?是否是值类型的成员存储在栈里,而引用类型的成员如图3-9所示的那样在栈和堆之间分成两半呢?答案是否定的。

请记住,对于一个引用类型,其实例的数据部分始终存放在堆里。既然两个成员都是对象数据的一部分,那么它们都会被存放在堆里,无论它们是值类型还是引用类型。

image

对于引用类型的任何对象,它所有的数据成员都存放在堆里,无论它们是值类型还是引用类型。

C#类型的分类

image

数组

数组是引用类型,与所有引用类型一样,有数据的引用以及数据对象本身。引用在栈或堆上,而数组对象本身总是在堆上。

image

尽管数组总是引用类型,但是数组的元素可以是值类型也可以是引用类型。

方法

方法的局部变量和参数是位于栈上的。

class MyClass
{
    public void ListInts(params int[] inVals)
    {
        if ((inVals != null) && (inVals.Length != 0))
            for (int i = 0; i < inVals.Length; i++) //处理数组
            {
                inVals[i] = inVals[i] * 10;
                Console.WriteLine("{0}", inVals[i]); //显示新值
            }
    }
}

class Program
{
    static void Main()
    {
        int first = 5, second = 6, third = 7; //声明3个int 
        MyClass mc = new MyClass();
        mc.ListInts(first, second, third); //调用方法

        Console.WriteLine("{0}, {1}, {2}", first, second, third);
    }
}

输出

50 
60 
70 
5, 6, 7 
image

栈帧

在调用方法的时候,内存从栈的顶部开始分配,保存和方法关联的一些数据项。这块内存叫做方法的栈帧(stack frame)。

class Program
{
    static void MethodA(int par1, int par2)
    {
        Console.WriteLine("Enter MethodA: {0}, {1}", par1, par2);
        MethodB(11, 18); //调用MethodB
        Console.WriteLine("Exit MethodA");
    }

    static void MethodB(int par1, int par2)
    {
        Console.WriteLine("Enter MethodB: {0}, {1}", par1, par2);
        Console.WriteLine("Exit MethodB");
    }

    static void Main()
    {
        Console.WriteLine("Enter Main");
        MethodA(15, 30); //调用MethodA 
        Console.WriteLine("Exit Main");
    }
}

输出

Enter Main 
Enter MethodA: 15, 30 
Enter MethodB: 11, 18 
Exit MethodB 
Exit MethodA 
Exit Main
image

递归

除了调用其他方法,方法也可以调用自身。这叫做递归。

调用方法自身的机制和调用其他方法其实是完全一样的。都是为每一次方法调用把新的栈帧压入栈顶。

class Program 
{ 
    public void Count(int inVal) 
    { 
        if (inVal == 0) 
            return; 
        Count(inVal - 1); //再一次调用方法
        Console.WriteLine("{0}", inVal); 
    } 
 
    static void Main() 
    { 
        Program pr = new Program(); 
        pr.Count(3); 
    }
}
image
上一篇下一篇

猜你喜欢

热点阅读