假定没有为类定义任何显式的构造函数,这样编译器就会为所有的类提供默认的构造函数,在后
台会进行许多操作,编译器可以很好地解决层次结构中的所有问题,每个类中的每个字段都会初始化
为默认值。但在添加了一个我们自己的构造函数后,就要通过派生类的层次结构高效地控制构造过程,
因此必须确保构造过程顺利进行,不要出现不能按照层次结构进行构造的问题。
  为什么派生类会有某些特殊的问题?原因是在创建派生类的实例时,实际上会有多个构造函数起
作用。要实例化的类的构造函数本身不能初始化类,还必须调用基类中的构造函数。这就是为什么要
通过层次结构进行构造的原因。
  为了说明为什么必须调用基类的构造函数,下面是手机公司MortimerPhones 开发的一个例子。这
个例子包含一个抽象类GenericCustomer,它表示顾客。还有一个(非抽象)类Nevermore60Customer,
它表示采用特定付费方式(称为Nevermore60 付费方式)的顾客。所有的顾客都有一个名字,由一个私
有字段表示。在Nevermore60 付费方式中,顾客前几分钟的电话费比较高,需要一个字段
highCostMinutesUsed,它详细说明了每个顾客该如何支付这些较高的电话费。抽象类GenericCustomer
的定义如下所示:
abstract class GenericCustomer
{
private string name;
// lots of other methods etc.
}
class Nevermore60Customer : GenericCustomer
{
private uint highCostMinutesUsed;
// other methods etc.
}
  不要担心在这些类中执行的其他方法,因为这里仅考虑构造过程。如果下载了本章的示例代码,
就会发现类的定义仅包含构造函数。
  下面看看使用new 运算符实例化Nevermore60Customer 时,会发生什么情况:
GenericCustomer customer = new Nevermore60Customer();
98 / 826
  显然,成员字段name 和highCostMinutesUsed 都必须在实例化customer 时进行初始化。如果没
有提供自己的构造函数, 而是仅依赖默认的构造函数, name 就会初始化为null 引用,
highCostMinutesUsed 初始化为0。下面详细讨论其过程。
highCostMinutesUsed 字段没有问题:编译器提供的默认Nevermore60Customer 构造函数会把它初
始化为0。
  那么name 呢?看看类定义,显然,Nevermore60Customer 构造函数不能初始化这个值。字段name
声明为private,这意味着派生的类不能访问它。默认的Nevermore60Customer 构造函数甚至不知道存
在这个字段。唯一知道这个字段的是GenericCustomer 的其他成员,即如果对name 进行初始化,就必
须在GenericCustomer 的某个构造函数中进行。无论类层次结构有多大,这种情况都会一直延续到最
终的基类System.Object 上。
  理解了上面的问题后,就可以明白实例化派生类时会发生什么样的情况了。假定默认的构造函数
在整个层次结构中使用: 编译器首先找到它试图实例化的类的构造函数, 在本例中是
Nevermore60Customer,这个默认Nevermore60Customer 构造函数首先要做的是为其直接基类
GenericCustomer 运行默认构造函数,然后GenericCustomer 构造函数为其直接基类System.Object 运行
默认构造函数,System. Object 没有任何基类,所以它的构造函数就执行,并把控制权返回给
GenericCustomer 构造函数。现在执行GenericCustomer 构造函数,把name 初始化为null,再把控制
权返回给Nevermore60Customer 构造函数,接着执行这个构造函数,把highCostMinutesUsed 初始化
为0,并退出。此时,Nevermore60Customer 实例就已经成功地构造和初始化了。
构造函数的调用顺序是先调用System.Object,再按照层次结构由上向下进行,直到到达编译器要
实例化的类为止。还要注意在这个过程中,每个构造函数都初始化它自己的类中的字段。这是它的一
般工作方式,在开始添加自己的构造函数时,也应尽可能遵循这个规则。
  注意构造函数的执行顺序。基类的构造函数总是最先调用。也就是说,派生类的构造函数可以在
执行过程中调用它可以访问的基类方法、属性和其他成员,因为基类已经构造出来了,其字段也初始
化了。如果派生类不喜欢初始化基类的方式,但要访问数据,就可以改变数据的初始值,但是,好的
编程方式应尽可能避免这种情况,让基类构造函数来处理其字段。
理解了构造过程后,就可以开始添加自己的构造函数了。
1. 在层次结构中添加无参数的构造函数
首先讨论最简单的情况,在层次结构中用一个无参数的构造函数来替换默认的构造函数后,看看
会发生什么情况。假定要把每个人的名字初始化为<no name>,而不是null 引用,修改GenericCustomer
中的代码,如下所示:
public abstract class GenericCustomer
{
private string name;
public GenericCustomer()
: base() // we could omit this line without affecting the compiled
code
{
name = "<no name>";
}

添加这段代码后,代码运行正常。Nevermore60Customer 仍有自己的默认构造函数,所以上面描
述的事件顺序仍不变,但编译器会使用定制的GenericCustomer 构造函数,而不是生成默认的构造函
数,所以name 字段按照需要总是初始化为<no name>。
注意,在定制的构造函数中,在执行GenericCustomer 构造函数前,添加了一个对基类构造函数
的调用,使用的语法与前面解释如何让构造函数的不同重载版本互相调用时使用的语法相同。唯一的
区别是,这次使用的关键字是base,而不是this,表示这是基类的构造函数,而不是要调用的类的构
99 / 826
造函数。在base 关键字后面的圆括号中没有参数,这是非常重要的,因为没有给基类构造函数传送
参数,所以编译器会调用无参数的构造函数。其结果是编译器会插入调用System.Object 构造函数的
代码,这正好与默认情况相同。
实际上,可以把这行代码删除,只加上为本章中大多数构造函数编写的代码:
public GenericCustomer()
{
name = "<no name>";
}
如果编译器没有在起始花括号的前面找到对另一个构造函数的任何引用,它就会假定我们要调用
基类构造函数--这符合默认构造函数的工作方式。
base 和 this 关键字是调用另一个构造函数时允许使用的唯一关键字,其他关键字都会产生编译
错误。还要注意只能指定一个其他的构造函数。
到目前为止,这段代码运行正常。但是,要通过构造函数的层次结构把级数弄乱的最好方法是把
构造函数声明为私有:
private GenericCustomer()
{
name = "<no name>";
}
如果试图这样做,就会产生一个有趣的编译错误,如果不理解构造是如何按照层次结构由上而下
的顺序工作的,这个错误会让人摸不着头脑。
'Wrox.ProCSharp.GenericCustomer()' is inaccessible due to its
protection level
有趣的是,该错误没有发生在GenericCustomer 类中,而是发生在Nevermore60Customer 派生类
中。编译器试图为Nevermore60Customer 生成默认的构造函数,但又做不到,因为默认的构造函数应
调用无参数的GenericCustomer 构造函数。把该构造函数声明为private,它就不可能访问派生类了。
如果为GenericCustomer 提供一个带有参数的构造函数,但没有提供无参数的构造函数,也会发生类
似的错误。在本例中,编译器不能为GenericCustomer 生成默认构造函数,所以当编译器试图为派生
类生成默认构造函数时,会再次发现它不能做到这一点,因为没有无参数的基类构造函数可调用。这
个问题的解决方法是为派生类添加自己的构造函数-- 实际上不需要在这些构造函数中做任何工作,这
样,编译器就不会为这些派生类生成默认构造函数了。

C# 继承细节的更多相关文章

  1. python继承细节

    不要子类化内置类型 内置类型(由C语言编写)不会调用用户定义的类覆盖的特殊方法. 例如,子类化dict作为测验: class DoppeDict(dict): def __setitem__(self ...

  2. C++继承细节 -2

    继承与动态内存分配 //基类定义 class BaseClass { private: char *label; public: BaseClass() {} BaseClass(const char ...

  3. C++继承细节 -1

    为什么基类析构函数最好要使用 virtual 进行修饰? class A { private: ...... public: ~A(); A() {} }; class B : public A { ...

  4. JAVA_SE基础——39.继承

    在面向对象程序设计中,可以从已有的类派生出新类. 这称做继承(inheritance). 白话解释: 例子1:继承一般是指晚辈从父辈那里继承财产,也可以说是子女拥有父母给予他们的东西. 例子2:猫和狗 ...

  5. JavaScript类继承

    和其他功能一样,ECMAScript 实现继承的方式不止一种.这是因为 JavaScript 中的继承机制并不是明确规定的,而是通过模仿实现的.这意味着所有的继承细节并非完全由解释程序处理.作为开发者 ...

  6. JavaScript中继承机制的模仿实现

    首先,我们用一个经典例子来简单阐述一下ECMAScript中的继承机制. 在几何学上,实质上几何形状只有两种,即椭圆形(是圆形的)和多边形(具有一定数量的边).圆是椭圆的一种,它只有一个焦点.三角形. ...

  7. ECMAScript 继承机制实现

    继承机制的实现 要用 ECMAScript 实现继承机制,您可以从要继承的基类入手.所有开发者定义的类都可作为基类.出于安全原因,本地类和宿主类不能作为基类,这样可以防止公用访问编译过的浏览器级的代码 ...

  8. 一文看懂JS继承

    继承是OOP中大家最喜欢谈论的内容之一,一般来说,继承都两种方式:接口继承和实现继承而JavaScript中没有接口继承需要的方法,因此只能依靠实现继承.在讲继承的实现之前,我们首先来回顾一下什么是继 ...

  9. JS如何实现继承?

    JS的继承是基于JS类的基础上的一种代码复用机制.换言之,有了代码,我们就不需要复制之前写好的方法,只要通过简捷的方式 复用之前自己写的或同事写的代码.比如一个弹出层,我们需要在上面做一些修改.同事写 ...

随机推荐

  1. selenium 学习笔记 ---新手学习记录(9) 问题总结(java)

    1.百度搜索目标--实例 boolean flag; //点击次数 int shcs=3; int i=0; //窗口焦点控制 String detailWindowHandle = null; // ...

  2. w3c标准的selection对象介绍

    简介 术语 属性 方法 document.activeElement document.designMode = 'on'; 简介 selection是对当前激活选中区(即高亮文本)进行操作. 在非I ...

  3. net-snmp启用python模块

    1. http://blog.jeoygin.org/2011/09/centos-5-install-python-2-6-and-python-setuptools-2-6.html 2. htt ...

  4. 射频识别技术漫谈(15)——Mifare1的安全性及7字节序列号M1卡

    Mifare1的安全性主要指卡中数据的安全性,要求卡中的数据不能被非法修改或窃听.数据的安全性主要使用加密技术来保证,加密技术有两个关键因素:加密算法和密钥.现代加密技术的一大特点是加密算法公开,如果 ...

  5. android搭建环境错误 daemon not running. starting it now on port 5037 ADB server didn't ACK

    android搭建环境错误 daemon not running. starting it now on port 5037 ADB server didn't ACK ADB server didn ...

  6. HDU 2719 The Seven Percent Solution

    #include <cstdio> #include <cstring> int main() { ]; ]!='#') { ; while (i<strlen(s)) ...

  7. HDU 3231 Box Relations

    题目大意: 给定一些正方体的关系,要求一组符合这些关系的正方体坐标,如果不存在符合条件的正方体坐标,IMPOSSIBLE.(Special Judge) 实力还是太弱了,完全不会…… #include ...

  8. JAVA GUI学习 - JOptionPane对话框组件学习

    /** * 对话框 - 学习笔记 * @author Wfei * */ public class JoptionPaneKnow extends JFrame { public JoptionPan ...

  9. UBER司机奖励政策

    高峰时段: 早高峰:早6:30-8:30点 晚高峰:晚4:00-7:00点 *周六日只有晚高峰 其他时间均为平峰时段 滴滴快车单单2.5倍,注册地址:http://www.udache.com/如何注 ...

  10. 黄聪:Python 字符串操作(string替换、删除、截取、复制、连接、比较、查找、包含、大小写转换、分割等)

    去空格及特殊符号 s.strip().lstrip().rstrip(',') 复制字符串 #strcpy(sStr1,sStr2) sStr1 = 'strcpy' sStr2 = sStr1 sS ...