基本语法
继承是面向对象的三大特性之一
有些类与类之间存在特殊的关系,例如下图中
我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑使用继承的技术,减少重复的代码。
例如下面的代码
#include <iostream>
using namespace std;
class Java
{
public:
void header()
{
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、CPP……(公共分类列表)" << endl;
}
void content()
{
cout << "Java学习视频" << endl;
}
};
class Python
{
public:
void header()
{
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、CPP……(公共分类列表)" << endl;
}
void content()
{
cout << "Python学习视频" << endl;
}
};
class CPP
{
public:
void header()
{
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、CPP……(公共分类列表)" << endl;
}
void content()
{
cout << "CPP学习视频" << endl;
}
};
void test()
{
cout << "Java学习视频如下" << endl;
Java().header();
Java().left();
Java().footer();
Java().content();
cout << "--------------------------------" << endl;
cout << "Python学习视频如下" << endl;
Python().header();
Python().left();
Python().footer();
Python().content();
cout << "--------------------------------" << endl;
cout << "CPP学习视频如下" << endl;
CPP().header();
CPP().left();
CPP().footer();
CPP().content();
}
int main()
{
test();
return 0;
}
我们发现Java、Python、CPP中除了自己独有的函数外都含有三个相同的函数
这时我们就可以使用继承的技术将类中重复的部分作为基类,减少代码量
#include <iostream>
using namespace std;
class Base
{
public:
void header()
{
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、CPP……(公共分类列表)" << endl;
}
};
class Java : public Base
{
public:
void content()
{
cout << "Java学习视频" << endl;
}
};
class Python : public Base
{
public:
void content()
{
cout << "Python学习视频" << endl;
}
};
class CPP : public Base
{
public:
void content()
{
cout << "CPP学习视频" << endl;
}
};
void test()
{
cout << "Java学习视频如下" << endl;
Java().header();
Java().left();
Java().footer();
Java().content();
cout << "--------------------------------" << endl;
cout << "Python学习视频如下" << endl;
Python().header();
Python().left();
Python().footer();
Python().content();
cout << "--------------------------------" << endl;
cout << "CPP学习视频如下" << endl;
CPP().header();
CPP().left();
CPP().footer();
CPP().content();
}
int main()
{
test();
return 0;
}
从上面的代码中我们可以看到
继承的语法为: class 子类: 继承方式 父类
其中子类又称为 派生类
父类又称为 基类
继承方式
继承方式与权限类似 一共有三种:
- 公共继承
- 保护继承
- 私有继承
从上表我们可以看到:
父类中私有权限的内容在子类中不管用什么继承方式都是不能被访问的,其余的权限内容的改变与不同的继承方式之间的关系如下表:
继承方式 | 父类权限 | 子类权限 |
公共继承 | 公共权限,保护权限 | 公共权限,保护权限 |
保护继承 | 公共权限,保护权限 | 保护权限 |
私有继承 | 公共权限,保护权限 | 私有权限 |
#include <iostream>
using namespace std;
class Base
{
public:
int m_A = 10;
protected:
int m_B = 10;
private:
int m_C = 10;
};
class Son1 : public Base
{
public:
void func()
{
m_A = 100;
m_B = 100;
// m_C = 100;
}
};
class Son2 : protected Base
{
public:
void func()
{
m_A = 100;
m_B = 100;
// m_C = 100;
}
};
class Son3 : private Base
{
public:
void func()
{
m_A = 100;
m_B = 100;
// m_C = 100;
}
};
class GrandSon : public Son3
{
public:
void func()
{
// m_A = 1000;
// m_B = 1000;
}
};
void test()
{
cout << Son1().m_A << endl;
// cout << Son1().m_B << endl;
// cout << Son2().m_A << endl;
// cout << Son2().m_B << endl;
}
int main()
{
test();
return 0;
}
继承中的对象模型与开发人员命令工具使用
问题:从父类继承过来的成员,哪些属于子类对象中?
我们通过代码来看看
#include <iostream>
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son : public Base
{
public:
int m_D;
};
void test()
{
std::cout << sizeof(Son) << std::endl;
}
int main()
{
test();
return 0;
}
代码运行结果为 16 说明父类中的所有成员均被子类继承,包括不能被访问的私有权限成员
我们也可以用vs的开发人员命令工具来查看Son类的成员结构
视频中演示的时2019版的,我的是2022版
首先在vs2022根目录搜索Developer Command Prompt
如果你的源文件不在C盘,开始我们需要跳转盘符,我的在D盘,输入D: 回车
跳转到D盘后在跳转到程序目录下,输入cd,空格输入程序目录 回车进入程序目录
输入dir查看当前目录的程序
接下来输入cl /d1 reportSingleClassLayoutSon(报告单个类的布局 类名)ConsoleApplication4.cpp(程序名(可以输入名称部分后tap键补齐)) 回车
我们可以看到Son类继承Base之后类的结构 包含了Base中的所有成员
继承中构造和析构顺序
子类继承父类之后,当创建子类对象,也会调用父类的构造函数
问题是:父类和子类的构造和析构顺序是谁先谁后?
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
cout << "父类构造函数调用" << endl;
}
~Base()
{
cout << "父类析构函数调用" << endl;
}
};
class Son : public Base
{
public:
Son()
{
cout << "子类构造函数调用" << endl;
}
~Son()
{
cout << "子类析构函数调用" << endl;
}
};
void test()
{
Son();
}
int main()
{
test();
return 0;
}
通过调用上面的代码,我们可以得到下面的结果:
可以看到,在类的继承中,父类的构造先于子类的构造,子类的析构先于父类的析构
继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问子类或父类中的同名数据呢?
- 访问子类同名成员 直接访问即可
- 访问父类同名成员 需要加作用域
#include <iostream>
using namespace std;
class Base
{
public:
int m_A = 10;
};
class Son : public Base
{
public:
int m_A = 100;
};
void test()
{
Son s;
cout << s.m_A << endl;
cout << s.Base::m_A << endl;
}
int main()
{
test();
return 0;
}
在上面代码中,s.m_A会直接访问子类中的成员,而在对象的点元素符后加上父类作用域,就可以访问父类中的成员。
需要注意的是,如果子类中没有与父类同名的成员,s.m_A会直接访问父类中的成员。
如下面代码,调用test函数得到结果 10
class Base
{
public:
int m_A = 10;
};
class Son : public Base
{
};
void test()
{
Son s;
cout << s.m_A << endl;
}
成员函数的调用与此相同
#include <iostream>
using namespace std;
class Base
{
public:
int m_A = 10;
void func()
{
cout << "Base func 调用" << endl;
}
};
class Son : public Base
{
public:
int m_A = 100;
void func()
{
cout << "Son func 调用" << endl;
}
};
void test()
{
Son s;
cout << s.m_A << endl;
cout << s.Base::m_A << endl;
s.func();
s.Base::func();
}
int main()
{
test();
return 0;
}
调用结果如下:
需要注意的是:当子类中出现了与父类中同名的函数,父类中的所有同名函数都会被屏蔽,无法直接访问,也不能通过函数重载方式直接访问。
例如下面的代码:
#include <iostream>
using namespace std;
class Base
{
public:
int m_A = 10;
void func()
{
cout << "Base func 调用" << endl;
}
void func(int a)
{
cout << "Base func(int a) 调用" << endl;
}
};
class Son : public Base
{
public:
int m_A = 100;
void func()
{
cout << "Son func 调用" << endl;
}
};
void test()
{
Son s;
cout << s.m_A << endl;
cout << s.Base::m_A << endl;
s.func();
s.Base::func();
// s.func(10); //无法通过函数重载直接调用父类中的同名函数
s.Base::func(10);
}
int main()
{
test();
return 0;
}
运行结果如下:
总结:
- 子类对象可以直接访问到子类中同名成员。
- 子类对象加作用域可以访问到父类同名成员。
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域才可访问同名函数。
继承同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上是如何进行访问的?
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员,直接访问
- 访问父类同名成员,加作用域
#include <iostream>
using namespace std;
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base func()函数调用" << endl;
}
static void func(int a)
{
cout << "Base func(int)函数调用" << endl;
}
};
class Son : public Base
{
public:
static int m_A;
static void func()
{
cout << "Son func()函数调用" << endl;
}
};
int Base::m_A = 10;
int Son::m_A = 100;
void test()
{
Son s;
cout << s.m_A << endl;
cout << s.Base::m_A << endl;
cout << Son::m_A << endl;
cout << Son::Base::m_A << endl;
s.func();
// s.func(10); //父类中与子类同名的函数都不能直接访问,只能通过加作用域访问。
s.Base::func(10);
Son::func();
Son::Base::func();
}
int main()
{
test();
return 0;
}
从上面的代码可以看到,静态成员变量与继承同名变量的处理相互之间没有冲突的地方。
需要注意的主要有以下几点:
- 静态成员变量需要类内声明类外初始化
- 静态成员变量可以通过对象进行访问也可以直接通过类名进行访问
- 父类与子类出现同名成员时,子类直接访问,父类加作用域
- 静态成员变量通过类名访问时,要先访问子类作用域在访问父类作用域
多继承语法
c++允许一个类继承多个类
语法:class 子类 : 继承方式 父类1,继承方式 父类2……
多继承可能会引发父类中有同名成员出现,需要加作用域区分
c++实际开发中不建议使用多继承
#include <iostream>
using namespace std;
class Base1
{
public:
Base1()
{
m_A = 10;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 20;
}
int m_A;
};
class Son : public Base1, public Base2
{
public:
Son()
{
m_C = 30;
m_D = 40;
}
int m_C;
int m_D;
};
void test()
{
Son s;
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;
}
int main()
{
test();
return 0;
}
在上面的代码中,Son的父类Base1和Base2有相同的成员变量m_A,再用Son访问时就要加上父类作用域加以区分。
菱形继承
菱形继承概念
- 两个派生类继承同一个基类
- 又有某个类同时继承两个派生类
这种继承被称为菱形继承,或者钻石继承
菱形继承经典案例:
菱形继承问题:
- 羊继承了动物的数据,驼继承了动物的数据,当羊驼使用数据时,就会产生二义性
- 羊驼继承了两份来自动物的数据,但其实这份数据我们只需要一份就可以
#include <iostream>
using namespace std;
class Animal
{
public:
int m_Age;
};
class Sheep : public Animal{};
class Tuo : public Animal{};
class SheepTuo : public Sheep, public Tuo{};
void test()
{
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;
}
int main()
{
test();
return 0;
}
例如在上面的代码中,Animal类中的m_Age变量被Sheep类和Tuo类继承,而SheepTuo类多继承了上面两类,使得SheepTuo类中有两个m_Age类
这时我们访问这个变量就要加其中一个父类的作用域,但实际上这个数据我们在羊驼类中只需要一份就可以,这时我们就需要在继承方式前加关键字virtual,
#include <iostream>
using namespace std;
class Animal
{
public:
int m_Age;
};
class Sheep : virtual public Animal{};
class Tuo : virtual public Animal{};
class SheepTuo : public Sheep, public Tuo{};
void test()
{
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()
{
test();
return 0;
}
这时称Animal类为虚基类,Sheep,Tuo类的继承方式为虚继承,
此时这四个类就共享一个m_Age变量,避免了空间的浪费
我们再通过开发人员命令工具看一下SheepTuo的类布局
可以看到Sheep原来的int类型变成了vbptr(virtual base pointer)虚基类指针, 指向下面的vbtable(virtual base table)虚基类表,这时通过SheepTuo访问成员变量时就会通过虚基类指针获得虚基类表中的偏移量,通过偏移量访问唯一的,Animal类中的m_Age成员变量。