本章将介绍面向对象编程中两个非常重要的概念——继承和多态。继承是C++语言中类机制的一部分,该机制使类与类之间可以建立一种上下级关系。继承是一种手段,利用继承机制,可以根据另一个类的操作和数据成员来创建新类。多态性是在继承的基础上实现,它可以实现一个方法有不同的操作。类的继承层次关系派生类对象的结构派生类的访问控制派生类的构造、析构和复制函数相关类类型之间的转换多态性虚函数和抽象类C++/CLI装箱与拆箱在C++/CLI中的继承机制C++/CLI接口类C++/CLI中的委托和事件类是C++中提供封装的逻辑单位,它封装了变量和函数。同时,C++还提供了继承机制,通过继承,过去的代码就不需要丢弃,只需要经过稍加修改后就重用。在人类文明的发展中,现在的文明就是建立其先人文明的基础上,事实上,事物的发展都是从建立其先前的基础上的。类的继承就反映了从先前简单代码发展到丰富的高级代码的过程。利用继承性,可以在已有类的基础上构造新类,这一性质使得类与类之间可以建立一种上下级的分类关系。继承关系使得我们可以用一种简单的方式来描述事物。例如,描述什么是鹰,可以描述为一种大型的食肉鸟类。鹰是一种鸟,所以鹰是鸟类的派生,但鹰又具有自己的特征,大型的以食肉为主的特征是它区别于其他鸟类的属性。由于鸟类通常的特征大家都知道,如,有翅膀,有两个脚等,所以用鸟类来描述鹰,只要列举出它自己的所具有特点即可。描述事物一般是从属性和操作上来描述的,例如,日期具有年、月、日的属性,显示日期,判断某年是否为闰年的操作。继承就是让子类继承父类的属性和操作,子类可以声明新的属性和操作,还可以替换哪些不适合其用途的父类操作。在新的应用中,父类的代码已经存在,无须修改,所要做的就是派生子类类,并在子类中添加和修改。所以继承可以重用父类的代码,提高代码的利用率。继承也是我们理解事物、解决问题的方法,继承帮助我们描述事物的层次关系,有效而精确地描述事物,理解事物直至本质。一旦看清了事物所处的层次关系,也就可以找到相关的解决办法。如果类BaseClass是父类:classBaseClass{private:inta,b;//私有成员public://公有成员};该类对象包含了两个整型数据成员所占用的内存空间。在该父类上派生子类的方式是在子类名后添加public关键字和父类名。如SubClass继承的BaseClass类,则:classSubClass:publicBaseClass{private:intc;//私有成员public://公有成员};子类SubClass的对象包含两部分:一部分为父类部分,即两个整型数据成员所占用的,空间;另一部分为子类新添加的整数空间。在继承机制下,类的访问控制符又有了新的意义。例如,对于父类的私有成员,在子类中是否可以直接访问?本节将对介绍父类中的访问控制对子类的影响。访问父类成员继承方式如果将基类中的数据成员变为私有的private,则上面的程序将无法编译,并提示无法访问Person类的private成员。也就是说,子类不能访问基类的private成员。原因在于虽然基类的private成员也中派生类的成员,但它们在派生类中仍然为基类所私有的,因此在派生类中不能访问基类的private成员。如果需要在派生类中访问基类的private成员,只能通过访问基类的public成员实现。继承可以是公有继承,也可以是保护继承和私有继承。多数情况下,类的继承是公有的,就像前面所看到的。也就是在子类名后使用public访问控制符限定基类名。如果将子类名和基类名之间的public关键字替换为private或protected,则继承就变为私有继承和保护继承。公有继承反映了派生类对基类使用的方式全部接受,在此基础之上进行扩充,以便能被外界更广泛地使用。因此,通过派生类对象仍然可以使用基类中原来公有的操作。派生类不能获取基类私有成员的访问权限。除此之外,公有继承将基类的保护成员或公有成员视为自己的保护和公有成员。类的构造函数作为类的一种特殊成员函数,它们不会被派生类继承,仍然存在于基类中,并且用于创建派生类对象中基类对象部分。因为创建派生类对象的基类部分实际上属于基类构造函数,而非派生类构造函数的任务。如前面,基类的私有成员在派生类中虽然会被继承,但是不可访问,因此这些任务必须交给基类的构造函数来完成。根据类机制,如果没有定义构造函数,将会执行默认的无参构造函数。对于派生类而言,派生类的默认无参数构造函数会首先调用父类的默认无参构造函数。如果在基类中提供了有参数的构造函数,并且没有重载默认的构造函数,则派生类的默认构造函数将无法正常工作。为了解决这个问题,可以自定义派生类的构造函数,并在派生类的构造函数中调用基类的构造函数。在派生类对象超出作用域时,或者要删除堆内存中的对象时,总是要涉及到派生类的析构函数和基类的析构函数。与构造函数一样,析构函数也不能被继承,在派生类中需要自行声明。下面为Person类和Employee添加析构函数,并跟踪何时调用这两个析构函数:Person::~Person(){coutPerson析构函数endl;}Employee::~Employee(){cout“Employee析构函数姓名:GetName()endl;}复制构造函数是使用一个已有对象创建同类对象时被自动调用。如下面的两条语句:PersonaPerson(刘丽,1979-10-23);PersoncopyPerson(aPerson);第一条语句调用构造函数创建Person类对象,第二条语句则调用复制构造函数创建Person类对象。如果不提供自定义的复制构造函数,则编译器会自动提供一个默认的复制构造函数,将初始化对象成员逐一复制到新对象对应的成员中。为了解复制构造函数的工作过程,可以给Person类添加自动定义的复制构造函数,然后为派生类Employee添加自定义构造函数。在有些时候,我们还需要多重继承,即一个派生类有两个以上的直接基类。例如,人类都不是单亲繁殖的,一个人总是从他母亲和父亲两个方面继承。多重继承的使用并不多见,在Java、C#等编程语言甚至没有多重继承,因此这里也不深入介析,只是简单说明多重继承基本工作源理。多重继承涉及到到用两个或多个直接基类来派生一个新类,它要比单一继承要复杂的多。多重继承意味着将多个基类的特性加在一起,形成一个包含所有基类功能的合成对象。这通常比较便于实现,而不便于反映对象之间的关系。每个派生类对象都至少包含一个基类对象,把派生类型转换为基类类型是合法的。在类层次结构中,向上的转换是合法的,反之,向下的转换则是不合理的。也就是说,只能用子类对象向其基类对象赋值,而不能用基类对象向其派生类对象赋值。因为基类对象不包含派生类的成员,无法对派生类的成员赋值。同样,同一基类的不同子类之间也不能赋值。多态性是面向对象程序设计的重要特征之一。它与前面介绍的封装性、继承构成了面向对象程序设计的三大特征。这三个特征是相互关联的,封装性是基础、继承是关键,多态性是补充,而多态是通过继承来实现的。所谓多态性是指同样的消息被不同类型的对象接收时,各类型的对象会采用完全不同的行为。这里所说的消息主要是指对类成员的调用。也就是说,不同功能的函数可以使用同一个函数名,这样就可以用一个函数调用不同内容的函数。从系统实现的角度看,多态性分为两类:静态多态性和动态多态性。虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。在C++中规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此,在声明派生类的虚函数时,可以添加virtual关键字,也可以不添加。如果在派生类中没有对基类的虚函数重新定义,则派生类将直接继承基类中的虚函数。虚函数实现的动态多态性是一个极为强在的机制。在交互式应用程序中,常常不能事先确定要处理哪种类型的对象,即在设计或编译期间不能确定类型,只能在运行期间确定。使用虚函数时应注意几点:1.函数的签名和返回类型在派生类和基类中声明、定义虚函数时,函数的名称和参数列表必须相同。一般情况下,在派生类中的返回类型也必须与基类相同,但当基类中的返回类型是类类型的指针或引用时例外。2.虚函数和类层结构虚函数的作用是允许在派生类中对基类的虚函数重新定义,它只能用于类的继承层次结构中。在基类中可以声明任意多个虚函数,但在有若干层的类层次结构中,各层的类都可以添加属于自己特有的虚函数。3.访问修饰符与虚函数虚函数在派生类中声明时,所用的访问修饰符可以不同于其在基类中声明时的访问修饰符。在通过指针调用虚函数时,基类中的访问修饰符将确定该函数是否可以在派生类中访问。如果虚函数在基类中声明为public,则无论它在派生类中的访问修饰符是什么,都可以通过基类的指针或引用为所有派生类调用该函数。有时在基类中将某一个成员函数定义为虚函数,并不是该基类本身的需要,而是考虑到派生类的需要,在基类为中预留一个函数名,函数的具体功能由派生类根据需要定义。纯虚函数的主要作用是允许函数的派生类版本进行多态性的调用。要把函数声明为纯虚函数与声明一般虚函数相同,但在类的声明中要添加“=0”。纯虚函数通常没有实现代码。抽象类是包含纯虚函数的类。因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的。抽象类的作用是作为一个类族的共同基类,即为一个类族提供一个公共接口。抽象类可以拥用数据成员和函数成员,判断抽象类的唯一条件是类中是否包含纯虚函数。抽象类中以包含多个纯虚函数,这种情况下,派生的子类必须给出每个纯虚函数的定义,否则该派生类仍然是抽象类。在C++/CLI中,默认情况下用户自定义的类都是派生类,因为数值类和引用类都是以System::Object为基类的。这意味着数值类和引用类都继承于System::Object类,具有System::Object类的功能。System::Object类的ToString()函数被定义为虚函数,在自己的类中重写该函数,并在需要时多态地调用该函数。基类System::Object还负责实现基本类型数值的装箱(boxing)和拆箱(unboxing)操作。装箱就是将一个值类型隐式或显式地转换成一个引用类型。把一个值类型的值装箱,就是创建一个引用类型的实例并将这个值复制给这个引用类对象,装箱后的引用类对象中的数据位于堆中,堆中的地址在栈中。被装箱的类型的值是作为一个拷贝赋给对象的。虽然数值类是以System::Object为基类,但是不能从现有类中派生数值类,自定义数值类只可以从接口类中派生。这意味着数值类中的多态性仅限于在System::Object类中被定义为虚函数的那些函数。当然,System::Object也是引用类的基类,所以在引用类中也需要重写这些函数。在C++/CLI中派生子类的方式与本地C++中相同,可以从现有的引用类派生出新的引用类。下面总结了在C++/CLI中派生类与ANSIC++派生类的区别:只有引用类可以是派生类。派生引用类的继承方式只能是public。引用类中没有定义的函数是抽象函数,必须使用abstract关键字声明。包括抽象函数的类为抽象类,抽象类需要在类名后添加abstract关键字。不包含抽象函数的类也可以被指定为抽象类,以防止从创建该类的对象。当在派生类中重写基类中的抽象函数时,必须显式使用override关键字表示重写。接口类指定一组将由其他类实现的函数,以提供标准化的、可提供某种具体功能的方法。数值类和引用类都可以实现接口。接口类中的所有函数只有声明,没有定义,这些函数是由实现该接口的各个类定义。在事件通信中,发送事件的类不知道哪个对象或方法将接收到它引发的事件。因此,在事件的发送方和接收方之间需要一个媒介。.NETFramework定义了一个特殊的类型——委托(Delegate),该类型能够封装一个或多个指针,这些指针指向具有特定形参列表和返回类型的函数。因此,