各种继承方式
公有继承 | 保护继承 | 私有继承 | |
公有成员变成 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
私有成员变成 | 还是基类的私有成语,只能通过基类接口访问 | 还是基类的私有成语,只能通过基类接口访问 | 还是基类的私有成语,只能通过基类接口访问 |
公有继承
公有继承主要体现的是is-a关系,即派生类is a kind of基类,派生类是基类的一种。派生类应当拥有基类的所有特性。因此基类的保护成员和公有成员都对应地变成了派生类的保护成员和公有成员。派生类不能直接访问基类的私有数据成员,但可以通过继承下来的保护或者公有成员函数来访问基类的私有数据成员。is-a继承了基类的接口。
私有继承
私有继承主要体现的是has-a关系,即派生类包含基类。有两种方式可以体现has-a关系,一是私有继承,二是使用包含。
包含是指一个类中声明的成员,该成员的类型是另一个类。举个例子,现在定义一个学生类,简单起见,该类包含有姓名和成绩数组。
class Student
{
private:
string name;
valarray<double> score;
public:
};
该类的成员name的类型是string类,score的类型是valarray模板类,以上就是一个简单的包含,一个类包含其他的类。
对于这个例子,如果使用私有继承的话,则应这样写:
class Student :private string, private valarray<double>
{
public:
};
has-a的特点是Student类只获得类string和valarray的实现,并没有获得接口。
通俗点说,例如string有szie()方法,返回字符串的大小。而对于Student类来说,是没有size()方法的,因为它没有获得这个接口,而只获得了实现。获得实现的意思就是在Student的成员函数中可以使用string.size()方法。正如上表所示,私有继承中,基类的所有成员都会变成私有成员,因此在Student外部是不能使用的,从外部看相当于Student没有这些接口。
对于Student类如果想要实现获得名字字符串的大小可以这么写:
对于包含:
class Student
{
private:
string name;
valarray<double> score;
public:
int getNameSize()
{
return name.size();
}
};
对于继承:
class Student :private string, private valarray<double>
{
public:
int getNameSize()
{
return string::size();
}
};
可以看到包含是使用“成员名.”的格式来调用所包含类的方法的,而私有继承是使用类名加作用域解析运算符来调用的。从这可以看出,包含和私有继承的最大差别就是:包含相当于往类中加入有名字标识的其他类,而私有继承则是往类中加入无名字标识的其他类。这也导致了它们调用方法的不同。其他方式也同理,如构造函数:
包含:
class Student
{
private:
string name;
valarray<double> score;
public:
Student(string _name, valarray<double> _score) :
name(_name), score(_score) {};
};
私有继承:
class Student :private string, private valarray<double>
{
public:
Student(string _name, valarray<double> _score) :
string(_name), valarray<double>(_score) {};
};
保护继承
使用保护继承时,基类的公有成员和保护成员都称为了派生类的保护成员。和私有继承一样,基类的接口在派生类中成员函数里也是可用的。但在此基础上,又派生出新的派生类,此时私有继承和保护继承的区别就呈现出来了。对于私有继承,最新的派生类的成员函数不能使用第一代基类的接口,而对于保护继承则可以。