第七章 组合与继承
创建新类时,不必每次都从头重新做起,可以利用已经存在的类,创建新的类
利用已经存在的类创建新类,有两种方法
.组合,继承本章主要内容
1,组合创建新类的方法
2,继承创建新类的方法 ——掌握
3,继承方式、访问控制 ——理解、掌握
4,构造函数与析构函数 ——掌握
5,继承成员的调整 ——掌握
6,多重继承、虚拟继承(虚基类继承)
15:13:38
§ 1 组 合 (composition)
新类由已经存在的类的对象组合而成,原有类的对象是新类的成员对象例:组合
//useful.h
class X{
int i;
public:
X(){i=0;}
void set(int ii){i=ii;}
int read()const{return i;}
int permute()
{return i=i*47;}
};
//composition.h
#include,useful.h”
class Y{
int i;
public:
X xObj;
Y(){i=0;}
void f(int ii){i=ii;}
int g()const{return i;}
};
void main(){
Y yObj;
yObj.f(47);
yObj.xObj.set(37);
}
X是已经存在的类,Y是由 X
类的对象组合而成的新类
Y是外围类,xObj是 Y类的公有成员对象
15:13:38
成员变量(对象)一般应为 private的
//composition.h
#include,useful.h”
class Y{
int i;
X xObj;
public:
Y(){i=0;}
void f(int ii){i=ii;}
int g()const{return i;}
void permute(){xObj.permute();}
};
//main.cpp
#include,composition.h”
void main(){
Y yObj;
yObj.f(47);
yObj.permute();
}
X是已经存在的类,Y是由 X
类的对象组合而成的新类
Y是外围类,xObj是 Y类的私有成员对象
15:13:38
§ 2 继承 (Inheritance)
描述类与类之间的,is a kind of”的关系,是 C++中实现代码重用 (code reuse)的重要机制
创建新类时不必从头开始,而把新类作为现有类的扩充或把新类作为是原有类的一种特殊情况父类、基类、超类类 X
继 承 增 加部 分 部 分子类、派生类类 Y
原有的类新类
类 Y从类 X扩充而来,则称类 Y继承类 X或类 Y从类 X派生
类 X称做类 Y的 基类 (Base Class),父类 (Parent class)
或 超类 (Super class)
类 Y称做类 X的 派生类 (Derived class:相对于基类 )或 子类 (Child class:相对于父类或 Sub Class:相对于超类 )
15:13:38
继承的语法
class 新类名:继承方式 已有类的类名
[,继承方式 已有类名的类名 2[,… ]]
{
private:
扩充的私有成员
protected:
扩充的保护成员
public:
扩充的公有成员
};
15:13:38
例:类的继承
class vehicle{
int wheels;
float weight;
float loading;
public:
vehicle(int wheels,float weight,float loading);
int get_wheels();
float get_weight();
float get_loading();
};
class Car:public vehicle{
int passenger_load;
public:
Car(int in_wheels,float in_weight,float in_loading,
int peoples=4);
int passengers();
};
vehicle类是基类或父类,car类是派生类或子类
Car类从其基类 vehicle类中继承了所有的成员变量和成员函数
( 构造函数与析构函数除外 )
void main(){
Car car(4,1000,1000,4);
cout << car.passengers()
<< endl;
cout << car.get_wheels()
<< car.get_weight()
<< car.get_loading()
<< endl;
}
15:13:38
说明
派生类继承了基类中除构造函数,析构函数和重载的,=”
运算符对应的函数以外的所有成员
在派生类中访问基类成员时,仍要受基类访问控制的限制,
派生类新添加的成员函数中不能直接访问基类的 private
成员,但可访问基类的 public成员和 protected成员例:在派生类中访问基类成员
//A.h
class A{
int x;
void f();
protected:
int y;
void g();
public:
int z;
void h();
};
#include,A.h”
class B:public A{
public:
void func(){
x=0;f(); //错,基类的私有成员
y=10;g(); //正确,基类的保护成员
z=10;h(); //正确,基类的公有成员
}
};
15:13:38
说明(续)
继承方式决定了从基类继承而来的成员在派生类中的访问控制权限,用关键字 public,private和 protected表示,
分别为公有继承,私有继承和保护继承
★ 私有继承使基类的保护成员和公有成员成为派生类的私有成员
★ 保护继承使基类的保护成员和公有成员成为派生类的保护成员
★ 公有继承使基类的保护成员和公有成员仍然成为派生类的保护成员和公有成员
C++中默认的继承方式是私有继承
一般多使用公有继承,故应重点掌握公有继承
15:13:38
公有继承基类的公有成员和保护成员成为派生类的公有成员和保护成员
#include <iostream>
using namespace std;
class A{
protected:
int x,y;
public:
void get_XY(){
cout <<,Enter two numbers of x,y:”;
cin >> x >> y;
}
void put_XY()
{ cout <<,x=,<< x <<,,y=,<< y <<?\n?;}
};
15:13:38
例:公有继承 (续 )
class B:public A{
protected:
int s;
public:
int get_S(){return s;}
void make_S(){s=x*y;} //x,y都是为 B的保护成员
};
class C:public B{
protected:
int h,v;
public:
void get_H(){cout <<,Enter H:”; cin >> h;}
int get_V(){return v;}
void make_V(){
make_S(); //s=x*y; //s,x,y都是 C的保护成员
v=get_S()*h; //v=x*y*h;
}
};
15:13:38
例:公有继承(续)
void main(){
A objA; B objB; C objC;
cout <<,it is object A:” << endl;
cin>>objA.x>>objA.y; //错,x和 y是 objA的保护成员
objA.get_XY(); objA.put_XY();
cout <<,it is object B:” << endl;
objB.get_XY(); //从类 A中继承而来的公有成员函数
objB.make_S(); //不能 objB.s=objB.x * objB.y;
//objB中的 x和 y是从基类 A中继承而来,
//是 B类的保护成员
cout <<,S=” << objB.get_S() << endl;
cout <<,it is object C:” << endl;
objC.get_XY();
objC.get_H();
objC.make_V();
cout <<,V=” << objC.get_V() << endl;
}
15:13:38
私有继承基类中的保护成员和公有成员全部成为派生类的私有成员
#include <iostream>
using namespace std;
class A{
protected:
int x,y;
public:
void get_XY()
{cout <<,Enter two numbers of x,y:”; cin>>x>>y;}
void put_XY()
{cout <<,x=,<< x <<,,y=,<< y << endl;}
};
15:13:38
class B:private A{
private:
int s;
public:
int get_S(){return s;}
void make_S(){
get_XY();//继承的 get_XY(),本类中成为私有成员函数
s=x*y; //继承而来的 x,y,本类的私有成员变量
}
};
void main(){
B objB;
cout <<,it is object B:” << endl;
objB.get_XY(); //错,get_XY()现成为 objB的私有成员
objB.make_S();
cout <<,S=,<< objB.get_S() << endl;
}
C++中默认的继承方式是私有继承,亦即此处也可写成
class B,A{
15:13:38
保护继承基类中的保护成员和公有成员成为派生类的保护成员说明(续)
继承时一个类可有一个或多个基类,称为 单一继承 和 多重继承
派生类又可以有自己的派生类,从而形成类层次例,公有继承
继承关系不可循环,所以类的层次通常表现为一棵树
基类的友元,构造函数,析构函数和重载的,=”运算符函数不能被派生类继承
15:13:38
§ 3 新类对象的存储组织
class X{
… //私有成员
public:
… //公有成员
protected:
… //保护成员
};
15:13:38
组合得到的新类的对象的存储结构
class Y{
X xObj;
private:
… // 新添加的私有成员
protected:
… // 新添加的保护成员
public:
… // 新添加的公有成员
};
Y objY;
新添加的成员
xObjobjY
15:13:38
派生类对象的存储组织最常用的是公有继承,故只考虑公有继承时的情况
class Y:public X{
… // 新添加的私有成员
protected:
… // 新添加的保护成员
public:
… // 新添加的公有成员
};
Y objY;
新添加的成员从基类 X中继承的成员objY
公有继承情况下,派生类对象的开始位置处是一份完整的基类继承成员,故 能使用基类对象的地方就能使用派生类对象
15:13:38
§ 4 新类的构造函数与析构函数
创建新类对象时,必须调用其相应的构造函数对其成员变量进行初始化
撤销新类对象时,必须调用其析构函数完成清理工作
通过组合得到新类的情况
对新类对象初始化时,必须要考虑如何对成员对象进行初始化
撤销新类对象时,必须考虑成员对象占据的资源如何释放
15:13:38
例:成员对象的初始化与撤销
class X{
int len;
char *v;
public:
X(char *str){
len=strlen(str);
v=new char[len+1];
strcpy(v,str);
}
~ X(){
delete []v;
}
};
class Y{
X objX;
int j;
public:
Y(char* str,int jj);
~ Y();
};
问题:
1.创建 Y类的对象时,其成员对象 objX该如何初始化?
2.撤销 Y类的对象时,如何撤销成员对象 objX?
{
Y objY(“张三,,10) ;

}
15:13:38
v和 len是 Y类的成 员对象
objX的私有成员变量,在 Y
类的构造函数中不可见,所以无法通过
objX.v=new char[sz+1];
objX.len=sz;
的形式给其赋初值
对 objX的初始化只能通过 Y
类的构造函数的初始化列表调用 X类的构造函数完成问题:
能否将 Y类的构造函数改为如下形式?
Y(char *str,int jj){
int sz=strlen(str);
objX.v=new char[sz+1];
objX.len=sz;
j=jj;
}
成员对象的初始化与撤销
对成员对象的初始化只能通过调用成员对象自己的构造函数进行
通过新类构造函数的初始化列表调用成员对象的构造函数
Y::Y(char *str,int jj):objX(str){ j=jj; }
撤销 Y类的对象时撤销其成员对象 objX
撤销 Y类的对象时调用 Y类的析构函数,Y类的析构函数会自动调用 objX的析构函数,以释放 objX占据的资源
Y::~ Y(){ }
此处不能显式调用 objX的析构函数,由系统 自动调用
且先执行 Y类的析构函数,再调用 objX的析构函数
15:13:38
通过继承得到新类的情况
在派生类中访问从基类继承而来的成员时,要受到基类访问控制的限制,所以对派生类中的基类成员变量不能通过赋值的方式设置初值,而 只能通过基类自身的构造函数对这些成员变量进行初始化例:派生类中基类成员变量的初始化
class X{
int i;
public:
X(){i=0;}
X(int ii)
{i=ii;}
};
class Y:public X{
int j;
public:
Y(){j = 0; i = 0; }
Y(int ii,int jj)
{j = jj; i = ii; }
};
基类的私有成员,此两处不能访问
15:13:38
通过 派生类的构造函数的初始化列表 调用基类的构造函数,
实现对派生类中的基类成员变量的初始化
class X{
int i;
public:
X(){i=0;}
X(int ii)
{i=ii;}
};
class Y:public X{
int j;
public:
Y():X(0){j = 0;}
Y(int ii,int jj):X(ii){j=jj;}
Y(int jj):X(){j = jj;}
};
此处调用基类 不带参的构造函数,不需要传递参数,故 可省略对 X()的调用亦即此构造函数也可按如下方式实现
Y(int jj){j = jj;}
基类提供了多个构造函数时,调用与派生类构造函数的初始化列表中出现的参数类型,参数数目均能匹配的一个先调用基类的构造函数,然后执行派生类的构造函数体
实现派生类构造函数的一般形式为派生类名,:派生类名 (形参列表 ):基类名 (实参列表 )
{ /*派生类构造函数体 */ }
例,派生类中基类成员变量的初始化
15:13:38
基类的析构函数
撤销派生类对象时,需释放派生类中基类对象占据的资源
撤销派生类对象时,会调用派生类的析构函数,由派生类的析构函数 自动调用 基类的析构函数
先执行派生类的析构函数,然后再调用基类的析构函数例,基类的析构函数
class X{
int i;
public:
~ X(){
cout<<,X()” <<endl;
}
};
class Y:public X{
int j;
public:
~ Y(){
cout <<,Y()” <<endl;
}
};
此处不能显式调用基类 X的析构函数,由系统 自动调用
15:13:38
组合与继承相结合的情况新类 Y从某个类 X派生得到,且类 Y中又包含了另一个类 Z的对象,该对象做新类的成员变量
创建类 Y的对象时调用类 Y的构造函数,由类 Y的构造函数调用基类 X的构造函数与成员对象所属的类 Z的构造函数
先调用基类的构造函数,再调用成员对象的构造函数,
然后才执行新类的构造函数体
撤销派生类 Y的对象时,调用类 Y的析构函数,由类 Y的析构函数调用成员对象和基类的析构函数
析构函数的调用顺序与构造函数的调用顺序完全相反例,组合与继承相结合
15:13:38
§ 5 继承成员的调整
从基类继承到派生类中的成员,不满足要求时,需要对其进行调整
常用的调整方式主要有
.成员函数的重载
.成员的重新定义
.用访问声明恢复访问控制
15:13:38
成员函数的重载派生类中定义了和基类中成员函数同名的函数,但与基类中同名函数的参数数目或参数类型不同 —— overload
例:成员函数的重载
class Base
{
public:
void f(){cout << "Base::f()" << endl;}
};
class Derived:public Base
{
public:
void f(int x){cout << "Derived::f()" << endl;}
};
15:13:38
void main()
{
Derived dObj;
dObj.f(10);
dObj.f();
}
派生类中定义了与基类中同名的成员函数,将会屏闭派生类中从基类继承而来的同名成员函数的作用,导致从基类继承而来的该同名成员函数在派生类外不可用需要调用派生类中被屏闭的基类成员函数时,可以使用作用范围解析的方式
void main()
{
Derived dObj;
dObj.Base::f(); //正确
}
//错误
//正确,调用派生类中定义的函数 f(int)
15:13:38
成员的重新定义:以成员函数的重新定义为主派生类中定义了与基类中同名的成员函数,且参数类型,
参数数目相同 —— override
例:成员函数的重新定义
class Base
{
public:
void f(int x){cout << "Base::f()" << endl;}
};
class Derived:public Base
{
public:
void f(int x){cout <<,Derived::f()" << endl;}
};
15:13:38
void main()
{
Derived dObj;
dObj.f(10); // 调用哪个类中的 f()?
}
需要调用派生类中被屏闭的基类成员函数时,可以使用作用范围解析的方式
void main()
{
Derived dObj;
dObj.Base::f(10); //正确
}
派生类中定义了与基类中同名的成员函数,将会屏闭派生类中基类同名成员函数的作用,导致派生类中基类的该同名成员函数在派生类外不可用
15:13:38
用访问声明恢复访问控制
私有继承使基类中的保护成员和公有成员成为派生类的私有成员;保护继承使基类成员中的保护成员和公有成员成为派生类的保护成员
这两种继承方式均使得基类中的公有成员继承到派生类中以后只能在派生类中被访问,而无法在派生类外使用
需在派生类外访问这些成员时,可以 在派生类的公有部分用访问声明 的方法将其恢复为派生类的公有成员
访问声明的一般形式为基类名,:成员名;
15:13:38
例,访问声明
class B:private A{
int s;
public:
int get_S(){return s;}
void make_S(){
get_XY();//继承的 get_XY(),本类中成为私有成员
s=x*y; //继承而来的 x,y,本类的私有成员变量
}
A::get_XY;
};
void main(){
B objB;
cout <<,it is object B:” << endl;
objB.get_XY(); //对,在类 B的公有部分进行了访问声明
objB.make_S();
cout <<,S=,<< objB.get_S() << endl;
}
15:13:38
对访问声明的说明:
访问声明仅调整名字的访问控制权限,若被声明的是成员变量,只能声明其名字,不能声明其类型;若被声明的是成员函数,只能声明其函数名,而不能带有参数返回值类型
访问声明只能将从基类继承而来的成员的访问控制恢复到其在基类中的访问控制
访问声明恢复从基类继承而来的所有同名成员的访问控制
class X{
public:
void f(){}
void f(int){}
};
class Y:private X{
public:
X::f;
};
此处访问声明对从基类继承而来的两个重载函数都起作用?基类中同名的成员处于不同的访问控制时,派生类中不能用访问声明
派生类中新添加了与基类中同名的成员时不能用访问声明
15:13:38
例,继承的应用实例考察一个点,圆,圆柱体的层次结构 。
首先定义点类 Point,然后从 Point类派生圆类 Circle( 为了方便,此处将圆看成是一种带半径的点 ),最后从 Circle
类派生圆柱体类 Cylinder,从而形成类层次 。
15:13:38
§ 6 多重继承
一个类从多个基类派生出来,即一个类有多个基类沙发 床沙发床
istream ostream
iostream
15:13:38
多重继承的语法
class DerivedClass:Access1 Base1,Access2 Base2,…
Accessn Basen
{ … };
例,多重继承
15:13:38
多重继承存在的问题,可能存在二义性
派生类的多个基类中有相同名字的成员,访问派生类中该成员时产生二义性例,派生类的多个基类有相同名字的成员解决办法:
.使用作用范围分解运算符,指明要使用的名字是从哪个基类继承而来的基类名,:成员名
.若产生二义性的名字是成员函数名时,在派生类中可重新定义一个同名的成员函数,使其屏闭从基类继承而来的同名成员函数
15:13:38
派生类有多个基类,这些基类又都从同一个基类派生而来,
在最下层的派生类中访问从最上层基类继承而来的成员时产生二义性例,家具、沙发、床和沙发床类
Furniture
GetWeight()
SetWeight()
Bed
Sleep()
Sofa
WatchTV()
SofaBed
FoldOut()
15:13:38
解决办法
在最下层派生类中访问从最上层基类继承而来的成员时,
使用上一级基类名加作用范围分解运算符,明确指出继承的路径例,作用范围解析
在继承路径上使用虚基类继承(又称虚拟继承)
例,虚基类继承
15:13:38