3 栈帧 递归 类成员 静态字段 常量 静态函数 属性 构造函数 析构函数 this readonly 索引器 分部方法 分部类
好记性不如烂笔头
栈帧
现在,我们已经知道了 本地变量(局部变量),参数都存放在栈上,现在我们研究下其组织。
在调用方法的时候,内存从栈的顶部开始分配,保存和方法关联的一些数据项。这块内存叫做方法的栈帧。
- 栈帧包含的内存保存如下内容
- 返回地址,也就是在方法退出的时候继续执行的位置。
- 这些参数分配的内存,也就是方法的值参数,或者还可能是参数数组(如果有的话);
- 各种和方法调用相关的其他管理数据项
- 在方法调用是,整个栈帧会压入栈。
- 在方法退出的时候,整个栈帧都会从栈上弹出。弹出的栈帧有的时候也叫做栈展开(unwind);
static void MethodA(int a,int b)
{
Console.WriteLine("Enter MethodA: {0} {1}",a,b);
MethodB(12,10);
Console.WriteLine("End MethodA");
}
static void MethodB(int a,int b)
{
Console.WriteLine("Enter MethodB: {0} {1}",a,b);
Console.WriteLine("End MethodB");
}
static void Main(string[] args)
{
MethodA(12, 24);
Console.ReadKey();
}
输出如下:
Enter MethodA: 12 24
Enter MethodB: 12 10
End MethodB
End MethodA
下图演示了调用方法是栈帧压入栈 和 弹出的过程。
递归
除了调用其他方法,方法也可以调用自身。这叫递归。
递归必须有结束条件,不然就一直递归下去了
public static int 递归方法(int value)
{
if (value <= 1)
{
return value;
}
return value * 递归方法(value-1);
}
递归方法(5); // 120
5 * (4*(3*(2))) = 120;
调用方法自身的机制和调用其他方法都是完全一样的,都是每一次调用方法把新的栈帧压入栈顶
public static void 递归方法2(int value)
{
if (value == 0) return;
递归方法2(value - 1);
Console.WriteLine("{0}",value);
}
输出:
1
2
3
4
5
原理:
‘
=深入了解类==
1 类成员
数据成员 | 函数成员(执行代码) |
---|---|
字段 | 方法 |
常量 | 属性 |
构造函数 | |
析构函数 | |
运算符 | |
索引 | |
事件 |
2 成员修饰符的顺序
学习过程中,我们看到字段和方法的声明可以包括 public
private
这样的修饰符。多个修饰符可以在一起使用,自然产生了一个问题:他们需要按什么顺序排列?
类成员声明语句由下列部分组成: 核心声明,一组可选的修饰符 和 一组可选的特性(attribute)。用于描述这个结构的语句如下。方括号表示可以选择的。
[特性] [修饰符] 核心声明
- 修饰符
- 如果有多修饰符,必须放在核心声明之前。
- 如果有多个修饰符,可以是任意顺序的
- 特性
- 如果有特性,必须放在修饰符前和核心声明前
- 如果有多个特性,可以是任意顺序的
3 静态字段
静态字段被类的所有实例共享,所有的实例都访问同一块内存位置。因此,如果改内存位置的值被一个实例改变,这种改变对所有实例都可见。
可以用static修饰符将字段声明为静态,如
class D
{
int Number; // 实例字段
static int Number2; // 静态字段
}
4 从类的外部访问静态成员
类名.静态字段 = 5; // 访问静态类成员
4.1 静态成员的生存期
- 之前我们已经看到了,只有在实例创建之后才能产生实例成员,在实例销毁之后实例成员也就不在了
- 但是即使类没有实例,也存在静态成员,并且可以访问
5 静态函数成员
- 同静态字段一样,独立于任何类的实例,没有实例也可以调用
- 静态函数不能访问实例成员,但是能访问其他静态成员
class A
{
public static void Func()
{
// ...
}
}
调用方法于静态成员一样
6 其他静态成员类型
数据成员 | 函数成员 |
---|---|
字段 | 方法 |
类型 | 属性 |
构造函数 | |
运算符 | |
事件 |
7 成员常量
只能在类中声明而不是方法
class MyClass
{
const int IntVal = 100; // 无法赋值,必须初始化
}
8 常量与静态量
成员产品比本地常量更有趣,因为他们表现得和像静态值。他们对每个实例都是可见的,没有实例也可以使用。与真正的静态量不同,常量没有自己的存储位置,而是在编译阶段被编译器做了替换
class A
{
public const double PI = 3.14159;
}
class Program
{
static void Main()
{
A.PI; // 3.14159
}
}
static const double PI = 3.14159; // 错误,不能将常量声明为static
9 属性
与字段类似,属性有如下特征
- 他有类型
- 他可以被赋值和读取
- 然而他和字段不同,属性1是一个函数成员
- 它不为数据存储分配内存!
- 它执行代码
- 属性是指的一组有两个匹配的,称为访问器的方法。
- set访问器为属性的赋值
- get访问器为属性的取值
int MyValue
{
set
{
// ..
}
get
{
// ..
}
}
9.1 属性声明和访问器
- set访问器总是:
- 拥有一个单独的,隐示的值参,名称为value,与属性的类型相同;
- 拥有一个返回类型void
- get访问器总是:
- 没有值参
- 拥有一个属性类型一样的返回类型
9.2 只读和只写属性
- 只有get 是只读 无法写入
- 只有set 是只写 无法读取
9.3 静态属性
- 不能访问类的实例
- 不管类是否有实例,他们都是存在的
- 当从类的外部访问,必须使用类名引用,而不是实例名
10 实例构造函数
- 构造函数用于初始化类实例的状态
- 如果希望能从类的外部创建类的实例,需要将构造函数声明为public
- 构造函数名与类名一样
- 构造函数不能有返回值
- 构造函数可以重载
class A{
public void A(){
// ...
}
}
10.1 默认构造函数
如果在类的声明中没有显示的声明构造函数,编译器会提供一个隐式的构造函数,他有以下特征:
-
没有参数
-
方法体为空
如果你声明了构造函数,那么编译器就不会设置默认的构造函数
10.2 静态构造函数
构造函数也可以声明为static。通常,静态构造函数用于初始化类的静态字段
- 初始化类级别的项
- 在引用任何静态成员之前
- 在创建累的任何实例之前
- 类只能有一个静态函数,而且不能带参数。
- 静态构造函数不能有访问修饰符
11 析构函数
执行在类的实例被销毁之前需要的清理或释放非托管资源的行为
非托管资源 pass....
12 readonly修饰符
字段可以用readonly修饰符声明。其作用类似于将字段声明为const
- const字段只能在字段的声明语句中初始化,而readonly字段可以在下列任意为设置他的值
- 字段声明语言,类似const
- readonly字段的值可以在运行时决定、
13 this关键字
this关键字在类中使用,是对当前实例的引用。他只能用在下列类成员的代码块中。
- 实例构造函数
- 实例方法
- 属性和索引器的实例访问器
14 索引器
14.1 什么是索引器
14.2 声明索引器
- 索引器没有名称。在名称的位置是关键字this。
- 参数列表在方括号中间。
- 参数列表中必须至少声明一个参数
14.3 索引器的set访问器
- 一个隐示参数,名称为value,value持有要保存的数据;
- 一个或跟多索引参数,表示数据应该保存到哪里。
- 他的返回值类型为void
14.4 索引器的get访问器
- 他的参数列表和索引器的声明中的相同
- 他的返回值类型与索引器相同类型
索引器可以重载 多个参数
15 访问器的访问修饰符
16 分布类 和分布类型
类的声明可以分割成就几个分部类的声明
- 每个分部类的声明都含有一些类成员的声明
- 累的分部类声明可以在同一文件中也可以在不同文件中
每个局部声明必须标为 partial class, 而不是单独的关键字class。分部类声明扛起来和普通类相同,除了附加的类型修饰符 partial
类型修饰符partial不是关键字,所以在器他上下文中,可以在程序把他用作标识符。但是之间用在关键字class,struct或interface之前时,他表示分部类型。
17 分部方法
- 定义分部方法声明
- 给出签名和返回值类型
- 声明的实现部分只是一个分号。
- 实现分部方法声明
- 给出标签名和返回值类型
- 正常书写代码块语句
- 定义声明和实现声明的签名和返回类型必须匹配。签名和返回类型有如下特征:
- 返回值必须是void
- 签名不能包括访问修饰符,这使分部方法是隐示私有的
- 参数列表补鞥呢包含out参数。