思考并回答以下问题:
- 类的成员都有哪些?
- 一个类的多个实例的成员数据在内存中是怎么存储的?
- 静态成员的数据是怎么存储的?
本章涵盖:
- 类成员
- 成员修饰符的顺序
- 实例类成员
- 静态字段
- 静态函数成员
- 其他静态类成员类型
- 成员常量
- 常量和静态
- 属性
- 实例构造函数
- 静态构造函数
- 对象初始化语句
- 析构函数
- readonly修饰符
- this关键字
- 索引
- 访问器的访问修饰符
- 分部类和分部类型
- 分部方法
类成员
之前的两章阐述了9种类成员类型中的两种:字段和方法。在这一章中,我会介绍除事件和运算符之外的类型的类成员,并讨论其特征。
表1列出了类的成员类型。已经介绍过的类型用菱形标记。将在本章阐述的类型用勾号标记。将在以后的章节中阐述的类型用空的选择框标记。
表1 类成员的类型
成员修饰符的顺序
在前面的内容中,你看到字段和方法的声明可以包括如public和private这样的修饰符。这一章会讨论许多其他的修饰符。多个修饰符可以在一起使用,自然就产生一个问题:它们需要按什么顺序排列呢?
类成员声明语句由下列部分组成:核心声明、一组可选的修饰符和一组可选的特性(attribute),用于描述这个结构的语法如下。方括号表示方括号内的成分是可选的。1
[ ] [修饰符] 核心声明
- 修饰符
- 如果有修饰符,必须放在核心声明之前。
- 如果有多个修饰符,可以是任意顺序。
- 特性
- 如果有特性,必须放在修饰符和核心声明之前。
- 如果有多个特性,可以是任意顺序。
例如,public和static都是修饰符,可以用在一起修饰某个声明。因为它们都是修饰符,所以可以放置成任何顺序。下面两行代码是语义等价的:1
2public static int MaxVal;
static public int MaxVal;
图1阐明了声明中各成分的顺序,到目前为止,它们可用于两种成员类型:字段和方法。注意,字段的类型和方法的返回类型不是修饰符——它们是核心声明的一部分。
图1 类成员的类型
实例类成员
类成员可以关联到类的一个实例,也可以关联到类的整体,即所有类的实例。默认情况下,成员被关联到一个实例。可以认为是类的每个实例拥有自己的各个类成员的副本,这些成员称为实例成员。
改变一个实例字段的值不会影响任何其他实例中成员的值。迄今为止,你所看到的字段和方法都是实例字段和实例方法。
例如,下面的代码声明了一个类D,它带有唯一整型字段Mem1, Main创建了该类的两个实例,每个实例都有自己的字段Mem1的副本,改变一个实例的字段副本的值不影响其他实例的副本的值。图6-2阐明了类D的两个实例。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class D
{
public int Mem1;
}
class Program
{
static void Main()
{
D d1 = new D();
D d2 = new D();
d1.Meml = 10;
d2.Mem1 = 28;
Console.WriteLine("d1 = {0}, d2 = {1}", d1.Mem1, d2.Mem1);
}
}
这段代码产生如下输出:1
d1 = 10, d2 = 28
图2 类D的每个实例都有自己的字段Mem1的副本
静态字段
除了实例字段,类还可以拥有静态字段。
- 静态字段被类的所有实例共享,所有实例都访问同一内存位置。因此,如果该内存位置的值被一个实例改变了,这种改变对所有的实例都可见。
- 可以使用static修饰符将字段声明为静态,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class D
{
int Mem1; // 实例字段
static int Mem2; // 静态字段
// ↑ 关键字
}
class Program
{
static void Main()
{
D d1 = new D();
D d2 = new D();
...
}
}
例如,图3左边的代码声明了类D,它含有静态字段Mem2和实例字段Mem1,Main定义了类D的两个实例。该图表明静态成员Mem2是与所有实例的存储分开保存的。实例中灰色的字段表明,从实例内部,访问或更新静态字段的语法和访问或更新其他成员字段一样。
- 因为Mem2是静态的,类D的两个实例共享单一的Mem2字段。如果Mem2被改变了,这个改变在两个实例中都能看到。
- 成员Mem1没有声明为static,所以每个实例都有自己的副本。
图3 静态和非静态数据成员
从类的外部访问静态成员
在前一章中,我们看到使用点运算符可以从类的外部访问public实例成员。点运算符由实例名、点和成员名组成。
就像实例成员,静态成员也可以使用点运算符从类的外部访问。但因为没有实例,所以必须使用类名,如下面代码所示:1
D.Mem2 = 5; // 访问静态成员
静态成员的生存期
静态成员的生命期与实例成员的不同。
- 之前我们已经看到了,只有在实例创建之后才产生实例成员,在实例销毁之后实例成员也就不存在了。
- 但是即使类没有实例,也存在静态成员,并且可以访问。
图4阐述了类D,它带有一个静态字段Mem2。虽然Main没有定义类的任何实例,但它把值5赋给该静态字段并毫无问题地把它打印出来。
图4 没有类实例的静态成员仍然可以被赋值并读取,因为字段与类有关,而与实例无关
图4中的代码产生以下输出:1
Mem2 = 5
说明
静态成员即使没有类的实例也存在。如果静态字段有初始化语句,那么会在使用该类的任何静态成员之前初始化该字段,但没必要在程序执行的开始就初始化。
静态函数成员
除了静态字段,还有静态函数成员。
- 如同静态字段,静态函数成员独立于任何类实例。即使没有类的实例,仍然可以调用静态方法。
- 静态函数成员不能访问实例成员。然而,它们能访问其他静态成员。
例如,下面的类包含一个静态字段和一个静态方法。注意,静态方法的方法体访问静态字段,