1 / 52

第十四章 目 录

第十四章 目 录. § 14.1 基类和派生类. § 14.2 派生类的定义格式. § 14.3 派生类的三种继承方式. § 14.4 派生类的构造函数和析构函数. § 14.5 多继承. § 14.6 多继承中的二义性问题. § 14.7 虚基类. 第十四章小结. 第十四章 继承性和派生类. 继承 (Hierarchy) 是 C++ 语言的一种重要机制。继承是指把已有的类作为基类来定义新的类。新类继承了其基类的属性和操作,还可以具有其基类不具备的自己特有的属性和操作。

bevis
Download Presentation

第十四章 目 录

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第十四章 目 录 §14.1 基类和派生类 §14.2 派生类的定义格式 §14.3 派生类的三种继承方式 §14.4 派生类的构造函数和析构函数 §14.5 多继承 §14.6 多继承中的二义性问题 §14.7 虚基类 第十四章小结

  2. 第十四章 继承性和派生类 继承(Hierarchy)是C++ 语言的一种重要机制。继承是指把已有的类作为基类来定义新的类。新类继承了其基类的属性和操作,还可以具有其基类不具备的自己特有的属性和操作。 继承性为软件的编写提供了强有力的机制,提高了软件的可靠性、易读性、有效性、可移植性和可重用性。 本章主要内容有:基类和派生类,单继承和多继承,虚基类。

  3. §14.1 基类和派生类(Base classes and derived classes) 生物学中的遗传(或称继承)的概念是指所有生物都从其祖先那里继承了一些特征。 继承的概念可以用于设计复杂的系统,它提供了将系统的组成部分组织成一个继承结构的方法,以利于对系统的描述。 同时它还提供了一个代码重用的结构。 在C++ 中,如果有一个类B 继承了类A,或从类A 派生出类B,通常称类A 为基类(父类),称类B 为派生类(子类)。 类B 不但拥有类A 的属性,而且还可以拥有自己新的属性。

  4. A X Y 外部存储器 B Z 软盘 硬盘 光盘 C 单继承 多继承 单继承 在C++ 中,继承分为单继承和多继承: 单继承——派生类只有一个直接基类的继承方式; 多继承——派生类有多个直接基类的继承方式。 如下图所示:

  5. §14.2 派生类的定义格式 1、单继承派生类的定义格式 class <派生类名>:<继承方式><基类名> { //<派生类新成员的定义> } 其中,派生类名是自定义的派生类名字,并且派生类是按指定的继承方式派生的。继承方式有: public 公有继承 private 私有继承 protected 保护继承

  6. 2、多继承派生类的定义格式 class <派生类名>:<继承方式1> <基类名1>, <继承方式2> <基类名2>,… { //<派生类新成员定义> }; 多继承派生类有多个基类,基类名之间用逗号分隔,每个基类名前都应有一个该基类的继承方式说明。 缺省的继承方式为私有继承。 例如:类A 是基类,类B 是类A 的派生类,定义格式如下:

  7. class A { public: A(int i) { a=i; } void print() { cout<<a<<endl; } private: int a; }; class B: public A { public: //… private: int b; };

  8. §14.3 派生类的三种继承方式 1、公有继承方式(public) 基类中的每个成员在派生类中保持同样的访问权限; 2、私有继承方式(private) 基类中的每个成员在派生类中都是private成员,而且它们不能再被派生的子类所访问; 3、保护继承方式(protected) 基类中的public成员和protected成员在派生类中都是protected成员,private成员在派生类中仍为private成员。

  9. Access Control(Including derived classes) A member of a class can be private, protected, or public: If it is private, its name be used only by member function and friends of the class in which it is declared. If it is protected, its name can be used only by member functions and friends of the class in which it is declared and by member functions and friends of classes derived from this class. If it is public, its name can be used by any function.

  10. general users derived class member functions and friends member functions and friends

  11. 在继承中,访问权限归纳如下: 1、不管是什么继承方式,派生类的成员函数和友元函数都可以访问基类中的公有成员和保护成员,但不能访问私有成员; 2、在公有继承时,派生类的对象只能访问公有成员,在保护继承和私有继承时,派生类的对象不能访问基类中任何成员。 例如:通过下面例子说明在继承方式中的访问权限问题 class Base { public: int b1; protected: int b2; private: int b3; };

  12. class D2:private Base { public: void test() { b1=8; //ok b2=9; //ok b3=10; //error } }; class D22:public D2 { public: void test() { b1=11; //error b2=12; //error b3=13; //error } }; class D1: public Base { public: void test() { b1=10; //ok, b1为public b2=20; //ok, b2为protected b3=30; //error } }; class D11:public D1 { prublic: void test() { b1=5; //ok b2=6; //ok b3=7; //error } };

  13. class D3:protected Base { public: void test() { b1=15; //ok b2=16; //ok b3=17; //error } }; class D33:public D3 { public: void test() { b1=18; //ok b2=19; //ok b3=20; //error } }; void main() { D11 d1; d1.b1=1; // ok d1.b2=2; d1.b3=3; D22 d2; d2.b1=4; d2.b2=5; d2.b3=6; D33 d3; d3.b1=7; d3.b2=8; d3.b3=9; }

  14. Base Base Base D1 D2 D3 public private protected D11 D22 D33 public public public 注意:在main()中定义的对象d1,d2 和d3,在给其成员赋值时,只有d1.b1是正确的,其余都是错误的,这是为什么呢?注意看下面的继承关系。

  15. 例如:分析下面程序,改错后运行 #include <iostream.h> class A { public: void f(int i) { cout<<i<<endl; } void g() { cout<<“A\n”; } }; class B:private A //private hierarchy { public: void h() { cout<<“B\n”; } A::f; // f()declared to public member }; void main() { B b; b.f(10); b.g(); //error b.h(); }

  16. 注意: • (1) 程序中A::f; 的含义是将类A 中的公有成员f() 从私有继承方式的派生类B 中申明为公有的,B 的派生类对象可以访问成员f()。因此在main()中,语句b.f(10); 是正确的。该语句称为访问申明,它是私有继承方式中的一种调用机制,即在私有继承方式下,用于恢复名字的访问权限; • 语句b.g(); 是错误访问,有编译错。若注释该语句,运行结果为:10 • B • 将class B:private A 改为class B:public A,则无编译错,原程序输出为:10 • A • B

  17. 例如:分析下面程序,改错后运行 #include <iostream.h> #include <string.h> class A { public: A(const char *name1) { strcpy(name,name1); } private: char name[80]; }; class B: protected A { public: B(const char *nm):A(nm) { } void print() const { cout<<“name:”<<name<<endl; } }; void main() { B b(“Liming”); b.print(); } 注意:该程序有编译错,查错改正,输出结果是什么?

  18. §14.4 派生类的构造函数和析构函数 通过继承关系,派生类包含了它的所有基类的成员。派生类对象的数据结构由基类中说明的数据成员和派生类中说明的数据成员共同构成。 在创建派生类对象时,派生类对象的初始化,不仅要给派生类中的数据成员初始化,还要给它的基类中的数据成员初始化。 如果派生类中还有子对象时,还应包含对子对象初始化。

  19. 1、派生类的构造函数 <派生类名>(<总参数表>):<基类构造函数名>(<参数表1>), <子对象名>(<参数表2>) { //<派生类中数据成员的初始化> } 构造函数的调用顺序如下: 基类构造函数; 子类构造函数; 派生类构造函数。

  20. 2、派生类的析构函数 执行派生类的析构函数时,也要调用基类及子对象的析构函数。 析构顺序如下: 先调用派生类的析构函数; 子对象的析构函数; 基类的析构函数。

  21. 例如:下面程序显示了构造函数的执行顺序 #include <iostream.h> class Base { public: Base() { cout<<“\n Base created\n”; } }; class D_class:public Base { public: D_class() { cout<<“D_class created\n”; } }; void main() { D_class d; } 运行结果: Base created D_class created 先执行基类的构造函数,再执行派生类的构造函数;执行缺省的析构函数时,先执行派生类,再执行基类。

  22. 例如:分析程序的输出结果,分析派生类构造函数及析构函数的特点。例如:分析程序的输出结果,分析派生类构造函数及析构函数的特点。 #include <iostream.h> class A { public: A() { a=0; } A(int i) { a=i; } void print() { cout<<a<<“,”; } private: int a; };

  23. class B: public A { public: B() { b1=b2=0; } //隐式调用基类A 的缺省构造函数 B(int i) { b1=0; b2=i; } //隐式调用A 的缺省构造函数 B(int i; int j, int k):A(i),b1(j),b2(k) { } //显式调用基类A 的带参构造函数 void print() { A::print(); cout<<b1<<“,”<<b2<<endl; } private: int b1,b2; };

  24. void main() { B b1; B b2(5); B b3(1,2,3); b1.print(); b2.print(); b3.print(); } 结果: 0, 0,0 0, 0,5 1, 2,3

  25. 注意:程序中对象b1,b2,b3调用了不同的三个构造函数实现初始化过程,其内存分配情况如下图所示。注意:程序中对象b1,b2,b3调用了不同的三个构造函数实现初始化过程,其内存分配情况如下图所示。 B(int i,int j,int k)创建b3 B()创建b1 B(int i)创建b2 A()创建 A()创建 A(int i)创建 a a a b1 b1 b1 b2 b2 b2 可见,创建b1,b2,b3三个对象共调用三次基类A 的构造函数,三次派生类B 本身的构造函数。 析构时也调用了六次析构函数,析构次序与构造次序相反。

  26. 例如:下面程序中派生类的数据成员为另一个类的对象,分析输出结果例如:下面程序中派生类的数据成员为另一个类的对象,分析输出结果 #include <iostream.h> #include <string.h> class Advisor { int noofmeetings; }; class Student { public: Student(char *pname=“no name”) { srtncpy(name,pname,sizeof(name)); average=semesterhours=0; }

  27. void addcourse(int hours,float grade) { average=semesterhours*average+grade*hours; //总分 semesterhours+=hours; //总修学时数 average/=semesterhours; //平均分 } int gethours() { return semesterhours; } float getaverage() { return average; }

  28. void display() { cout<<“name=\””<<name<<“\””<<“,hours=” <<semesterhours<<“,average=”<<average<<endl; } protected: char name[40]; int semesterhours; float average; }; class Graduatestudent: public Student { public: getqulifier() { return qualifiergrade; }

  29. protected: Advisor advisor; //类对象成员 int qualifiergrade; }; void main() { Student ds(“Wang ming undergrade”); Graduatestudent gs; ds.addcourse(3,2.5); ds.display(); gs.addcourse(3,3.0); //派生类对象访问公共成员函数 gs.display(); }

  30. 结果: name=“Wang ming undergrade”, hours=3,average=2.5 name=“no name”, hours=3, average=3.0 派生类对象 gs 的创建初始化过程如图所示: name[40] 调用基类构造函数初始化 semesterhours average noofmetings 调用Advisor 类构造函数初始化 qualifergrade 调用Graduatestudent 派生类本身的构造函数初始化 gs 对象的内存空间

  31. §14.5 多继承 …… 基类1 基类2 基类n 派 生 类 如果派生类有两个或两个以上的直接基类,称为多继承。如下图所示:

  32. 多继承派生类的定义格式: class <派生类名>:<继承方式1><基类名1>, <继承方式2><基类名2>, … { //派生类的类体 } 多继承派生类的构造函数格式: <派生类名>(<总参数表>):<基类名1>(<参数表1>), <基类名2>(<参数表2>), <子对象名3>(<参数表3>), … { //派生类构造函数 } 执行顺序:先执行所有基类的构造函数,再执行派生类本身的构造函数,包含子对象在内。

  33. 例如:给出一个多继承的例子 class A { …… }; class B { …… }; class C { …… }; class D: public A, public B, public C { …… }; 派生类D 继承了三个基类,继承方式都是公有继承。类D 的成员包含了类A 、类B 和类C 中的成员,以及它本身的成员。 如果要创建类D 的对象,首先要顺序执行类A 、类B 和类C 的构造函数,再执行派生类D本身的构造函数。

  34. Location Location Point GMessage Circle Mcircle 例如:介绍一个在圆内显示正文的例子,该程序是使用单继承和多继承产生派生类的应用实例 首先定义Gmessage类,它从指定的x 和y 坐标开始显示一个字符串; 然后定义Circle类,它以指定圆心坐标和半径显示一个圆; 再定义一个新类Mcircle,它是由Gmessage 类和Circle 类派生的,具有显示圆又显示信息的功能,包含有两个基类的特征。 继承关系及程序显 示的图形如图所示: Universe Word C++

  35. 程序如下: //Mcircle.cpp #include <graphics.h> #include <string.h> //for string function #include <conio.h> //for console I/O #include “point.h” class Circle:public Point { protected: int radius; public: Circle(int Initx, int Inity, int InitRadius); void show(); };

  36. class Gmessage: public Location { char *msg; //message to be displayed int font; //BGI font to use int field; //size of field for text scaling public: Gmessage(int Msgx, int Msgy, int MsgFont, int FidldSize, char* Text); void show(); }; class Mcircle: public Circle, public Gmessage { public: Mcircle(int Mcircx, int Mcircy, int Mcircradius, int Font, char* Msg); void show(); //show circle with message };

  37. Circle::Circle(int Initx,intity,int InitRadius):Point(Initx, Inity) { Radius=InitRadius; } void Circle::show { visible=true; circle(x,y,radius); //draw the circle } Gmessage::Gmessage(int Msgx, int Msgy, int MsgFont, int FieldSize, char *Text):Location(Msgx, Msgy) { font=MsgFont; //standard fonts defined in graph.h field=FieldSize; //width of area in which to fit text msg=Text; //point at message }

  38. void Gmessage::show() { int size=Field/(8*strlen(Msg)); //8 pixels per char settestjustify(CENTER_TEXT,CENTER_TEXT); //centers in circle settextstyle(Font,HORIZ_DIR,size); outtextxy(x,y,msg); } Mcircle::Mcircle(int Mcircx, int Mcircy, int McircRadius, int Font, char *Msg):Circle(Mcircx, Mcircy, McircRadius),Gmessage(Mcircx, Mcircy, Font, 2*McircRadius, Msg) { } void Mcircle::show { Circle::show(); Gmessage::show(); }

  39. void main() { int graphdriver=DETECT; int graphmode; initgraph(&graphdriver, &graphmode, “…\\bgi”); \\路径指定,如:“d:\\tc\\bgi” Mcircle small(250,100,25,SANS_SERIF_FONT ,“C++”); small.show(); Mcircle medius(250,150,100,TRIPLEX_FONT, “World”); medius.show(); Mcircle large(250,250,225,GOTHIC_FONT, “Universe”); large.show(); getch(); closegraph(); }

  40. //point.h #include <graphics.h> enum Boolean {false, true} class Location { protected: int x; int y; public: Location(int Initx, int Inity) { x=Initx; y=Inity; } int Getx() { return x; } int Gety() { return y; } };

  41. class Point: public Location { protected: Boolean Visible; public: Point(int Initx, int Inity):Location(Initx,Inity) { Visible=false; } void show() { Visible=true; putpixel(x,y,getcolor()); } void hide() { Visible=false; putpixel(x,y,getcolor()); } Boolean IsVisible() { return Visible; } void MoveTo(int Newx, int Newy) { hide(); x=Newx; y=Newy; show(); } };

  42. 程序注释: 1、GMessage::show()中使用了三个C++ 的库函数,原型在graphics.h中。 settextjustify 设置文本对齐方式 settextstyle 设置当前文本属性 outtextxy 在指定位置显示一字符串 2、main()中使用了三种字符显示文本: SANS_SERIF_FONT TRIPLEX_FONT GOTHIC_FONT 3、多继承派生类构造函数Mcicrcle 通过调用两个基类构造函数对基类数据进行初始化。首先调用Circle 构造函数,该构造函数将调用Point 构造函数,而Point 构造函数又调用Location 构造函数。然后调用Gmessage 构造函数,该构造函数又调用Location构造函数。

  43. §14.6 多继承中的二义性问题 一般地,派生类成员的访问是唯一的。但是在多继承的情况下,可能出现派生类对其类成员访问的不唯一性,即二义性。下面是出现二义性的两种情况: 1、调用不同类的具有相同名字成员时可能出现二义性,例如: #include <iostream.h> class A { public: void f(); };

  44. class B { public: void f(); void g(); }; class C: public A, public B { public: void g(); void h(); }; Void main() { C C1; C1.f(); //出现二义性 }

  45. 程序中“C1.f();”出现二义性,访问的f()是类A 中的还是类B 中的呢?解决办法是用类名对成员加以限定,即: C1.A::f(); 或者C1.B::f(); 2、访问共同基类的成员时可能出现二义性 当一个派生类有多个基类,而这些基类又有一个共同的基类,对这个共同基类中成员的访问可能出现二义性。 例如: #include <iostream.h> class A { public: int a; };

  46. void main() { C Cobj; Cobj.a; //有二义性 Cobj.A::a; //有二义性 } class B1: public A { private: int b1; }; class B2: public A { private: int b2; }; Class C: public B1, public B2 { public: int f(); private: int c; }; 在“Cobj.a;”中是对B1 继承的基类A 的成员a,还是对B2继承基类A 的成员a 进行访问呢?故有二义性。消除二义性的方法可改写为: Cobj.B1::a; Cobj.B2::a;

  47. A(a) A(a) B1(b1) B2(b2) C(f(),c) 程序中4个类的关系用DAG图示法表示如下: 从图中看出,类A 是派生类C 两条继承路径上的一个公共基类,因此这个公共基类会在派生类对象中产生两个基类子对象。如果要使这个公共基类在派生类中只产生一个基类子对象,则需要将这个基类设置为虚基类。

  48. §14.7 虚基类 引进虚基类的目的是为了解决二义性问题,使用公共基类在其派生类对象中只产生一个基类子对象。 虚基类说明格式如下:virtual <继承方式><基类名> 例如: class A { public: void f(); private: int a; }; class B: virtual public A { protected: int b; }; class C: virtual public A { protected: int c; }; class D: public B, public C { public: int g(); private: int d; };

  49. A(f(),a) B(b) C(c) D(g(),d) 例中,类A 是类D 的虚基类。类A,类B,类C 和类D 之间的关系用DAG图示如下: 从图中可见,虚基类子对象被合并成一个子对象,这种“合并”作用,使得可能出现的二义性被消除。

  50. 下面访问是正确的: D dobj; void D::g(); { f(); //合法 } D dd; A *pd; pd=&dd; //合法 指向基类指针可访问派生类对象 C++规定,虚基类子对象是由最近派生类的构造函数通过调用虚基类的构造函数进行初始化的。

More Related