1、1,第4章 派生类与继承,计算机与信息工程学院软件工程系 E-mail: jade_ 杨玉星,C+面向对象程序设计,2,课程回顾:,3.9 类的组合 3.10 常类型,3,本章内容:,4.1 继承与派生类 4.2 派生类的构造函数和析构函数 4.3 调整基类成员在派生类中的访问属性的其他方法 4.4 多继承 4.5 赋值兼容规则,4,4.1 继承与派生类,5,引例:,class person protected:char name10;int age;char gender;public:void print(); class employee protected:char name10;in
2、t age;char gender;char department20;float salary;public:print();,6,4.1.1 为什么要使用继承?,为了避免代码重复,提高可重用性,C+中引入了继承机制。,7,4.1.2 派生类的声明,/定义一个基类class person protected:char name10;int age;char gender;public:/;/定义一个派生类class employee:public person protected:char department20;float salary;public:/;,8,声明一个派生类的一般格式:
3、,class 派生类名:继承方式 基类名 /派生类新增的数据成员和成员函数;,9,三种继承方式:,(1) 公有继承class employee:public person/; (2) 私有继承class employee:private person/; (3) 保护继承class employee:protected person/;,10,从已有类派生出新类时,可以在派生类内完成以下几种功能:(1) 可以增加新的数据成员;(2) 可以增加新的成员函数;(3) 可以重新定义基类中已有的成员函数;(4) 可以改变现有成员的属性。,11,4.1.3 基类成员在派生类中的访问属性,12,4.1.4
4、 派生类对基类成员的访问规则,派生类对基类成员的访问形式主要有以下两种:(1) 内部访问: 由派生类中新增成员对基类继承来 的成员的访问。(2) 对象访问: 在派生类外部,通过派生类的对象对 从基类继承来的成员的访问。,13,1. 私有继承的访问规则当类的继承方式为私有继承时,基类的public成员和protected成员被继承后作为派生类的private成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问。基类的private成员在私有派生类中是不可直接访问的,所以无论是派生类成员还是通过派生类的对象,都无法直接访问从基类继承来的private成员,但是可以通过基类提
5、供的public成员函数间接访问。,14,私有继承的例子:,#include class base public:void setx(int n)x=n;void showx()coutxendl; private:int x; ;,15,class derived: private base public:void setxy(int n,int m)setx(n);y=m;void showxy()/coutx;coutyendl; private:int y; ;,16,main() derived obj;/obj.setx(10);/obj.showx();obj.setxy(20,
6、30);obj.showxy();return 0; ,17,表4-2 私有继承的访问规则,18,2. 公有继承的访问规则当类的继承方式为公有继承时,基类的public成员和protected成员被继承到派生类中仍作为派生类的public成员和protected成员,派生类的其他成员可以直接访问它们。但是,类的外部使用者只能通过派生类的对象访问继承来的public成员。基类的private成员在私有派生类中是不可直接访问的,所以无论是派生类成员还是通过派生类的对象,都无法直接访问从基类的private成员,但是可以通过基类提供的public成员函数间接访问它们。例: 公有继承的访问规则,19,
7、#include class base public:void setxy(int m,int n)x=m;y=n;void showxy()cout“x=“xendl;cout“y=“yendl; private:int x; protected:int y; ;,20,class derived: public base public:void setxyz(int m,int n,int i)setxy(m,n);z=i;void showxyz()cout“x=“xendl;cout“y=“yendl;cout“z=“zendl; private:int z; ;,21,main()
8、derived obj;obj.setxyz(30,40,50);obj.showxy();obj.showxyz();return 0; ,22,表4-3 公有继承的访问规则,23,3. 保护继承的访问规则当类的继承方式为保护继承时,基类的public成员和protected成员被继承到派生类中都作为派生类的protected成员,派生类的其他成员可以直接访问它们,但是类的外部使用者不能通过派生类的对象来访问它们。基类的private成员在私有派生类中是不 可直接访问的,所以无论是派生类成员还是通过派生类的对象,都无法直接访问基类的private成员。,24,表4-4 保护继承的访问规则,2
9、5,4.2 派生类的构造函数和析构函数,26,4.2.1 派生类构造函数和析构函数的执行顺序,通常情况下,当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数; 当撤消派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。,27,例:,#include class Base public: Base() cout“Constructing base classn“; Base()cout“Destructing base classn“; ; class Derive:public Base public:Derive()cout“Constructing der
10、ived classn“;Derive()cout“Destructing derived classn“; ;main() Derive op;return 0;,28,4.2.2 派生类构造函数和析构函数的构造规则,在C+中,派生类构造函数的一般格式为: 派生类名(参数总表):基类名(参数表) 派生类新增成员的初始化语句; ,29,例: #include class Base public: Base(int n) cout“Constructing base classn“;i=n;Base() cout“Destructing base classn“; void showi() co
11、utiendl; private: int i; ;,30,class Derive :public Base public:Derive(int n,int m):Base(m) cout“Constructing derived class“endl; j=n; Derive() cout“Destructing derived class“endl; void showj() coutjendl;private:int j;main() Derive obj(50,60);obj.showi(); obj.showj();return 0;,31,当派生类中含有内嵌对象成员时,其构造函数
12、的一般形式为:派生类名(参数总表):基类名(参数表1),内嵌对象名1(内嵌对象参数表1),内嵌对象名n(内嵌对象参数表n)派生类新增成员的初始化语句;,32,在定义派生类对象时,构造函数的执行顺序如下:1.调用基类的构造函数;2.调用内嵌对象成员的构造函数(有多个对象成员时,调用顺序由它们在类中声明的顺序确定);3.派生类的构造函数体中的内容;撤消对象时,析构函数的调用顺序与构造函数的调用顺序正好相反。 (注意对象的作用域。),33,4.3 调整基类成员在派生类中访问属性的其他方法,34,4.3.1 同名成员: #include class A protected: int i;public:
13、 A(int n) i = n; void show() coutiendl; ; class B : public Aint i;public: B (int n) : A(n-1) i = n; void show() A:show(); coutiendl; ; void main()B ob(1);ob.show();,35,4.3.2 访问声明: #include class A protected: int i;public: A(int n) i=n; void show() coutiendl; ; class B : private Apublic: B (int n) :
14、A(n-1) i=n; A:show; ; void main()B ob(1);ob.show();,36,几点说明: 数据成员也可以使用访问声明; 访问声明只含不带参数的函数名或变量名; 访问声明不能改变成员在基类中的访问属性,即:基类中在哪一部分声明,在派生类中也在相同部分声明; 基类中的重载函数若在派生类中使用了访问声明,则该声明会对所有的同名函数起作用。,37,4.4 多继承,38,4.4.1 多继承的声明,有两个以上基类的派生类声明的一般形式如下:class 派生类名:继承方式1 基类名1,继承方式n 基类名n/ 派生类新增的数据成员和成员函数;,39,4.4.2 多继承的构造函数
15、与析构函数,多继承构造函数定义的一般形式如下:派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),基类名n(参数表n)/ 派生类新增成员的初始化语句,40,实例:,#include class A public:int a;A(int i=1)a=i;cout“A:a is “aendl; ; class B public:int a,b;B(int i=2, int j=1)a=i;b=j;cout“B:a is “aendl;cout“B:b is “bendl; ;,41,class C:public A, public B int c; public:C(int i,in
16、t j,int k):A(i),B(i,j)c=k;cout“A:a=“A:aendl;cout“B:a=“B:aendl;/cout“C:a=“C:aendl;cout“B:b=“B:bendl;cout“C:b=“C:bendl;cout“C:c=“cendl; ; main() C c(4,5,6); ,42,4.4.3 虚基类:1. 为什么要引入虚基类 ?,#include class base public:base() a=5; cout“base a=“aendl; protected:int a; ; class base1:public base public:base1()
17、 a=a+10; cout“base1 a=“aendl; ;,43,class base2:public base public:base2()a=a+20;cout“base2 a=“aendl; ; class derived:public base1,public base2 public:derived() cout“base1:a=“base1:aendl;cout“base2:a=“base2:aendl; ; main() derived obj;return 0; ,44,base basebase1 base2derived图1 非虚基类的类层次图,45,2. 虚基类的概念
18、,虚基类的声明是在派生类的声明过程,其语法形式如下:class 派生类名:virtual 继承方式 类名/,46,例:,#include class base public:base() a=5; cout“base a=“aendl; protected:int a; ; class base1:public virtual base public:base1() a=a+10; cout“base1 a=“aendl; ;,47,class base2:virtual public base public:base2()a=a+20;cout“base2a=“aendl; ; class
19、derived:public base1,public base2 public:derived() cout“base1:a=“base1:aendl;cout“base2:a=“base2:aendl; ; main() derived obj;return 0; ,48,basebase1 base2derived图2 虚基类的类层次图,49,3. 虚基类的初始化,在使用虚基类机制时应该注意以下几点: (1) 如果在虚基类中定义有带形参的构造函数,并且没有定义缺省形式的构造函数,则整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用,以初始化在
20、虚基类中定义的数据成员。 (2) 建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。该派生类的其他基类对虚基类构造函数的调用都自动被忽略。,50,(3) 若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类构造函数; (4) 对于多个虚基类,构造函数的执行顺序仍然是先左后右,自上而下; (5) 对于非虚基类,构造函数的执行顺序仍是先左后右,自上而下; (6) 若虚基类由非虚基类派生而来,则仍然先调用基类构造函数,再调用派生类的构造函数。,51,4.5 赋值兼容规
21、则,52,4.5 赋值兼容规则,所谓赋值兼容规则是指在需要基类对象的任何地方(通常是访问由公有派生所得到的成员时)都可以使用公有派生类的对象来替代。这样,公有派生类实际上就具备了基类的所有特性,凡基类能解决的问题,公有派生类也能解决。,53,例如,下面声明的两个类: class Base ; class Derived:public Base ; 根据赋值兼容规则, 以下几种情况是合法的:,54,(1) 可以用派生类对象给基类对象赋值。例如:Base b;Derived d;b=d;这样赋值的效果是,对象b中所有数据成员都将具有对象d中对应数据成员的值,反之不然。 (2) 可以用派生类对象来初
22、始化基类的引用。例如:Derived d;Base ,55,(3) 可以把派生类对象的地址赋值给指向基类的指针。例如:Derived d;Base *bptr=,56,例:,#include class A int a; public:A(int i=1)a=i;void showa()coutaendl; ; class B:public Aint b; public:B(int i,int j):A(i) b=j; void showab()showa(); coutbendl; ;,57,main() B b1(4,5); b1.showab();A a1=b1; a1.showa();/a1.showab();/b1=a1; A ,