一、構造器
- 構造器(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 也被清空。