《C++程序设计》课件第9章_第1页
《C++程序设计》课件第9章_第2页
《C++程序设计》课件第9章_第3页
《C++程序设计》课件第9章_第4页
《C++程序设计》课件第9章_第5页
已阅读5页,还剩79页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第9章继承9.1继承与组合9.2继承方式9.3派生类的构造与析构9.4派生类的使用本章小结习题

C++最重要的特征之一是代码重用,但是如果希望更进一步,则不仅仅是要拷贝代码和修改代码,而是要做更多的工作。在C++中可重用性是通过继承(inheritance)这一机制来实现的。继承不仅在很大程度上提高了代码的可重用性和维护上的方便性,而且是面向对象编程的核心思想。有时一个类的数据成员和成员函数与原有的类基本相同或者接近,就可以利用原有的类加上新的内容来实现,以减少重复的工作。这就是C++的继承机制。本章将介绍继承在C++这门面向对象的语言中所体现出来的核心作用,利用继承的机制在已存在的类基础上构造一个新类。已存在的类称为“基类”(baseclass)或“父类”(fatherclass)。新建立的类称为“派生类”(derivedclass)或“子类”(sonclass)。9.1继承与组合9.1.1继承的概念与语法图9.1呈现了生活中比较常见的交通工具的类层次。最顶部的“交通工具”可称为“基类”或“父类”,这个基类有三个子类(也可以称为“派生类”),分别为“空中交通工具”、“陆地交通工具”和“水上交通工具”,称这些子类是从基类继承而来的。而其中“陆地交通工具”还有它自己的子类,这就涉及到了多级继承的问题。在这个结构图中,有四层类结构,其中包含了三层继承关系。这里,每个子类都是其基类的特定化版本。图9.1交通工具的类层次

通过图9.1这个例子可以看出:通过继承,可以用一种既简单又形象的方式来描述一种事物。比如,如果要描述什么叫狗,就可以说:它是一种会“汪汪”叫的哺乳动物。在这个例子中,狗就是哺乳动物的子类。换种说法,狗是哺乳动物的一种,而且狗又同时具备它自己独有的特征,就是会“汪汪”叫,这个狗独有的特性,是区别于其他哺乳动物的属性。由于哺乳动物有很多的共性,因此在采取这种方式来描述狗时,只需将其所有的特性描述清楚就可以了。由此可以看出,继承使得我们描述某种事物的能力大大增强,而且简单、形象。继承是C++ 语言的一种重要机制,该机制自动地为一个新类提供来自于另一个已存在的类的操作和其数据结构等,这样只需要在新类中定义已存在的类中没有的内容来构建这个新类。

继承的语法形式如下:

classB:继承方式类A{ //私有成员

private: //公有成员

public: //保护成员

protected: …};

在这个例子中所提到的继承方式(public、private、protected)在第2章的2.3节已经有过介绍。在继承中,继承方式可以控制子类允许从基类中所能继承的成员内容。而继承方式对继承的类的控制方式就类似于在前面介绍的对于在一个类中的成员的存取控制。换句话说,继承方式规定了子类对基类成员的访问权限。关于这一知识点的内容,将在下一节详细介绍。需要注意的是,不论继承的方式是公有继承、私有继承还是保护继承,基类中的private属性都无法被子类进行访问。

【程序9.1】#include<iostream>usingnamespacestd;classA{private:inta;protected: intb;public: intc; A(){a=1;b=2;c=3;} voidsetA(inta){this->a=a;} voidsetB(intb){this->b=b;} voidsetC(intc){this->c=c;} voidprint(){cout<<"a="<<a<<endl<<"b="<<b<<endl<<"c="<<c<<endl;}};classB:protectedA{public: B():A(){} voidoutput(){cout<<"b="<<b<<endl;} //可以在公有、保护继承中使用

};voidmain(){Aoa; cout<<"A::a="<<*(int*)(&oa)<<endl; cout<<"A::b="<<*((int*)(&oa)+1)<<endl; cout<<"A::c="<<*((int*)(&oa)+2)<<endl; oa.print();

Bob; cout<<"B::a="<<*(int*)(&ob)<<endl; cout<<"B::b="<<*((int*)(&ob)+1)<<endl;cout<<"B::c="<<*((int*)(&ob)+2)<<endl; //ob.print(); //可以在公有继承中使用

ob.output(); //可以在公有、保护继承中使用

cout<<"sizeofA="<<sizeof(A)<<endl <<"sizeofB="<<sizeof(B)<<endl;//A和B具有相同的结构大小

}

运行结果如下:

从程序9.1中可以看出,子类还是继承了基类的属性,只是对基类中的私有成员没有访问权限。这是因为私有成员作为某一个类的私有内容,不会因为其他原因就更改这种私有关系,除了该类自身的成员或该类的友元(见2.4节)外,其他任何类,即使是它的派生类,也无法对它的私有成员进行访问,而且派生类也可以有自己的私有成员或者其他增加的部分。类继承如图9.2所示。从图9.2中可以看出,类B从类A的继承为程序员提供了代码的可重用性,而子类自身所增加的内容则在源代码的基础上进行了扩充和改进。图9.2类继承

在C++中,继承这种结构关系可以有很多级,而且具有单向和传递性。举个简单的例子:类A继承于类B,而类B又继承于类C,则类A间接继承于类C。这类似于某继承人继承了其父亲的财产,而其父亲又继承了其爷爷的财产,那么该继承人就是间接继承了其爷爷的财产。当然,C++中的继承和现实生活中的情况一样,其传递性是单向的,正如其爷爷不可能再继承该继承人的财产。根据继承关系中基类的数量,一般可以将继承分为单一继承和多重继承两种情况,这是因为一个子类可以同时继承于多个基类。所以,简单明了地说,当子类只有一个基类时,称之为单一继承,而当子类同时继承于多个基类时,称之为多重继承。图9.1中的继承关系均为单一继承,而且可以很容易地看出这些继承关系都是单向的,而且具有传递性。多重继承其实是单一继承的扩展形式,因为子类与每个基类之间的继承关系都可以看做是一个单独的单一继承。少用多重继承不仅可以使程序的结构变得简单明了,而且可以增加程序代码的可读性。9.1.2组合的概念与语法相对于继承来说,组合是一种更为简单的代码重用的方式。在一些C++ 书籍中,将继承称作是一种“is-a”的关系,将组合称作是一种“has-a”的关系。根据这个简单的类比,可以很容易地理解组合与继承在代码重用方面的本质区别:继承是从已有的类中进行派生或者新增所需要的属性;而组合是在新类中创建已有类的对象,将其作为自己的类成员来使用。当然,从代码的重用性方面来说,组合和继承都是在已有的类的基础上来创建新类。其实,可以从另一个角度更形象地去理解组合:组合既然意味着一个类的成员是另一个类的成员,那么一般所有的程序中都有组合的影子,只不过一般的程序中的成员不是已有的类的对象,而是基本的数据类型的对象。而在其他面向对象化的编程语言中,将基本数据类型也封装成类,那么说一般的程序中用到了组合则更加形象。

在前面介绍继承的概念时,举了狗是一种哺乳动物的例子来说明继承的关系,此处现在还是通过这个例子来介绍组合的概念。现在有类Head、类Body和类Tail,那么在创建Dog的实体类的时候,就可以在其内部分别定义Head类、Body类和Tail类的对象来构造Dog类,就像拼装一样,将已有的东西拼凑或者再增加一些新的属性来创建一个全新的类。

classHead //定义Head类

{public: intsize; …};classBody //定义Body类

{public: doublewidth; …};classTail //定义Tail类

{public:

doublelength; …};classDog //构造Dog类

{ Headhead; //创建Head类的对象

Bodybody; //创建Body类的对象

Tailtail; //创建Tail类的对象

};

在继承中,无论继承方式如何,子类都无法访问基类的私有成员。在组合中,新类将已存在的类作为它的成员,即使是自己的成员,无论是public、private还是protected成员,同样不能访问。也就是说,不管是在继承还是在组合中,都不能访问已存在的类私有变量或方法。除非通过其内部定义的public方法来获取它的私有变量成员,get()方法就是解决方案之一。

前面已经介绍过,从代码的重用性方面来说,组合和继承都是在已有的类的基础上来创建新类,但是在使用的选择方面则有所不同。继承在使用已存在的类时,可以根据自身的需求对其进行修改和增加,以满足特定的要求。组合在对已存在的类进行使用时,只能原封不动地将其实例化,并且将实例化的对象拿过来使用,而不能做任何修改,所以这在一定程度上也显示了组合在代码重用方面的局限性。当然,如果需要对一个类进行较大的修改而不是扩展的时候,继承就不如构造一个新的类了。

因此,在对代码进行重用时,是选择继承还是选择组合,需要视具体情况而定,看所需要的重用是否需要对已有代码进行修改或扩充。在后面章节中将要介绍的多态的概念中常见的动态绑定和向上转型时,多用到继承。9.2继承方式本节将讨论继承方式对继承的影响。一般将继承方式分为三种:公有继承(public)、私有继承(private)和保护继承(protected)。继承方式的作用是控制子类对基类成员的访问权限,这类似于普通的类的成员的存取控制,从它们相同的关键字就能看出它们的相似性了。类的protected成员和private成员都是对外不可访问的。在继承中,基类的protected成员对子类是可见的,也就是说,子类可以继承基类的protected成员。因此,在没有继承层次结构关系时,可以认为private修饰符和protected修饰符没有什么区别。9.2.1私有继承继承方式的关键字在缺省时默认表示为private,即私有继承。所以在需要声明私有继承时,可以显式地声明,也可以缺省。私有继承可以将那些从基类中继承而来的成员作为自己的私有成员来使用,而且只能当作私有的。换句话来说,基类中可以被继承的成员(包括public成员和protected成员)在子类中都变成了子类的私有成员。根据私有继承的定义,它的使用只是代表着对继承的实现,而基类中的接口或者其他成员在对子类的使用中都会被隐藏掉,也就是说,在子类中仅仅是看了基类的对象却无法使用。如果只是为了将基类的可继承的成员继承过来当作private成员,还不如直接重新定义新的private成员来得更快,效率更高。但如果需要在外部使用这种成员就需要在子类的构造中为其添加上public声明即可。如下例所示:

【程序9.2】#include<iostream>#include<string>usingnamespacestd;

classPerson{public: stringgetName(){return"Name";}stringgetSex(){return"Sex";} intgetAge(){return20;}};classStudent:privatePerson{public: usingPerson::getName; usingPerson::getSex;};main(){ Students; s.getName(); //可以调用,已将其声明为public成员

s.getSex(); //可以调用,已将其声明为public成员

s.getAge(); //不可以调用,因为是private成员

return0;}

这个例子中,getAge函数在基类Person类中虽是public成员,但是在私有继承后,在子类Student类中就变成private成员了;而getName函数和getSex函数本应该与getAge函数一样,在基类中为private成员,但是因为在子类的构造中将其声明为public成员,所以此时在外界就可以调用子类中的这两个函数了。从这里可以看出,当基类中有某些(而不是全部)接口或成员不希望在子类中被外界所访问时,就需要私有继承了,而那些允许被外界访问的接口或成员,就可以在子类中重新为其声明为public成员。所以私有继承的作用是用来隐藏基类中某些不想被外界使用的功能。此处需要注意的是,在子类中虽然可以还原成员在基类中的访问权限,但是也仅仅是还原,并不能使其访问权限与在基类中不一致。例如,在基类中为protected成员,在私有继承的子类中只能将其还原为protected成员,而不能是public成员。在私有继承时,基类成员对于子类来说,public成员和protected成员是可见的;而对于子类的对象来说,基类成员的可见性与一般类及其对象的可见性相同,即public成员是可见的,而其他成员是不可见的。而且在私有继承时,基类的所有可继承的成员在被子类继承后都变为子类的私有成员,所以基类的成员只能由直接派生类访问,而不能再往下继承了,即无法实现多层次的私有继承。9.2.2受保护继承前面已介绍过,一个类的protected成员不能被类外部的其他用户所访问,但是可以被该类的子类所访问。也就是说,对于外界来说,基类中的某些接口或成员想对外界隐藏起来,但是不对它的子类隐藏,允许其访问。受保护继承在很大程度上和私有继承类似,在受保护继承中,基类的public成员和protected成员在子类中都成为其protected成员,而私有成员仍不能被继承。和私有继承一样,受保护继承也有个较难区分的概念:在受保护继承时,基类成员对于子类来说,public成员和protected成员都是可见的;而对于子类的对象来说,基类成员的可见性与一般类及其对象的可见性相同,即public成员是可见的,而其他成员是不可见的。因此,我们也可以顺理成章地推出,在受保护继承时,基类的成员也只能由直接派生类访问,而无法再往下继承。9.2.3公有继承公有继承让基类中能够被继承的成员(public成员和protected成员)的访问控制权限在公有继承的子类中保持不变,即基类的public成员在子类中依然保持public类型,而基类的protected成员在子类中依然保持protected类型。公有继承是用得最广泛的继承方式。基类对象的可见性对于子类的对象来说,与一般类及其对象的可见性相同,即public成员是可见的,而其他成员是不可见的。在这里,protected成员与private成员是一样的,没有什么区别。9.2.4多重继承在前面介绍继承的语法时,已经提到过,根据基类的数量可以将继承分类,当基类的数量大于一个时,称这样的继承为多重继承。下面来看看多重继承的基本语法:

class子类:继承方式基类A,继承方式基类B…{ //类成员

};

在这里,每一个基类都是相对独立的,因为从某种程度上来说,多重继承其实就是单一继承的一种扩展形式,因为子类与每个基类之间的继承关系都可以看做是一个单独的单一继承。

多重继承时,各个基类的继承方式与单一继承其实是一样的,可以是公有继承、私有继承或受保护继承,而且每个基类的继承方式都可以不一样,各个基类之间只需要用逗号隔开即可。

【程序9.3】#include<iostream>usingnamespacestd;classA{protected: inta;public:voiddisplayA(){ cout<<A:":a="<<a<<endl;}};classB{protected: intb;public: voiddisplayB() { cout<<"B::b="<<b<<endl;}};//类C同时继承了类A和类BclassC:publicA,publicB{public: voidset(inti,intj) { a=i; b=j;}};intmain(){ Cc; c.set(5,6); c.displayA(); //调用类A的函数

c.displayB(); //调用类B的函数

return0;}

运行结果如下:

在这个例子中可以很清楚地看到,类C同时继承了类A的属性a和类B的属性b。但是在多重继承中,很容易出现的错误就是在不同的基类中出现同名,从而会发生命名的冲突问题。比如,在不同的基类中出现了同名的属性或方法名,那么子类该继承哪一个呢?如在上例中,如果同时将类A中的displayA()函数和类B中的displayB()函数都改名为display(),那么该程序便无法通过编译。因为当类C同时继承类A和类B时,无法告诉编译器它将要继承哪个基类的display()函数,编译器会提示错误:display()isambiguous(不明确的)。当然,这个问题是可以解决的,即在调用同名的成员时,在前面加上作用域就可以将同名的成员区分开来。比如在上例中,两个打印结果的函数都改名为display()后,在调用时可以这样:

c.A::display(); //调用类A的display函数

c.B::display(); //调用类B的display函数

这样,编译器就可以很清楚地知道程序员到底想要调用哪个基类中的同名成员了,从而消除了同名的冲突问题。这种方法只是解决多重继承所带来的多义性问题中的解决方案之一,另外还有一种比较高效的方法,就是在下一章将要介绍的虚继承,在此就不详细阐述了。一般来说,建议少用多重继承,因为多重继承相对于单一继承来说存在较多的隐患。除了上面提到的同名产生的二义性(多义性)问题外,在较大规模项目的开发过程中,继承不仅数量多,而且其关系层次很复杂,此时不仅较难消除同名带来的二义性问题,还会产生很多新的问题。实际上,对于初学者或者在一般的小型开发中,单一继承所能实现的功能就已经够用了。

下面再介绍一下容易与多重继承混淆的多级继承。在前面已经介绍过,继承是具有单向传递性的,一个子类继承于一个基类的同时也可以是另外一个类的基类,即它也可以有自己的子类,此时就可以称这三个类之间的关系结构为多级继承。这三个类之间的关系也就类似于爷爷、父亲和孙子的三代关系。在多级继承的关系结构中,基类中的成员的访问控制权限在子类中是依次传递的,但需要注意的是,这需要有个大前提,即在多级继承中,每一层的继承关系均为public继承。因为只有public继承能对原有成员的访问控制权限保持连贯性;在前面也介绍过,当private继承和protected继承时,基类的成员都只能由它的直接子类访问,而无法再继续往下继承。9.3派生类的构造与析构在C++的继承结构中,对于子类的构造和析构方面最重要的问题是:子类和基类的构造函数分别在什么时候调用、谁先调用;在多重继承时,各个基类的构造函数的调用顺序和析构函数的调用顺序。9.3.1成员对象的初始化在创建一个类的对象时,都会调用它的构造函数,那么在继承关系中,当需要创建一个子类的对象时,如何调用它的构造函数呢?它和基类的构造函数又有什么关系呢?下面举例说明。【程序9.4】#include<iostream>usingnamespacestd; classA{public: A(){cout<<"ConstructorA"<<endl;} ~A(){cout<<"DeconstructedA"<<endl;}};classB:publicA{public: B(){cout<<"ConstructorB"<<endl;} ~B(){cout<<"DeconstructedB"<<endl;}};

main(){ Bb;}

运行结果如下:

从这个程序片段的例子可以看出,在创建子类的对象时,不仅仅要调用自身的构造函数,而且和基类的构造函数也有关系。通过上面的例子还可以看出,在创建子类的对象时,是先调用基类的构造函数,然后才调用自身的构造函数,这种顺序对于继承的理解也是很重要的。

在进行析构时,调用子类和基类的析构函数的顺序则和构造函数的调用顺序恰恰相反,这点也不难理解。其实,在这里列举的都是比较简单的例子,因为在子类的构造函数和基类的构造函数中都没有参数。如果它们的构造函数中需要参数又该怎么办呢?因为不可能先构造出基类的对象再给它的各个成员赋值,这样显然是不正确的。这个时候需要将参数传给适当的基类的构造函数,来达到这个目的。

【程序9.5】#include<iostream>usingnamespacestd;classA{protected: inta;public: A(inti) { a=i; cout<<"ConstructorA"<<endl;}~A(){cout<<"DeconstructedA"<<endl;}};classB:publicA{ intb;public: B(inti,intj):A(j) { b=i; cout<<"ConstructorB"<<endl;}~B(){cout<<"DeconstructedB"<<endl;}voidprint(){ cout<<"a="<<a<<endl; cout<<"b="<<b<<endl;}};intmain(){ BobjB(10,20); objB.print();return0;}

运行结果如下:

在上面的程序片段中,子类B的构造函数有i和j两个int类型的值,但是子类B自身的构造函数只是使用了参数i而已,同时将另一个参数j传给基类A的相应的构造函数:

B(inti,intj):A(j)

一般来说,子类的构造函数在声明它自身所需要的参数的同时,也必须声明基类所需要的参数,并且将参数传递给基类相对应的构造函数。因为在继承中,基类的构造函数的参数必须由子类的构造函数传递给它。即使子类自身的构造函数不需要任何参数,但如果基类的构造函数需要参数,那么子类的构造函数在被调用的时候也需要基类的构造函数相应的参数,用来传递给基类。在组合中,是将其他的类作为自身的成员来使用,那么它们之间的构造函数关系是怎么样的呢?其实这和继承很类似:在组合的结构中,如果要创建新类的对象,则必须先调用它的成员类的构造函数,然后才调用自身的构造函数。9.3.2构造次序对于普通结构层次的程序来说,不同类的构造函数的调用顺序和创建它们各自对象的顺序是一致的。但是,对于多重继承来说,却并不是这样的。在介绍多重继承的语法时可以看到,声明子类时在子类的名称后面均是它所继承的基类,而且排列是有一定顺序的,可以暂且称之为类继承表顺序。对于类的成员来说,这些基类的排列顺序并没有多大的意义,但是对于讨论多重继承中的构造函数的调用顺序却是至关重要的。在多重继承中子类和基类的构造函数的调用顺序与单一继承一样,要创建子类的对象必须先调用基类的构造函数再调用子类自身的构造函数。那么在多重继承中,多个基类的构造函数之间的调用顺序是怎样的呢?下面举例说明。【程序9.6】#include<iostream>usingnamespacestd;classA{public: A(){cout<<"ConstructorA"<<endl;}~A(){cout<<"DeconstructedA"<<endl;}};classB{public: B(){cout<<"ConstructorB"<<endl;}~B(){cout<<"DeconstructedB"<<endl;}};classC:publicA,publicB{public: C(){cout<<"ConstructorC"<<endl;}~C(){cout<<"DeconstructedC"<<endl;}};intmain(){ CobjC; return0;}

运行结果如下:

从上面的程序可以看出,当子类有多个基类时,这些基类的构造函数的调用顺序和它们在类继承表中出现的顺序一致,不论它们的成员在子类的成员初始化列表中出现的顺序如何。

关于虚继承需要说明的一点是:在多继承中,如果有虚基类,那么不管在类继承表中声明的顺序如何,虚基类的构造函数在任何非虚基类的构造函数之前调用。此外,如果在类的继承关系结构中有多个虚基类的实例,那么虚基类只被初始化一次,即它的构造函数只被调用一次。在多重继承中,当基类的构造函数需要参数时,和单一继承类似,也是由子类的构造函数将参数传递给它们。

【程序9.7】#include<iostream>usingnamespacestd;classA{protected: inta;public: A(inti) { a=i; cout<<"ConstructorA"<<endl;} ~A(){cout<<"DeconstructedA"<<endl;}};classB{protected: intb;public: B(inti) { b=i; cout<<"ConstructorB"<<endl; } ~B(){cout<<"DeconstructedB"<<endl;}};classC:publicA,publicB{ intc;public: C(inti,intj,intk):A(j),B(k) { c=i; cout<<"ConstructorC"<<endl; } voidprint() { cout<<"a="<<a<<endl;cout<<"b="<<b<<endl; cout<<"c="<<c<<endl; } ~C(){cout<<"DeconstructedC"<<endl;}};intmain(){ CobjC(10,20,30); objC.print(); return0;}

运行结果如下:从这个例子可以看出,在多重继承时,构造函数的参数的传递其实和单一继承是类似的,只需注意对于不同的基类的构造函数,要传递相对应的参数。下面再来看看多个成员类的组合。当一个类中有不止一个的成员类时,这些成员类的构造函数的调用顺序和它们在新类中的声明顺序一样,不论它们的成员在新类的成员初始化列表中出现的顺序如何。如果同时存在继承和组合,那么子类、基类以及子类里的成员类的构造函数的调用顺序又是怎样的呢?下面详细总结一下在继承和组合中,构造函数的调用的相关内容:

(1)如果类里面有成员类,则成员类的构造函数优先被调用;(组合)

(2)如果要创建子类的对象,则基类的构造函数优先被调用,同时也优先于派生类里的成员类;(继承和组合同时存在)

(3)如果有多个基类,则构造函数的调用顺序是各个基类在类继承表中出现的顺序而不是它们在成员初始化表中的顺序;(多重继承)

(4)成员类对象构造函数:如果有多个成员类对象,则构造函数的调用顺序是对象在类中被声明的顺序而不是它们出现在成员初始化表中的顺序;(多个成员类的组合)

(5)子类构造函数:作为一般规则,子类构造函数不能直接向一个基类数据成员赋值而是把值传递给适当的基类构造函数;(构造函数的参数的传递)

(6)虚基类的构造函数在任何非虚基类构造函数前被调用;(虚基类的调用顺序)。

(7)如果类继承中包括多个虚基类的实例,则基类只被初始化一次。

【程序9.8】

classbase;

classbase2;

classlevel1:publicbase2,virtualpublicbase;

classlevel2:publicbase2,virtualpublicbase;

classtoplevel:publiclevel1,virtualpubliclevel2;

toplevelview;其构造函数的调用顺序如下:

base(); //虚基类仅被构造一次,而且优先于非虚基类的构造函数被调用

base2();

level2(); //虚基类

base2();

level1();

toplevel();

9.3.3析构次序不论构造函数的调用顺序如何,永远都遵循一个定理:先构造后析构,后构造先析构。例如,在继承中,子类的构造函数在基类的构造函数之后调用,那么子类的析构函数就要先调用,然后才调用基类的析构函数。9.4派生类的使用9.4.1类对象创建与使用下面系统地介绍一下关于子类对象的创建与使用的方法。创建子类的对象其实和普通类的对象的创建一样,只是在调用子类的构造函数之前,会先调用基类的构造函数,通过基类相应的构造函数为那些从基类继承而来的成员进行初始化。下面再来看看9.3.2节中第二个关于向基类构造函数传递相应参数为其成员进行初始化的例子中的一个片段:classC:publicA,publicB{ intc;public: C(inti,intj,intk):A(j),B(k) { c=i; cout<<"ConstructorC"<<endl;}~C(){cout<<"DeconstructedC"<<endl;}voidprint(){ cout<<"a="<<a<<endl; cout<<"b="<<b<<endl;cout<<"c="<<c<<endl;}

};intmain(){ CobjC(10,20,30); objC.printf();

return0;

}类C从类A和类B中分别继承了成员变量a和b,并通过调用类A和类B相应的构造函数对这两个成员变量进行初始化,所以此时类C中的a为20,b为30,c为10。这种为基类的构造函数传递参数的方式是唯一能够初始化从基类继承而来的成员变量的方式,因为不可能先创建基类的实例对象,再为其成员变量赋值。因此,必须在调用子类构造函数来进行初始化的同时,将基类构造函数所需的参数传递给它,让其进行关于从基类继承而来的成员变量的初始化工作。上面的例子中,在主函数中创建了子类C的对象objC,和普通的类成员变量一样,类中的private成员和protected成员是不能被外界所访问的;在类C中,从类A和类B中分别继承来的a和b以及类C中的c均为类中默认的private成员属性。因此,即使类C的实例化对象objC的成员变量已经被初始化,也无法被外界所访问。但是有一个特殊的情况,如果这个子类还有自己的子类,即在作为其他类的子类的同时自己也作为一个基类,那么这时它的子类是可以访问它的protected成员的。protected关键字与private关键字的不同就体现在继承结构关系中,类的protected成员是可以被子类所访问的。总的来说,子类的对象在创建和使用方面,大部分都与普通的类的对象的创建与使用方法一样,只是需要注意子类中从基类继承来的成员变量的访问权限的特殊之处。9.4.2向上映射当需要将子类的对象、引用或指针转化成基类的对象、引用或指针时,称这种操作为“向上映射”。

【程序9.9】

#include<iostream>

#include<string>

usingnamespacestd;

classA

{

public: voidprint()

{ cout<<"基类成员函数"<<endl;

}

};

classB:publicA

{

public: voidprint() { cout<<"子类成员函数"<<endl;

}

};

voidprint(Aa) //需要调用基类的引用

{ a.print(); //调用基类的成员函数

}

intmain()

{ Bb; //声明子类的对象

print(b); //实现“向上映射”

return0;

}运行结果如下:本来外部的print()函数希望接收的参数是基类的对象,但是却将一个子类的对象作为参数对其进行调用,这是一种合理的做法。因为在前面介绍继承的时候就已经说过,继承是一种“is-a”的结构关系,所以,一般来说,将特殊的赋值给一般的,这种做法对于编译器来说是安全的。但是,对于函数的调用者来说,传入的是子类的对象,自然希望能够调用子类的print函数。那么,虽然程序语法是正确的,表现出来的结果却不是程序员想要的。这种现象的出现是由于早捆绑引起的。所谓早捆绑,就是在程序运行之前的编译、连接阶段就将函数体与函数的调用进行了关联。在C语言中,对所有函数的调用都是一种早捆绑。上面例子中的调用也是一种早捆绑,在编译阶段,编译器就已经将类外部的print()函数与基类中的成员关联起来,所以在运行时,即使传入的是子类的对象,执行的依然是基类的成员函数。为了解决这种问题,通常的解决办法是使用晚捆绑,即在程序的运行阶段将函数的调用与函数体关联起来,以对象为依据,确定具体要调用哪个函数。也就是说,晚捆绑相对于早捆绑来说,体现的是一种面向对象的思想。但是,晚捆绑的使用必须与虚函数(用virtual关键字修饰)结合在一起。关于虚函数的概念将在下一章关于多态的内容中再介绍,这里就不详细说明了。向上映射是安全的,但是向下映射就不安全了,因为无法将一般化的东西赋值给特殊化的。比如,定义了一个类Bird(鸟),其中定义了一个private的成员函数fly()。再定义一个类Ostrich(鸵鸟),它继承于Bird类。此时,不可能将Bird类的对象赋给Ostrich类,因为鸵鸟是不会飞的,无法为其调用fly()函数。向下映射不仅不具有安全性,而且向下映射的实现必须通过类型的强制转换才能实现。所以建议尽量不要使用向下映射。9.4.3指针和引用的向上类型转换子类在从基类继承成员的时候,可以加上一些自己特有的成员属性或函数。那么,在进行向上映射的时候,子类中这些“超出”基类的成员部分将会被编译器如何处理呢?将子类的对象转化成基类的对象时,向上映射会将子类中那些“超出”基

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论