570 likes | 811 Vues
C++ 语言基础. 第 7 章 继承性和派生类. C + + 最重要的性能之一是代码重用:通过“继承”机制,创建新类来重用代码,扩充和完善旧的程序以适应新的需求; “继承”提供了无限重复利用资源的一种途径。. § 7.1 基类和派生类 § 7.2 单继承 § 7.3 多继承 § 7.4 虚基类. 基类:已存在的用来派生新类的类(父类). 派生类:由已存在的类派生出的新类(子类). 单继承:从一个基类派生的继承. 多继承:从多个基类派生的继承. 单继承. 多继承. A. X. Y. Z. B. C. C++ 语言基础. § 7.1 基类和派生类.
E N D
C++语言基础 第7章 继承性和派生类 • C + +最重要的性能之一是代码重用:通过“继承”机制,创建新类来重用代码,扩充和完善旧的程序以适应新的需求; • “继承”提供了无限重复利用资源的一种途径。 §7.1基类和派生类 §7.2单继承 §7.3多继承 §7.4虚基类
基类:已存在的用来派生新类的类(父类) 派生类:由已存在的类派生出的新类(子类) 单继承:从一个基类派生的继承 多继承:从多个基类派生的继承 单继承 多继承 A X Y Z B C C++语言基础 §7.1 基类和派生类
C++语言基础 一、派生类的定义格式 单继承: class <派生类名>:<继承方式> <基类名> { <派生类新定义成员> }; 多继承: class <派生类名>:<继承方式1><基类名1>, <继承方式2><基类名2>… { <派生类新定义成员> }; 继承方式关键字:public 、 protected、private(缺省)
C++语言基础 二、派生类的三种继承方式 • 公有继承public • 基类的公有成员和保护成员在派生类中保持原有状态; • 私有继承private • 基类的公有成员和保护成员作为派生类的私有成员; • 保护继承protected • 基类的公有和保护成员作为派生类的保护成员;
C++语言基础 比较: 不同继承方式下基类和派生类特性
不同继承方式下的垂直访问例 //(1) Compile the following codes // (2) Change the inheriting way from PUBLIC to PORTECTED and PRIVTE, compile again #include<iostream.h> class A { public: A(int z1, int z2, int z3,int z4 ) { intValAPubl=z1; intValAProt=z2; intValAPrivaX=z3; intValAPrivaY=z4;} int MaxA(); int intValAPubl; protected: int MinA(); int intValAProt; private: int intValAPrivaX, intValAPrivaY; };
int A::MaxA() { return intValAPrivaX>=intValAPrivaY?intValAPrivaX:intValAPrivaY; } int A::MinA() { return intValAPrivaX<=intValAPrivaY?intValAPrivaX:intValAPrivaY; } class B: public A { public: int MaxB(); int intValBPubl; protected: int MinB(); int intValBProt; private: int intValBPriva; };
int B::MaxB() { int tem; //cout<<intValAPrivaX<<intValAPrivaX<<endl; // Attempt to plumb visit private member of calss A, it is an error tem=MaxA(); // Plumb visit public functin of class A return tem>=intValBPriva?tem:intValBPriva; } int B::MinB() { int tem; tem=MinA(); // Plumb visit protected functin of calss A return tem<=intValBPriva?tem:intValBPriva; }
class C: public B { public: int MaxC(); int intValCPubl; void display(); protected: int MinC(); int intValCprot; private: int intValCPriva; };
int C::MaxC() { int tem; // tem=maxA() // while the class B inherit form class A in private, //the line is worng. tem=MaxB(); return tem>=intValCPriva?tem:intValCPriva; } int C::MinC() { int tem; tem=MinB(); return tem<=intValCPriva?tem:intValCPriva; }
void C::display() { cout<<"Plumb visit grandfather class A public member"<<endl; cout<<intValAPubl<<endl; cout<<"Plumb visit grandfather class A protected member"<<endl; cout<<intValAProt<<endl; //cout<<intValAPrivaX<<intValAPrivaX<<endl; // Attempt to plumb visit private member of calss A, it is an error cout<<"Plumb visit father class B public member"<<endl; cout<<intValBPubl<<endl; cout<<"Plumb visit father calss B protected member"<<endl; cout<<intValBProt<<endl; cout<<"Plumb visit father calss B private member"<<endl;
//cout<<intValBPriva<<endl; // Attempt to plumb visit private member of father class, wrong. cout<<"Myself public member"<<endl; cout<<intValCPubl<<endl; cout<<"Myself protected member"<<endl; cout<<intValCprot<<endl; cout<<"Myself private member"<<endl; cout<<intValCPriva<<endl; }
水平访问:派生类的对象对基类的访问 垂直访问:派生类的派生类对基类的访问 C++语言基础 结论: 基类对象只能访问基类中的public成员; 任何一种继承方式,派生类的成员函数都可以访问基类的public和protected成员; 只有公有继承时,派生类对象才可以访问基类的public成员; “可见性”即“可访问性”!
C++语言基础 水平访问 垂直访问 结论: 公有继承public:水平访问和垂直访问对基类中的公有成员不受限制 私有继承private:水平访问和垂直访问对基类中公有成员均无法施行 保护继承protected:水平访问同于私有继承,垂直访问同于公有继承
C++语言基础 三、基类与派生类的关系 • 派生类是基类的具体化 • 基类是派生类的抽象,派生类通过增加行为和属性将抽象类变为某种有用的类型; • 派生类是基类定义的延续 • 先定义一个抽象基类, 其中有些操作并未实现,然后定义非抽象的派生类,实现抽象基类中定义的操作; • 派生类是基类的组合 • 多继承时,派生类是所有基类行为的组合。
C++语言基础 利用“继承”创建新类时: ——说明新类与原有类的区别; 派生类与基类的区分方法: ——添加数据成员和成员函数; • 继承使大量原有的程序代码可以复用 • 类是“可复用的软件构件”
基类 Point 派生类1 派生类2 Line Ellipse C++语言基础 class Point { public: virtual void draw( )=0; protected: int x0,y0; }; [例] class Line: public Point { public: void draw( ) {……} protected: int x1,y1; }; class Ellipse: public Point { public: void draw( ) {……} protected: int x2,y2; };
C++语言基础 §7.2 单继承 • 每个派生类只能有一个基类(一个基类可以有多个派生类) 一、成员访问权限的控制 二、构造函数和析构函数 三、子类型化和类型适应
A B C C++语言基础 一、成员访问权限的控制 [例7.1] class B: public A { public: void f2(); protected: int j2; private: int i2; }; class C: public B { public: void f3(); }; class A { public: void f1(); protected: int j1; private: int i1; }; (基类和派生类是相对的)
C++语言基础 [例7.1] 公有继承下
C++语言基础 • 公有继承 • 派生类的成员函数可以访问基类中public 和protected成员; • 派生类的对象可以访问基类中的public成员; • 私有继承 • 派生类的成员函数可以访问基类中public 和protected成员; • 派生类的对象不可以访问基类中的任何成员; • 保护继承 • 派生类的成员函数可以访问基类中public 和protected成员; • 派生类的对象不可以访问基类中的任何成员;
C++语言基础 结论: • 任何一种继承方式,派生类的成员函数都可以访问基类的public和protected成员; • 只有公有继承,派生类对象才可以访问基类的public成员;
3. 哪个语句有错误? 去掉该语句后运行结果: C++语言基础 [例7.2] class A { public: void f(int i) {cout<<i<<endl;} void g() {cout<<"g\n"; g(); //函数调用 } }; class B: A { public: void h() {cout<<"h\n";} A::f; }; void main() { B d1; //d1是派生类B的对象 d1.f(6); d1.g(); //访问基类A公有成员 d1.h(); } 私有 1. 继承方式? 2. A::f ? 将A类中的公有成员说明为B类中的公有成员 6 h 6 g h 4. 改为class B: public A ? 5. 改为class B: protected A? 错误
C++语言基础 [例7.3] #include <iostream.h> #include <string.h> class A {public : A(const char *nm) {strcpy(name,nm);} private: char name[80]; }; class B: public A {public: B(const char *nm):A(nm) {} void PrintName() const; }; void B::PrintName() const { cout<<"name:"<<name<<endl; } void main() { B b1("Wang li"); b1.PrintName (); } 编译错误信息、如何修改? 将name的访问权限改为:protected
派生类中说明的数据成员 派生类对象数据结构 基类中说明的数据成员 C++语言基础 二、构造函数和析构函数 • 构造函数 功能:创建对象时,用给定值将对象初始化; • “基类子对象”: • 派生类对象中由基类中说明的数据成员和成员函数构成的封装体; • 由基类中的构造函数初始化; • 构造函数不能被继承! • 派生类构造函数必须通过调用基类的构造函数来初始化“基类子对象”。
基类的构造函数 • 子对象类的构造函数 • 派生类的构造函数 C++语言基础 • 派生类构造函数格式 <派生类名>(<派生类构造函数总参数表>): <基类构造函数>(<参数表1>),<子对象名>(<参数表2>) { <派生类中数据成员初始化> } • 派生类构造函数的调用顺序
C++语言基础 class A {public: A() {a=0;cout<<“A's default constructor called.\n";} A(int i) {a=i;cout<<“A’s constructor called.\n";} …… private: int a; }; class B: publicA {public: B() {b=0;cout<<“B’s default constructor called.\n”; } B(int i, int j, int k):A(i), aa(j) {b=k;cout<<B’s constructor called.\n”;} …… private: int b; A aa; //子对象 }; [例7.4] 隐式调用基类A和子对象aa的缺省构造函数 ②子对象类构造函数 ①基类构造函数
A’s default Constructor called. A’s default Constructor called. 对象数组 B’s default Constructor called. A’s default Constructor called. A’s default Constructor called. B’s default Constructor called. A’s constructor called. A’s constructor called. 构造 匿名对象 B’s constructor called. B’s destructor called. 析构 A’s destructor called. A’s destructor called. 1, 5, 2 3, 7, 4 B’s destructor called. A’s destructor called. 析构 析构bb[1] A’s destructor called. C++语言基础 [例7.4] void main() { B bb[2]; //对象数组 bb[0]=B(1,2,5); bb[1]=B(3,4,7); for(int i=0; i<2; i++) bb[i].Print(); } ……(同上) ……(同上)
C++语言基础 [例7.4] 派生类B的构造函数 B(int i,int j, int k): A(i), aa(j) { b=k; cout<<"B's constructor called.\n"; } 派生类中数据成员的初始化 还可写成: B(int i,int j, int k): A(i), aa(j), b(k) { cout<<"B's constructor called.\n"; }
C++语言基础 2. 析构函数 • 析构函数不能被继承! • 执行派生类的析构函数时,基类的析构函数也被调用; • 顺序与执行构造函数的顺序相反: • 先执行派生类的析构函数,再执行基类的析构函数。
基类构造函数使用一个或多个参数时,派生类必须定义构造函数,提供将参数传递给基类构造函数的途径;基类构造函数使用一个或多个参数时,派生类必须定义构造函数,提供将参数传递给基类构造函数的途径; (有时派生类构造函数体为空,仅起到参数传递的作用) C++语言基础 3. 使用派生类构造函数注意: • 基类中有缺省构造函数或没定义构造函数时,派生类构造函数的定义中可以省略对基类构造函数的调用;
隐式调用基类A的缺省构造函数 C++语言基础 [例7.6-1] class A {public: A() {a=0;} //基类缺省构造函数 A(int i) {a=i;} void print() {cout<<a<<",";} private: int a; }; class B: public A {public: B() {b1=b2=0;} B(int i) {b1=i; b2=0;} B(int i, int j, int k): A(i), b1(j), b2(k) { } void print() {A::print(); cout<<b1<<","<<b2<<endl;} private: int b1, b2; }; void main() { B d1; B d2(5); B d3(4,5,6); d1.print() ; d2.print(); d3.print(); } 0,0,0 0,5,0 4,5,6
class B {public: B(int i, int j) {b1=i;b2=j;} //基类构造函数 ...... private: int b1, b2; }; class D: public B {public: D(int i, int j, int k, int l, int m): B(i,j), bb(k,l) { d1=m; } ...... private: int d1; B bb;//子对象 }; 参数传递 C++语言基础 [例7.6-2]
C++语言基础 三、子类型化和类型适应 • 子类型化 • 子类型 • 特定的类型B,当且仅当它至少提供了类A的行为,则称“类B是类A的子类型”; • 子类型关系不可逆、不对称 • 公有继承实现子类型化 • 类B公有继承了类A,则类B是类A的一个子类型;\\对照子对象理解子类型 • 对A类对象操作的函数可用于操作B类对象。
C++语言基础 [例] class A {public: void Print() const {cout<<“A::Print() called.\n”;} }; class B: public A {public: void f() {…} }; void f1(const A &r) { r.Print(); } void main() { B b; f1(b ); } 类B是类A的子类型 对A类对象操作的函数,可用于操作子类型B的对象; 派生类对象可用于基类对象所能使用的场合。
C++语言基础 2. 类型适应 • “B类型适应A类型” • B类型的对象能够用于A类型的对象所能使用的场合 • 子类型化与类型适应一致 • B类是A类的子类型,则B类型适应于A类型(对象,指针,引用) • 减轻程序编码负担 • 若一个函数可以用于某类型的对象,则它也可用于该类型的各个子类型的对象,不必为处理子类型的对象去重载函数。
C++语言基础 [例7.7] class A {public: A() {a=0;} A(int i) {a=i;} void print() {cout<<a<<endl;} int geta() {return a;} private: int a; }; class B: public A {public: B() {b=0;} B(int i,int j):A(i),b(j) {} void print() {A::print(); cout<<b<<endl;} private: int b; }; 类B是类A的子类型 B类型适应于A类型; 可将B类对象赋值给A类对象、B类指针赋值给A类指针。
aa=bb --将B类对象赋值给A类对象; --将对象bb的“基类子对象”赋值给aa; pa=pb --将B类指针赋值给A类指针; --pa和pb有相同的指向, pa指向B类对象中的“基类子对象”, 但pa无法访问类B中定义的成员 C++语言基础 [例7.7] void fun(A &d) { cout<<d.geta ()*10<<endl;} void main() { B bb(9, 5); A aa(5); aa=bb; aa.print(); A *pa=new A(8); B *pb=new B(1,2); pa=pb; pa->print(); fun(bb); } 9 1 90
C++语言基础 §7.3 多继承 • 派生类有多个基类,与每个基类的关系仍是单继承 一、多继承的定义 二、多继承的构造函数 三、二义性问题
基类A 基类B 派生类C C++语言基础 一、多继承的定义 class <派生类名>: <继承方式1><基类名1>,<继承方式2><基类名2>… { <派生类类体> }; class A {……}; class B {……}; class C: public A, public B {……}; [例]
C++语言基础 二、多继承的构造函数 <派生类名>(<总参数表>): <基类名1>(<参数表2>), <基类名2 > (<参数表2>), …<子对象名 >(<参数表n>), … { <派生类构造函数体> }; • 派生类构造函数执行顺序: • 所有基类构造函数 • 定义派生类时所指定的各基类顺序,与成员初始化列表中的各项顺序无关。 • 子对象类构造函数 • 派生类构造函数
C++语言基础 class B1 {public: B1(int i) {b1=i;cout<<“Constructor B1."<<i<<endl;} void print() {cout<<b1<<endl;} private: int b1; }; class B2 {public: B2(int i) {b2=i;cout<<“Constructor B2."<<i<<endl;} void print() {cout<<b2<<endl;} private: int b2; }; class B3 {public: B3(int i) {b3=i;cout<<“Constructor B3."<<i<<endl;} int getb3() {return b3;} private: int b3; }; [例7.8]
class A: public B2, public B1 {public: A(int i, int j, int k , int l):B1(i), B2(j), bb(k) //派生类构造函数 { a=l; cout<<"Contructor A."<<l<<endl; } void print() { B1::print(); B2::print(); //限定作用域, 解决冲突 cout<<a<<","<<bb.getb3()<<endl; } private: int a; B3 bb; //子对象 }; Constructor B2. 2 Constructor B1. 1 Constructor B3. 3 Constructor A. 4 1 2 4, 3 C++语言基础 [例7.8] void main() { A aa(1, 2, 3, 4); aa.print(); }
class A { public: void f(); }; class B { public: void f(); void g(); }; class C: public A, public B { public: void g(); void h(); }; 例: 无公共基类的多继承 A{ f( ) } B{ f( ), g( ) } C{ g( ), h( ) } C++语言基础 三、二义性问题 多继承时,在派生类中对基类某个成员的访问不唯一
无公共基类的多继承 A{ f( ) } B{ f( ), g( ) } C{ g( ), h( ) } C++语言基础 • C c1; • c1.f( ); (二义性) • 改为: c1.B::f( ); 或 c1.A::f( ); • 在类C中定义一个同名成员函数 f( ),根据需要决定调用 A::f( )还是B::f( ) • void C::h( ) { f( );} (二义性) • 该为:A::f( )或B::f( ) • c1.g( ) 无二义性 • 派生类中成员支配基类同名成员。
class A {public: int 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; }; 例: A{a} A{a} 有公共基类的多继承 B1{b1} B2{b2} C{f( ), c} C++语言基础 C c1; c1.a; c1.A::a;(二义性) c1.B1::a; c1.B2::a; ( )
C++语言基础 class B2: public A {public: B2(int i, int j): A(i) { b2=j; cout<<"con.B2\n";} void print() { A::print(); cout<<b2<<endl;} ~B2() {cout<<"des.B2\n";} private: int b2; }; [例7.9] class A {public: A(int i) {a=i; cout<<“con.A\n";} void print() {cout<<a<<endl;} ~A() {cout<<"des.A\n";} private: int a; }; class B1: public A {public: B1(int i, int j): A(i) { b1=j; cout<<"con.B1\n";} void print() {A::print(); cout<<b1<<endl;} ~B1() {cout<<"des.B1\n";} private: int b1; };
C++语言基础 con. A con.B1 con.A con.B2 con.C 1 2 3 4 5 des.C des.B2 des.A des.B1 des.A [例7.9] class C: public B1, public B2 {public: C(int i, int j, int k, int l, int m): B1(i,j), B2(k,l), c(m) { cout<<"con.C\n";} void print() { B1::print(); B2::print(); cout<<c<<endl; } ~C() {cout<<"des.C\n";} private: int c; }; void main() { C c1(1,2, 3,4, 5); c1.print(); } 创建类C的对象时, 类A的构造函数调用两次: 由类B1和类B2的构造函数分别调用
说明 virtual <继承方式> <基类名> (定义派生类时,写在派生类名的后面) C++语言基础 §7.4 虚基类 一、虚基类的引入和说明 • 引入 • 为了使公共基类在派生类中只产生一个基类子对象,克服二义性。
例: class A { public: void f(); protected: int a; }; class B:virtual publicA { protected: int b;}; class C: virtual public A { protected: int c;}; class D: public B, publicC { public: int g(); private: int d; }; A{f( ), a} B{b} C{c} D{g( ), d} 不同继承路径的虚基类子对象被合并成一个子对象 (消除二义性) C++语言基础 D n; n.f( ); ( ) void D::g( ) {f( );} ( )