C#基础语法 —(4)方法的定义、调用与调试


一、方法的由来

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

1.C语言函数示例

/*
头文件引入到源文件后,程序可以对标准输入输出系统进行操作
stdio-标准输入输出; .h-头文件
*/
#include <stdio.h> 

//Function fun
//声明Add函数-函数头+函数体
double Add(double a, double b) //函数头:函数返回值double;函数名Add;参数列表a,b
{
	return a + b;
}

//在main函数里调用Add函数
int main()
{
	double x = 3.0;
	double y = 5.0;
	double result = Add(x, y);

	printf("%f+%f=%f",x,y,result); // %f-浮点数
	return 0;
}

运行结果:
在这里插入图片描述

2.C++语言函数示例

#include <iostream>

//定义Add函数
double Add(double a, double b)
{
	return a + b;
}

//在main函数里调用Add函数
int main()
{
	double x = 3.0;
	double y = 5.0;
	double result = Add(x, y);

	std::cout << x<<"+"<<y<<"="<<result;
	return 0;
}

运行结果:
在这里插入图片描述
Notes:
   ①C++ 里面完成了函数向方法的过渡。
   ②当一个函数以类或结构体的成员的身份出现时,它就被称为方法。方法有一个别名叫“成员函数”。

3.C++语言对方法的定义和调用

例1:
Student.h:

#pragma once
class Student
{
public:
	Student();
	~Student();

	//对方法的声明
	void SayHello();
};

Student.cpp:

#include "Student.h"
#include <iostream>

Student::Student()
{
}

Student::~Student()
{
}

//方法的定义
void Student::SayHello()
{
	std::cout << "Hello I'm a student!";
}

Source.cpp:

#include <iostream>
#include "Student.h" //自定义的头文件-引用Student.h

int main()
{
    Student *pStu = new Student();
    pStu->SayHello();
    return 0;
}

在这里插入图片描述
例2:
Student.h:

#pragma once
class Student
{
public:
	Student();
	~Student();	

	//对Add方法的声明
	double Add(double a, double b);
};

Student.cpp:

#include "Student.h"
#include <iostream>


Student::Student()
{
}


Student::~Student()
{
}


//Add方法的定义
double Student::Add(double a,double b)
{
	return a + b;
}

Source.cpp:

#include <iostream>
#include "Student.h" //自定义的头文件-引用Student.h

int main()
{
	Student *pStu = new Student();	

	double x = 3.0;
	double y = 5.0;
	double result = pStu->Add(x, y);
	
	std::cout << x << "+" << y << "=" << result;
	return 0;
}

在这里插入图片描述
Notes:
   ①函数在成为类的成员之后叫做方法,方法永远都是类(或结构体)的成员
   ②C# namespace 里不能有方法
在这里插入图片描述

二、方法的复用(reuse,重用)

①没有使用方法的复用

namespace CSharpMethodExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Calculator c = new Calculator();
            Console.WriteLine(c.GetCircleArea(10));
        }
    }

    //计算圆柱体积、圆锥体积、圆面积
    class Calculator
    {        
        //圆柱体积
        public double GetCylinderVolume(double r, double h)
        {
            return 3.14 * r * r * h;
        }

        //圆锥体积
        public double GetConeVolume(double r, double h)
        {
            return 3.14 * r * r * h / 3;
        }

        //圆面积
        public double GetCircleArea(double r)
        {
            return 3.14 * r * r;
        }        
    }
}

②使用了方法的复用

namespace CSharpMethodExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Calculator c = new Calculator();
            Console.WriteLine(c.GetCircleArea(10));
        }
    }

    //计算圆柱体积、圆锥体积、圆面积
    class Calculator
    {        
        //圆柱体积
        public double GetCylinderVolume(double r, double h)
        {
            return GetCircleArea(r) * h;
        }

        //圆锥体积
        public double GetConeVolume(double r, double h)
        {
            return GetCylinderVolume(r, h) * h / 3;
        }

        //圆面积
        public double GetCircleArea(double r)
        {
            return 3.14 * r * r;
        }        
    }
}

三、方法的声明与调用

在这里插入图片描述

1.方法的声明

在这里插入图片描述

2.静态(static)方法和实例方法

①实例方法(非静态方法)

namespace CSharpMethodExample
{
    class Program
    {
        static void Main(string[] args)
        {
       		 //实例方法GetCircleArea隶属于实例c,与实例是绑定的
            Calculator c = new Calculator();
            Console.WriteLine(c.GetCircleArea(10));
        }
    }
  
    class Calculator
    {
        //实例方法GetCircleArea
        public double GetCircleArea(double r) 
        {
            return 3.14 * r * r;
        }        
    }
}

②静态方法

namespace CSharpMethodExample
{
    class Program
    {
        static void Main(string[] args)
        {
            //静态GetCircleArea隶属于类Calculator,与类是绑定的
            double result = Calculator.GetCircleArea(100);
            Console.WriteLine(result);                
        }
    }
  
    class Calculator
    {
        //静态方法GetCircleArea
        public static double GetCircleArea(double r) 
        {
            return 3.14 * r * r;
        }        
    }
}

3.方法的调用

namespace CSharpMethodExample
{
    class Program
    {
        static void Main(string[] args)
        {
            //调用静态方法GetCircleArea,参数的类型和个数要匹配
            double result = Calculator.GetCircleArea(3.0);//实参3.0             
            Console.WriteLine(result);
        }
    }
  
    class Calculator
    {        
        public static double GetCircleArea(double r)//形参r
        {
            return 3.14 * r * r;
        }        
    }
}

四、构造器

  • 构造器(constructor)是类型的成员之一
  • 狭义的构造器指的是“实例构造器”(instance constructor)
  • 如何使用构造器
  • 声明构造器
  • 构造器的内存原理

构造函数译为构造器,成员函数译为方法,它们本质都还是函数。

1.构造器的声明与调用

①默认构造器

namespace ConstructorExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu = new Student();//()-调用Student类的构造器
            Console.WriteLine(stu.ID);
        }
    }

    /// <summary>
    ///  声明一个类,没有构造器的话;使用默认构造器
    /// </summary>
    class Student
    {
        public int ID;
        public string Name;         
    }
}

  默认构造器将int型的ID初始化为0:
在这里插入图片描述
②无参数构造器

namespace ConstructorExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu = new Student();//()-调用Student类的构造器
            Console.WriteLine(stu.ID);
            Console.WriteLine(stu.Name);
        }
    }

 
    class Student
    {
        //自定义构造器Student
        public Student()
        {
            this.ID = 1;
            this.Name = "No Name";
        }

        public int ID;
        public string Name;         
    }
}

在这里插入图片描述
③带参数构造器

namespace ConstructorExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu = new Student(2, "小光光");
            Console.WriteLine(stu.ID);
            Console.WriteLine(stu.Name);
        }
    }

 
    class Student
    {
        //自定义带参数构造器
        public Student(int initID,string initName)
        {
            this.ID = initID;
            this.Name = initName;
        }

        public int ID;
        public string Name;         
    }
}

在这里插入图片描述

Code Snippet
Ctrl + Tab * 2:快速生成构造器代码片段
在这里插入图片描述

2.构造器内存原理

①默认构造器图示

namespace ConstructorExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu = new Student();//引用变量-stu;实例-new Student;默认构造器-()         
        }
    }
 
    class Student
    {                               
        public int ID;//int是结构体类型,在内存中占4个字节
        public string Name;//string是类类型-引用类型,在内存中占4个字节;存储的是实例的地址
    }
}

图中左侧指的是栈,右侧指的是堆
栈内存分配是从高地址往低地址分配(二进制从下往上,从左往右),直到分配到栈顶。

在这里插入图片描述
②带参数构造器图示

namespace ConstructorExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu = new Student(1,"Mr.Okay");         
        }
    }
 
    class Student
    {
        public Student(int initID,string initName)
        {
            this.ID = initID;
            this.Name = initName;
        }
                                              
        public int ID;//int是结构体类型,在内存中占4个字节
        public string Name;//string是类类型-引用类型,在内存中占4个字节;存储的是实例的地址
    }
}

在这里插入图片描述

五、方法的重载(Overload)

在这里插入图片描述
WriteLine方法重载示例:
在这里插入图片描述
在这里插入图片描述

1.声明带有重载的方法

①方法签名中类型形参的个数构成重载:

    class Calculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }

        public int Add<T>(int a, int b)//类型形参<T>
        {
            T t;//…
            return a + b;
        }
    }

②方法签名中形参的类型(从左往右)和个数构成重载:

    class Calculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }

        public int Add(int a, int b, int c)//形参个数不同
        {
            return a + b + c;
        }

        public double Add(int x, double  y)//形参类型不同
        {
            return x + y;
        }
    }

③方法签名中形参的种类(值、引用或输出)构成重载:

    class Calculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }

        public int Add(ref int a, int b)//形参的种类
        {
            return a + b;
        }     
    }

2.重载的调用

namespace ConstructorExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Calculator c = new Calculator();
            int x = c.Add(100, 200);
            int y = c.Add(100,200,300);
            double z = c.Add(100D, 200D);            
        }
    }

    class Calculator
    {
        public int Add(int a, int b)
        {
            return a + b;
        }

        public int Add(int a, int b, int c)
        {
            return a + b + c;
        }

        public double Add(double a, double b)
        {
            return a + b;
        }
    }
}

六、对方法进行debug

  • 设置断点(breakpoint)
  • 观察方法调用时的 call stack
  • Step-in、Step-over、Step-out
  • 观察局部变量的值与变化

    ①调用堆栈 (call stack)
    调试状态下,菜单 调试 → 窗口 → 调用堆栈
    在这里插入图片描述
    ②逐语句(Step-in)
      进入调用的函数内,逐语句进行调试
    ③逐过程(Step-over)
    ④跳出(Step-out)
      用于跳出当前方法并返回到调用它的方法
    ⑤局部变量值的变化
    在这里插入图片描述

七、方法的调用与栈

  • 方法调用时栈内存的分配
    • 对 stack frame 的分析

 stack frame:一个方法被调用时,它在栈内存中的布局。
 C# 中调用方法时的变量归 Caller(主调函数) 管,不归 Callee(被调用者) 管。
 压变量入栈,C# 是从左至右的顺序。

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

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

namespace ConstructorExample
{
    class Program
    {
        static void Main(string[] args)
        {
            double a = Calculator.GetConeVolume(100, 90);//参数类型是double
        }
    }

    class Calculator
    {
        //计算圆面积
        public static double GetCircleArea(double r)
        {
            return Math.PI * r * r;
        }

        //计算圆柱体积
        public static double GetCylinderVolume(double r, double h)
        {
            double a = GetCircleArea(r);
            return a * h;
        }

        //计算圆锥体积
        public static double GetConeVolume(double r, double h)
        {
            double cv = GetCylinderVolume(r, h);
            return cv / 3;
        }
    }
}

分步讲解:
1.进入 Main 方法,调用 GetConeVolume 方法前
  在栈上开辟了 Main 方法的 stack frame。
在这里插入图片描述
2.Main 方法中调用 GetConeVolume 时
  Main方法在调用GetConeVolume,需要传两个参数,将两个参数压入栈中。(压参数入栈, 是从左至右的顺序。)
  因为 C# 中调用时的参数归 Caller (主调函数)管,此处即归 Main 管。

参数r,h — double类型,8个字节
在这里插入图片描述

在这里插入图片描述
3.进入 GetConeVolume 后
  局部变量是需要入栈的,GetConeVolume 方法中的 局部变量cv 入栈。
  r,h 也是局部变量,但已经作为参数被 Main 方法压入栈了,所以它只需要压 cv 即可。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
4.GetConeVolume 调用 GetCylinderVolume 时
  将两个参数r,h压入栈中。
在这里插入图片描述
5.进入 GetCylinderVolume 后
  局部变量 a 入栈。
在这里插入图片描述
6.GetCylinderVolume 调用 GetCircleArea 时
  GetCircleArea 只有一个参数,将其压入栈即可。
在这里插入图片描述
7.进入 GetCircleArea 后
  GetCircleArea 中没有局部变量,但它在栈上也占内存,它有自己的栈针。
在这里插入图片描述
8.GetCircleArea 返回后
  返回值存在 CPU 的寄存器(register)里面。
  call stack 少了一层。
  函数返回后,它所占有的 stack frame 就清空了。
在这里插入图片描述
9.GetCylinderVolume 返回后
在这里插入图片描述
10.GetConeVolume 返回后
  GetConeVolume 的 stack frame被清空。
  Main 方法中调用 GetConeVolume 时压入栈中的两个参数也出栈了。
在这里插入图片描述
11.Main 返回后(程序结束)
  Main 方法的 stack frame 也被清空。
在这里插入图片描述

发布了47 篇原创文章 · 获赞 74 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/IT_xiao_guang_guang/article/details/104186497