继承的概念和定义
继承的概念
继承是面向对象程序设计使代码可以复用的重要手段,当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
继承的定义
定义格式
继承父类成员访问关系的变化
类成员/继承方式 | public继承 | protected继承 | private继承 |
---|
基类的public成员 | 派生类的public成员 | 派生类的private成员 | 派生类的private成员 | 基类的protect成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 | 基类的private成员 | 派生类中不可见 | 派生类中不可见 | 派生类中不可见 |
父类和子类的对象赋值转换
-
子类对象可以赋值给父类的对象/父类的指针/父类的引用。一般情况下子类对象都比父类对象大,这里赋值的时候会产生切片或者叫切割。 切片/切割 -
父类对象不能赋值给子类对象。但是指针和引用可以,但是必须进行强制类型转换,并且这个行为十分危险,可能存在越界访问。
class Person
{
public:
string _name;
};
class Student:public Person
{
public:
int _age;
int _major;
};
int main()
{
Student a;
Person b = a;
Person* c = &a;
Person& b = a;
a = b;
Person* b1 = &b;
Student* a1 = (Student*)b1;
return 0;
}
继承中的作用域
- 在继承体系中父类和子类都有自己独立的作用域
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
子类的默认成员函数
默认成员函数
- 构造函数
- 析构函数
- 拷贝构造函数
- 赋值操作
原则
继承下来的调用父类处理,子类的按照普通类的基本规则。 那么什么情况下需要我们自己写?
- 父类没有默认构造,需要我们自己写默认构造
- 若子类没有资源需要释放,就需要我们自己显示的写析构函数
- 若子类存在深浅拷贝问题,就需要实现拷贝构造和赋值解决深浅拷贝问题
如何写? 父类成员调用父类对应的构造,拷贝构造,operator=和析构处理,自己成员按照普通类处理。 析构函数的名称会被统一处理为destructor() - 子类的析构函数和父类的析构函数会构成隐藏
- 子类的析构函数结束时会自动调用父类的析构函数
- 子类的析构函数不需要调用父类的析构函函数它会自动调用,这样才能保证先析构子类成员,再析构父类成员。
继承与友元
友元关系不能继承,就比如父亲的朋友不一定是你的朋友。
继承与静态成员
父类定义了static成员,则整个继承体系中只有一个这样的静态成员,无论派生出多少个子类,都只有一个static成员,因此静态成员可以继承下来,但是只有一份。
菱形继承以及菱形虚拟继承
单继承
一个子类只有一个直接父类时称这个继承关系为单继承。
多继承
一个子类有两个或者两个以上的直接父类时称这个继承关系为多继承。
菱形继承
菱形继承是多继承的一种特殊情况 当这样的继承关系出现的时候,对于Assistant会形成数据的冗余以及二义性,因为在其中出现了两份Person。 我们看下面一串代码,
在非虚继承中
#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;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
当我们查看d对象的地址,我们看到里面的存储是这样子存储的。并且是先继承谁先存储谁,倘若我们将代码中的D类换为
class D : public C, public B
{
public:
int _d;
};
我们要是将继承的顺序进行调换,那么我们将看到在d对象中就先存谁的顺序就会变换,先继承的先进行存储。
在虚继承中
当我们进行调试
- 首先创建了D对象
- _a出现在这个位置
- C中的_a将之前B中的_a进行了覆盖
- 当我i们再观察内存发现_a存在一个公共的区域
我们发现当我们进行虚继承时是这样子进行存储的,A处在一个公共的区域,但是BC前面的东西还并不知道是什么 我们查看这里的地址, 发现这个地址里面记录着一个数字,这个数字就是B和C相对于公共成员A的偏移量/相对距离。 此时这个A类一般称为虚基类,因为是虚继承下来的,在D里面,A存放在一个公共的区域,可能B,C都需要进行寻找A,这时候就通过虚基表中记录的偏移量来进行查找,这个BC之前的这个值就为虚基表的地址,虚基表中存储的就是偏移量,偏移量上面还有一个空间,既然为虚基表,表里面不会只存一个值,至于这个值具体是什么,在后面的多态进行讲。
继承和组合
- public继承是一种is-a的关系,也就是说每个子类对象都是一个父类对象。
- 组合是一种has-a的关系,假设B组合了A,那么每个B对象中都有一个A对象
- 优先使用组合而不是类继承。
|