一、构造器
- 构造器(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 的分析
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 也被清空。