1.概念:
是面向对象程序设计使代码可以复用的总要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加性能。这样产生的类,称为派生类。
继承的定义格式:
class Student :public Person
这里的student就是派生类(子类)名称,public是继承权限-->public/protected/private,基类(父类)名称
2.继承权限:
1.public继承:父类public子类public,父类protected子类protected,父类private子类不可访问
2.protected继承:父类public子类protected,父类protected子类protected,父类private子类不可访问
3.private继承:父类public子类private,父类protected子类private,父类private子类不可访问。
我在写代码验证的时候,在继承权限是public,发现父类是protected时,子类可以继承,但是类外不可访问。那么究竟是protected还是private呢? 就在写一个类,继承权限为public,此时看看在类中可以对此进行访问吗,结果证明可以访问,那么可以说明父类是protected的子类也是protected。同理,堆protected和private也进行了验证。
通过验证的结果,我们发现:
(1)public继承是一个is-a的原则。
两方面对此说明:
1)调用形式:每个父类可用的成员对子类也可用,因为每个子类也都是一个父类对象。也就是说 可用把子类也看成一个父类,在所有使用父类对象的位置,都可使用子类进行代替。
基类中的成员变量:
public:
int _pub;
int _pro;
int _pri;
子类中成员变量:
public:
int _pubD;
int _proD;
int _priD;
2)对象中各个变量在内存中的布局。
在内存中:
因此public继承是一个基类
(2)protected/private 是一个has-a的关系。
1)对象模型是一样的 2)全权限变了 3)类外不可访问
这个可以称为组合(内嵌),基类的对象在派生类包含了一份。
3.赋值的兼容规则-------必须为public继承
(1)基类对象=派生类对象。因为基类部分+派生类特有是派生类的,因此派生类可以给基类赋值
(2)反过来不可以。因为基类小于派生类。没办法全部赋值。
(3)基类*p=&派生类对象 / 基类&r=派生类对象 。 可以的。基类指针或者引用可以访问派生类继承下来的那一块空间。
(4)反过来不可以。因为派生类会访问基类一段非法空间。
但可以:派生类* pd=(派生类*)&基类 进行强转。虽然编译可以通过,但是存在风险。
4.作用域
(1)基类和派生类是不同的作用域。
(2)基类与派生类相同名称不能形成重载。而是同名隐藏。
同名隐藏:基类和派生类具有相同名称的成员(成员变量||函数)与成员的类型,参数无关。通过派生类对对象相同名称的成员的调用后,优先调用派生类的。 也就是说基类中与派生类中相同名称的成员被称为成员隐藏。
若想调用基类的成员:基类对象.派生类::成员
d.B::TestFunc();
5.继承体系中派生类对象的构造和析构
(1)构造派生类对象:调用派生类的构造函数:1:在派生类构造函数的初始化列表中完成对基类的构造-->2.在构造派生类自己的-->3.执行构造函数的函数体。 因为结果显示先打印基类,在打印派生类。
注意:1)如果基类中有非缺省的构造函数,派生类必须显示给出自己的构造函数,并在其构造函数的初始化列表调用其基类的构造函数,完成对基类的构造。
2)如果基类中构造函数是缺省的,派生类构造函数是否显示给出根据需要。
(2)销毁派生类对象:调用派生类的析构函数。1.派生类先完成派生类自己资源释放-->2.在派生类析构函数的后面插了一句:call 基类的析构函数
面试题:实现一个不能被继承的类
1.将基类的构造函数给为私有的,派生类就不可创建对象。通过静态方法实现基类对象的实例化与释放
class B
{
public:
static B* get(){
return new B;
}
static void del(B* obj){
if (obj)
delete obj;
}
private:
B()
:_b(10)
{}
int _b;
};
class D :public B
{};
int main()
{
//D d; //此时对象无法创建,可是父类的对象也没有办法去访问,所以我们我提供方法来进行访问
B *obj = B::get();
B::del(obj);
return 0;
}
2.在c++11 里面可以通过final关键字
class B final
{};
6.友元不可继承,static可以继承
因为友元不是类的成员函数。举个例子:你爸有个好朋友你叫王叔,你把称为王哥。但你不能叫王哥。因为你把的朋友不是你的朋友。
static是在整个继承体系里面只有一个这样的成员。无论派生多少子类,都只有一个static成员实例。
7.继承体系下派生的对象模型
(1)单继承:一个子类只有一个父类
(2)多继承:一个子类有两个或两个以上的父类
class A
{
public:
int a;
};
class B
{
public:
int b;
};
class C :public A, public B
{
public:
int _c;
};
int main()
{
C c;
c.a = 1;
c.b = 2;
c._c = 3;
cout << sizeof(c) << endl; //12
return 0;
}
对象模型:
(3)菱形继承(钻石继承)
单继承+多继承=菱形继承
class B
{
public:
int _b;
};
class C1: public B
{
public:
int _c1;
};
class C2: public B
{
public:
int _c2;
};
class D : public C1, public C2
{
public:
int _d;
};
int main()
{
D d;
d._b = 1;
cout << sizeof(d) << endl; //20
}
此时发现出现错误,因为此时_b有两份, 这里不知道应该调用哪一份。
d.C1::_b = 1;
可以加上作用域来进行访问。但是此时_b还是存了两份,浪费内存空间。
它的对象模型:
因为引入:菱形虚拟继承:来解决存在数据冗余及二义性问题。
在继承权限前面加上virtual关键字即可构成虚拟继承。
class B
{
public:
int _b;
};
class C1:virtual public B
{
public:
int _c1;
};
class C2:virtual public B
{
public:
int _c2;
};
class D :public C1, public C2
{
public:
int _d;
};
int main()
{
D d;
d._b = 1;
d._c1 = 2;
d._c2 = 3;
d._d = 4
cout << sizeof(d) << endl;
}
sizeof(d):发现大小成了24.多了4个字节。
刚调试到对象创建。此时就我们发现有两个地址,我们在进去看看这两个地址
发现一个是14,一个是0c,我们再看看对象模型
此时看到基类在最底下,正好看到C1偏移基类是20个字节,C2偏移基类14个字节。说明明个虚拟继承都会多出来4个字节来记录自己相对于基类的偏移量
我们可以得出结论:
virtual.1-->这个对象就会多出4个字节 2--->取4个字节中的内容,当做地址。3--->取出基类成员的偏移量。对其进行赋值。
虚拟继承的特点:
(1)对象模型:基类在下,派生类在上
(2)对象模型多了4个字节,指向偏移表格的地址
(3)是通过偏移量表格来访问基类成员的
(4)编译器一定会给其合成构造函数,多压入一个1,作用就是在看是否为虚拟继承。(0和1做了比较,不相等就将其地址压入)