环境:
编译器:CLion2021.3;操作系统:macOS Ventura 13.0.1
文章目录
- 一、继承的基本语法
- 二、继承方式
- 2.1 public继承
- 2.2 protected继承
- 2.3 private继承
- 2.4 继承规则
- 三、继承中的对象模型
- 四、继承中的构造和析构顺序
- 五、继承同名成员处理方式
- 六、继承同名静态成员处理方式
- 七、多继承语法
- 八、菱形继承和虚继承
地表最强C++系列传送门:
「地表最强」C++核心编程(一)内存分区模型
「地表最强」C++核心编程(二)引用
「地表最强」C++核心编程(三)函数提高
「地表最强」C++核心编程(四)类和对象----封装
「地表最强」C++核心编程(五)文件操作——暂未更新
继承是C++的三大基本特性之一。
一、继承的基本语法
语法: class 子类 : 继承方式 父类
其中子类也称为派生类,父类也称为基类。派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。
//公共页面
class BasePage {
public:
void header() {
cout << "首页、公开课、登录、注册...(公共头部)" << endl;
}
void footer() {
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left() {
cout << "Java,Python,C++...(公共分类列表)" << endl;
}
};
//Java页面
class Java : public BasePage {
public:
void content() {
cout << "JAVA学科视频" << endl;
}
};
//Python页面
class Python : public BasePage {
public:
void content() {
cout << "Python学科视频" << endl;
}
};
//C++页面
class CPP : public BasePage {
public:
void content() {
cout << "C++学科视频" << endl;
}
};
二、继承方式
继承方式一共有三种:公共继承、保护继承、私有继承。
2.1 public继承
class Base1 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共继承
class Son1 : public Base1 {
public:
void func() {
m_A = 10; //可访问 public权限
m_B = 10; //可访问 protected权限
// m_C; //不可访问
}
};
void myClass() {
Son1 s1;
s1.m_A; //ok,其他类只能访问到公共权限
// s1.m_B; //err,其他类只能访问到公共权限,m_B是protected,类外不可以访问
}
2.2 protected继承
//保护继承
class Base2 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2 : protected Base2 {
public:
void func() {
m_A; //可访问 protected权限
m_B; //可访问 protected权限
//m_C; //不可访问
}
};
void myClass2() {
Son2 s;
//s.m_A; //不可访问,此时m_A变成了protected
}
2.3 private继承
//私有继承
class Base3 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3 : private Base3 {
public:
void func() {
m_A; //可访问 private权限
m_B; //可访问 private权限
//m_C; //不可访问
}
};
class GrandSon3 : public Son3 {
public:
void func() {
//Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
// m_A;//err
// m_B;//err
// m_C;//err
}
};
2.4 继承规则
三、继承中的对象模型
父类中所有非静态成员属性都会被继承下去,私有成员属性只是访问不到,因为被编译器隐藏了,但是也被继承下去了。
class Base {
public:
int m_A;
protected:
int m_B;
private:
int m_C; //私有成员只是被隐藏了,但是还是会继承下去
};
//公共继承
class Son : public Base {
public:
int m_D;
};
void test01() {
cout << "sizeof Son = " << sizeof(Son) << endl;//16,说明private也被继承了
}
四、继承中的构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数,此时先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。
class Base {
public:
Base() {
cout << "Base构造函数!" << endl;
}
~Base() {
cout << "Base析构函数!" << endl;
}
};
class Son : public Base {
public:
Son() {
cout << "Son构造函数!" << endl;
}
~Son() {
cout << "Son析构函数!" << endl;
}
};
void test01() {
//继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
Son s;
}
五、继承同名成员处理方式
当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
- 访问子类同名成员,直接访问即可,也就是
对象.属性
- 访问父类同名成员,需要加作用域,也就是
对象.父类::属性
需要注意的一点是:当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数,否则无法访问。强调一下,只要函数名相同,其他的不管一不一样,都会被隐藏。
class Base {
public:
int m_A;
public:
Base() {
m_A = 100;
}
void func() {
cout << "Base - func()调用" << endl;
}
void func(int a) {
cout << "Base - func(int a)调用" << endl;
}
};
class Son : public Base {
public:
int m_A;
public:
Son() {
m_A = 200;
}
//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
void func() {
cout << "Son - func()调用" << endl;
}
};
void test01() {
Son s;
cout << "Son下的m_A = " << s.m_A << endl;
cout << "Base下的m_A = " << s.Base::m_A << endl;//加个作用域就可以访问父类的
s.func();
s.Base::func();
//子类中的同名成员函数会隐藏掉父类有所有同名成员函数,包括重载的,要想访问父类中被隐藏的同名成员函数的需要加作用域
// s.func(10);//err,父类中的有参函数被隐藏,而子类又没有有参函数,需要加作用域
s.Base::func(10);//ok
}
六、继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致,只不过有两种访问的方式:通过对象和通过类名。比非静态多了一个通过类名访问,这是静态成员本身的特性所导致的。
关于静态成员,可以参考「地表最强」C++核心编程(四)类和对象—对象初始化和清理第八点。
class Base {
public:
static int m_A;
static void func() {
cout << "Base - static void func()" << endl;
}
static void func(int a) {
cout << "Base - static void func(int a)" << endl;
}
};
int Base::m_A = 100;
class Son : public Base {
public:
static int m_A;
static void func() {
cout << "Son - static void func()" << endl;
}
};
int Son::m_A = 200;
//同名成员属性
void test01() {
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
//通过类名访问
cout << "通过类名访问: " << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
//第一个::代表通过类名访问,第二个::代表访问父类作用域下
cout << "Base 下 m_A = " << Son::Base::m_A << endl;//这样通过子类
// cout << "Base 下 m_A = " << Base::m_A << endl;//这是直接通过父类访问,也是对的
}
//同名成员函数
void test02() {
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
s.func();
s.Base::func();
//通过类名访问
cout << "通过类名访问: " << endl;
Son::func();
Son::Base::func();
//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
Son::Base::func(100);
// Son::func(100);//err,父类中的同名函数被隐藏了
}
七、多继承语法
C++允许一个类继承多个类,就是多继承。但多继承可能会引发从父类中继承多个同名成员,需要加作用域区分。C++实际开发中不建议用多继承。
语法: class 子类 : 继承方式 父类1 , 继承方式 父类2…
class Base1 {
public:
int m_A;
public:
Base1() {
m_A = 100;
}
};
class Base2 {
public:
int m_A;
public:
Base2() {
m_A = 200; //若是m_B则不会出问题,但是改为mA就会出现不明确
}
};
//语法:class 子类:继承方式 父类1 ,继承方式 父类2
class Son : public Base2, public Base1 {
public:
int m_C;
int m_D;
public:
Son() {
m_C = 300;
m_D = 400;
}
};
//多继承容易产生成员同名的情况,这是就要通过类名作用域来区分调用的是哪一个基类的成员
void test01() {
Son s;
cout << "sizeof Son = " << sizeof(s) << endl;
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;
}
八、菱形继承和虚继承
两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承被称为菱形继承,或者钻石继承。
菱形继承的问题:
1.羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
2.草泥马继承自动物的数据继承了两份,但这个据我们只需要一份,这导致资源浪费而且毫无意义。
通过虚继承就可以解决菱形继承带来的问题。
语法: class 子类 : virtual 继承方式 父类
virtual关键字使得继承方式变成了虚继承,此时两个子类从同一父类那里继承来的是虚基类指针vbptr(virtual vase pointer),这个指针指向了各自的vbtable(虚基类表),这个表中记录了一个偏移量,通过这个偏移量就可以找到需要的数据,这个数据只有一份。
class Animal {
public:
int m_Age;
};
//虚继承,此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {
};
class Tuo : virtual public Animal {
};
//羊驼类
class SheepTuo : public Sheep, public Tuo {
};
void test01() {
SheepTuo st;
//相同数据加作用域区分即可
st.Sheep::m_Age = 100;
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;//100
st.Tuo::m_Age = 200;//由于虚继承数据只有一份,操作这个会更改之前的赋值
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;//200,确实更改了
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;//200
cout << "st.m_Age = " << st.m_Age << endl;//由于virtual,这种访问方式可以了,否则是错误的
}