- 继承
一、基本概念
1 基类和派生类
1.1 只有两层的继承关系中,被继承者称为基类(父类);继承者称为派生类(子类)
1.2 多层继承关系中,类A通过类B间接派生出类C,则类A和类B称为类C的祖先类;类B和类C是类A的后代类;
一个类的祖先类包含了该类的基类和基类的祖先类;一个类的后代类包含了该类的派生类和派生类的后代类;
2 继承形式
单重继承、多重继承、重复继承;
3 继承成员的访问控制
问:派生类B继承基类A后,基类A中的数据成员和成员函数在类B中是否可以访问?其他类能否访问类B继承的类A的成员?
答:取决于由类A中成员的访问控制方式和类B在继承类A时指定的继承控制方式共同决定。
基类中private成员以完全封闭的形式存在,只容许本类内部成员及友元对其访问,因此不论继承控制方式如何,在派生类中都无法访问该成员。
基类中protected成员以半封闭的形式存在,容许派生类的成员对其访问,因此不论继承控制方式如何,在派生类中都可访问该成员;但这些成员在派生类中对外表现形式有些变化,如继承控制方式是private,基类中的protected成员在派生类中变成protected成员,在派生类中可以访问这些成员,但派生类以外的其他不可访问该成员;
基类中public成员以开放的形式存在,容许其他类的成员对其进行访问,因此不论在继承是继承控制方式如何,在派生类中都可访问这些成员,但这些成员在派生类中对外表现形式有些变化。
4 继承语法class <派生类名>: <基类名列表>
{<数据成员和成员函数的声明>};
基类名列表表示为:<继承控制方式> <基类名1>, <继承控制方式> <基类名2>, ......
继承控制方式默认为private;
一般使用公有继承,使基类中所有公有成员在派生类中也保持公有;
举例:小汽车和跑车之间的关系用继承描述。
其中,小汽车是基类,跑车是派生类,通过公有继承方式从小汽车类中派生得到。
在跑车类中新增数据成员——颜色,同时增加对这个数据成员操作的成员函数。
#include <iostream>
#include <string>
using namespace std;
class Car{
public:
Car(int theweight, int thespeed)
{
weight = theweight;
speed = thespeed;
}
void setWeight(int theweight)
{
weight = theweight;
}
void setSpeed(int thespeed)
{
speed = thespeed;
}
int getWeight()
{
return weight;
}
int getSpeed()
{
return speed;
}
private:
int weight;
int speed;
};
class sportCar :public Car
{
public:
sportCar(int theweight, int thespeed, string thecolor) :Car(theweight,thespeed)
{
color = thecolor;
}
void setColor(string thecolor)
{
color = thecolor;
}
string getColor()
{
return color;
}
private:
string color;
};
int main()
{
Car car(100, 100);
sportCar sportcar(100, 200, "white");
cout << car.getWeight() << "\t" << car.getSpeed() << endl;
cout << sportcar.getWeight() << "\t" << sportcar.getSpeed() << "\t" << sportcar.getColor() << endl;
return 0;
}
二、派生类中继承成员函数的重定义
继承目的是在一般性的类的基础上生成具有特殊性的类。
若在派生类中定义一个函数原型和继承成员函数一样的成员函数,则该成员函数实现对继承成员函数的重定义。
当通过派生类对象调用成员函数,编译器对函数调用的处理方法:
1 在派生类中查找该函数,若在派生类中有该函数的定义,则调用派生类中定义的函数;否则进入2;
2 在基类中查找该函数的定义,找到则调用;否则进入3;
3 继续2,若查找完所有的祖先类都未找到该函数的定义,则报未定义错误。
这体现派生类中定义的函数有优先权,即派生类中的重定义函数屏蔽基类中的相应函数。
三、继承层次中的构造函数和析构函数
1 派生类对象的存储空间
在类对象的存储空间中,只存放从基类继承的非静态数据成员和派生类中定义的非静态数据成员;
类的静态数据成员和成员函数由类的所有对象公用,只需存储一份。
2 派生类并不继承基类的构造函数和析构函数派生类必须定义自己的构造函数和析构函数;
在生成派生类对象时,由派生类的构造函数调用其直接基类的构造函数,对从基类继承的数据成员进行初始化;
若自己未定义构造函数和析构函数,系统会自动生成一个默认的构造函数,该构造函数的函数体为空。
3 派生类的构造函数通常,一个类中包含默认的构造函数和带参数的构造函数;
3.1 派生类构造函数作用
a 通过初始化列表给基类构造函数传递参数,调用基类的带参数构造函数初始化基类的数据成员;或调用基类的默认构造函数初始化基类的数据成员;
b 初始化派生类中定义的数据成员
3.2 带参数列表的构造函数的语法:
<派生类名> (<形参列表>):<基类名>(<传递给基类的构造函数的实参列表>)
4 构造函数和析构函数的调用次序生成派生类对象时,构造函数的调用次序是:首先调用直接基类的狗在函数,再调用派生类的构造函数;
析构函数的调用次序正好相反。
当建立一个后代类对象,需追溯到其最远祖先,由最远祖先开始逐级调用构造函数初始化该后代类继承得到的数据,最后调用后代类自己的构造函数。
举例:
#include <iostream>
#include <string>
using namespace std;
class A{
public:
A()
{
cout << "Construct Base Object.\n";
}
~A()
{
cout << "Destruct Base Object.\n";
}
};
class B :public A{
public:
B()
{
cout << "Construct Derived Level 1 Object.\n";
}
~B()
{
cout << "Destruct Derived Level 1 Object.\n";
}
};
class C :public B{
public:
C()
{
cout << "Construct Derived Level 2 Object.\n";
}
~C()
{
cout << "Destruct Derived Level 2 Object.\n";
}
};
int main()
{
C c;
return 0;
}
输出:
Construct Base Object.
Construct Derived Level 1 Object.
Construct Derived Level 2 Object.
Destruct Derived Level 2 Object.
Destruct Derived Level 2 Object.
Destruct Base Object.
- 组合
一、组合的语法表示和图形表示
class Wheel{
... ... // 成员定义省略
};
class Car {
public:
... ...
private:
int weight;
int speed;
Wheel wheel[4]; // 一个Car对象中包含4个Wheel对象
};
class SportCar:public Car{
... ...
};
其中,被包含对象称为嵌入对象。
通常情况下,将嵌入对象作为私有成员;但若想保留嵌入对象的共有接口,也可将嵌入对象作为共有成员。
二、组合与构造函数和析构函数
1 创建一个包含嵌入对象的对象时,构造函数的调用次序是:
首先按类声明中嵌入对象出现的次序,分别调用各嵌入对象的构造函数;
执行本类的构造函数;
2 当一个类既是派生类,又组合其他类,创建该类对象时构造函数的调用次序是:
调用基类的构造函数;
按类声明中嵌入对象出现的次序,分别调用各嵌入对象的构造函数;
最后执行派生类的构造函数;
三、继承与组合的比较
继承表示一般性和特殊性的关系,使用继承方法可创建已存在类的特殊版本;
组合表示组成关系,当一个对象是另一个对象的组成部分时,使用组合方法可用已存在的类组装新的类;
- 多重继承与重复继承
一、多重继承
1 多重继承中,派生类有多个基类,派生类与每个基类之间关系仍看作是单继承关系;
class A{
... ...
};
class B{
... ...
};
class C:public A,public B{
... ...
};
通过多重继承,派生类C具有两个基类(类A和类B),在类C对象的存储空间中除了存放C类中定义的非静态数据成员,还存放从类A和类B继承下来的非静态数据成员;
多重继承的应用背景是有时描述一个概念C,该概念具有双重特性,即可以说是A,也可以说是B;
2 举例
Device1定义一个设备类,具有音量、开关等属性作为被保护的数据成员,在数据上的操作包括:构造函数、显示开关状态、显示音量;
Device2定义另一个设备类,具有待机时间、通话时间、电池电量等属性作为被保护的数据成员,在数据上的操作包括:构造函数、显示设备属性、显示电池电量;
有一个新设备类DeviceNew,既是Device1定义的设备,也是Device2定义的设备;
通过共有多重继承,定义DeviceNew类,在该类中定义属性重量及显示重量的函数。
#include <iostream>
using namespace std;
class Device1{
public:
Device1();
Device1(int vol, bool onORoff);
void showPower();
void showVol();
protected:
int volume;
bool powerOn;
};
class Device2{
public:
Device2();
Device2(int newTalkTime, int newStandbyTime, float powerCent);
void showProperty();
void showPower();
protected:
int talkTime;
int standbyTime;
float power;
};
class DeviceNew:public Device1, public Device2{
public:
DeviceNew();
DeviceNew(float newWeight, int vol, bool onORoff, int newTalkTime, int newStandbyTime, float powerCent):Device2(newTalkTime,newStandbyTime),Device1(vol, onORoff);
float getWeight();
private:
float weight;
};
Device1::Device1()
{
cout<<"Initialize device 1 by default constructor in Device1."<<enel;
volume=5;
powerOn=false;
}
Device1::Device1(int vol, bool onORoff)
{
cout<<"Initialize device 1 by constructor with parameters in Device1."<<endl;
volume=vol;
powerOn=onORoff;
}
void Device1::showPower()
{
cout<<"The status of power is: ";
switch(powerOn)
{
case true: cout<<"Power on."<<endl;break;
case false: cout<<"Power off."<<endl;break;
}
}
void Device1::showVol()
{
cout<<"Volume is "volume<<endl;
}
Device2::Device2()
{
cout<<"Initialize device 2 by default constructor in Device2"<<endl;
talkTime=10;
standbyTime=300;
power=100;
}
Device2::Device2(int newTalkTime, int newStandbyTime, float powerCent)
{
cout<<"Initialize device 2 by constructor with parameters in Device2."<<endl;
talkTime=newTalkTime;
standbyTime=newStandbyTime;
power=powerCent;
}
void Device2::showProperty()
{
cout<<"The property of the device:"<<endl;
cout<<"talk time: "<<talkTime<<"hours"<<endl;
cout<<"standbyTime: "<<standbyTime<<"hours"<<endl;
}
void Device2::showPower()
{
cout<<"Power: "<<power<<endl;
}
DeviceNew::DeviceNew()
{
cout<<"Initialize device new by default constructor in DeviceNew."<<endl;
weight=0.56;
}
DeviceNew::DeviceNew(float newWeight, int vol, bool onORoff, int newTalkTime, int newStandbyTime, float powerCent):Device2(newTalkTime, newStandbyTime, powerCent),Device1(vol, onORoff)
{
cout<<"Initialize device new by constructor with parameters in DeviceNew."<<endl;
weight=newWeight;
}
float DeviceNew::getWeight()
{
return weight;
}
int main()
{
DeviceNew device; // 生命一个派生类
cout<<"The weight of the device is "<<device.getWeight()<<endl; // getWeight()函数是DEVICE_NEW类自身定义的
device.showVol(); // showVol()函数是从DEVICE1类继承下来的
device.showProperty(); // showProperty()函数是从DEVICE2类继承下来的
return 0;
}
运行结果:
Initialize device 1 by default constructor in Device1.
Initialize device 2 by default constructor in Device2.
Initialize device new by default constructor in DeviceNew.
The weight of the device: 0.56
Volume is 5
The property of the device:
talk time: 10 hours
standbyTime: 300 hours
二、多重继承的构造函数
多重继承下派生类构造函数和单继承下派生类构造函数相似,必须同时负责调用该派生类所有基类的构造函数;
派生类构造函数的形参表必须满足所有基类构造函数所需参数(或者使用常量表达式调用基类构造函数);
若基类的构造函数带有参数,则由派生类构造函数通过初始化列表的方式将参数传递给基类构造函数;
派生类构造函数格式如下:
<派生类名> (<形参表>): <初始化列表>
{
<派生类构造函数>
}
其中,若派生类调用基类的默认构造函数,则初始化列表为空;若不为空,则初始化列表的语法形式为:<基类名1>(<参数表1>),<基类名2>(<参数表2>),...
派生类构造函数的执行顺序是先执行所有基类的构造函数,再执行派生类本身的构造函数,处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的各基类顺序,与派生类构造函数中所定义的初始化列表的顺序无关。即执行基类构造函数的顺序取决于定义派生类时指定的基类的顺序。
如上例,在定义派生类时,采用以下继承方式:class DeviceNew: public Device1, public Device2 ,它决定了基类构造函数的调用次序是先Device1,后Device2;
修改main函数为:
int main()
{
DeviceNew device(0.7,3,false,10,250,80);
cout<<"The weight of the device: "<<device.getWeight()<<endl;
device.showVol();
device.showProperty();
return 0;
}
运行结果:
Initialize device 1 by constructor with parameters in Device1.
Initialize device 2 by constructor with parameters in Device2.
Initialize device new by constructor with parameters in DeviceNew.
The weight of the device: 0.7
Volume is 3
The property of the device:
talk time: 10 hours
standbyTime: 250 hours
三、多重继承中存在的问题:名字冲突
名字冲突指在多个基类中具有相同名字的成员时,在派生类中这个名字会产生二义性。
如,在执行device.showPower();语句时,编译器无法确定要调用的是从哪个基类继承下来的showPower()函数(是Device1类还是Device2类?),因此产生二义性;
解决办法有二:
1 用作用于操作符::明确派生类对象要访问的是从哪个基类继承下来的成员
device.Device1::showPower();
device.Device2::showPower();
2 在派生类中重定义有名字冲突的成员
在派生类DeviceNew中重定义showPower()函数:
woid showPower()
{
Device1::showPower();
Device2::showPower();
}
当通过派生类对象调用showPower()函数时,首先检查在派生类中是否定义了该函数,若定义了,调用派生类中的showPower()函数;
四、重复继承
当派生类的多个基类具有相同的祖先时,会出现重复继承的情形,即一个类重复多次继承了某个祖先类;
如,Derived类通过其两个基类Base1和Base2重复继承了Base类两次,Derived类对象的存储空间中会包含从Base1和Base2中继承下来的数据成员,而Base1和Base2中又包含从Base类中继承下来的数据成员,故Derived类对象的存储存储空间中包含了Base类中非静态数据成员的两个副本,致使二义性。
#include <iostream>
using namespace std;
class Base{
public:
void setData(int newData)
{
data=newData;
}
protected:
int data;
};
class Base1: public Base{
public:
void setData1(int newData, int newData1)
{
data=newData;
data1=newData1;
}
protected:
int data1;
};
class Base2: public Base{
public:
void setData2(int newData, int newData2)
{
data=newData;
data2=newData2;
}
protected:
data2=newData2;
};
class Derived: public Base1, public Base2{
public:
void setData3(int newData, int newData1, int newData2)
{
data=newData; // 对data访问有二义性
data1=newData1;
data2=newData2;
}
};
int main()
{
Derived dObj;
dObj.setData3(3,4,5);
return 0;_
}
解决该问题方法有二:
1 采用作用域运算发::明确选择哪个副本中的数据
class Derived: public Base1, public Base2{
public:
void setData3(int newData, int newData1, int newData2)
{
Base1::data=newData;
Base2::data=newData;
data1=newData1;
data2=newData2;
}
};
2 采用虚基类方法,使派生类对象的存储空间中只保留被重复继承的祖先类的一个对象副本
虚基类和普通基类的区别是在继承控制保留字之前加virtual,当用virtual限定的基类被重复继承时,只在派生类对象的存储空间中保留其数据的一个副本;
class Base1: virtual public Base{ // Base是其虚基类
public:
void setData1(int newData, int newData1)
{
data=newData;
data1=newData1;
}
protected:
int data1;
};
class Base2: virtual public Base{ // Base是其虚基类
public:
void setData2(int newData, int newData2)
{
data=newData;
data2=newData2;
}
protected:
data2=newData2;
};