1.三种继承方式
C++继承分为公有继承(public),保护继承(protected)和私有继承(private);
(1)公有继承(public)
基类的共有成员和保护成员作为派生类的成员时,保持原有状态,而基类的私有成员仍旧是私有的,不能被这个派生类所访问;
(2)保护继承(protected)
基类的公有成员和保护成员都变成派生类的保护成员,只能被派生类的成员函数或友元访问,基类的私有成员仍旧是私有的;
(3)私有继承(private)
基类的公有成员和保护成员都变成派生类的私有成员,不能被这个派生类所访问;
继承方式 | 基类的public成员 | 基类的protected成员 | 基类的private成员 | 继承引起的访问控制关系变化 |
公有继承public | 派生类的public成员 | 派生类的protected成员 | 不可见 | 基类的非私有成员在派生类中的访问属性不变 |
保护继承protected | 派生类的protected成员 | 派生类的protected成员 | 不可见 | 基类的非私有成员在派生类中变为保护成员 |
私有继承private | 派生类的private成员 | 派生类的private成员 | 不可见 | 基类的非私有成员在派生类中变为私有成员 |
总结:
1. 基类的私有成员在派生类中不能被访问;
2. public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象;
3. protected继承和private继承是一个实现继承,基类的部分成员并未完全成为子类接口的一部分,是has-a 的关系原则,非特殊情况下不会使用这两种继承关系,绝大多数的场景下使用的都是公有继承;
4. 不管是哪种继承方式,在派生类内部都可以通过访问基类的公有成员和保护成员;
5. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,一般最好显示的写出继承方式;
6. 在实际运用中一般都是使用public继承,极少场景下才会使用protected继承和private继承;2.继承与转换--赋值兼容规则--public继承
1.派生类对象可以赋值给基类对象(通过切片/切割);
2.基类对象不可以赋值给派生类对象;
3.基类指针/引用可以指向派生类对象(相当于指向派生类中基类的部分);
4.派生类指针/引用不能指向基类对象(会造成越界访问);
3.继承体系中的作用域
1.在继承体系中基类和派生类的作用域相互独立;
2.基类和派生类中有同名成员,派生类会屏蔽基类对成员得到直接访问(在派生类成员函数中,可以使用 基类::基类成员 访问)----隐藏/重定义;
3.一般情况下,在继承体系中最好不要定义同名成员;
4.派生类的默认成员函数
在继承体系中,如果派生类中没有显示定义这六个默认成员函数,编译系统会默认合成这六个默认成员函数;
构造函数,拷贝构造函数,析构函数,赋值操作符重载,取地址操作符重载,const修饰的取地址操作符重载;
注意:子类对象构造时,先父类构造,后子类构造;
子类对象析构时,先子类析构,后父类析构;
5.单继承,多继承,菱形继承
1.单继承--一个子类只有一个直接父类时称这个继承关系为单继承;
2.多继承--一个子类有两个或两个以上的父类时称这个继承关系为多继承;
多继承带来的二义性问题:
例:
#include<iostream>
using namespace std;
class A
{
int _d;
};
class B
{
int _d;
};
class C:public A,public B
{
};
int main()
{
C c;
//不能这样写,会产生二义性问题
//c._d = 1;
//正确,必须明确类成员所属哪个类
c.A::_d = 1;
c.B::_d = 2;
return 0;
}
由于多继承带来的二义性问题,所以尽量应避免同名成员,或应明确指出成员所属类;
6.虚继承--解决菱形继承带来的二义性和数据冗余问题
例:
#include<iostream>
using namespace std;
class A
{
public:
int _a;
};
class B: public A
{
public:
int _b;
};
class C: public A
{
public:
int _c;
};
class D:public B,public C
{
public:
int _d;
};
int main()
{
D d;
return 0;
}
类A,B,C,D构成菱形继承,这里采用一般继承,d对象模型为:
可以看出,D类中存在两个重复的A类成员,带来了二义性和数据冗余问题;
虚继承概念:
C++使用虚继承,解决从不同路径继承来的同名数据成员在内存中有不同的拷贝造成的数据不一致问题,将共同基类设为虚基类,这时从不同路径继承来的同名数据成员在内存中只有一个拷贝,同名函数也只有一个映射;
注意:
虚继承:在继承定义中包含virtual关键字的继承关系;
虚基类:在虚继承体系中通过不同路径继承而来的共同基类;
通过虚继承解决上例中存在的问题:
//vs2008
#include<iostream>
using namespace std;
class A
{
public:
int _a;
};
class B:virtual public A
{
public:
int _b;
};
class C:virtual public A
{
public:
int _c;
};
class D:public B,public C
{
public:
int _d;
};
int main()
{
A a;
B b;
C c;
D d;
a._a =1;
b._a = 2;
b._b = 3;
c._a = 4;
c._c = 5;
d._a = 6;
d._b = 7;
d._c = 8;
d._d = 9;
return 0;
}
查看a,b,c,d对象在内存中的存储情况:
对象a:
对象b:
对象c:
对象d:
可见在VS下,使用了偏移量来实现虚继承;
d对象模型为:
在虚继承关系中,需要有一个记录虚继承关系的虚基类表,此时子类中需要虚基类表指针来指向虚基类表;