一.继承的基本概念
继承是面向对象编程(OOP)中的一个核心特性。它允许我们从一个已经存在的类(父类)创建新类(子类),子类可以继承父类的属性和方法,从而实现代码复用。
父类(基类):提供属性和方法的类。
子类(派生类):从父类继承并可能扩展或修改其功能的类。
使用继承,可以减少代码的冗余
形如:
class person
{
protected:
int _ID;
int _age;
};
class stu :public person
{
public:
int _grade;
};
1.继承方式和访问限定符
继承方式和访问限定符皆由 public,private,protected 构成,但是不同的继承方式和访问限定符的组合会导致不同的访问方式
ps:protected 表示外部不可访问,而继承的可以访问
继承方式\访问限定符 | public | private | protected |
---|---|---|---|
public | public | private | protected |
private | private | private | private |
protected | protected | private | protected |
其实概括起来就一句话,谁”约束最大“就是谁(private约束最大)。
在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承。
2.基类与派生类的互相赋值
基类是可以直接接受派生类的赋值的,很好理解,这是一个裁剪的过程
C++允许这样裁剪过来,但是不允许基类裁剪到派生类。
允许指针的转换
派生类的指针 = 基类的指针
那么如下的语法是否可行呢:基类的指针=派生类的指针?
class person{
int _b;
}
class stu : public person{
int _a;
}
int main(){
person a;
person* tmp =&a;
stu* tmp2 = (stu*)tmp;
}
在强转的情况下,基类的指针=派生类的指针。
3.继承中的作用域
在继承体系之中,基类和派生类的作用域有些许区别。
1.当基类和派生类的成员有相同名称的成员时,优先访问派生类的成员,基类的成员被隐藏,需要加上作用域才行,如person::_a进行显示访问。
2.在实际的工程之中应当避免使用同名的成员。
4.派生类的6个默认成员函数
在继承之中,基类与派生类的6个默认成员函数是如何互动的
#include <iostream>
using namespace std;
class person
{
protected:
person(int a,int b):_ID(a),_age(b){}
person(const person& x) {
_ID = x._ID;
_age = x._age;
}
person operator=(const person& x) {
_age = x._age;
_ID = x._ID;
return *this;
}
int _ID;
int _age;
};
class stu :public person
{
public:
stu(int a,int b, int c):person(a,b),_grade(c){}
stu(const stu& x):person(x){//在初始化列表调用基类的拷贝构造//直接运用裁剪;
_grade = x._grade;
}
stu operator =(const stu& x) {
_grade = x._grade;
person::operator=(x);//裁剪
return *this;
}
~stu() {
//析构不用写基类的析构
}
int _grade;
};
int main()
{
return 0;
}
如何创造一个无法被继承的类呢?–将构造或析构给private起来,也就无法被调用了。
ps:继承是无法继承友元函数的。
ps:继承的静态成员变量始终一个。
二.棱形继承
有普通的单继承,就有复杂的多继承。
有复杂的多继承就有更复杂的棱形继承。
CPP的缺陷之一也就来源于此。
可能会造成如下的问题:
1父类成员重复继承:子类可能会重复继承父类的同一个成员,导致不确定性。
2父类构造函数重复调用:如果不同的路径都调用了父类的构造函数,那么父类的
3构造函数会被调用多次,可能导致资源初始化的问题。
4调用父类方法的冲突:子类通过多条路径继承了父类的方法,可能会产生方法调用冲突。
代码会非常冗杂,并且会产生多重含义,模糊含义。(因为两个基类又继承了同一个基类,他们同一个基类但是会存储在不同的位置,有着不同的值,造成多义性)
1.如何解决
——————使用虚拟继承————————
CPP为了解决这种情况,开发了虚拟继承的方式。
两个基类继承的基类在同一片区域。
如下
class high {
public:
int h;
};
class mid_1 : virtual public high
{
public:
int m1;
};
class mid_2 : virtual public high {
public:
int m2;
};
class lat :public mid_2, public mid_1
{
public:
int l;
};
int main()
{
lat tmp;
tmp.l = 1;
tmp.m1 = 2;
tmp.m2 = 3;
tmp.mid_2::h = 100;
tmp.mid_1::h = 200;
return 0;
}
三.总结
继承与组合之间的关系:
继承是一个“is”
组合时一个“has”
继承在逻辑上表示它是什么,组合在逻辑上表示它有什么。
继承是一个”白箱操作“,组合是一个“黑箱操作”,继承可以清晰的看到内部有啥,而组合因为其类的封装,我们只需使用即可
继承的耦合度高,组合的耦合度低。
在一般情况下应该 多多使用组合而非继承