1、11.1类的继承与派生类的继承与派生11.2 派生类的构造函数和析构函数派生类的构造函数和析构函数11.3 多重继承多重继承11.4 虚基类虚基类11.5 小结小结继承继承是是C+的一种重要机制,是程序可重的一种重要机制,是程序可重用与扩充的一个重要方面。这一机制使得用与扩充的一个重要方面。这一机制使得程序员可以在已有类的基础上建立新类。程序员可以在已有类的基础上建立新类。从而扩展程序功能、体现类的多态性特征。从而扩展程序功能、体现类的多态性特征。l自然世界的类概念是自然世界的类概念是抽象的抽象的,类的下面还有,类的下面还有子类,子类继承父类的各种特征。子类,子类继承父类的各种特征。l面向对象
2、程序设计允许你声明一个新类作为面向对象程序设计允许你声明一个新类作为另一个类的派生。另一个类的派生。l派生类派生类(也称子类)可以声明新的属性(成(也称子类)可以声明新的属性(成员)和新的操作(成员函数)。员)和新的操作(成员函数)。l继承继承可以让你可以让你重用重用父类代码,专注于为子类父类代码,专注于为子类编写新代码。编写新代码。l我们称最初的类为我们称最初的类为基类基类,根据它生成的类称,根据它生成的类称为派生类(子类),这种派生可以是多层次为派生类(子类),这种派生可以是多层次的。的。11.1.1 继承与派生的概念继承与派生的概念哺乳动物类哺乳动物类猫类猫类犬类犬类波斯猫波斯猫安哥拉猫
3、安哥拉猫西施犬西施犬沙皮犬沙皮犬l例如例如:class personprivate:char name10;int age;char sex;public:void print();class employeeprivate:char name10;int age;char sex;char department20;float salary;public:void print();声明一个派生类的一般格式为:声明一个派生类的一般格式为:class 派生类名派生类名:派生方式派生方式 基类名基类名/派生类新增的数据成员和成员函数派生类新增的数据成员和成员函数“保护成员保护成员”是由是由prot
4、ected声明的类成员。声明的类成员。保护成员不能被外界引用,这点和私有成员保护成员不能被外界引用,这点和私有成员类似,但它可以被派生类的成员函数引用。类似,但它可以被派生类的成员函数引用。下面用派生关系改写下面用派生关系改写person和和employee类。类。class personprivate:char name10;int age;char sex;public:void print();class employee:public personprivate:char department20;float salary;public:/l公有派生公有派生class employee
5、:public person /l私有派生私有派生class employee:private person /l保护派生保护派生class employee:protected person /l公有继承:公有继承:基类的公有成员和保护成员作为派基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的。类的私有成员仍然是私有的。l保护继承:保护继承:基类的所有公有成员和保护成员都基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的
6、私有成员仍然类成员函数或友元访问,基类的私有成员仍然是私有的。是私有的。l私有继承:私有继承:基类的公有成员和保护成员都作为基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的派生类的私有成员,并且不能被这个派生类的子类所访问。子类所访问。l缺省继承方式为缺省继承方式为private.继承方式基类特性派生类特性公有继承PublicprotectedprivatePublicProtected不可访问私有继承PublicprotectedprivatePrivatePrivate不可访问保护继承PublicprotectedprivateProtectedProtected不可
7、访问#include class Apublic:void f1();protected:int j1;private:int i1;class B:public Apublic:void fn2();protected:int j2;private:int i2;class C:public B public:void f3();问题:问题:1.B中成员函数中成员函数f2()能否访问基类中的成能否访问基类中的成员:员:f1(),i1,j1?2.B的对象的对象b1能否访问的成员?能否访问的成员?3.的成员函数的成员函数f3()能否访问直接基类能否访问直接基类B中的中的成员:成员:f2(),i2
8、,j2?4.派生类派生类C的对象的对象c1是否可以访问直接基类是否可以访问直接基类B的成员?能否访问间接其类的成员?能否访问间接其类A的成员:的成员:f1(),i1,j1?回答:回答:1.可以访问可以访问f1(),j1,不可访问不可访问i1;2.可以访问可以访问f1(),不可访问不可访问i1,j1;3.可以访问可以访问f2(),j2,f1(),j1,不可访问不可访问i1,i2;4.可以访问可以访问f2(),f1(),其他的都不可以访问其他的都不可以访问;结论:结论:在公有继承时,派生类的成员函数可在公有继承时,派生类的成员函数可访问基类中的公有成员和保护成员;派生类访问基类中的公有成员和保护成
9、员;派生类的对象仅可访问基类中的公有成员。的对象仅可访问基类中的公有成员。【例例11.2】私有派生类对基类成员的访问。私有派生类对基类成员的访问。#include class base /声明一个基类声明一个基类 int x;public:void setx(int n)x=n;void showx()coutxendl;class derived:private base /声明一个私有派生类声明一个私有派生类 int y;public:void sety(int n)y=n;void showxy()coutxyendl;/非法,派生类不能访问基类的私有成员非法,派生类不能访问基类的私有成
10、员;【例例 11.3】私有派生类对基类成员的访问。私有派生类对基类成员的访问。#include class base int x;public:void setx(int n)x=n;void showx()coutxendl;class derived:private base int y;public:void sety(int n)y=n;void showy()coutyendl;main()derived obj;obj.setx(10);/非法非法 obj.sety(20);/合法合法 obj.showx();/非法非法 obj.showy();/合法合法 return 0;l如果
11、将如果将derived类改为公有派生,则类改为公有派生,则main函数函数中的调用合法否?(例中的调用合法否?(例11.4)l如果将如果将derived类改为保护派生,则类改为保护派生,则main函数函数中的调用合法否?(例中的调用合法否?(例11.4)思考思考l在派生类中声明的名字可以支配基类中声在派生类中声明的名字可以支配基类中声明的同名的名字。如果在派生类的成员函明的同名的名字。如果在派生类的成员函数中直接使用该名字的话,则表示使用派数中直接使用该名字的话,则表示使用派生类中声明的名字,例如:生类中声明的名字,例如:class Xpublic:int f();class Y:public
12、 Xpublic:int f();int g();void Y:g()f();/表示被调用的函数是表示被调用的函数是Y:f(),而不是,而不是X:f()基类都有显示的或隐式的构造函数和析基类都有显示的或隐式的构造函数和析构函数。当创建一个派生类对象时,如何调构函数。当创建一个派生类对象时,如何调用基类的构造函数对基类数据初始化,以及用基类的构造函数对基类数据初始化,以及在撤销派生类对象时,又如何调用基类的析在撤销派生类对象时,又如何调用基类的析构函数对基类对象的数据成员进行善后处理,构函数对基类对象的数据成员进行善后处理,是本节所要讨论的内容。是本节所要讨论的内容。l通常情况下,当创建派生类时
13、,首先执行通常情况下,当创建派生类时,首先执行基类的构造函数,随后再执行派生类的构基类的构造函数,随后再执行派生类的构造函数;当撤销派生类对象时,则先执行造函数;当撤销派生类对象时,则先执行派生类的析构函数,随后再执行基类的析派生类的析构函数,随后再执行基类的析构函数。构函数。l参见例参见例11.6【例例11.6】掌握基类和派生类的构造函数和析构函数掌握基类和派生类的构造函数和析构函数的执行顺序。的执行顺序。#include class basepublic:base()cout基类的构造函数基类的构造函数endl;base()cout基类的析构函数基类的析构函数endl;class deri
14、ve:public basepublic:derive()cout派生类的构造函数派生类的构造函数endl;derive()cout派生类类的析构函数派生类类的析构函数endl;main()derive op;return 0;l派生类构造函数的一般格式:派生类构造函数的一般格式:l在定义派生类对象时,构造函数的执行顺序如在定义派生类对象时,构造函数的执行顺序如下:下:(1)基类的构造函数基类的构造函数 (2)对象成员的构造函数对象成员的构造函数 (3)派生类的构造函数派生类的构造函数l例例11.7派生类构造函数名(参数表):基类构派生类构造函数名(参数表):基类构造函数名(参数表),对象成员
15、名造函数名(参数表),对象成员名1 1(参数表)(参数表),对象成员名对象成员名n n(参数表)(参数表)派生类中其它数据成员初始化派生类中其它数据成员初始化 【例例11.7】派生类构造函数给基类构造函数传递参数。派生类构造函数给基类构造函数传递参数。#include class baseint x;public:base(int a)cout基类的构造函数基类的构造函数endl;x=a;base()cout基类的析构函数基类的析构函数endl;void showx()coutxendl;class derived:public baseint y;public:derived(int a,i
16、nt b):base(a)/派生类的构造函数,要缀上基类的构造函数派生类的构造函数,要缀上基类的构造函数 cout派生类的构造函数派生类的构造函数endl;y=b;derived()cout派生类的析构函数派生类的析构函数endl;void showy()coutyendl;void main()derived obj(10,20);obj.showx();obj.showy();return 0;含有对象成员的派生类构造函数含有对象成员的派生类构造函数l当派生类中含有对象成员时,派生类必须当派生类中含有对象成员时,派生类必须负责该对象成员的构造,其构造函数的一负责该对象成员的构造,其构造函数
17、的一般形式为:般形式为:派生类构造函数名派生类构造函数名(参数表参数表):):基类构造函基类构造函数名数名(参数表参数表),),对象成员名对象成员名1(1(参数表参数表),),对象成员名对象成员名n(n(参数表参数表)/【例例11.8】含有对象成员的派生类构造函数的执行情况含有对象成员的派生类构造函数的执行情况#include class baseint x;public:base(int a)cout基类的构造函数基类的构造函数endl;x=a;base()cout基类的析构函数基类的析构函数endl;void showx()coutxendl;class derived:public ba
18、sepublic:base d;/d为基类对象,作为派生类的对象成员为基类对象,作为派生类的对象成员derived(int a,int b):base(a),d(b)/派生类的构造函数,缀上基类构造函数派生类的构造函数,缀上基类构造函数/和对象成员的构造函数和对象成员的构造函数 cout派生类的构造函数派生类的构造函数endl;derived()cout派生类的析构函数派生类的析构函数endl;void main()derived obj(10,20);obj.showx();obj.d.showx();l当基类的构造函数不带参数时,派生类不一定需要当基类的构造函数不带参数时,派生类不一定需要
19、定义构造函数,然而当基类的构造函数那怕只带有定义构造函数,然而当基类的构造函数那怕只带有一个参数,它所有的派生类都必须定义构造函数,一个参数,它所有的派生类都必须定义构造函数,甚至所定义的派生类的构造函数的函数体有可能为甚至所定义的派生类的构造函数的函数体有可能为空,仅仅起到参数传递作用。空,仅仅起到参数传递作用。l若基类使用缺省构造函数或不带参数的构造函数,若基类使用缺省构造函数或不带参数的构造函数,则在派生类中定义构造函数时可略去,此时若派生则在派生类中定义构造函数时可略去,此时若派生类也不需要构造函数,则可以不定义构造函数。类也不需要构造函数,则可以不定义构造函数。l如果派生类的基类也是
20、一个派生类,则每个派生类如果派生类的基类也是一个派生类,则每个派生类只需要负责直接基类的构造。只需要负责直接基类的构造。l由于析构函数是不带参数的,在派生类中是否要定由于析构函数是不带参数的,在派生类中是否要定义析构函数与它所属的基类无关。义析构函数与它所属的基类无关。l多重继承多重继承:一个派生类具有多个基类。一个派生类具有多个基类。在多重继承中,公有继承和私有继承对于在多重继承中,公有继承和私有继承对于基类成员在派生类中的可访问性与单继承基类成员在派生类中的可访问性与单继承相同。相同。玩具玩具车车玩具车玩具车class class 派生类名:继承方式派生类名:继承方式1 1 基类名基类名1
21、,1,继承方式继承方式n n 基类名基类名n n 派生类新定义成员派生类新定义成员 ;11.3.1 多重继承的声明多重继承的声明#includeclass Xint a;public:void setX(int x)a=x;void showX()couta=aendl;class Yint b;public:void setY(int y)b=y;void showY()coutb=bendl;class Z:public X,private Yint c;public:void setZ(int x,int y)c=x;setY(y);void showZ()showY();coutc=c
22、endl;void main()Z obj;obj.setX(3);obj.showX();/obj.setY(4);/obj.showY();obj.setZ(6,8);obj.showZ();l对基类成员的访问必须是无二义性的。对基类成员的访问必须是无二义性的。l消除下例中的二义性,可写为消除下例中的二义性,可写为obj.X:f();/调用类调用类X的的f()obj.X:f();/调用类调用类Y的的f()例例11.10多重继承时,对基类访问存在二义性的情况。多重继承时,对基类访问存在二义性的情况。#includeclass base1public:void show();class bas
23、e2public:void show();class derive:public base1,private base2public:void showderive();void main()derive obj;obj.show();/二义性错误,不知调用的是二义性错误,不知调用的是 /base1的的show()/还是还是base2的的show()多重继承构造函数定义的一般形式如下:多重继承构造函数定义的一般形式如下:派生类构造函数名派生类构造函数名(参数表参数表):基类构造函:基类构造函数名数名1(参数表参数表),基类构造函数名,基类构造函数名2(参数参数表表),基类构造函数名,基类构造函
24、数名n(参数表参数表)/派生类中其它数据成员初始化派生类中其它数据成员初始化 【例例11.8】含有对象成员的派生类构造函数的执行情况。含有对象成员的派生类构造函数的执行情况。#include class baseint x;public:base(int a)cout基类的构造函数基类的构造函数endl;x=a;base()cout基类的析构函数基类的析构函数endl;void showx()coutxendl;class derived:public basepublic:base d;/d为基类对象,作为派生类的对象成员为基类对象,作为派生类的对象成员derived(int a,int b
25、):base(a),d(b)/派生类的构造函数,缀上基类构造函数派生类的构造函数,缀上基类构造函数/和对象成员的构造函数和对象成员的构造函数 cout派生类的构造函数派生类的构造函数endl;derived()cout派生类的析构函数派生类的析构函数endl;void main()derived obj(10,20);obj.showx();obj.d.showx();l当引用派生类的成员时,首先在派生类自身的当引用派生类的成员时,首先在派生类自身的作用域中寻找这个成员,如果没有找到,则在作用域中寻找这个成员,如果没有找到,则在它的基类中寻找。如果一个派生类是从多个基它的基类中寻找。如果一个派
26、生类是从多个基类派生出来的,而这些基类又有一个共同的基类派生出来的,而这些基类又有一个共同的基类,则在这个派生类中访问这个共同的基类中类,则在这个派生类中访问这个共同的基类中的成员时,可能会产生二义性。的成员时,可能会产生二义性。lvirtual 关键字,同继承方式关键字的先后顺序关键字,同继承方式关键字的先后顺序无关紧要。无关紧要。【例例11.12】多重派生产生二义性的情况多重派生产生二义性的情况#includeclass baseprotected:int a;public:base()a=5;class base1:public basepublic:base1()coutbase1 a
27、=aendl;class base2:public basepublic:base2()coutbase2 a=aendl;class derived:public base1,public base2public:derived()coutderived a=aendl;main()derived obj;return 0;derivedbase1base2basebase非虚基类的类层次图非虚基类的类层次图l如果采用虚基类,则会消除二义性如果采用虚基类,则会消除二义性derivedbase1base2base 虚基类的类层次图虚基类的类层次图l虚基类构造函数的调用顺序虚基类构造函数的调用顺
28、序(1)若同一层次中包含多个虚基类,这些虚基类)若同一层次中包含多个虚基类,这些虚基类的构造函数按对它们的说明先后次序调用。的构造函数按对它们的说明先后次序调用。(2)若虚基类由非虚基类派生而来,则仍然先调)若虚基类由非虚基类派生而来,则仍然先调用基类的构造函数,再调用派生类的构造函数。用基类的构造函数,再调用派生类的构造函数。(3)若同一层次中同时包含虚基类和非虚基类,)若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类的构造函数。的构造函数,最后调用派生类的构造函数。data_recStaff:v
29、irtual data_recstudent:virtual data_recstudent_staffprofessor应用虚基类构建学生、学校职员、教授、兼职学应用虚基类构建学生、学校职员、教授、兼职学校职员的学生之间的类关系。校职员的学生之间的类关系。lstudent last_name;first_namestreet_address;citystate;zip;majorid_number;level;lStafflast_name;first_namestreet_address;citystate;zip;depthourly_wage;last_name;first_namestreet_address;citystate;zip;11.5 小结小结l继承的重要性是支持程序代码复用,它不继承的重要性是支持程序代码复用,它不仅能够从已存在的类中派生出新类,新类仅能够从已存在的类中派生出新类,新类能够继承基类的成员,而且可以通过重载能够继承基类的成员,而且可以通过重载基类成员函数,产生新的行为。基类成员函数,产生新的行为。l本章介绍了本章介绍了C+中继承的概念与用法;单中继承的概念与用法;单一继承和多重继承的概念;派生类的构造一继承和多重继承的概念;派生类的构造函数和析构函数的构造规则;虚基类的引函数和析构函数的构造规则;虚基类的引入和用法等。入和用法等。