什么是继承?
继承是面向对象设计的重要部分,允许在原有类特性上进行扩展和代码复用!
表示 is a 的概念。鸵鸟是一种鸟,鸵鸟就可以继承鸟这个类,但是鸟会飞,鸵鸟不会飞,这时候派生类继承了自己没有的属性是不合理的。更严谨——当B是A的一种,且有A的全部属性,这时候才适合继承。
然而,使用场景更广泛的是 has a ,也就是组合的形式。头由眼耳口鼻组成,而不是说头是眼耳口鼻的一种。
访问权限和继承权限
访问权限是指对于本类或者对象的成员来说的,继承权限是在访问权限的基础上谈的,这一点很容易理不清。
访问权限:
private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.
protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问
其中protected的作用是不想让成员在类外被使用,但是可以让子类使用,因此可以说在继承中才能体现其作用。
继承权限:
因为派生类完全继承了基类的成员变量,所以现在讨论继承过来的成员的权限问题。
public:
public和protected成员权限在派生类没有改变,private在派生类不可见
protected:
public降级为protected, protected不变,private不可见
private:
public降级为private,protected降级为private,private不可见
如何证明子类继承了一个protected权限的成员?
分别在子类内部访问成员,在类外用对象访问成员。
其中public继承最为常用,每个父类成员对子类也可用, 子类 IS-A 父类。
class和struct的默认继承权限分别是private和public。
派生类默认的成员函数
与一般类一样,在需要时编译器合成相应的六个成员函数。
- 基类没有定义构造函数或者有缺省构造,则派生类不需要显式定义。
class Date {
public:
//缺省构造
Date()
{
_day = 1;
}
Date(int)
{
_day = 2;
}
private:
int _day;
};
class Time :public Date
{
public:
//假如不写,编译器也会合成默认构造
Time()
:Date()//隐式调用
{
}
private:
int _time;
};
- 如果基类只有带参构造函数,则派生类必须在构造函数参数列表调用基类构造函数。
class Date {
public:
Date(int)
{
_day = 2;
}
private:
int _day;
};
class Time :public Date
{
public:
Time()
:Date(1)
{
}
private:
int _time;
};
构造函数的调用顺序
- 基类构造函数
- 成员类对象构造函数(多个按声明顺序)
- 派生类构造函数
赋值兼容规则
由于构造函数是先父后子,自然在内存中的模型就是先放父成员后放子成员。
复制兼容原则是大的可以赋给小的,小权限指针可以指向大权限指针。
翻译一下就是派生类可以切割给基类对象赋值,父类指针可以限制的指向子类。
基类那些成员被派生继承了?友元可以继承吗?
除了基类的构造,析构,赋值函数没有被继承,其他成员被继承。
在派生类中访问基类的私有成员只能通过基类public或protect函数访问。
因此在写派生类需要注意
1. 一条铁则:编译器总是要构造父类对象的,若父类有默认构造,则调用默认构造。若没有默认构造,则需要在派生类初始化列表显示调用父类构造。
2. 基类虚析构(多态导致派生类不能正常析构)
3. 赋值函数里调用父类赋值函数初始化父类成员(不能操作父类私有,也没有父类构造)
static成员因为在静态区,父类的static变量和函数在派生类中依然可用,但是受访问性控制。
友元关系:父亲的朋友不是儿子的朋友。
菱形继承和虚拟继承
菱形继承的模型:基类A,派生类B1,B2都继承A,C继承B1和B2.
缺陷:C类里有两份A类成员,分别从B1B2继承来的,存在二义性,必须用类限定符访问
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()
{
cout << sizeof(C1) << endl;
cout << sizeof(D) << endl;
C1 c;
c._b = 5;
c._c1 = 6;
D d;
d._b = 1;
d._c1 = 2;
d._c2 = 3;
d._d = 4;
system("pause");
}
虚拟继承——声明virtual
0x004FFD54 30 7b 93 00
0x004FFD58 06 00 00 00
0x004FFD5C 05 00 00 00
通过C1的对象模型可以看到前4字节存放一个指针,然后先放派生类成员再放基类成员。
虚拟菱形继承
0x004FFD34 e0 7b 93 00 ?{?.
0x004FFD38 02 00 00 00 ....
0x004FFD3C e8 7b 93 00 ?{?.
0x004FFD40 03 00 00 00 ....
0x004FFD44 04 00 00 00 ....
0x004FFD48 01 00 00 00
虚拟菱形继承D的模型,先按继承顺序存放C1,C2,再放本类,最后放基类成员。
虚拟菱形继承实现了基类成员仅存放一份,消除二义性。
怎么实现的?
虚拟继承的派生类对象前四字节存放指针,指针指出了派生类相对基类的偏移量,从而两个派生类相对于基类成员有了不同的偏移,可以保证访问到同一个基类对象。这里的偏移量是在构造函数里填充的,编译器自动合成默认构造。