继承与继承体系中派生类的对象模型
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。
继承格式
继承权限和访问限定符
继承方式 | 基类public成员 | 基类protected成员 | 基类private成员 |
继承引起 的访问控制关系 变化概述 |
public继承 | 仍为public成员 |
仍为protected成员 |
不可见 |
基类的非私有成 员在子类的访问属性 都不变 |
protected继承 | 变为protected成员 | 仍为protected成员 | 不可见 |
基类的非私有成员 都成为子类的保护成员 |
private继承 | 变为private成员 | 变为private成员 | 不可见 |
基类的非私有成 员都成为子类的私有成员 |
【总结】
(1)基类private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。 可以看出保护成员限定符是因继承才出现的
(2)public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象
解释:用到父类对象的地方均可用子类对象进行替换
(3)protected/private继承是一个
(4)不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)
(5)使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式
(6)在实际运用中一般使用都是public继承,极少场景下才会使用 protetced/private继承
派生类对象的构造与析构
【继承体系下派生类和基类构造函数的调用次序】
以下面的代码为例:
class Base
{
public:
Base(int b):_b(b)
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
int _b;
};
class Derived :public Base
{
public:
Derived(int d)
:Base(2)
,_d(d)
{
cout << "Derived()" << endl;
}
~Derived()
{
cout << "~Derived()" << endl;
}
int _d;
};
int main()
{
Derived d(3);
return 0;
}
若想构造一个派生类的对象,我们首先调用派生类的构造函数,在派生类的构造函数的初始化列表中调用基类的构造函数,先把基类构造好,然后返回到派生类中继续执行,直到把派生类构造好
【继承体系下派生类和基类析构函数的调用次序】
先调用派生类的析构函数,在派生类最后一条有效语句后调用基类的析构函数
继承体系下派生类的对象模型
【单继承】
单继承:子类只继承一个父类,如下:
class B
{
public:
int _b;
};
class D :public B
{
public:
int _d;
};
int main()
{
D d;
d._b = 1;
d._d = 2;
return 0;
}
派生类的对象模型:
【多继承】
多继承:一个派生类由两个基类继承而来,如下:
class B1
{
public:
int _b1;
};
class B2
{
public:
int _b2;
};
class D :public B1,public B2
{
public:
int _d;
};
int main()
{
D d;
d._b1 = 1;
d._b2 = 2;
d._d = 3;
return 0;
}
派生类D的对象模型
【菱形继承】
菱形继承:如图
代码如下:
菱形继承具有二义性:如上图,当派生类D要访问成员变量_b时,要指明是从类C1中继承下来的_b,还是从类C2中继承下来的_b,否则就会产生歧义
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;//具有二义性,是C1中的_b,还是C2中的_b
d.C1::_b = 1;
d.C2::_b = 2;
d._c1 = 3;
d._c2 = 4;
d._d = 5;
return 0;
}
派生类D的对象模型
【菱形虚拟继承】
由于上面的菱形继承,派生类D继承了两遍_b,要多开辟空间,还会造成二义性,菱形虚拟继承就解决了这个问题,菱形虚拟继承在派生类C1,C2的继承权限前面加上一个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;
return 0;
}
派生类D的对象模型
赋值兼容规则
在public继承权限下,子类和派生类对象之间有:
1)派生类对象可以赋值给基类对象,基类对象不能赋值给派生类对象
int main()
{
B b;//定义基类对象b
D d; //定义派生类对象d
b = d;//用派生类对象给基类对象b赋值
//d = b;//不可以
return 0;
}
解释:
2)基类的指针/引用可以指向派生类对象,派生类的指针/引用不可以指向基类对象
int main()
{
B b1;//定义基类变量b1
D d1;//定义派生类变量d1
B& r = b1;//用基类的引用r指向派生类变量
return 0;
}
解释:
此时的r是派生类中从基类继承过来部分的别名,r与d1中基类部分共享同一段内存单元,r与b1有相同的起始地址,如下图:
上一篇: 关系数据库设计:谈谈规范化技术