标签:
C#的类型和对象在应用计算机内存时,大体用到两种内存,一个叫堆栈,另一个叫托管堆,下面我们用直角长方形来代表堆栈,用圆角长方形来代表托管堆。
首先讨论一下方法内部变量的存放。
先举个例子,有如下两个方法,Method_1和Add,分别如下:
public void Method_1()
{
int value1=10; //1
int value2=20; //2
int value3=Add(value,value); //3
}
public int Add(int n1,int n2)//4
{
rnt sum=n1+n2;//5
return sum;//6
}
这段代码的执行,用图表示为:
上述的每个图片,基本对应程序中的每个步骤。在开始执行Method_1的时候,先把value1压入堆栈顶,然后是value2,接下来的是调用方法Add,因为方法有两个参数是n1和n2,所以把n1和n2分别压入堆栈,因为此处是调用了一个方法,并且方法有返回值,所以这里需要保存Add的返回地址,然后进入Add方法内部,在Add内部,首先是给sum赋值,所以把sum压入栈项,然后用return返回,此时,先前的返回地址就起到了作用,return会根据地址返回去的,在返回的过程中,把sum推出栈顶,找到了返回地址,但在Method_1方法中,我们希望把Add的返回值赋给value3,此时的返回地址也被推出堆栈,把value3压入堆栈。虽这个例子的结果在这里没有多大用途,但这个例子很好的说明了在方法被执行时,变量与进出堆栈的情况。这里也能看出为什么方法内部的局变量用过后,不能在其他方法中访问的原因。
其次来讨论一下类和对象在托管堆和堆栈中的情况。
先看一下代码:
class Car
{
public void Run()
{
Console.WriteLine("一切正常");
}
public virtual double GetPrice()
{
return 0;
}
public static void Purpose()
{
Console.WriteLine("载人");
}
}
class BMW : Car
{
public override double GetPrice()
{
return 800000;
}
}
上面是两个类,一个Father一个Son,Son继承了Father,因为你类中有一个virtual的BuyHouse方法,所以Son类可以重写这个方法。
下面接着看调用代码。
public void Method_A()
{
double CarPrice;//1
Car car = new BMW();//2
CarPrice = car.GetPrice();//调用虚方法(其实调用的是重写后的方法)
car.Run();//调用实例化方法
Car.Purpose();//调用静态方法
}
这个方法也比较简单,就是定义一个变量用来获得价格,同时定义了一个父类的变量,用子类来实例化它。
接下来,我们分步来说明。
看一下运行时堆栈和托管堆的情部我:
这里需要说明的是,类是位于托管堆中的,每个类又分为四个类部,类指针,用来关联对象;同步索引,用来完成同步(比如线程的同步)需建立的;静态成员是属于类的,所以在类中出现,还有一个方法列表(这里的方法列表项与具体的方法对应)。
当Method_A方法的第一步执行时:
这时的CarPrice是没有值的
当Method_A方法执行到第二步,其实第二步又可以分成
Car car;
car = new BMW();
先看Car car;
car在这里是一个方法内部的变量,所以被压到堆栈中。
再看 car = new BMW();
这是一个实例化过程,car变成了一个对象
这里是用子类来实例化父类型。对象其实是子类的类型的,但变量的类型是父类的。
接下来,在Method_A中的调用的中调用car.GetPrice(),对于Car来说,这个方法是虚方法(并且子类重写了它),虚方法在调用是不会执行类型上的方法,即不会执行Car类中的虚方法,而是执行对象对应类上的方法,即 BMW中的GtPrice。
如果Method_A中执行方法Run(),因为Run是普通实例方法,所以会执行Car类中的Run方法。
如果调用了Method_A的Purpose方法,即不用变量car调用,也不用对象调用,而是用类名Car调用,因为静态方法会在类中分配内存的。如果用Car生成多个实例,静态成员只有一份,就是在类中,而不是在对象中。
标签:
原文地址:http://www.cnblogs.com/shiguangshuo/p/4857203.html