目录
概述
概念
继承面向编程三大思想之一,它允许一个类(该类一般称为子类或者派生类)从另一个类(父类或者基类)中继承属性和方法。利用继承,子类可以重用父类的代码的同时,拓展和修改父类的行为。
继承涉及的概念
- 基类:也就是父类,其主要被子类继承
- 派生类:子类,继承子父类,在拥有父类代码的同时还可以拓展自己的功能
- 访问控制
- public继承:基类public成员在派生类也是public;基类中protected成员在派生类中也是protected
- protected继承:基类的public成员和protected成员在派生类中都会变成protected
- private继承:基类的public成员和protected成员在派生类中都变成private类型
- 总结:以上无论那种继承方式,都无法访问父类的Private 成员,这类似父类的小秘密,不想被其他派生类继承。
#include <iostream>
using namespace std;
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class DerivedPublic : public Base {
public:
void accessMembers() {
publicVar = 1; // 可以访问
protectedVar = 2; // 可以访问
// privateVar = 3; // 不能访问
}
};
class DerivedProtected : protected Base {
public:
void accessMembers() {
publicVar = 1; // 可以访问
protectedVar = 2; // 可以访问
// privateVar = 3; // 不能访问
}
};
class DerivedPrivate : private Base {
public:
void accessMembers() {
publicVar = 1; // 可以访问
protectedVar = 2; // 可以访问
// privateVar = 3; // 不能访问
}
};
int main() {
DerivedPublic dp;
dp.publicVar = 1; // 可以访问
// dp.protectedVar = 2; // 不能访问
DerivedProtected dpr;
// dpr.publicVar = 1; // 不能访问
DerivedPrivate dpv;
// dpv.publicVar = 1; // 不能访问
return 0;
}
定义
继承基类成员访问方式变化总结
- 基类private成员,无论哪种方式继承,派生类中都不可见
- class默认继承方式是private,struct的默认继承方式是public,所以如果涉及到继承的时候,需要关注其成员变量和函数的类型
类成员/继承方式 | public继承 | protected继承 | private继承 |
基类public继承 | 派生类的public | 派生类的protected | 派生类的private |
基类protected继承 | 派生类protected | 派生类的protected | 派生类的private |
基类的private继承 | 派生类不可见 | 派生类不可见 | 派生类不可见 |
基类与派生类对象赋值转换
总结
- 基类指针和引用可以指向派生类对象,这使得多态性成为可能。
- 派生类对象转换为基类对象时会发生对象切片,派生类的特定部分会被丢弃。
- 基类对象不能直接转换为派生类对象,需要使用
dynamic_cast
来进行安全的类型转换
基类指针与引用
基类指针和引用可以指向派生类对象
派生类对象转换为基类对象
派生类对象被转换为基类对象的时候,会发生对象切片,即派生类的对象中与基类不同的部分会被切除。
基类对象转换为派生类对象
基类对象是不可以直接转换为派生类对象的,因为基类对象不包含派生类的成员。如果需要对其进行强转,则必须保证基类对象实际是某个派生类对象的子对象,同时使用类型转换操作符 dynamic_cast
继承中的作用域
总结
- 成员隐藏:派生类中同名成员会隐藏基类中的成员
- 作用域解析运算符:使用::可以访问被隐藏的基类成员
- 成员变量的隐藏:类似于成员函数,派生类中的同名成员变量会隐藏基类中的成员变量
- 使用using声明:可以引入基类成员以避免隐藏
成员隐藏
派生类中如果定义了与基类中相同的成员函数或者变量,基类中的成员将会被隐藏
借助作用域解析运算符访问被隐藏的成员
成员变量的隐藏
下面代码中,隐藏了基类中的value的数值,而是只显示子类的数值
使用using声明引入基类成员
代码中可以借助using 声明,将基类中的成员引入到派生类中使用
派生类的默认成员函数
总结
- 默认构造函数:派生类的默认构造函数会调用基类的默认构造函数去初始化基类的部分代码
- 拷贝构造函数:派生类的默认拷贝函数调用基类的拷贝构造函数来复制基类部分
- 赋值操作符:派生类默认赋值操作符则是调用基类的赋值操作符完成基类的复制
- 析构函数:派生类调用基类的析构函数析构基类的部分,如果基类的析构函数是虚函数,即派生类的析构函数也是虚函数
- 移动构造和移动赋值操作符:同上
派生类中默认成员函数,其中基类代码,都是去调用基类的默认成员函数完成。
继承与友元
- 友元关系是不可以被继承的,即基类的友元是无法访问其子类私有成员和保护成员的
- 派生类可以通过基类的友元来访问基类的私有成员
继承与静态成员
总结
- 静态成员变量在派生类中仍然是属于基类的,其可以通过或派生类来访问,但是其在内存中只存在唯一一个
- 静态成员函数
- 可以被派生类继承和隐藏,同样可以通过基类或派生类名对其进行访问
单继承 多继承 菱形继承
单继承:一个派生类只能够有一个直接的基类
多继承:一个派生类中用于很多直接的基类,从而从多个基类中继承属性和方法
菱形继承:一个类从两个基类中继承,然而这两个基类又是继承自同一个基类。菱形继承会导致数据成员的多次拷贝,从而引发二义性问题,主要解决办法则是利用虚继承。
虚基表
实现分析
- 类定义:每个含有虚函数的类都有一张虚函数表,表内存储虚函数的地址
- 对象实例化: 对象在实例化后,包含有一个指向虚基表的指针,构造函数初始化该指针
- 虚基类访问:通过虚基表中的偏移量,确保正确访问到虚基类的成员
Derived1和Derived2因为是虚继承,内部都有一个指向虚基表的指针,可以通过该指针访问基类中的函数以及内容。 具体来说是通过虚指针到虚基表中找到继承基类函数的首地址进行访问。