继承
类之间的关系:包含、使用、继承
(1)简单使用
class Parent
{
public:
void print()
{
cout << "a: " << a << "b: " << b << endl;
}
private:
int a;
int b;
};
//class Child : private Parent
//class Child : protected Parent
class Child : public Parent
{
private:
int c;
};
void main()
{
Child c1;
system("pause");
}
-
子类拥有父类的所有成员变量和成员函数;
-
子类可以拥有父类没有的方法和属性 ;
-
子类就是一种特殊的父类 ;
-
子类对象可以当作父类对象使用 ;
(2)派生类的访问控制
- C++中的继承方式会影响子类的对外访问属性:
public继承:父类成员 在 子类 中保持原有访问级别;
private继承:父类成员 在 子类 中变成private成员;
protected继承:父类中 public 成员会变成 protected;父类中 protected 仍然是;private仍然是;
- 判断子类的对外访问属性
看调用语句,在子类的内部还是外部;
看子类如何从父类继承;
看父类中的访问级别;
(3)继承中的构造和析构
- 类型兼容性
子类就是特殊的父类 (base *p = &child);
父类 指针(引用)指向 子类对象:Parent *p = &chile;
子类指针做函数父类参数;printP(&chile);
子类引用做函数父类参数;printP(chile);
子类对象 初始化 父类对象;Parent p = chile;
- 继承中的对象模型
类在C++编译器的内部可以理解成结构体,子类是由父类成员叠加子类新成员得到的;
objA: | objA.x | objA.y |
-->
objB: | objB.x | objB.y | objB.s |
-->
objC: | objC.x | objC.y | objC.s | objC.n | objC.m |
- 父类与子类的构造函数有什么关系
在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化;
在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理;
- 继承中的构造析构调用原则
// 创建子类对象 --> 首先调用父类的构造函数 --> 再执行子类的构造函数
Child c1(1, 2, 3);
// 当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
Child(int a, int b, int c) : Parent(a, b);
// 析构函数调用的先后顺序与构造函数相反
- 继承与组合混搭情况下,构造和析构调用原则
先构造父类,再构造成员变量(组合对象变量)、最后构造自己;
先析构自己,在析构成员变量(组合对象变量)、最后析构父类
- 继承中的同名成员变量处理方法
当子类成员变量与父类成员变量同名时,子类依旧从父类继承同名成员 ;
在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符);
同名成员存储在内存中的不同位置 ;
- 子类中的 static 关键字
基类定义的静态成员,将被所有派生类共享;
static 成员遵守派生类的访问控制;
子类访问静态成员:类名::成员; 对象名.成员
注意:
class Parent
{
public:
static int i;
}
int Parent::i = 100;
// 初始化静态成员,并为静态成员分配内存! 才能给子类使用
(4)多继承
- 多继承
一个类有多个直接基类的继承关系称为多继承
class child : public Base1, public Base2
- 虚继承
多继承二义性原因:
如果一个子类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性
class B {public: int b;};
class B1 : public B {public: int b1;};
class B2 : public B {public: int b2;};
class C : public B1, public B2
{
public:
int c;
}
void test()
{
C c;
c.B; //err 无法判断B从哪里继承
}
多继承二义性解决:
// 虚继承 virtual
class B1 : virtual public B {public: int b1;};
class B2 : virtual public B {public: int b2;};
// 使用虚继承后,基类B的构造函数只调用了一次
//可以判断在子类C定义的对象,内存模型中B1、B2共有一份B。。。
(5)总结
- 继承是面向对象程序设计实现软件重用的重要方法。程序员可以在已有基类的基础上定义新的派生类。
- 派生类对基类成员的访问由继承方式和成员性质决定。
- 创建派生类对象时,先调用基类构造函数初始化派生类中的基类成员。调用析构函数的次序和调用构造函数的次序相反。
- C++提供虚继承机制,防止类继承关系中成员访问的二义性。