方法的由来

  • 方法(method)的前身是C/C++语言的函数(function)
    • 方法是面对对象范畴的概念,在非面对对象的语言中仍然称为函数
    • 使用C/C++语言做对比
  • 方法永远都是类(或结构体)的成员
    • C#语言中函数不可能独立于类(或结构体)之外
    • 只有作为类(或结构体)的成员时才被称为方法
    • C++中是可以,称为“全局函数”
  • 类(或结构体)最基本的成员之一
    • 最基本的成员只有两个:字段(成员变量)、方法(成员函数),本质还是数据+算法
    • 方法表示类(或结构体)“能做的事情”
  • 为什么需要方法和函数

    • 1.隐藏复杂的逻辑
    • 2.复用(reuse,重用)

      方法的声明与调用

  • 声明方法的语言详解

    • 参见C#与语言文档(声明/定义不分家)
    • Parameter全称“formal parameter”形式上的参数,简称“形参”
    • Parameter是一种变量
  • 为方的命名规范
    • 大小写规范
    • 需要以动词或则动词宾语作为名字
  • 静态方法(static)和实例方法
  • 调用方法

    • Argument中文C#文档的官方译为“实际参数”,简称“实参”,可以理解为调用方法时的真实条件
    • 调用方法时的argument列表要与定义方法时的parameter列表相匹配

      构造器

  • 构造器(constructor)是类型的成员之一

    • 当你声明一个类的时候没有声明构造器时,类会自动生成一个默认的构造器
  • 狭义的构造器值“实例构造器”(instance contructor)
  • 如何声明构造器(tab快捷键:ctor)
    • 构造器(方法)的名称和类名相同,没有返回值。
    • 可以声明多个不同用途的构造器,但是参数列表必须不同
  • 如何调用构造器
    • 创建不同参数类型和数量的实例时,会自动调用声明后相同参数列表的构造器
  • 构造器的内存原理

    • “默认构造器” 在栈内存里面创建四个字节的内存,在堆内存里面创建和成员内存相同的区域,然后将堆内存按照每个成员变量的大小进行分割,然后默认值刷0。
    • “带参数的构造器” 在栈内存里面创建四个字节的内存,在堆内存里面创建和成员总内存相同的区域,然后将堆内存按照每个成员变量的大小进行分割,如果是值类型将参数列表的值赋给堆内存相应的变量,
    • 如果是引用类型就继续在堆内存里面找引用类的堆存储

      方法的重载(OverLoad)

  • 声明带有重载的方法

    • 方法签名(method signature)由方法的名称、类型形参的个数和它的每个形参(按从左到右的顺序)的类型和种类(值、引用或输出)组成。方法签名不包含返回类型,返回值不一样和重载没关系。
    • 实例构造函数签名由它的每一个形参(按从右到左的顺序)的类型和种类(值、引用或输出)组成。方法签名不包括形参名字,名字不一样,形参类型一样不能重载。
    • 重载决策(到底调用哪一个重载):用于在给定了参数列表和一组候选函数成员的情况下,选择一个最佳函数成员来实施调用

      如何对方法进行Debug

  • 设置断点

    • 设置断点后,当你在调试模式下运行程序时,程序运行到断点处会暂时停止运行,等你观察结束后再继续
  • 观察方法调用时的call stack
    • call stack的顶层就是当前调用的程序,下一行就是表示从哪一行调用的。当call stack有多层时,显示的有从什么地方调用的这个方法的整个调用链,最底层就是主程序中的某一行。
    • 当call static层数特别多时,说明此时调用这个方法的时候会占用大量的栈资源。
    • 递归方法就是不断的调用自己,它的栈资源会占用得越来越多,如果在栈资源耗尽之前没有结束递归就会造成栈资源崩溃。

untitled.png

  • Step-in,Step-over,Step-out
    • Step-in
      • 步进调试程序,可以查看一个程序得每一步得过程。
      • 在这个期间可以随时看到右下角的call stack一层一层的减少
    • Step-over(F10)
      • 不进入函数的调用周期,直接到函数结束
      • 先用Step-over快速跳过我们知道没有问题的函数调用步骤,到详细的函数调用时使用Step-in进入函数详细查看其过程(配合使用)
    • Step-out(Shift + F1)
      • 返回到调用它的方法哪一行,也就是返回到call stack里面的第二行的程序
  • 观察局部变量的值的变化(Locals)

    • 可以在调式时实时查看当前的所有变量(形参、实参)的当前值以及类型
    • 当某个变量的某个值由白变红时,说明这个变量在经过上一步的操作后刚进行的变化(赋值、计算)

      方法的调用与栈

  • 方法调用时栈内存的分配

    • 对stack frame的分析
    • stack frame:当一个方法被调用的时候,它在栈内存中的布局。

C# 中调用方法时的变量归 Caller(主调函数) 管,不归 Callee(被调用者) 管。
压变量入栈,C# 是从左至右的顺序。

图示是为了重点解释方法、变量、参数的压栈,实际情况下还要压入返回地址等。
返回值一般存在 CPU 的寄存器里面,特殊情况寄存器存不下该返回值时,会到栈上开辟空间。

stack overflow 就是栈无限向上延伸(分配变量、参数、栈针等),最后溢出了。

代码

  1. using System;
  2. namespace CSharpMethodExample
  3. {
  4. class Program
  5. {
  6. static void Main(string[] args)
  7. {
  8. double result = Calculator.GetConeVolume(100, 90);
  9. }
  10. }
  11. class Calculator
  12. {
  13. public static double GetCircleArea(double r)
  14. {
  15. return Math.PI * r * r;
  16. }
  17. public static double GetCylinderVolume(double r,double h)
  18. {
  19. double a = GetCircleArea(r);
  20. return a * h;
  21. }
  22. public static double GetConeVolume(double r,double h)
  23. {
  24. double cv = GetCylinderVolume(r, h);
  25. return cv / 3;
  26. }
  27. }
  28. }

分步讲解

1.进入 Main 方法,调用 GetConeVolume 方法前

在栈上开辟了 Main 方法的 stack frame。
08 09 方法的定义、调用和调试 - 图2

2.Main 方法中调用 GetConeVolume 时

将两个参数压入栈中。因为 C# 中调用时的参数归 Caller(主调者) 管,此处即归 Main 管。从下往上,先压左边的,后压右边的。
08 09 方法的定义、调用和调试 - 图3

3.进入 GetConeVolume 后

局部变量是需要入栈的,GetConeVolume 方法中的 cv 入栈。
r,h 也是局部变量,但已经作为参数被 Main 方法压入栈了,所以它只需要压 cv 即可。
08 09 方法的定义、调用和调试 - 图4

4.GetConeVolume 调用 GetCylinderVolume 时

将两个参数压入栈中。
08 09 方法的定义、调用和调试 - 图5

5.进入 GetCylinderVolume 后

局部变量 a 入栈。
08 09 方法的定义、调用和调试 - 图6

6.GetCylinderVolume 调用 GetCircleArea 时

GetCircleArea 只有一个参数,将其压入栈即可。
08 09 方法的定义、调用和调试 - 图7

7.进入 GetCircleArea 后

GetCircleArea 中没有局部变量,但它在栈上也占内存,它有自己的栈针。
08 09 方法的定义、调用和调试 - 图8

8.GetCircleArea 返回后

返回值存在 CPU 的寄存器(register)里面。
call stack 少了一层。
函数返回后,它所占有的 stack frame 就清空了。
08 09 方法的定义、调用和调试 - 图9

9.GetCylinderVolume 返回后

08 09 方法的定义、调用和调试 - 图10

10.GetConeVolume 返回后

GetConeVolume 的 stack frame被清空。
Main 方法中调用 GetConeVolume 时压入栈中的两个参数也出栈了。
08 09 方法的定义、调用和调试 - 图11

11.Main 返回后(程序结束)

Main 方法的 stack frame 也被清空。
08 09 方法的定义、调用和调试 - 图12