六、类的大小
类的成员函数放在公共代码区,所有该类的对象共享这些成员函数,每个对象的大小为类内成员变量的大小之和,遵循内存对齐原则
类的实例使用 new 运算符创建,该运算符为新的实例分配内存,调用构造函数初始化该实例,并返回对该实例的引用。下面的语句创建两个 Point 对象,并将对这两个对象的引用存储在两个变量中:
Point p1 = new Point(0, 0);
Point p2 = new Point(10, 20);
当不再使用对象时,该对象占用的内存将自动收回。在 C# 中,没有必要也不可能显式释放分配给对象的内存.
c#中类和对象详解
c#中类和对象详解
1.1 类和对象
类 (class) 是最基础的 C# 类型。类是一个数据结构,将状态(字段)和操作(方法和其他函数成员)组合在一个单元中。类为动态创建的类实例 (instance) 提供了定义,实例也称为对象 (object)。类支持继承 (inheritance) 和多态性 (polymorphism),这是派生类 (derived class) 可用来扩展和专用化基类 (base class) 的机制。
使用类声明可以创建新的类。类声明以一个声明头开始,其组成方式如下:先指定类的属性和修饰符,然后是类的名称,接着是基类(如有)以及该类实现的接口。声明头后面跟着类体,它由一组位于一对大括号 { 和 } 之间的成员声明组成。
下面是一个名为 Point 的简单类的声明:
public class Point
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
类的实例使用 new 运算符创建,该运算符为新的实例分配内存,调用构造函数初始化该实例,并返回对该实例的引用。下面的语句创建两个 Point 对象,并将对这两个对象的引用存储在两个变量中:
Point p1 = new Point(0, 0);
Point p2 = new Point(10, 20);
当不再使用对象时,该对象占用的内存将自动收回。在 C# 中,没有必要也不可能显式释放分配给对象的内存。
1.1.1 成员
类的成员或者是静态成员 (static member),或者是实例成员 (instance member)。静态成员属于类,实例成员属于对象(类的实例)。
下表提供了类所能包含的成员种类的概述。
成员 | 说明 |
常量 | 与类关联的常数值 |
字段 | 类的变量 |
方法 | 类可执行的计算和操作 |
属性 | 与读写类的命名属性相关联的操作 |
索引器 | 与以数组方式索引类的实例相关联的操作 |
事件 | 可由类生成的通知 |
运算符 | 类所支持的转换和表达式运算符 |
构造函数 | 初始化类的实例或类本身所需的操作 |
析构函数 | 在永久丢弃类的实例之前执行的操作 |
类型 | 类所声明的嵌套类型 |
1.1.2 可访问性
类的每个成员都有关联的可访问性,它控制能够访问该成员的程序文本区域。有五种可能的可访问性形式。下表概述了这些可访问性。
可访问性 | 含义 |
public | 访问不受限制 |
protected | 访问仅限于此类和从此类派生的类 |
internal | 访问仅限于此程序 |
protected internal | 访问仅限于此程序和从此类派生的类 |
private | 访问仅限于此类 |
1.1.3 基类
类声明可通过在类名称后面加上一个冒号和基类的名称来指定一个基类。省略基类的指定等同于从类型 object 派生。在下面的示例中,Point3D 的基类为 Point,而 Point 的基类为 object:
public class Point
{
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public class Point3D: Point
{
public int z;
public Point3D(int x, int y, int z): Point(x, y) {
this.z = z;
}
}
一个类继承它的基类的成员。继承意味着一个类隐式地包含其基类的所有成员,但基类的构造函数除外。派生类能够在继承基类的基础上添加新的成员,但是它不能移除继承成员的定义。在前面的示例中,Point3D 类从 Point 类继承了 x 字段和 y 字段,每个 Point3D 实例都包含三个字段 x、y 和 z。
从某个类类型到它的任何基类类型存在隐式的转换。因此,类类型的变量可以引用该类的实例或任何派生类的实例。例如,对于前面给定的类声明,Point 类型的变量既可以引用 Point 也可以引用 Point3D:
Point a = new Point(10, 20);
Point b = new Point3D(10, 20, 30);
1.1.5.3 静态方法和实例方法
使用 static 修饰符声明的方法为静态方法 (static method)。静态方法不对特定实例进行操作,并且只能访问静态成员。
不使用 static 修饰符声明的方法为实例方法 (instance method)。实例方法对特定实例进行操作,并且能够访问静态成员和实例成员。在调用实例方法的实例上,可以通过 this 显式地访问该实例。而在静态方法中引用 this 是错误的。
1.1.5.4 虚方法、重写方法和抽象方法
若一个实例方法的声明中含有 virtual 修饰符,则称该方法为虚方法 (virtual method)。若其中没有 virtual 修饰符,则称该方法为非虚方法 (non-virtual method)。
在调用一个虚方法时,该调用所涉及的那个实例的运行时类型 (runtime type) 确定了要被调用的究竟是该方法的哪一个实现。在非虚方法调用中,实例的编译时类型 (compile-time type) 是决定性因素。
虚方法可以在派生类中重写 (override)。当某个实例方法声明包括 override 修饰符时,该方法将重写所继承的具有相同签名的虚方法。虚方法声明用于引入新方法,而重写方法声明则用于使现有的继承虚方法专用化(通过提供该方法的新实现)。
抽象 (abstract) 方法是没有实现的虚方法。抽象方法使用 abstract 修饰符进行声明,并且只有在同样被声明为 abstract 的类中才允许出现。抽象方法必须在每个非抽象派生类中重写。
下面的示例声明一个抽象类 Expression,它表示一个表达式树节点;它有三个派生类 Constant、VariableReference 和 Operation,它们分别实现了常量、变量引用和算术运算的表达式树节点。
1.1.5.5 方法重载
方法重载 (overloading) 允许同一类中的多个方法具有相同名称,条件是这些方法具有唯一的签名。在编译一个重载方法的调用时,编译器使用重载决策 (overload resolution) 确定要调用的特定方法。重载决策将查找与参数最佳匹配的方法,如果没有找到任何最佳匹配的方法则报告错误信息。下面的示例演示重载决策的工作机制。Main 方法中的每个调用的注释表明实际被调用的方法。
1.1.6.1 构造函数
C# 支持两种构造函数:实例构造函数和静态构造函数。实例构造函数 (instance constructor) 是实现初始化类实例所需操作的成员。静态构造函数 (static constructor) 是一种用于在第一次加载类本身时实现其初始化所需操作的成员。
构造函数的声明如同方法一样,不过它没有返回类型,并且它的名称与其所属的类的名称相同。如果构造函数声明包含 static 修饰符,则它声明了一个静态构造函数。否则,它声明的是一个实例构造函数。
实例构造函数可以被重载。例如,List 类声明了两个实例构造函数,一个无参数,另一个接受一个 int 参数。实例构造函数使用 new 运算符进行调用。下面的语句分别使用 List 类的每个构造函数分配两个 List 实例。
List list1 = new List();
List list2 = new List(10);
实例构造函数不同于其他成员,它是不能被继承的。一个类除了其中实际声明的实例构造函数外,没有其他的实例构造函数。如果没有为某个类提供任何实例构造函数,则将自动提供一个不带参数的空的实例构造函数。
1.1.6.6 析构函数
析构函数 (destructor) 是一种用于实现销毁类实例所需操作的成员。析构函数不能带参数,不能具有可访问性修饰符,也不能被显式调用。垃圾回收期间会自动调用所涉及实例的析构函数。
垃圾回收器在决定何时回收对象和运行析构函数方面允许有广泛的自由度。具体而言,析构函数调用的时机并不是确定的,析构函数可能在任何线程上执行。由于这些以及其他原因,仅当没有其他可行的解决方案时,才应在类中实现析构函数。