第 7 章 继承性和派生类
DESCRIPTION
C++ 语言基础. 第 7 章 继承性和派生类. C + + 最重要的性能之一是代码重用:通过“继承”机制,创建新类来重用代码,扩充和完善旧的程序以适应新的需求; “继承”提供了无限重复利用资源的一种途径。. § 7.1 基类和派生类 § 7.2 单继承 § 7.3 多继承 § 7.4 虚基类. 基类:已存在的用来派生新类的类(父类). 派生类:由已存在的类派生出的新类(子类). 单继承:从一个基类派生的继承. 多继承:从多个基类派生的继承. 单继承. 多继承. A. X. Y. Z. B. C. C++ 语言基础. § 7.1 基类和派生类. - PowerPoint PPT PresentationTRANSCRIPT
1
C++ 语言基础
第 7章 继承性和派生类§§7.17.1 基类和派生类基类和派生类§§7.27.2 单继承§§7.37.3 多继承§§7.47.4 虚基类
C + + 最重要的性能之一是代码重用:通过“继承”机制,创建新类来重用代码,扩充和完善旧的程序以适应新的需求; “继承”提供了无限重复利用资源的一种途径。
2
A
B C
单继承X Y
Z
多继承
C++ 语言基础
§§7.1 7.1 基类和派生类基类和派生类基类:已存在的用来派生新类的类(父类)派生类:由已存在的类派生出的新类(子类)单继承:从一个基类派生的继承多继承:从多个基类派生的继承
3
一、派生类的定义格式class < 派生类名 > : < 继承方式 > < 基类名 >{ < 派生类新定义成员 >} ;
class < 派生类名 > : < 继承方式 1>< 基类名 1>, < 继承方式 2>< 基类名 2>…{ < 派生类新定义成员 >};
继承方式关键字: public 、 protected 、 private( 缺省 )
单继承 :
多继承:
C++ 语言基础
4
二、派生类的三种继承方式1. 公有继承 public– 基类的公有成员和保护成员在派生类中保持原有状态;2. 私有继承 private– 基类的公有成员和保护成员作为派生类的私有成员;3. 保护继承 protected– 基类的公有和保护成员作为派生类的保护成员 ;
C++ 语言基础
5
不同继承方式下基类和派生类特性C++ 语言基础
比较:
6
不同继承方式下的垂直访问例//(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;};
7
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;};
8
int B::MaxB(){
int tem;//cout<<intValAPrivaX<<intValAPrivaX<<endl; // Attempt to plumb visit private member of calss A, it is an errortem=MaxA(); // Plumb visit public functin of class Areturn tem>=intValBPriva?tem:intValBPriva;
}
int B::MinB(){
int tem;tem=MinA(); // Plumb visit protected functin of calss Areturn tem<=intValBPriva?tem:intValBPriva;
}
9
class C: public B{ public: int MaxC();
int intValCPubl; void display();protected:
int MinC();int intValCprot;
private:int intValCPriva;
};
10
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;
}
11
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;
12
//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;
}
13
C++ 语言基础
1. 基类对象只能访问基类中的 public 成员;2. 任何一种继承方式,派生类的成员函数都可以访问基类的 public 和 protected 成员;3. 只有公有继承时,派生类对象才可以访问基类的 pu
blic 成员;“ 可见性”即“可访问性”!
结论:
水平访问:派生类的对象对基类的访问垂直访问:派生类的派生类对基类的访问
14
C++ 语言基础
公有继承 public :水平访问和垂直访问对基类中的公有成员不受限制私有继承 private :水平访问和垂直访问对基类中公有成员均无法施行保护继承 protected :水平访问同于私有继承,垂直访问同于公有继承
结论: 垂直访问水平访问
15
三、基类与派生类的关系C++ 语言基础
1.派生类是基类的具体化 基类是派生类的抽象,派生类通过增加行为和属性将抽象类变为某种有用的类型;2.派生类是基类定义的延续 先定义一个抽象基类, 其中有些操作并未实现,然后定义非抽象的派生类,实现抽象基类中定义的操作;3.派生类是基类的组合
多继承时,派生类是所有基类行为的组合。
16
利用“继承”创建新类时 : ——说明新类与原有类的区别;
C++ 语言基础
派生类与基类的区分方法 : ——添加数据成员和成员函数; 继承使大量原有的程序代码可以复用 类是“可复用的软件构件”
17
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++ 语言基础
Point
Line Ellipse
派生类 1 派生类 2
基类[ 例 ]
18
§§7.2 7.2 单继承C++ 语言基础
一、成员访问权限的控制二、构造函数和析构函数三、子类型化和类型适应
每个派生类只能有一个基类(一个基类可以有多个派生类)
19
一、成员访问权限的控制class A
{ public:
void f1();
protected:
int j1;
private:
int i1;
};
[ 例 7.1]
C++ 语言基础
class B: public A
{ public:
void f2();
protected:
int j2;
private:
int i2;
};
class C: public B
{ public:
void f3();
};
A
B
C
( 基类和派生类是相对的 )
20
C++ 语言基础
[ 例 7.1] 公有继承下
21
C++ 语言基础
2. 私有继承– 派生类的成员函数可以访问基类中 public 和 pro
tected 成员 ;– 派生类的对象不可以访问基类中的任何成员 ;
3. 保护继承– 派生类的成员函数可以访问基类中 public 和 pro
tected 成员 ;– 派生类的对象不可以访问基类中的任何成员 ;
1. 公有继承– 派生类的成员函数可以访问基类中 public 和 prot
ected 成员 ;– 派生类的对象可以访问基类中的 public 成员 ;
22
C++ 语言基础
任何一种继承方式,派生类的成员函数都可以访问基类的 public 和 protected 成员;结论:
只有公有继承,派生类对象才可以访问基类的 public 成员;
23
1. 继承方式?2. A::f ?
4. 改为 class B: public A ?
5. 改为 class B: protected A?
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();}
6h
6gh
3. 哪个语句有错误?去掉该语句后运行结果:
将 A 类中的公有成员说明为 B 类中的公有成员
私有
错误
24
编译错误信息、如何修改?将 name 的访问权限改为: protected
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 ();
}
25
C++ 语言基础
二、构造函数和析构函数
“ 基类子对象”:–派生类对象中由基类中说明的数据成员和成员函数构成的封装体 ;–由基类中的构造函数初始化 ;
功能:创建对象时,用给定值将对象初始化 ;派生类中说明的数据成员基类中说明的数据成员派生类对象数据结构
1. 构造函数
构造函数不能被继承! 派生类构造函数必须通过调用基类的构造函数来初始化“基类子对象”。
26
C++ 语言基础
< 派生类名 >(< 派生类构造函数总参数表 >): < 基类构造函数 >(< 参数表 1>),< 子对象名 >(< 参数表 2>){ < 派生类中数据成员初始化 >}
1) 派生类构造函数格式
2) 派生类构造函数的调用顺序
iii. 派生类的构造函数
i. 基类的构造函数ii. 子对象类的构造函数
27
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: public A{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 的缺省构造函数
28
C++ 语言基础
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();
}
[ 例 7.4]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.
……( 同上 )
1, 5, 23, 7, 4
……( 同上 )
对象数组
匿名对象
析构
A’s constructor called.A’s constructor called.B’s constructor called.
构造B’s destructor called.A’s destructor called.A’s destructor called.
析构
B’s destructor called.A’s destructor called.A’s destructor called.
析构 bb[1]
29
B(int i,int j, int k): A(i), aa(j), b(k)
{
cout<<"B's constructor called.\n";
}
C++ 语言基础
B(int i,int j, int k): A(i), aa(j)
{
b=k;
cout<<"B's constructor called.\n";
}
派生类 B 的构造函数
还可写成:
[ 例 7.4]
派生类中数据成员的初始化
30
C++ 语言基础
2. 析构函数• 析构函数不能被继承 !• 执行派生类的析构函数时 ,基类的析构函数也被调用;• 顺序与执行构造函数的顺序相反:• 先执行派生类的析构函数 ,再执行基类的析构函数。
31
C++ 语言基础
3. 使用派生类构造函数注意:1) 基类中有缺省构造函数或没定义构造函数时,派生类构造函数的定义中可以省略对基类构造函数的调用;2) 基类构造函数使用一个或多个参数时,派生类必须定义构造函数,提供将参数传递给基类构造函数的途径;
(有时派生类构造函数体为空,仅起到参数传递的作用)
32
0,0,0
0,5,0
4,5,6
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;};
隐式调用基类 A 的缺省构造函数
void main()
{ B d1;
B d2(5);
B d3(4,5,6);
d1.print() ;
d2.print();
d3.print();
}
33
C++ 语言基础[ 例 7.6-2] 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; // 子对象};
参数传递
34
C++ 语言基础
三、子类型化和类型适应1) 子类型 特定的类型 B ,当且仅当它至少提供了类 A 的行为,则称“类 B 是类 A 的子类型”;
1. 子类型化
3) 公有继承实现子类型化– 类 B 公有继承了类 A ,则类 B 是类 A 的一个子类型; \\对照子对象理解子类型– 对 A 类对象操作的函数可用于操作 B 类对象。
2) 子类型关系不可逆、不对称
35
class A{public: void Print() const {cout<<“A::Print() called.\n”;}};
class B: public A
{public:
void f() {…}
};
对 A 类对象操作的函数,可用于操作子类型 B 的对象;
C++ 语言基础
void f1(const A &r){ r.Print(); }void main(){ B b; f1( b ); }
[ 例 ]
类 B 是类 A 的子类型 派生类对象可用于基类对象所能使用的场合。
36
C++ 语言基础
2. 类型适应1) “B 类型适应 A 类型”– B 类型的对象能够用于 A 类型的对象所能使用的场合2) 子类型化与类型适应一致– B 类是 A 类的子类型,则 B 类型适应于 A 类型(对象 , 指针 ,引用)3) 减轻程序编码负担– 若一个函数可以用于某类型的对象,则它也可用于该类型的各个子类型的对象,不必为处理子类型的对象去重载函数。
37
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;};
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;};
类 B 是类 A 的子类型 可将 B 类对象赋值给 A 类对象、 B 类指针赋值给 A 类指针。 B 类型适应于 A 类型;
38
C++ 语言基础
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 );}
[ 例 7.7]
91
90
-- 将对象 bb 的“基类子对象”赋值给 aa;
aa=bb-- 将 B 类对象赋值给 A 类对象 ;
--pa 和 pb 有相同的指向 , pa 指向 B类对象中的“基类子对象” , 但 pa无法访问类 B 中定义的成员
pa=pb-- 将 B 类指针赋值给 A 类指针 ;
39
C++ 语言基础
§§7.3 7.3 多继承 派生类有多个基类,与每个基类的关系仍是单继承
一、多继承的定义二、多继承的构造函数三、二义性问题
40
class A
{……};
class B
{……};
class C: public A, public B
{……};
基类A 基类B
派生类C
C++ 语言基础
一、多继承的定义class < 派生类名 >: < 继承方式 1>< 基类名 1> , < 继承方式 2>< 基类名 2>…{ < 派生类类体 >} ;
[ 例 ]
41
C++ 语言基础二、多继承的构造函数
派生类构造函数执行顺序:1) 所有基类构造函数 定义派生类时所指定的各基类顺序,与成员初始化列表中的各项顺序无关。2) 子对象类构造函数3) 派生类构造函数
< 派生类名 >(< 总参数表 >): < 基类名 1>(< 参数表 2>), < 基类名 2 > (< 参数表 2>), …< 子对象名 >(< 参数表 n>), …{ < 派生类构造函数体 >};
42
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]
43
C++ 语言基础
void main(){ A aa(1, 2, 3, 4); aa.print();}
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; // 子对象};
[ 例 7.8]
Constructor B2. 2Constructor B1. 1Constructor B3. 3Constructor A. 4124, 3
44
C++ 语言基础三、二义性问题
多继承时,在派生类中对基类某个成员的访问不唯一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( ) }
无公共基类的多继承
45
1. C c1;
c1.f( ); (二义性)1) 改为: c1.B::f( ); 或 c1.A::f( );
2) 在类 C 中定义一个同名成员函数 f( ) ,根据需要决定调用 A::f( ) 还是 B::f( )
2. void C::h( ) { f( );} (二义性) 该为: A::f( ) 或 B::f( )
3. c1.g( ) 无二义性 派生类中成员支配基类同名成员。
C++ 语言基础
A{ f( ) } B{ f( ), g( ) }
C{ g( ), h( ) }
无公共基类的多继承
46
C c1;
c1.a; c1.A::a; (二义性)c1.B1::a; c1.B2::a; ( )
C++ 语言基础
A{a} A{a}
B1{b1} B2{b2}
C{f( ), c}
有公共基类的多继承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;};
例:
47
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; };
C++ 语言基础
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;};
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;};
[ 例 7.9]
48
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
创建类 C 的对象时 , 类 A的构造函数调用两次 : 由类B1 和类 B2 的构造函数分别调用
C++ 语言基础[ 例 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();}
49
C++ 语言基础
§§7.4 7.4 虚基类一、虚基类的引入和说明1. 引入 为了使公共基类在派生类中只产生一个基类子对象,克服二义性。2. 说明
virtual < 继承方式 > < 基类名 >(定义派生类时,写在派生类名的后面)
50
D n;
n.f( ); ( )void D::g( )
{f( );} ( )
C++ 语言基础例:class A
{ public: void f(); protected: 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;};
A{f( ), a}
B{b} C{c}
D{g( ), d}
不同继承路径的虚基类子对象被合并成一个子对象(消除二义性)
51
C++ 语言基础二、虚基类的构造函数
1. 为保证“虚基类子对象”只被初始化一次,虚基类的构造函数必须只被调用一次;2. “最派生类”:建立对象时所指定的类 由“最派生类”的构造函数调用虚基类的构造函数,完成虚基类子对象的初始化;3. 从虚基类直接或间接继承的所有派生类构造函数都要列出对虚基类构造函数的调用,但只有最派生类构造函数中的调用被执行,其基类中列出的对虚基类构造函数的调用在执行中被忽略!
A{A( ) ,~A( )}
B{B( )} C{C( )}
D{D( )}
虚基类
最派生类
派生类构造函数中要调用基类构造函数; 派生类对象中只有一个“虚基类子对象”;
52
class D: public B, public C
{public:
D(const char *s1, const char *s2, const char *s3, const char *s4)
:B(s1,s2), C(s1,s3), A(s1)
{cout<<s4<<endl;}
};
C++ 语言基础class A{public: A(const char *s) {cout<<s<<endl;} ~A(){}};class B: virtual public A{public: B(const char *s1, const char *s2):A(s1) {cout<<s2<<endl;}};
class C: virtual public A
{public:
C(const char *s1, const char*s2):A(s1)
{cout<<s2<<endl;}
};
[ 例 7.10]
A{A( ) ,~A( )}
B{B( )} C{C( )}
D{D( )}
虚基类
最派生类
53
class A
class B
class C
class D
成员初始化列表中出现对虚基类和非虚基类构造函数的调用,则虚基类的构造函数先执行。
C++ 语言基础
void main( ){ D *ptr=new D("class A", "class B", "class C", "class D"); delete ptr;}
[ 例 7.10]
A{A( ) ,~A( )}
B{B( )} C{C( )}
D{D( )}
虚基类
最派生类
54
C++ 语言基础
1. 编程实现下列要求:1) 定义基类 Animal ,私有成员变量 age ;2) 定义派生类 dog ,在其成员函数“ SetAge” 中直接为 age 赋值,观察出现什么问题,如何修改?3) 完成 Animal 类与 dog 类的构造函数和析构函数,观察基类和派生类的构造函数和析构函数的执行情况。
实 验 四实 验 四
55
C++ 语言基础
实 验 四实 验 四2. 编写一个能输入、输出学生和教师相关数据的程序:
• 学生数据: 学号、姓名、性别、年龄、班号、总成绩;• 教师数据: 职工号、姓名、性别、年龄、部门、职称;要求声明一个 person 类,作为学生类和教师类的基类。
56
C++ 语言基础
57
class Person
{public:
Person(char *na, char se, int ag) {……}
void print() {……}
private: ……
};
基类
class Student : public Person
{public:
Student(char *na, char se, int ag, …): Person(na,se,ag) {……}
void print() {……}
private:
……
};
派生类
C++ 语言基础