1、1,继承与组合,C+语言程序设计,2,主要内容,类的继承与派生 类成员的访问控制 单继承与多继承 派生类的构造与析构 类的组合 前向引用声明 用组合还是继承?,3,类的继承与派生,保持已有类的特性而构造新类的过程称为继承。 在已有类的基础上新增自己的特性而产生新类的过程称为派生。 被继承的已有类称为基类(或父类)。 派生出的新类称为派生类。,4,继承与派生问题举例,类的继承与派生,5,继承与派生问题举例,类的继承与派生,6,继承与派生问题举例,类的继承与派生,7,继承与派生问题举例,类的继承与派生,8,继承与派生的目的,继承的目的:实现代码重用。 派生的目的:当新的问题出现,原有程序无法解决(
2、或不能完全解决)时,需要对原有程序进行改造。,类的继承与派生,9,派生类的声明,class 派生类名:继承方式 基类名 成员声明; ,类的继承与派生,10,继承方式,不同继承方式的影响主要体现在: 派生类成员对基类成员的访问权限 通过派生类对象对基类成员的访问权限 三种继承方式 公有继承 私有继承 保护继承,类成员的访问控制,11,公有继承(public),基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可直接访问。 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。 通过派生
3、类的对象只能访问基类的public成员。,类成员的访问控制,12,例1 公有继承举例,class Point /基类Point类的声明 public: /公有函数成员void InitP(float xx=0, float yy=0)X=xx;Y=yy;void Move(float xOff, float yOff)X+=xOff;Y+=yOff;float GetX() return X;float GetY() return Y; private: /私有数据成员float X,Y; ;,类成员的访问控制,class Rectangle: public Point /派生类声明 publ
4、ic: /新增公有函数成员void InitR(float x, float y, float w, float h)InitP(x,y);W=w;H=h;/调用基类公有成员函数float GetH() return H;float GetW() return W; private: /新增私有数据成员float W,H; ;,13,#include #include using namecpace std; int main() Rectangle rect;rect.InitR(2,3,20,10);/通过派生类对象访问基类公有成员rect.Move(3,2); coutrect.GetX
5、(),rect.GetY(),rect.GetH(),rect.GetW()endl;return 0; ,14,15,私有继承(private),基类的public和protected成员都以private身份出现在派生类中,但基类的private成员不可直接访问。 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。 通过派生类的对象不能直接访问基类中的任何成员。,类成员的访问控制,16,例2 私有继承举例,class Rectangle: private Point /派生类声明 public: /新增外部接口void In
6、itR(float x, float y, float w, float h)InitP(x,y);W=w;H=h; /访问基类公有成员void Move(float xOff, float yOff) Point:Move(xOff,yOff);float GetX() return Point:GetX();float GetY() return Point:GetY();float GetH() return H;float GetW() return W; private: /新增私有数据float W,H; ;,类成员的访问控制,#include #include using nam
7、ecpace std; int main() /通过派生类对象只能访问本类成员Rectangle rect;rect.InitR(2,3,20,10);rect.Move(3,2);coutrect.GetX(), rect.GetY(),rect.GetH(),rect.GetW()endl;return 0; ,17,18,保护继承(protected),基类的public和protected成员都以protected身份出现在派生类中,但基类的private成员不可直接访问。 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成
8、员。 通过派生类的对象不能直接访问基类中的任何成员,类成员的访问控制,19,protected 成员的特点与作用,对建立其所在类对象的模块来说,它与 private 成员的性质相同。 对于其派生类来说,它与 public 成员的性质相同。 既实现了数据隐藏,又方便继承,实现代码重用。,类成员的访问控制,20,例3 protected 成员举例,class A protected:int x; int main() A a;a.x=5; /错误 ,类成员的访问控制,class A protected:int x; class B: public Apublic:void Function();
9、; void B:Function() x=5; /正确 ,21,22,访问权修饰符总结,访问权修饰符:public, protected,private,23,类型兼容规则,一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止。具体表现在: 派生类的对象可以被赋值给基类对象。 派生类的对象可以初始化基类的引用。 指向基类的指针也可以指向派生类。 通过基类对象名、指针只能使用从基类继承的成员,类型兼容,24,基类与派生类的对应关系,单继承 派生类只从一个基类派生。 多继承 派生类从多个基类派生。 多重派生 由一个基类派生出多个不同的派生类。 多层派生 派生类又作为基类,继续派生新的类。
10、,单继承与多继承,25,多继承时派生类的声明,class 派生类名:继承方式1 基类名1, 继承方式2 基类名2,. 成员声明; 注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承。,单继承与多继承,26,多继承举例,class Apublic:void setA(int);void showA();private:int a; ; class Bpublic:void setB(int);void showB();,private:int b; ; class C : public A, private Bpublic:void setC(int, int, int);void sh
11、owC();private:int c; ;,单继承与多继承,void A:setA(int x) a=x; void B:setB(int x) b=x; void C:setC(int x, int y, int z) /派生类成员直接访问基类的/公有成员setA(x); setB(y); c=z; /其它函数实现略,int main() C obj;obj.setA(5);obj.showA();obj.setC(6,7,9);obj.showC(); / obj.setB(6); 错误 / obj.showB(); 错误return 0; ,27,28,继承时的构造函数,基类的构造函数
12、不被继承,派生类中需要声明自己的构造函数。 声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,自动调用基类构造函数完成。 派生类的构造函数需要给基类的构造函数传递参数,派生类的构造、析构函数,29,单一继承时的构造函数,派生类名:派生类名(基类所需的形参,本类成员所需的形参):基类名(参数表) 本类成员初始化赋值语句; ;,派生类的构造、析构函数,30,单一继承时的构造函数举例,#include using namecpace std; class Bpublic:B();B(int i);B();void Print() const;private:int b;
13、;,派生类的构造、析构函数,B:B() b=0;cout“Bs default constructor called.“endl; B:B(int i) b=i;cout“Bs constructor called.“ endl; B:B() cout“Bs destructor called.“endl; void B:Print() const coutbendl; ,31,class C:public B public:C();C(int i,int j);C();void Print() const;private:int c; ;,32,C:C() c=0;cout“Cs defau
14、lt constructor called.“endl; C:C(int i,int j):B(i) c=j;cout“Cs constructor called.“endl; C:C() cout“Cs destructor called.“endl; void C:Print() const B:Print(); coutcendl; void main() C obj(5,6); obj.Print(); ,33,34,多继承时的构造函数,派生类名:派生类名(基类1形参,基类2形参,.基类n形参,本类形参):基类名1(参数), 基类名2(参数), .基类名n(参数) 本类成员初始化赋值语
15、句; ;,派生类的构造、析构函数,35,派生类与基类的构造函数,当基类中声明有默认形式的构造函数或未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数。 若基类中未声明构造函数,派生类中也可以不声明,全采用缺省形式构造函数。 当基类声明有带形参的构造函数时,派生类也应声明带形参的构造函数,并将参数传递给基类构造函数。,派生类的构造、析构函数,36,多继承且有内嵌对象时 的构造函数,派生类名:派生类名(基类1形参,基类2形参,.基类n形参,本类形参):基类名1(参数), 基类名2(参数), .基类名n(参数),对象数据成员的初始化 本类成员初始化赋值语句; ;,派生类的构造、析构函数,3
16、7,构造函数的调用次序,1 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。 2 调用成员对象的构造函数,调用顺序按照它们在类中声明的顺序。 3 派生类的构造函数体中的内容。,派生类的构造、析构函数,38,拷贝构造函数,若建立派生类对象时调用缺省拷贝构造函数,则编译器将自动调用基类的缺省拷贝构造函数。 若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数。例如: C:C(C &c1):B(c1) ,派生类的构造、析构函数,39,例5 派生类构造函数举例,#include using namecpace std; class B1 /基类B1,构造函数有参数 pu
17、blic:B1(int i) cout“constructing B1 “iendl; ; class B2 /基类B2,构造函数有参数 public:B2(int j) cout“constructing B2 “jendl; ; class B3 /基类B3,构造函数无参数 public:B3()cout“constructing B3 *“endl; ;,派生类的构造、析构函数,class C: public B2, public B1, public B3 public: /派生类的公有成员C(int a, int b, int c, int d): B1(a),memberB2(d)
18、,memberB1(c),B2(b) private: /派生类的私有对象成员B1 memberB1;B2 memberB2;B3 memberB3; ; void main() C obj(1,2,3,4); ,运行结果: constructing B2 2 constructing B1 1 constructing B3 * constructing B1 3 constructing B2 4 constructing B3 *,40,41,继承时的析构函数,析构函数也不被继承,派生类自行声明 声明方法与一般(无继承关系时)类的析构函数相同。 不需要显式地调用基类的析构函数,系统会自动
19、隐式调用。 析构函数的调用次序与构造函数相反。,派生类的构造、析构函数,42,例6 派生类析构函数举例,派生类的构造、析构函数,#include using namecpace std; class B1 /基类B1声明 public:B1(int i) cout“constructing B1 “iendl;B1() cout“destructing B1 “endl; ; class B2 /基类B2声明 public:B2(int j) cout“constructing B2 “jendl; B2() cout“destructing B2 “endl; ; class B3 /基类B
20、3声明 public:B3()cout“constructing B3 *“endl;B3() cout“destructing B3 “endl; ;,class C: public B2, public B1, public B3 public:C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b) private:B1 memberB1;B2 memberB2;B3 memberB3; ; void main() C obj(1,2,3,4); ,43,44,例6 运行结果,constructing B2 2 c
21、onstructing B1 1 constructing B3 * constructing B1 3 constructing B2 4 constructing B3 * destructing B3 destructing B2 destructing B1 destructing B3 destructing B1 destructing B2,45,组合的概念,类中的成员数据是另一个类的对象。 可以在已有的抽象的基础上实现更复杂的抽象。,类 的 组 合,46,例7,class Point private:float x,y; /点的坐标public:Point(float h,fl
22、oat v); /构造函数float GetX(void); /取X坐标float GetY(void); /取Y坐标void Draw(void); /在(x,y)处画点 ; /.函数的实现略,类 的 组 合,47,class Line private:Point p1,p2; /线段的两个端点public:Line(Point a,Point b); /构造函数Void Draw(void); /画出线段 ; /.函数的实现略,49,48,类组合的构造函数设计,原则:不仅要负责对本类中的基本类型成员数据赋初值,也要对对象成员初始化。 声明形式: 类名:类名(对象成员所需的形参,本类成员形参
23、):对象1(参数),对象2(参数), 本类初始化 ,类 的 组 合,49,类组合的构造函数调用,构造函数调用顺序:先调用内嵌对象的构造函数(按内嵌时的声明顺序,先声明者先构造)。然后调用本类的构造函数。(析构函数的调用顺序相反) 若调用默认构造函数(即无形参的),则内嵌对象的初始化也将调用相应的默认构造函数。,类 的 组 合,50,类的组合 例8,class Part /部件类 public:Part();Part(int i);Part();void Print();private:int val; ;,类 的 组 合,51,class Whole public:Whole();Whole(
24、int i,int j,int k);Whole();void Print();private:Part one;Part two;int date; ;,53,52,Whole:Whole() date=0; Whole:Whole(int i,int j,int k):two(i),one(j),date(k) /.其它函数的实现略,54,53,前向引用声明,类应该先声明,后使用 如果需要在某个类的声明之前,引用该类,则应进行前向引用声明。 前向引用声明只为程序引入一个标识符,但具体声明在其它地方。,类 的 组 合,54,前向引用声明举例,class B; /前向引用声明 class A
25、public:void f(B b); ; class B public:void g(A a); ;,类 的 组 合,55,前向引用声明注意事项,使用前向引用声明虽然可以解决一些问题,但它并不是万能的。需要注意的是,尽管使用了前向引用声明,但是在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类的对象。请看下面的程序段: class Fred; /前向引用声明 class Barney Fred x; /错误:类Fred的声明尚不完善; class Fred Barney y;,类 的 组 合,56,前向引用声明注意事项,class Fred; /前向引用声明cla
26、ss Barney public:void method()x-yabbaDabbaDo(); /错误:Fred类的对象在定义之前被使用private:Fred* x; /正确,经过前向引用声明,可以声明Fred类的对象指针;class Fred public:void yabbaDabbaDo();private:Barney* y;,类 的 组 合,57,前向引用声明注意事项,应该记住:当你使用前向引用声明时,你只能使用被声明的符号,而不能涉及类的任何细节。,类 的 组 合,58,用组合还是继承?,继承和组合都是重要的重用方法在OO开发的早期,继承被过度使用C+的“继承”特性可以提高程序的
27、可复用性。正因为“继承”太有用、太容易用,才要防止乱用“继承”。我们应当给“继承”一些使用规则。 规则1:如果类A 和类B 毫不相关,不能为了使B 的功能更多些而让B继承A 的功能和属性。,59,用组合还是继承?,规则2:若在逻辑上B 是A 的“一种”(a kind of ),则允许B 继承A 的功能和属性。 例如:男人(Man)是人(Human)的一种,男孩(Boy)是男人的一种。 那么类Man 可以从类Human 派生,类Boy 可以从类Man 派生。class Human ; class Man : public Human ; class Boy : public Man ;,60,用
28、组合还是继承?,看起来很简单,但是实际应用时可能会有意外,继承的概念在程序世界与现实世界并不完全相同。 例如从生物学角度讲,鸵鸟(Ostrich)是鸟(Bird)的一种,按理说类Ostrich 应该可以从类Bird 派生。但是鸵鸟不能飞,那么Ostrich:Fly 怎么理解? class Bird public: virtual void Fly(void); ; class Ostrich : public Bird ;,61,用组合还是继承?,又如:从数学角度讲,圆(Circle)是一种特殊的椭圆(Ellipse),按理说类Circle 应该可以从类Ellipse 派生。但是椭圆有长轴和短
29、轴,如果圆继承了椭圆的长轴和短轴,岂非画蛇添足?所以更加严格的继承规则应当是:若在逻辑上B 是A 的“一种”,并且A 的所有功能和属性对B 而言都有意义,则允许B 继承A 的功能和属性。,62,用组合还是继承?,若在逻辑上A 是B 的“一部分”(a part of),则不允许B 从A 派生,而是要用A 和其它东西组合出B。 例如眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是头(Head)的一部分,所以类Head 应该由类Eye、Nose、Mouth、Ear 组合而成,不是派生而成。 如果允许Head 从Eye、Nose、Mouth、Ear 派生而成,那么Head 将自动具有Lo
30、ok、Smell、Eat、Listen 这些功能。下面程序十分简短并且运行正确,但是这种设计方法却是不对的。 class Head : public Eye, public Nose, public Mouth, public Ear ;,63,用组合还是继承?,class Eye public: void Look(void); ; class Nose public: void Smell(void); ; class Mouth public: void Eat(void); ; class Ear public: void Listen(void); ;,class Head public: void Look(void) m_eye.Look(); void Smell(void) m_nose.Smell(); void Eat(void) m_mouth.Eat(); void Listen(void) m_ear.Listen(); private: Eye m_eye; Nose m_nose; Mouth m_mouth; Ear m_ear; ;,正确的设计,虽然代码冗长,但概念清晰,64,Thinks!,