C#中的方法

方法是什么无需多言,本文重在深入讨论以下几点:方法的定义与调用、构造器(一种特殊的方法)、方法的重载、方法的调用与栈,涉及到一些内存分配的内容。

指路:一个超级好的C#教程

方法的由来

方法(method)的前身是C/C++语言的函数(function)。方法是面向对象范畴的概念,在面向对象语言中仍然称为函数。
方法永远都是类或结构体的成员,不能独立于类或者结构体之外,只有作为类或结构体的成员时才被成为方法。

C++中是可以的,称为“全局函数”

方法表示类或者结构体“能做什么事”,字段和方法(成员变量与成员方法)是类或者结构体最基本的成员之一。
方法的目的:隐藏复杂逻辑和代码复用。

方法的声明和调用

C#里方法的声明和定义是不分家的。

C++里是可以分家的,声明放在头文件里,定义放在代码文件里

方法的声明包括两部分:函数头和函数体。

  • 函数头:方法特性(可选) 方法修饰符组合(可选) partial(可选) 返回值类型 方法名 参数列表(泛型参数列表) 泛型约束(可选,如果是泛型方法的话)
    方法修饰符:

    • new
    • public
    • private
    • protected
    • internal
    • static
    • virtual
    • sealed
    • override
    • abstract
    • extern
    • async
  • 函数体:语句块({}括起来的内容)或 ;,只有一个 ;时代表这个方法是一个抽象方法。

    1
    public abstract string getName();

声明方法时的参数(Parameter)叫做形式参数,形参是一种变量。
调用方法时传入的参数叫实参(Argument),实参是值,值类型要与变量类型相匹配。

静态方法和实例方法

  • 静态方法
    定义和调用:属于类的方法,在使用时可直接进行通过类名.方法名进行调用,不需要创建实例。用 Static 修饰,静态方法只可以使用类的静态成员和其他静态方法,或者说静态方法只能访问类级别的属性和方法,不能访问实例级别的属性和方法,这是因为静态方法在没有实例的情况下就可以被调用。
    内存占用:静态方法的方法体只有一份,与类的类型信息相关联。这一份方法体的内存占用相对较小。调用静态方法不需要创建类的实例,因此在调用时无需额外的实例上下文,这使得静态方法在内存上可能更加轻量。
  • 实例方法
    定义和调用:属于实例的方法,在使用时需要创建对象,通过对象名.方法名进行调用。成员方法可以访问实例级别的成员(字段、属性、其他成员方法),也可以访问静态成员。
    内存占用:每个类的实例都有一份实例方法的方法体。相对于静态方法,实例方法的方法体在内存中可能占用更多空间,因为每个实例都有自己的拷贝。调用实例方法需要一个实例上下文。这个上下文包括实例变量和其他实例相关的信息。这些数据的内存占用会随着实例的创建和销毁而变化。

构造器(Constructor)

构造器又叫构造函数,所以构造器是一种特殊的函数,构造器是类的成员之一。狭义的构造器又叫“实例构造器”,用于构建实例在内存当中的内部结构。可以自己写构造器,如果没有写的话,C# 会自动添加一个默认构造器。一个类可以有多个构造器,只要我们自定义了构造器,就无法再使用默认构造器。

构造器的内存原理

1
2
3
4
5
6
7
8
9
10
class Student {
public int ID;
public string Name;
}

class Program {
static void Main(string[] args) {
Student stu = new Student();//调用默认构造器
}
}

stu 是局部变量,我们在栈上为 stu 这个变量分配内存地址。stu 是一个引用变量,所以我们先分配四个字节给 stu,全部置为 0;new Student 会在堆上分配一块实例大小的空间(一个int,一个string占用的内存大小),然后把这块空间的地址交还给 stu 变量。() 会帮我们把这块空间切割好,有四个字节分给 int 字段, 四个字节分给 string 字段,然后初始化。
如果我们的构造函数是带参的,调用时需要传入值,示例:

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

class Program {
static void Main(string[] args) {
Student stu = new Student(10, "Mr.Li");//调用默认构造器
}
}

那么在上述的过程之外,因为 string 是引用类型,所以要在堆上再分配一块内存,把 “Mr.Li” 转成二进制存在这块内存中,然后把这块内存的地址返回给实例中分给 string 的内存中存储。

方法重载(Overload)

方法签名由方法的名称、类型形参的个数和它的每一个形参(从左到右的顺序)的类型和种类(值,引用或输出)组成,方法签名不包含返回类型。
方法重载:是指在同一个类中定义多个具有相同名称但参数列表不同的方法。具体说来,重载要求两个或多个方法的方法名相同,但它们的参数类型、参数个数或参数顺序至少有一个不同。

重载决策:用于在给定了参数列表和一组候选函数成员的情况下,选择一个最佳函数成员来实施调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//一个简单的方法重载示例
public class Calculator
{
// 重载方法 1
public int Add(int a, int b)
{
return a + b;
}

// 重载方法 2
public double Add(double a, double b)
{
return a + b;
}
}

方法的调用与栈