美文网首页C#学习的心得笔记
刘铁猛C#(9)方法的定义、调用与调试(下)

刘铁猛C#(9)方法的定义、调用与调试(下)

作者: 北欧的蓝天 | 来源:发表于2019-10-25 03:21 被阅读0次

    Class Student 还没有声明构造器,默认构造器打印输出的结果是0(初始化的结果)。

    构造器的组成

    有效的修饰标识符 + 构造器名称 + 参数列表 + 函数体

    不用返回值。

    构造器的名字跟类名必须完全一致。

    快捷键 cto + Tab *2

    Student stu = new Student();

    上述语句的解释:

    * 先创建一个Student类的引用变量 stu,引用变量放在栈里,4字节

    * 然后用new 创建它的实例,实例存放在堆里

    * ()代表调用这个实例的构造器,此处是一个默认构造器,没有任何参数

    在内存中上述语句的操作

    Student类有两个字段

    public int ID;  //整型,4字节,结构体类型

    public string Name;  //string 4字节,类类型,也是引用类型,其变量存储的是实例的地址

    内存中会出现两次存储转移。

    1)调用默认构造器的内存存储

    将实例的地址30000006转换成为2进制,传输给Student Class的引用变量 stu。 stu就可以用来指引用默认构造器创建的实例的内存地址。

    2)调用带有参数的构造器的内存存储

    调用带有参数的构造器,相当于给实例赋值  

    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。

    Console.WriteLine(); 

    +17 overloads,说明有17种不一样的方法,都叫WriteLine,但是方法里面的参数是不一样的,所以是不同的方法。

    如果仅仅是返回值不同,方法名和参数列表都一样,也不能重载。比如

    只有返回值的类型不同,一个是int,一个是double,其他都一样,所以会报错。

    只改形参名字,也会报错。

    改成不同的类型就可以编译成功

    类型形参<T>可以参与方法的签名。

    参数种类也参与方法的前面。

    参数种类和参数类型不是一回儿事。

    参数种类有三种:值变量,引用变量(ref),输出变量(out)。

    如下,改变了种类,尽管类型一样,还是可以编译成功。

    根据方法的参数,来决定应该调用哪个方法。

                Caculator c = new Caculator();

                double a = Caculator.GetCirleArea(100);

    传入值100 给参数 r,所以程序运行起来后,断点处的 r 也该是100。 

    Call stack

    Main调用的Caculator.GetCirleArea()方法,最上面是当前被调用的方法(被调者Callee),下面是谁调用它(主调者Caller)。

    双击就可到达该语句。

    多层调用,stack层级也会增加。

    double b = Caculator.GetConeVolume(100, 6);

    以上调用会报错,因为必须要通过实例才能够调用非静态方法。修改成下面这样就对了。

    Call Stack(调用栈),在调用栈里的层级越深,在内存栈里占用的资源就越多。

    三种调试的方式

    step into(F11)

    此时b还是0.

    F11 后,到它调用的方法处,发现参数值已经被传进来了,但是cv还是零。因为还需要调用上一级方法

    再按F11,到上一级的调用方法,参数值也被传入了,此时a还是0,因为也需要调用上一级方法

    再按F11,到达上一级方法,r值已经被传入了。这一句执行完,这一级的方法就有了具体的值。

    再F11, 将GetCirleArea(double 100)的值传给下一级GetCylinderVolume(double r, double h),此时a还是0,因为这一句还没执行完。

    等执行完这一句之后,a就有了值,从而GetCylinderVolume(double r, double h),也有了值。

    再F11,将GetCylinderVolume(double r, double h)的值传给GetConeVolume(double r, double h),此时cv还是0。

    再F11,执行完上面这句后,cv就有了值,从而将值返回给GetConeVolume(double r, double h)。

    再F11,回到主程序,此时b还是0,因为这句还没有执行。

    再F11,执行完这一句,b就有值了。

    step over:F10,会直接触发这个断点,不需要等传值了。

    检查一下,发现没有问题,那么前面也不需要检查了,直接检查后面的内容,也就是下一级调用方法。

    step out:shif+F11,返回调用它的方法。

    除了用鼠标观察参数变化,也可以用locals栏,观察本地变量的数据变化。

    还能用别针,显性本地变量的变化窗口

    当本地变量的值发生变化时,就会变红。

    方法的调用,与栈的关系

    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的寄存器中,不会放在内存的堆栈中。函数调用结束后,参数所占内存就被清空。

    相关文章

      网友评论

        本文标题:刘铁猛C#(9)方法的定义、调用与调试(下)

        本文链接:https://www.haomeiwen.com/subject/tpodvctx.html