目录
1.继承的概述
1.1概念
继承是面向对象三大特性之一。我们发现在定义类的时候,下级别的成员除了拥有上一级别的的共性,还有自己的特性,这时候我们可以考虑使用继承的技术来减少重复的代码。
1.2继承的基本用法
例如我们看到很多的网站中,都有公共的头部、公共的底部、甚至公共的左侧列表,只有中心内容不同,接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处。
#include<iostream>
using namespace std;
class java {//java网站类
public:
void header() {
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void foter() {
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left(){
cout << "Java、python、C++……(公共分类列表)" << endl;
}
void content() {
cout << "java学科视频" << endl;
}
};
//Python页面
class Python {//java网站类
public:
void header() {
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void foter() {
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left() {
cout << "Java、python、C++……(公共分类列表)" << endl;
}
void content() {
cout << "Python学科视频" << endl;
}
};
//C++学习页面
class Cpp{//java网站类
public:
void header() {
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void foter() {
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left() {
cout << "Java、python、C++……(公共分类列表)" << endl;
}
void content() {
cout << "C++学科视频" << endl;
}
};
void test02() {
java ja;
cout << "java下载视频页面如下" << endl;
ja.header();
ja.foter();
ja.left();
ja.content();
cout << "-----------------------" << endl;
cout << "Python下载视频页面如下" << endl;
Python py;
py.header();
py.foter();
py.left();
py.content();
cout << "-----------------------" << endl;
cout << "C++下载视频页面如下" << endl;
Cpp cp;
cp.header();
cp.foter();
cp.left();
cp.content();
}
void main() {
test02();
}
可以看到,代码没有问题,但是有太多的冗余代码,这样看起来太low。所以我们可以考虑使用继承的方式:
class BasePage {//公共的界面
public:
void header() {
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void foter() {
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;
}
};
void test02() {
Java ja;
cout << "java下载视频页面如下" << endl;
ja.header();
ja.foter();
ja.left();
ja.content();
cout << "-----------------------" << endl;
cout << "Python下载视频页面如下" << endl;
Python py;
py.header();
py.foter();
py.left();
py.content();
cout << "-----------------------" << endl;
cout << "C++下载视频页面如下" << endl;
Cpp cp;
cp.header();
cp.foter();
cp.left();
cp.content();
}
void main() {
test02();
}
1.3继承的好处
减少重复冗余的代码
1.4语法
class 子类:继承方式 父类 ,如下所示:
子类:也成为派生类,父类也称为基类
2.继承方式
2.1继承语法
class 子类:继承方式 父类
2.2继承方式
- 公共继承
- 保护继承
- 私有继承
解析:
- 子类不能继承父类的私有属性
- 子类如果是公共继承,在父类中是公共的属性在子类中同样是,在父类中是保护继承的属性,在子类中也是如此
- 如果子类是保护继承,父类中的公共属性,在子类中变为保护权限,父类保护,子类也是保护
- 如果子类是私有继承父类中的公共权限和保护权限属性,在子类中都变为私有权限
#include<iostream>
using namespace std;
//公共继承
class Base1 {
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son :public Base1 {
public:
void func() {
m_A = 10;//没有报错说明父类中的公共类成员在子类中依然是公共权限
m_B = 10;//没有报错说明父类中的保护类成员在子类中依然是保护权限
//m_C = 10;报错了,说明父类中的私有属性,子类拿不到
}
int m_D;
};
void test02() {
Son s1;
s1.m_A = 100;//m_A是公共权限,类内可以访问,类外也可以访问
//s1.m_B = 100;m_B是保护权限,类内可以访问,但是类外不可访问
cout << "size of Son =" << sizeof(Son) << endl;
}
void main() {
test02();
}
我们也可以利用开发人员命令提示工具查看对象模型(VS自带的Developer Command Prompt)
- 查看需要运行的程序在哪个盘,如果在D盘就输入D:就跳转到D盘
- 跳转文件的路径:cd 具体的路径
- 查看对象模型输入:cl /d1 reportSingleClassLayout类名 “文件名”
如下所示:
3.继承中的构造与析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序谁先谁后呢?
#include<iostream>
using namespace std;
//继承中的构造与析构的顺序
class Base1 {
public:
Base1() {
cout << "Base1的构造函数" << endl;
}
~Base1() {
cout << "Base1的析构函数" << endl;
}
};
class Son :public Base1 {
public:
Son() {
cout << "Son的构造函数" << endl;
}
~Son() {
cout << "Son的析构函数" << endl;
}
};
void test01() {
//Base1 b;
Son s;
}
void main() {
test01();
}
可以看出,继承中的构造与析构顺序如下:
先构造父类,再构造子类,析构的顺序与构造的顺序相反
4.继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或者父类中的同名的数据?
- 访问子类同名成员 直接访问即可
- 访问父类的同名成员需要加作用域
4.1继承同名成员属性处理方式
#include<iostream>
using namespace std;
//继承中的构造与析构的顺序
class Base1 {
public:
Base1() {
m_A = 100;
}
int m_A ;
};
class Son :public Base1 {
public:
Son() {
m_A = 200;
}
int m_A;
};
void test01() {
Son s;
cout << "Son m_A=" <<s. m_A << endl;
}
void main() {
test01();
}
可以看出如果直接访问,m_A是200,说明如果出现同名直接访问是访问的自身的成员。
如果需要拿到父类的成员:
只需要加一个父类的作用域即可访问到同名中父类的属性。
4.2继承同名成员函数处理方式
#include<iostream>
using namespace std;
//继承中的构造与析构的顺序
class Base1 {
public:
Base1() {
m_A = 100;
}
void func() {
cout << "这是base中的函数调用" << endl;
}
int m_A ;
};
class Son :public Base1 {
public:
Son() {
m_A = 200;
}
void func() {
cout << "这是son中的函数调用" << endl;
}
int m_A;
};
void test01() {
Son s;
cout << "Son m_A=" <<s. m_A << endl;
cout << "Base m_A=" << s.Base1::m_A << endl;
}
void test02() {
Son s;
s.func();//当出现重名调用的还是子类的
s.Base1::func();//调用父类的成员函数
}
void main() {
test01();
test02();
}
5.同名静态成员处理
问题:继承同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一样。
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
静态成员的属性特点:
- 编译阶段分配内存
- 所有对象共享一份数据
- 类内声明,类外要初始化
#include<iostream>
using namespace std;
class Base1 {
public:
static int m_A ;//类内声明
};
int Base1::m_A = 100;//类外初始化
class Son :public Base1 {
public:
static int m_A;
};
int Son::m_A = 200;
void test01() {
Son s;
cout << "Son m_A=" << s.m_A << endl;
}
void main() {
test01();
}
可以看到直接访问是访问的子类的,要想访问父类需要加一个作用域
访问可以通过对象访问,也可以通过类名访问,如下:
#include<iostream>
using namespace std;
class Base1 {
public:
static int m_A ;//类内声明
};
int Base1::m_A = 100;//类外初始化
class Son :public Base1 {
public:
static int m_A;
};
int Son::m_A = 200;
void test01() {
//通过对象访问
Son s;
cout << "Son m_A=" << s.m_A << endl;
cout << "Base m_A=" << s.Base1::m_A << endl;
//通过类名访问
cout << "通过类名访问:" << endl;
cout << "Son 下 m_A=" << Son::m_A<<endl;
cout << "Base 下 m_A=" << Son::Base1::m_A << endl;
}
void main() {
test01();
}
继承静态的成员属性以及成员函数的完整代码如下:
#include<iostream>
using namespace std;
class Base1 {
public:
static void func() {
cout << "Base static void func()" << endl;
}
static int m_A ;//类内声明
};
int Base1::m_A = 100;//类外初始化
class Son :public Base1 {
public:
static void func() {
cout << "Son static void func()" << endl;
}
static int m_A;
};
int Son::m_A = 200;
void test01() {
//通过对象访问
Son s;
cout << "Son m_A=" << s.m_A << endl;
cout << "Base m_A=" << s.Base1::m_A << endl;
//通过类名访问
cout << "通过类名访问:" << endl;
cout << "Son 下 m_A=" << Son::m_A<<endl;
cout << "Base 下 m_A=" << Son::Base1::m_A << endl;
}
void test02() {
//通过对象访问
cout << "通过对象访问" << endl;
Son s;
s.func();
s.Base1::func();
//通过类访问
cout << "通过类访问" << endl;
Son::func();
Son::Base1::func();
}
void main() {
test01();
cout << "***********************" << endl;
test02();
}
6.多继承语法
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_B = 200;
}
int m_B;
};
//子类继承Base1和Base2
class Son :public Base1,public Base2{
public:
Son() {
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
void test01() {
Son s;
cout << "sizeof Son=" << sizeof(s) << endl;
}
void main() {
test01();
}
我们可以看看类的结构,具体如何打开的见上一个博客。
我们可以看出size是16,Son这个类,在Base1中继承了m_A在Base2中继承了m_B,自己还有m_C和m_D。
7.菱形继承案例
7.1菱形继承概念
- 两个派生类继承同一个基类
- 又有某个类同时继承两个派生类
- 这种继承称为菱形继承,或者钻石继承
例如:羊和骆驼继承了动物这个类,草泥马这个动物又继承了羊和骆驼这个类,若动作有一个m_A这个属性,那么羊和骆驼也继承了动物类中的m_A,那么草泥马这个类就有两份m_A,而我们只需要一份即可,那怎么办?
#include<iostream>
using namespace std;
//动物类
class Animal {
int m_Age;
};
//羊类
class Sheep:public Animal {
};
//骆驼类
class Camel:public Animal {
};
//羊驼类(草泥马类)
class SheepCamel :public Sheep, public Camel {
};
void test01() {
SheepCamel sc;
sc.m_Age = 10;
}
void main() {
test01();
}
test01中直接对m_Age=10赋值会出现“SheepCamel m_Age不明确”的错误。所以:当菱形继承,两个父类拥有相同的数据,需要加以作用域区分
那么草泥马(羊驼)的m_Age到底是多少呢,我们只需要一份数据就可以了。我们打出报告可以看到:SheepCamel继承了两个类,一个是Sheep一个是Camel,有两个m_Age,但是我们只需要一个m_Age那么该如何解决呢?
利用虚继承可以解决菱形继承的问题。即为,在继承之前加一个关键字virtual变为虚继承。
#include<iostream>
using namespace std;
//动物类
class Animal {
public:
int m_Age;
};
//利用虚继承解决菱形继承问题,继承之前加上关键词virtual变为虚继承
//Animal类称为虚基类
//羊类
class Sheep:virtual public Animal {
};
//骆驼类
class Camel:virtual public Animal {
};
//羊驼类(草泥马类)
class SheepCamel :public Sheep, public Camel {
};
void test01() {
SheepCamel sc;
sc.Sheep::m_Age = 10;
sc.Camel::m_Age = 28;
//当菱形继承,两个父类拥有相同的数据,需要加以作用域区分
cout << "sc.Sheep::m_Age = " << sc.Sheep::m_Age << endl;
cout << "sc.Camel::m_Age=" << sc.Camel::m_Age << endl;
cout << "SheepCamel m_Age=" << sc.m_Age << endl;
}
void main() {
test01();
}
经过修改后发现年龄都变成28了。我们再看看报告:
可以看到与没有加关键词virtual时的结构完全不一样了。可以看到SheepCamel的m_Age只有一份了,而从Sheep和Camel继承下来的是vbptr。vbptr(virtual base pointer)表示虚基类指针