• C#学习 - 方法的定义、调用、调试


    方法

    • 方法(Method)是由C/C++中的函数(Function)发展而来的
    //C语言
    #include 
    int Add(int x, int y)
    {
    	return x + y;
    }//函数
    int main(void)
    {
    	int a = 4;
    	int b = 2;
    	int c = Add(a, b);
    	printf("%d + %d = %d\n", a, b, c);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    //C++
    #include 
    int Add(int x, int y)
    {
    	return x + y;
    }//函数
    int main()
    {
    	int a = 4;
    	int b = 2;
    	int c = Add(a, b);
    	std::cout << a << " + " << b << " = " << c;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    方法是面向对象,当一个函数以类的成员出现的时候就叫方法,所以方法又叫成员函数
    在编写C++程序时,选择添加类(Class),然后输入类名,后面的 .h 文件就是类的声明,而 .cpp 文件就是类的定义(在C#中类的声明和定义是放在一起的)C++程序添加类

    //ABC.h - 类的声明
    #pragma once
    class ABC
    {
    public:
    	void ShowHello();
    };
    
    //ABC.cpp - 类的定义
    #include "ABC.h"
    #include 
    void ABC::ShowHello()
    {
    	std::cout << "Hello World";
    }
    
    //use.cpp
    #include 
    #include "ABC.h"
    int main()
    {
    	ABC* pABC = new ABC();
    	//此处已经有了C#方法的雏形了
    	pABC->ShowHello();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 方法是类(或结构体)的成员

    C#中函数不能独立于类(或结构体)之外
    只有作为类(或结构体)的成员出现时,函数才能被称为方法

    namespace ConsoleApp1
    {
        int Add(int x, int y)
        {
            return x + y;
        }
        internal class Program
        {
            static void Main(string[] args)
            { }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上段代码中的函数没有在类中,编译会报错

    • 方法是类(或结构体)最基本的成员之一
      类(或结构体)有两个最基本的成员 - 字段和方法(成员变量和成员函数)
      方法表示类(或结构体)所能干的事情
    • 使用方法和函数的目的
      1. 隐藏复杂的逻辑;
      2. 把大算法分解为小算法;
      3. 复用;
    //未复用
    class Tool
    {
    	public double GetCicleArea(double R)
    	{
    		return 3.14 * R * R;
    	}
        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;
        }
    }
    //复用
    class Tool
    {
    	public double GetCicleArea(double R)
    	{
    		return 3.14 * R * R;
    	}
        public double GetCylinderVolume(double R, double H)
        {
            return GetCicleArea(R) * H;
        }
        public double GetConeVolume(double R, double H)
        {
            return GetCyliderVolume(R, H) / 3;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    将一个大的算法分解为小的算法,再由算法一个一个解决,就是自顶向下逐步求精的方法

    方法的声明与调用

    方法的声明

    函数头 + 函数体
    函数头:特性 + 有效的方法的修饰符 + partial + 返回类型 + 方法名 + 类型参数列表 + ( + 形式参数列表 + )+ 类型参数约束句子
    其中只有 返回类型 & 方法名 & **()**是必须要的;其中 类型参数约束句子 只有在有 类型参数列表 出现时才能出现
    方法名最好使用动词或动词短语,所有单词首字母大写(Pascal命名法)
    形式参数(parameter,简称:形参):是一种变量,会参与构成方法的算法逻辑

    静态方法和实例方法

    静态方法与类绑定,非静态方法(实例方法)与实例绑定

    using System;
    
    namespace ConsoleApp1
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                Tool t = new Tool();
                Console.WriteLine(t.Add(1, 2));
                //实例方法与实例绑定
                Console.WriteLine(Tool.Sub(1,2));
                //静态方法与类绑定
            }
        }
        class Tool
        {
            public int Add(int x, int y)
            { 	//实例方法
                return x + y;
            }
            public static int Sub(int x, int y)
            {	//静态方法
                return x - y;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    调用方法

    方法调用:方法名 + ( + 实际参数列表 + )
    实际参数(Argument,简称:实参):实际参数列表需要与定义方法时的形式参数列表相匹配
    以上面那段代码为例:

    int x = Tool.Sub(1);
    //此时实际参数个数与形式参数不匹配
    int y = Tool.Sub(1.0, 2.5);
    //此时实际参数类型与形式参数不匹配
    
    • 1
    • 2
    • 3
    • 4

    构造器

    构造器(Constructor)是类型的成员之一,构造器就是构造函数
    狭义的构造器就是实例构造器(Instance constructor)

    using System;
    
    namespace ConsoleApp1
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                Human human = new Human();
                //上行代码中的 () 就是调用构造器
                Console.WriteLine(human.ID);
                Console.WriteLine(human.Name);
    
                People people = new People();
                Console.WriteLine(people.ID);
                Console.WriteLine(people.Name);
    
                Student student = new Student(1, "None");
                //构造器带参数时调用也需要带参数
                Console.WriteLine(student.ID);
                Console.WriteLine(student.Name);
            }
        }
        class Human
        { //当声明一个类后,没有准备构造器时,编译器会自动为其准备一个默认构造器
            //默认构造器可以把内存中的对象的字段进行初始化,就是将 ID 和 Name 进行初始化
            public int ID;
            public string Name;
        }
        class People
        {
            public People()
            {   //创建构造器时,构造函数名要与类一致
                this.ID = 0;
                this.Name = "NULL";
            }
            public int ID;
            public string Name;
        }
        class Student
        {
            public Student(int id, string name)
            {
                this.ID = id;
                this.Name = name;
            }
            public int ID;
            public string Name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    一个类中可以有多个构造器

    class People
    {
        public People(int id, string name)
        {
            this.ID = id;
            this.Name = name;
        }
        public People()
        {
            this.ID = 0;
            this.Name = "NULL";
        }
        public int ID;
        public string Name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    构造器的内存原理

    默认构造器

    Human human = new Human();
    
    • 1
    class Human
    {
    	public int ID;
    	public string Name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    第一个代码创建了一个human变量,human变量存储在栈区中(栈区存储由高字节位到低字节位)
    new操作符开始执行时,在堆区找足够的内存空间作为实例的内存,而 int 需要占4字节,string 需要占4字节,所以最后占用了8个字节。构造时就对这8个字节进行切割,前4个为int类型,后4个为string类型,然后默认构造器将这8个字节中的值全赋值为0
    最后将实例的地址存储在human变量中

    带参数的构造器

    Human human = new Human(1, "One");
    
    • 1
    class Human
    {
    	public Human(int id, string name)
    	{
    		this.ID = id;
    		this.Name = name;
    	}
    	public int ID;
    	public string Name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    依旧是在栈区分配human变量的内存空间,然后在堆区分配8字节,然后开始切割这8个字节,再在前4个字节中存入1,在后4个字节中存入“One”
    最后把实例的地址放进human变量的内存空间中

    方法的重载(Overload)

    当一个类中的两个方法的名称一致时,方法签名不能一致
    方法签名(Method signature)由方法名称、类型形参的个数和方法的形参(由左到右的顺序)的类型、种类(值、引用、输出)组成,方法签名不包含返回类型

    class Tool
    {
        public int Add(int a, int b)
        { return a + b; }
        public double Add(double a, double b)
        { return a + b; }
        public int Add(int a, int b, int c)
        { return a + b + c; }
        public int Add(ref int a, out int b)
        { b = 10; return a + b; }
        //ref就是引用、out就是输出
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    构造器也可以有重载,构造器的签名由每一个形参(从左到右的顺序)的类型和种类(值、引用、输出)组成
    重载决策:根据调用方法时实参的类型来决定调用哪一个方法。如:

    Console.WriteLine(100);
    Console.WriteLine("Hello World");
    
    • 1
    • 2

    对方法进行debug

    debug可以找到bug发生的地方,也可以了解到程序运行的原理

    设置断点(breakpoint)

    设置断点后,运行程序时会自动停在断点设置处断点
    红色就是断点标识,设置快捷键是F9,然后按F5进行调试,就会执行时停到断点处调试后
    当红点标识变成上图标识时,就是程序执行停在了那里

    观察方法调用时的调用堆栈(call stack)

    Call stack
    上图中第一行就是断点处的方法(函数)
    第二行就是调用它的函数,可以双击跳到所需位置
    实际代码中此处可能会层层叠加,最后一行就是最外层调用,而红点标识处就是断点处

    逐语句(Step-into)、逐过程(Step-over)、跳出(Step-out)

    Step-into(F11)会进入所调用的方法中去
    Step-over(F10)不会进入所调用的方法中,没有Step-into细致
    Step-out(Shift+F11)可以从一个方法中直接回到调用它的那段代码上

    观察局部变量的值与变化

    在监视中观察监视
    将鼠标移到变量处,可以标识出变量的值,当语句调试到那一处时,会自动将被标识的变量的值显示出来

    方法调用时栈内存的分配

    stack frame:一个方法被调用时,它在栈内存当中的布局
    当代码执行到一个方法时,在栈区中分配一个内存
    当在A方法中调用一个B方法时用到了实际参数,传的参数也会分配到栈区中,在C#中这些实际参数归A方法管
    分配参数内存时,在C#中先分配左边的参数,再分配右边的参数
    当一个方法调用结束后,会回收给它分配的内存,且传递的实参所占的内存也会被回收
    方法的返回值会存在cpu的寄存器(一种高速内存)中,当寄存器空间不够存放返回值时才会存放在栈区

    using System;
    
    namespace ConsoleApp1
    {
        internal class Program
        {
            static void Main(string[] args)
            {
                Tool tool = new Tool();
                Console.WriteLine(tool.Add(1, 2));
            }
        }
        class Tool
        {
            public int Add(int a, int b)
            {
                int c = Sub(a, b);
                return c;
            }
            public int Sub(int a, int b)
            {
            	int c = a - b;
                return c;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    栈区内存分配
    上图就是上段代码执行到方法Sub时的栈区内存分配,继续执行就会从上往下慢慢回收已经调用结束的方法所占的内存
    注:上图只标识了参数内存分配,实际上还有其他很多元素会分配到栈区中

  • 相关阅读:
    深度学习 神经网络
    JavaScript 数组 【‘遍历数组’,‘增‘,‘删’,‘改‘,‘查‘】 详解 +增删查改数组案例
    汇川伺服【选型目录】
    光流法总结
    汽车电子——产品标准规范汇总和梳理(UDS诊断)
    VSCode汉化设置
    ES集群中节点与分片的区别
    springboot+rocketmq(4):实现延时消息
    Mac/Linux 安装 Go 详解
    回溯算法 —— 子集问题
  • 原文地址:https://blog.csdn.net/Pokipata/article/details/132806059