![](https://img.haomeiwen.com/i13659249/6721ab6b3b34fe2b.png)
![](https://img.haomeiwen.com/i13659249/67be47a4d0f57e78.png)
![](https://img.haomeiwen.com/i13659249/5cfa78dc91001359.png)
Class Student 还没有声明构造器,默认构造器打印输出的结果是0(初始化的结果)。
构造器的组成
有效的修饰标识符 + 构造器名称 + 参数列表 + 函数体
不用返回值。
构造器的名字跟类名必须完全一致。
快捷键 cto + Tab *2
![](https://img.haomeiwen.com/i13659249/7c4c3e94f7ca22a6.png)
![](https://img.haomeiwen.com/i13659249/ada7ece503093798.png)
Student stu = new Student();
上述语句的解释:
* 先创建一个Student类的引用变量 stu,引用变量放在栈里,4字节
* 然后用new 创建它的实例,实例存放在堆里
* ()代表调用这个实例的构造器,此处是一个默认构造器,没有任何参数
在内存中上述语句的操作
Student类有两个字段
public int ID; //整型,4字节,结构体类型
public string Name; //string 4字节,类类型,也是引用类型,其变量存储的是实例的地址
内存中会出现两次存储转移。
1)调用默认构造器的内存存储
![](https://img.haomeiwen.com/i13659249/18ffe239c9451d3e.png)
将实例的地址30000006转换成为2进制,传输给Student Class的引用变量 stu。 stu就可以用来指引用默认构造器创建的实例的内存地址。
2)调用带有参数的构造器的内存存储
![](https://img.haomeiwen.com/i13659249/9c91f0489bd1f87c.png)
调用带有参数的构造器,相当于给实例赋值
Student stu = new Student(1,“Kim”);
string是一个类类型,只生成引用变量,需要在堆内存中再找一块位置,容纳实际值Kim
Student stu = new Student(1, "Kim");
1)分配内存给 stu(栈内)
2)调用构造器,根据参数类型,分配内存给实例(堆里)
3)int ID,int是个数值类型,值为1,可以直接存储在构造器所在内存空间里。
4)string 是个类类型,string 所声明的变量,只能是个指针变量,所以string Name中的Name,作为一个指针变量,不能直接存储姓名的真实值,只能存储真实值所在的地址。
5)所以还要在堆中的另一个位置存储姓名的真实值“Kim”。
string Name
Name作为一个指针变量,只能存储真实值"Kim"所在的内存位置,40000015,转换为2进制,存在构造器所占用内存的紫色处。
()调用构造器,创建实例。作为实例,内存位于堆内。按照参数类型进行分配,并决定了其中应该存储什么数据。
1)数值类型,真实值可以直接存在构造器所在的内存中。
2)指针类型,只能存真实值所在的地址。
最后将实例所处的地址传给栈内的stu。
Student stu,为Student类创建了一个实例stu()。
stu本身不是实例,只是指针。
只有调用构造器stu()后,stu()才是一个实例。
实例stu() 存放在堆中。
指针 stu 指向 实例stu() 在内存中的地址。
实例stu()获取了所有所需的参数之后,将自己在内存中的地址传给 指针 stu。
![](https://img.haomeiwen.com/i13659249/1deb5507a7ba4c5d.png)
![](https://img.haomeiwen.com/i13659249/8925839372d7a578.png)
Console.WriteLine();
+17 overloads,说明有17种不一样的方法,都叫WriteLine,但是方法里面的参数是不一样的,所以是不同的方法。
如果仅仅是返回值不同,方法名和参数列表都一样,也不能重载。比如
![](https://img.haomeiwen.com/i13659249/19ffd13418684a27.png)
只有返回值的类型不同,一个是int,一个是double,其他都一样,所以会报错。
只改形参名字,也会报错。
![](https://img.haomeiwen.com/i13659249/c0bf573d57ae3f8c.png)
改成不同的类型就可以编译成功
![](https://img.haomeiwen.com/i13659249/37a7cf58d511ba94.png)
类型形参<T>可以参与方法的签名。
![](https://img.haomeiwen.com/i13659249/c8771bc300af6d73.png)
参数种类也参与方法的前面。
参数种类和参数类型不是一回儿事。
参数种类有三种:值变量,引用变量(ref),输出变量(out)。
如下,改变了种类,尽管类型一样,还是可以编译成功。
![](https://img.haomeiwen.com/i13659249/bef2e4e9d3b9e0a9.png)
根据方法的参数,来决定应该调用哪个方法。
![](https://img.haomeiwen.com/i13659249/30ecd8bfbfdc4f0f.png)
Caculator c = new Caculator();
double a = Caculator.GetCirleArea(100);
传入值100 给参数 r,所以程序运行起来后,断点处的 r 也该是100。
![](https://img.haomeiwen.com/i13659249/9fc99f8f4ddafe84.png)
Call stack
![](https://img.haomeiwen.com/i13659249/61f817e3f0de3fc3.png)
Main调用的Caculator.GetCirleArea()方法,最上面是当前被调用的方法(被调者Callee),下面是谁调用它(主调者Caller)。
双击就可到达该语句。
多层调用,stack层级也会增加。
double b = Caculator.GetConeVolume(100, 6);
![](https://img.haomeiwen.com/i13659249/957b8530afd78923.png)
![](https://img.haomeiwen.com/i13659249/0af942dc39855536.png)
以上调用会报错,因为必须要通过实例才能够调用非静态方法。修改成下面这样就对了。
![](https://img.haomeiwen.com/i13659249/52a99884efcdf9bc.png)
Call Stack(调用栈),在调用栈里的层级越深,在内存栈里占用的资源就越多。
三种调试的方式
step into(F11)
![](https://img.haomeiwen.com/i13659249/e36cc2b07b61a8d0.png)
此时b还是0.
![](https://img.haomeiwen.com/i13659249/5df6a6414e7e0d06.png)
![](https://img.haomeiwen.com/i13659249/848b9fa18f4d791f.png)
F11 后,到它调用的方法处,发现参数值已经被传进来了,但是cv还是零。因为还需要调用上一级方法
![](https://img.haomeiwen.com/i13659249/4be825c967fe0e17.png)
![](https://img.haomeiwen.com/i13659249/c052daecb9dff3d3.png)
![](https://img.haomeiwen.com/i13659249/f82514530c196e66.png)
再按F11,到上一级的调用方法,参数值也被传入了,此时a还是0,因为也需要调用上一级方法
![](https://img.haomeiwen.com/i13659249/06a3e8c416a770a0.png)
![](https://img.haomeiwen.com/i13659249/2248175f3541d9e9.png)
![](https://img.haomeiwen.com/i13659249/c678fc520c1effba.png)
再按F11,到达上一级方法,r值已经被传入了。这一句执行完,这一级的方法就有了具体的值。
![](https://img.haomeiwen.com/i13659249/4363ce396177aae6.png)
再F11, 将GetCirleArea(double 100)的值传给下一级GetCylinderVolume(double r, double h),此时a还是0,因为这一句还没执行完。
![](https://img.haomeiwen.com/i13659249/5755ede1c6e101e5.png)
![](https://img.haomeiwen.com/i13659249/fd0191e741bc750c.png)
等执行完这一句之后,a就有了值,从而GetCylinderVolume(double r, double h),也有了值。
![](https://img.haomeiwen.com/i13659249/fc6cd3340adbbeda.png)
再F11,将GetCylinderVolume(double r, double h)的值传给GetConeVolume(double r, double h),此时cv还是0。
![](https://img.haomeiwen.com/i13659249/a30e8741d4921e6f.png)
再F11,执行完上面这句后,cv就有了值,从而将值返回给GetConeVolume(double r, double h)。
![](https://img.haomeiwen.com/i13659249/2d1dee3723dc92dc.png)
再F11,回到主程序,此时b还是0,因为这句还没有执行。
![](https://img.haomeiwen.com/i13659249/e8c2fb82f00a4d1e.png)
再F11,执行完这一句,b就有值了。
![](https://img.haomeiwen.com/i13659249/83312b84cf51beae.png)
step over:F10,会直接触发这个断点,不需要等传值了。
检查一下,发现没有问题,那么前面也不需要检查了,直接检查后面的内容,也就是下一级调用方法。
![](https://img.haomeiwen.com/i13659249/a93bb9406665383b.png)
step out:shif+F11,返回调用它的方法。
![](https://img.haomeiwen.com/i13659249/cab0a1a3dc3d3ed9.png)
除了用鼠标观察参数变化,也可以用locals栏,观察本地变量的数据变化。
![](https://img.haomeiwen.com/i13659249/4aeb4140d285af07.png)
还能用别针,显性本地变量的变化窗口
![](https://img.haomeiwen.com/i13659249/aaa102940476689c.png)
![](https://img.haomeiwen.com/i13659249/ed01d94d9bcc0738.png)
当本地变量的值发生变化时,就会变红。
![](https://img.haomeiwen.com/i13659249/02f177ecef3d2649.png)
![](https://img.haomeiwen.com/i13659249/9393d51ecffa31db.png)
方法的调用,与栈的关系
static void Main(string[] args)
{
Caculator c = new Caculator();
double b = Caculator.GetConeVolume(100, 6);
}
public static double GetConeVolume(double r, double h)
{
double cv = GetCylinderVolume(r, h);
return cv / 3;
}
主调者Caller,Main
被调者Callee,GetConeVolume
调用时要传两个参数,100,6进去。在C#中,这两个参数归Caller管,也就是要放在Main的内存空间里(Stack Frame)。
尽管数值100和6是整型,但是因为方法的形参是double,内存中依然会按double类型分配8个字节。从左到右存入数值。
方法的返回值一般存在CPU的寄存器中,不会放在内存的堆栈中。函数调用结束后,参数所占内存就被清空。
网友评论