** 第九章 ** ** ** ** 方法 ** ** **
** 一、 ** ** 实例构造器 **
1、 前面提到用 new 操作符创建对象时的三部曲:
l 为对象分配内存
l 初始化对象的附加成员(方法表指针和 SyncBlockIndex )
l 调用实例构造器初始化实例状态
在分配内存时,系统将所有内存位置均置为 0 值,这就是为什么字段初始化而未赋值时均为 0 或 null 值。
** 不 ** 调用 实例构造器的情况:
l 调用 Object.MemberwiseClone() 方法创建实例(分配内存;初始化附加成员;将源对象字节拷贝到新创建的对象)
l 反序列化对象时
2、 为避免为实例字段产生过多的构造器代码,应避免在声明字段时为字段符初值,而是在无参构造器中为它们符初值,在其他重载的构造器中调用无参构造器。
3、 值类型实例构造器
l C# 编译器不会自动调用其构造器,必须显式调用构造器才能起作用
l C# 编译器不允许为值类型定义无参实例构造器(下面会介绍可以定义无参类型构造器)
l 不能为结构中的字段在声明的同时赋初值,可通过定义带参构造器的方式进行
l 必须在结构的构造器中为所有字段赋初值
** 二、 ** ** 类型构造器 **
1、 类型构造器的一些限制:
l 不能带任何参数
l 类型构造器总为私有的,不能用其他访问修饰符
2 、类型构造器被调用的时机:
l 第一个实例被创建,或者类型的第一个字段或成员第一次被访问之前
l 非继承静态字段被第一次访问之前
类型构造器在类型的生命周期中只被调用一次;
3 、一些限制:
l 若类型构造器中抛出异常,则该类型变成不可访问,访问其中的任何字段或方法均会抛出 System.TypeInitializationException 异常
l 类型构造器只能访问类型的静态字段
l 类型构造器不应该调用基类型的类型构造器,因静态字段并非继承而是编译时静态绑定
** 三、 ** ** 操作符重载 **
1、 操作符重载
C# 中对操作符重载的一些限制:
l 必须声明为 public static
l 必须有一个参数为操作符所属类型
l 不能改变操作符原始定义的引数个数
l 若定义了 true 操作符也必须同时定义 false 操作符,二者都必须返回 bool 值
l ++ 、 -- 操作符必须返回其所隶属之类型的一个实例
l 可被重载的一元操作符: + 、 - 、!、 ~ 、 ++ 、 -- 、 true 、 false
l 可被重载的二元操作符: + 、 - 、 * 、 / 、 % 、!、 ^ (异或)、 < 、 > 、 << 、 >> 、 == 、! = 、 <= 、 >=
l 不允许被重载的操作符: && 、 || 、 = 、?:、 += 、 -= 、 /= 、 %= 、 |= 、 ^= 、 <<= 、 >>= ,实际上其中一些“复式操作符”在二元操作符被重载后自动生成,而不能显式定义
l 必须成对重载的操作符:( == ,! = )、( < , > )、( <= , >= )
l ++ 、 -- 操作符重载时不能区分其为前置或后置的
2、 操作符重载与语言互操作性
编译器会为重载的操作符生成一个特殊名称的方法,如 + (加)操作符生成 op_Addition ()方法,并为该方法的定义条目上加上 specialname 标记。当某种语言不能进行操作符重载时,可以直接定义具有该特殊名称的方法,以在其他语言中调用 ; 或直接调用具有该特殊名称的方法以适应某种语言不能解析操作符的限制。如: vb 中不能重载操作符,可显式定义 op_Addition() 方法以在 C# 中调用; C# 中定义的 + 操作符不能被 VB 识别,可显式调用 op_Addition() 方法获得同样的功能。
** 四、 ** ** 转换操作符 **
转换操作符的一些限制:
l 必须为 public static
l 必须指定关键字 implicit 或 explicit ,原则为:从本类型转换为其他类型使用 implicit ,将其他类型转换为本类型用 explicit ,不能都使用 implicit
** 五、 ** ** 方法参数 **
1、 引用参数
l 缺省情况下为值传递
l 标志为 out 的参数,在调用方法前 不必 初始化,但返回之前必须赋值,没有被初始化的参数是不能被使用
l 标志为 ref 的参数,在调用方法前 必须 初始化,否则触发编译错误
l 可以使用 ref 或 out 来进行方法的重载,但不能通过区分 ref 和 out 来重载方法
l 按引用方式传递的变量(实参)必须和方法声明的参数(形参)类型完全相同,否则触发编译错误。
2、 可变数目参数
使用 params 关键字及对象数组的方式指定可变参数序列。一些限制:
l 只有方法的最后一个参数才能使用可变数目参数
** 六、 ** ** 虚方法 **
1、 虚方法的调用机理
CLR 使用以下两个 IL 指令调用方法:
u call 根据类型(即引用的静态类型、声明类型)来调用一个方法
u callvirt 根据对象(即引用的动态类型、实际类型)来调用一个方法
对于虚方法使用 call 来调用的情况有:
l base. 虚方法(),
l 密封类型引用虚方法,因为没有必要检验密封类型的实际类型
l 值类型,避免被装箱
使用 callvirt 调用非虚方法的情况:
l 应用变量为 null 时,使用 callvirt 才会抛出 System.NullReferenceException 异常,而 call 不会抛出
无论 call 或 callvirt 调用方法,均会有一个隐含的 this 指针作为方法的第一个参数,它指向正在操作的对象
2、 虚方法的版本控制:
用下面的例子说明:
using System;
class BaseClass
{
public void NonVirtualFunc()
{
Console.WriteLine("Non virtual func in base class");
}
public virtual void VirtualFunc()
{
Console.WriteLine("Virtual func in base class");
}
}
class DevicedClass : BaseClass
{
// 若不使用 new 关键字则编译器会有 warning :
// “ DevicedClass.NonVirtualFunc() ”上要求关键字
//new ,因为它隐藏了继承成员“ BaseClass.NonVirtualFunc() ”
public new void NonVirtualFunc()
{
Console.WriteLine("Non virtual func in deviced class");
}
// 若不添加关键字 override 或 new ,则编译器会有 warning :
// “ DevicedClass.VirtualFunc() ”将隐藏继承的成员“ BaseClass.VirtualFunc()
// ”。若要使当前成员重写该实现,请添加关键字 override 。否则,添加关键字
//new 。
public override void VirtualFunc()
{
Console.WriteLine("Virtual func in deviced class");
}
}
class TestClass
{
public static void Main ()
{
// 派生类实例调用 非虚 及 虚函数
DevicedClass dc = new DevicedClass();
dc.NonVirtualFunc();
dc.VirtualFunc();
// 基类实例调用 非虚 及 虚函数
BaseClass bc = new BaseClass();
bc.NonVirtualFunc();
bc.VirtualFunc();
// 指向派生类实例的基类引用 调用 非虚 及 虚函数
BaseClass bc1 = dc;
bc1.NonVirtualFunc();
bc1.VirtualFunc();
}
<p cla