什么是继承
继承的面向对象的特征之一,继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称子类或者派生类,而被继承的类就叫做父类或者基类。继承呈现了⾯向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复用,继承是类设计层次的复⽤。
继承的好处
继承可以很好地对代码进行复用,并且也可以很容易看出类与类之间的关系。
继承的缺点
继承会提高代码的耦合度,相比“组合”对耦合度的提高程度会更高。并且如果使用了多继承的话就很容易形成菱形继承,形成了菱形继承的话就还有用到虚继承防止内存浪费和指代不明等问题,而这又会导致代码可读性下降。
继承的语法
说完了继承的优缺点,接下来就来说一下继承要怎么使用吧
class A
{
public:
A()
{}
protected:
void func(){};
private:
int _age;
string _name;
};
class B: A // 1.在类名后面跟冒号加需要继承的类名,此时继承方式默认为public
{
public:
B(){}
private:
string number;
};
class C : public A //这里用访问限定符指定了继承方式为public, 类名 + “:” + 继承类型 +需要继承的类名
{
public:
C() {}
private:
string number;
};
以上的B类与C类是等价的,因为C++的继承方式默认为public。
继承类型和父类的关系
子类继承父类之后,类成员的访问权限也可能会因为继承类型的不同而发生改变。具体看下方这张图。
一句话概括就是每个成员都是从自身原本的访问权限和继承类型当中选访问权限较小的一个当做该成员的访问类型
关于父类的private成员,虽然子类无法访问父类的private成员,但是这些成员依然是被原原本本继承下来的。
继承中的切片现象
C++中,子类对象/指针/引用 可以赋值给父类的对象/指针/引用,此时父类是指向子类中属于父类的那一部分的,这个现象就叫做切片
因此,如果一个子类继承了两个父类,那么将该子类赋值给第二个或以上被继承的父类的时候就会发生指针偏移,即该父类指针指向的位置不是子类的内存中的开始位置,而是指向子类中存放该父类的位置。
实现一个无法被继承的类
如果我们想要实现一个无法被继承的类的话只需要在类名后面加上关键词final即可。
class A final
{
public:
A()
{}
protected:
void func(){};
private:
int _age;
string _name;
};
继承与友元之间的关系
父类的友元无法被继承,其道理就像是说父亲的朋友不一定是儿子的朋友一样,如果希望子类也拥有该友元的话需要再在子类当中声明一次。
继承与静态成员的关系
在整个继承体系当中只会存在一份静态成员变量,也就说静态成员是子类和父类公用的,在子类改变该成员的值的话,父类的也会跟着改变。
多继承与菱形继承
所谓多继承,即一个类继承了多个父类,如下图
而菱形继承则是如下图
如果只是多继承的话问题还不是很大,但是出现了多继承的话就很容易出现菱形继承,一旦出现多继承就会出现数据冗余和⼆义性的问题。
那上图来说的话也就是C中有两份D的数据,并且这两份数据是相互独立的,访问的时候会出现二义性的问题,需要加上作用域来指定是来自哪里的数据。
而如果要比较好地解决问题的话就要用到虚继承了。
class D
{
string number;
};
class A: virtual public D
{
int _age;
string _name;
};
class B: virtual public D
{
string number;
};
class C : public A, public B
{
string number;
};
虚继承,即在继承前加上virtual关键字,此时C再继承A和B的时候就只包含一份D的数据了,虽然避免了菱形继承的一些问题,但这还导致了代码的可读性降低的问题,能不用建议还是不要使用。