目录
1、继承的概念和定义
继承是面向对象程序设计使代码可以复用的最重要的手段,继承是类设计层次的复用
例如:
class person
{
public:
void print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};
class student : public person
{
protected:
int _stuid; // 学号
};
class teacher : public person
{
protected:
int _jobid; // 工号
};
其中,person是基类,也称为父类;student是派生类,也称子类
继承关系和访问限定符
继承基类成员访问方式的变化
(1)基类private成员在派生类中无论以什么方式继承都是不可见的
不可见:基类的私有成员还是被继承到了派生类的对象中,但是语法上限制派生类对象不管在类内或者类外都不能去访问
举例:person中的私有成员_id在student中不可见,但是已经被student继承
(2)基类private成员在派生类中不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected
(3)关键字class默认的继承方式是private,struct默认继承方式是public
2、基类和派生类对象赋值转换
- 子类向父类的转换叫做切片(子类赋值给父类的对象/父类的指针/父类的引用)
- 父类对象不能赋值给子类对象
- 父类的指针或引用不能直接转换为子类的指针/引用,必须通过强制类型转换,但是这种方式不安全,访问不到子类的新增成员
3、继承中的作用域
(1)继承体系中,基类和派生类都有独立的作用域
(2)子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫同名隐藏,如果需要访问父类中同名的成员变量,可以通过制定父类作用域访问
注意:在继承体系里最好不要定义同名的成员
B中的fun和A中的fun不构成重载,因为不是在同一作用域;B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
4、派生类的默认成员函数
(1)构造函数
父类构造在子类构造初始化列表处自动调用,先执行父类的构造函数,再执行子类的构造函数的函数体;
如果父类没有默认构造需要显式指定,继承自父类的成员必须调用父类构造完成初始化,不能直接在子类构造中进行初始化
(2)拷贝构造
没有显式定义子类的拷贝构造,指向拷贝构造对象时,子类默认的拷贝构造自动调用父类的拷贝构造;显式定义子类的拷贝构造,指向拷贝构造对象时,子类的拷贝构造自动调用父类的拷贝构造
举例:
当没有显式定义子类的拷贝构造时:
显式定义子类的拷贝构造时:
(3)赋值运算符
子类默认的赋值运算符自动调用父类的赋值运算符;显式定义的子类赋值运算符不会自动调用父类的赋值运算符
子类的operator=和父类的operator=构成同名隐藏
举例:
没有显式定义子类的赋值运算符:
显式定义子类的赋值运算符:
在子类赋值运算符中显式调用父类赋值运算符
(4)析构
子类的析构会自动调用父类的析构
底层编译之后,子类析构与父类析构函数同名,构成同名隐藏,编译器在任何情况下都会自动调用父类的析构函数,不需要在子类析构中显式调用父类析构,会导致父类析构最终会被调用两次
5、继承与友元、继承与静态成员
友元关系不能继承,即就是基类友元不能访问子类私有成员和保护成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员
6、菱形继承
单继承:一个子类只有一个直接父类
多继承:一个子类有两个或两个以上直接父类
菱形继承是多继承的一种特殊情况
举例:
从上面的例子中我们可以看出菱形继承有数据冗余和二义性的问题,在D的对象中,A的成员会有两份
解决办法:虚拟继承(在B和C继承A时使用虚拟继承)
通过B和C的两个指针指向一张表,这张表叫虚基表,这两个指针叫虚基表指针。
虚基表中存的是偏移量,通过偏移量可以找到下面的A
7、继承和组合
继承是“是”的关系,每个派生类对象都是一个基类对象
组合是“包含”的关系,假如B组合了A,那么每个B对象中都有一个A对象
继承允许根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用
对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用,组合类之间没有很强的依赖关系,耦合度低。
优先使用对象组合
组合举例:
继承举例: