继承是面向对象的三大特性之一
有些类与类之间存在特殊关系,例如下图:
我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码
1. 继承的基本语法
我们看到的很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同
接下来我分别利用普通的写法和继承写法来实现网页中的内容,看一下继承存在的意义和好处
普通实现:
普通实现页面
Java页面
class Java
{
public:
void header()
{
cout << "首页、公共课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(公共分类列表)" << endl;
}
void content()
{
cout << "Java学科视频" << endl;
}
};
//Python页面
class Python
{
public:
void header()
{
cout << "首页、公共课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};
//C++页面
class Cpp
{
public:
void header()
{
cout << "首页、公共课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(公共分类列表)" << endl;
}
void content()
{
cout << "C++学科视频" << endl;
}
};
继承实现:
//继承实现页面
//公共页面类
class BasePage
{
public:
void header()
{
cout << "首页、公共课、登陆、注册...(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++...(公共分类列表)" << endl;
}
};
//继承的好处:减少重复的代码
//语法:class 子类名 : 继承方式 父类名
//子类 也称为 派生类
//父类 也称为 基类
//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;
}
};
总结:
继承的好处:可以减少重复的代码
class A : public B;
A 类称为 子类 或 派生类
B 类称为 父类 或 基类
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过来的表现其共性,而新增的成员体现了其个性。
2. 继承方式
继承的语法:class 子类 : 继承方式 父类
继承的方式一共有三种:
- 公共继承(无法访问父类的私有内容,其余内容正常访问且不改变权限)
- 保护继承(无法访问父类的私有内容,其余都变为保护权限)
- 私有继承(无法访问父类的私有内容,其余都变为私有权限)
#include<iostream>
using namespace std;
//继承方式
//公共继承
class Base_1
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son_1 : public Base_1
{
public:
void func()
{
m_a = 10; //父类中的公共权限成员,到子类中依然是公共权限
m_b = 20; //父类中的保护权限成员,到子类中依然是保护权限
// m_c = 30; //父类中的私有权限成员,子类访问不到
}
};
//保护继承
class Base_2
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son_2 : protected Base_2
{
public:
void func()
{
m_a = 50; //父类中的公共权限成员,到子类变为保护权限
m_b = 60; //父类中的保护权限成员,到子类中依然是保护权限
// m_c = 30; //父类中的私有权限成员,子类访问不到
}
};
//私有继承
class Base_3
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class Son_3 : private Base_3
{
public:
void func()
{
m_a = 50; //父类中的公共权限成员,到子类变为私有权限
m_b = 60; //父类中的保护权限成员,到子类变为私有权限
// m_c = 30; //父类中的私有权限成员,子类访问不到
}
};
void test01()
{
Son_1 s1;
s1.m_a = 100;
// s1.m_b = 200; //Son_1中,m_b是保护权限,类外访问不到
}
void test02()
{
Son_2 s2;
// s2.m_a = 500; //Son_2中,m_a变为保护权限,类外访问不到
// s2.m_b = 200; //Son_2中,m_b是保护权限,类外访问不到
}
void test03()
{
Son_3 s3;
// s3.m_a = 500; //Son_3中,m_a变为私有权限,类外访问不到
// s3.m_b = 200; //Son_3中,m_b变为私有权限,类外访问不到
}
class GrandSon_3 : public Son_3
{
public:
void func()
{
// m_a = 1000; //Son_3中,m_a为私有权限,子类访问不到
// m_b = 2000; //Son_3中,m_b为私有权限,子类访问不到
}
};
int main()
{
test01();
test02();
test03();
return 0;
}
3.继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象?
示例:
#include<iostream>
using namespace std;
//继承中的对象模型
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son : public Base
{
public:
int m_D;
};
void test01()
{
//16
//父类中的所有非静态成员属性都会被子类继承
//父类中的私有成员属性 被编译器 隐藏了,虽然访问不到,但是仍然继承
cout << "size of Son = " << sizeof(Son) << endl;
//12
cout << "size of Base = " << sizeof(Base) << endl;
}
int main()
{
test01();
return 0;
}
输出:
---------------------------------------------------------------------------------
size of Son = 16
size of Base = 12
在windows系统下,可以利用开发人员命令提示工具查看对象模型,步骤如下:
- 打开vscode的开发人员命令提示符
- 跳转到项目文件所在的盘符
F:
- 跳转文件路径
cd 具体路径
cl /d1 reportSingleClassLayout类名 文件名
效果如下图所示:
4. 继承中构造和析构的顺序
子类继承父类后,当创建子类对象时,也会调用父类的构造函数
问题:父类和子类的构造函数和析构函数的顺序是谁先谁后?
示例:
#include<iostream>
using namespace std;
//继承中构造和析构的顺序
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;
}
int main()
{
test01();
return 0;
}
输出:
---------------------------------------------------------------------------------
Base的构造函数
Son的构造函数
Son的析构函数
Base的析构函数
总结:继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
5. 继承时同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
示例:
#include<iostream>
using namespace std;
//继承中同名成员处理
class Base
{
public:
Base()
{
m_a = 100;
}
void func()
{
cout << "Base类的func()函数调用" << endl;
}
void func(int a)
{
cout << "Base类的func(int a)函数调用" << endl;
}
int m_a;
};
class Son : public Base
{
public:
Son()
{
m_a = 200;
}
void func()
{
cout << "Son类的func()函数调用" << endl;
}
int m_a;
};
//成员同名属性的处理方式
void test01()
{
Son s;
//默认访问子类中的同名成员
cout << "Son 类中的 m_a = " << s.m_a << endl;
//如果想通过子类对象访问到父类中的同名成员,需要加作用域
cout << "Base 类中的 m_a = " << s.Base::m_a << endl;
}
//成员同名函数的处理方式
void test02()
{
Son s;
//默认调用子类中的同名成员
s.func();
//如果想通过子类对象访问到父类中的同名成员,需要加作用域
s.Base::func();
s.Base::func(100);
}
int main()
{
test01();
test02();
return 0;
}
输出:
---------------------------------------------------------------------------------
Son 类中的 m_a = 200
Base 类中的 m_a = 100
Son类的func()函数调用
Base类的func()函数调用
Base类的func(int a)函数调用
总结:
- 子类对象可以直接访问到子类中的同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类对象与父类拥有同名的成员函数,子类会隐藏父类中的同名成员函数,加作用域可以访问到父类中的同名函数
6. 继承中同名静态成员的处理方式
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员对象的特点:
- 所有对象共享同一份数据
- 编译阶段就分配内存
- 类内声明,类外初始化
静态成员函数的特点:
- 只能访问静态成员变量,不能访问非静态成员变量
- 所有函数共享同一份函数实例
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用欲
示例:
#include<iostream>
using namespace std;
//继承中同名静态成员的处理方式
class Base
{
public:
static int m_a;
static void func()
{
cout << "Base::static void func()" << 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()
{
//1、通过对象访问
cout << "通过对象访问:" << endl;
Son s;
cout << "Son 类中的 m_a = " << s.m_a << endl;
cout << "Base 类中的 m_a = " << s.Base::m_a << endl;
//2、通过类名访问
cout << "通过类名访问:" << endl;
cout << "Son 类中的 m_a = " << Son::m_a << endl;
//第一个::代表通过类名方式访问,第二个::代表访问父类作用域
cout << "Base 类中的 m_a = " << Son::Base::m_a << endl;
}
// 同名静态成员函数
void test02()
{
//1、通过对象访问
cout << "通过对象访问:" << endl;
Son s;
//默认调用子类中的同名成员
s.func();
//如果想通过子类对象访问到父类中的同名成员,需要加作用域
s.Base::func();
//2、通过类名访问
cout << "通过类名访问:" << endl;
Son::func();
Son::Base::func();
}
int main()
{
test01();
cout << "-----------------------------" << endl;
test02();
return 0;
}
输出:
---------------------------------------------------------------------------------
通过对象访问:
Son 类中的 m_a = 200
Base 类中的 m_a = 100
通过类名访问:
Son 类中的 m_a = 200
Base 类中的 m_a = 100
-----------------------------
通过对象访问:
Son::static void func()
Base::static void func()
通过类名访问:
Son::static void func()
Base::static void func()
总结:同名静态成员变量和非静态成员变量的处理方式一样,只不过有两种访问方式(通过对象和通过类名)
7. 多继承语法
C++允许一个类继承多个类
语法:class 子类 : 继承方式 父类1, 继承方式 父类2, ...
多继承可能会遇到不同父类中出现同名成员的情况,此时需要加作用域区分
C++实际开发当中,不建议用多继承
示例:
#include<iostream>
using namespace std;
//多继承语法
class Base1
{
public:
Base1()
{
m_a = 100;
}
int m_a;
};
class Base2
{
public:
Base2()
{
m_a = 200;
}
int m_a;
};
class Son : public Base1, public Base2
{
public:
Son()
{
m_c = 300;
m_d = 400;
}
int m_c;
int m_d;
};
//同名静态成员属性
void test01()
{
Son s1;
//16
cout << "size of Son = " << sizeof(Son) << endl;
//当父类中有同名成员时,需要加作用域加以区分
cout << "Base1::m_a = " << s1.Base1::m_a << endl;
cout << "Base2::m_a = " << s1.Base2::m_a << endl;
}
int main()
{
test01();
return 0;
}
输出:
---------------------------------------------------------------------------------
size of Son = 16
Base1::m_a = 100
Base2::m_a = 200
总结:多继承中如果出现父类中成员同名的情况,子类使用时要加作用域
8. 菱形继承
菱形继承概念:
- 两个派生类继承同一个基类
- 又有某个类同时继承这两个派生类
- 这种继承方式被称为菱形继承,也称为钻石继承
菱形继承存在的问题:
- 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性
- 草泥马继承了两次动物的数据,这是一种内存浪费,实际上只需要保留一份就可以
示例:
#include<iostream>
using namespace std;
//动物类
class Animal
{
public:
int m_age;
};
//利用虚继承,解决菱形继承的问题
//在继承之前,加上关键字 virtual 变为虚继承,此时 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 = 18;
st.Tuo::m_age = 28;
//菱形继承时,如果访问同一父类的成员变量,需要指出访问哪一个父类的成员变量
cout << "st.Sheep::m_age = " << st.Sheep::m_age << endl;
cout << "st.Tuo::m_age = " << st.Tuo::m_age << endl;
//菱形继承同样的数据保存了两份,导致资源浪费
cout << "st.m_age = " << st.m_age << endl;
}
int main()
{
test01();
return 0;
}
输出:
---------------------------------------------------------------------------------
st.Sheep::m_age = 28
st.Tuo::m_age = 28
st.m_age = 28
普通继承(保留两份相同的m_Age数据)
虚继承(只保留一份m_Age数据)
vbptr:即虚基类指针,指向虚基类表vbtable
v - virtual
b - base
ptr - pointer