继承是面向对象三大特性之一
有些类与类之间存在特殊的关系,例如下图中:
我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码
例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同
接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处
普通实现:
#include <iostream> using namespace std; // 普通实现界面 // Java页面 class Java { public: void header() { cout << "首页、公开课、登录、注册...(公共头部)"; } void footer() { cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; } void left() { cout << "Java、Python、C++...(公共列表信息)" << endl; } void content() { cout << "Java学科视频" << endl; } }; // Python页面 class Python { public: void header() { cout << "首页、公开课、登录、注册...(公共头部)"; } void footer() { cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; } void left() { cout << "Java、Python、C++...(公共列表信息)" << endl; } void content() { cout << "Python学科视频" << endl; } }; // C++页面 class CPP { public: void header() { cout << "首页、公开课、登录、注册...(公共头部)"; } void footer() { cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; } void left() { cout << "Java、Python、C++...(公共列表信息)" << endl; } void content() { cout << "C++ 学科视频" << endl; } }; void test01() { cout << "Java 下载视频页面如下:" << endl; Java ja; ja.header(); ja.footer(); ja.left(); ja.content(); cout << "--------------------" << endl; cout << "Python 下载视频页面如下:" << endl; Python py; py.header(); py.footer(); py.left(); py.content(); cout << "--------------------" << endl; cout << "C++ 下载视频页面如下:" << endl; CPP Cpp; Cpp.header(); Cpp.footer(); Cpp.left(); Cpp.content(); } int main() { test01(); system("pause"); return 0; }
继承实现:
#include <iostream> using namespace std; // 继承实现页面 // 公共页面类 class BasePage { public: void header() { cout << "首页、公开课、登录、注册...(公共头部)"; } 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; } }; void test01() { cout << "Java 下载视频页面如下:" << endl; Java ja; ja.header(); ja.footer(); ja.left(); ja.content(); cout << "--------------------" << endl; cout << "Python 下载视频页面如下:" << endl; Python py; py.header(); py.footer(); py.left(); py.content(); cout << "--------------------" << endl; cout << "C++ 下载视频页面如下:" << endl; CPP Cpp; Cpp.header(); Cpp.footer(); Cpp.left(); Cpp.content(); } int main() { test01(); system("pause"); return 0; }
总结:
继承的好处:可以减少重复的代码
class A : public B;
A 类称为子类 或 派生类
B 类称为父类 或 基类
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。
继承的语法:class 子类 : 继承方式 父类
继承方式一共有三种:
示例:
#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 = 20; // 父类中的保护权限成员 到子类中依然是保护权限成员 // m_C = 10; // 父类中的私有权限成员 子类访问不到 } }; void test01() { Son s1; s1.m_A = 100; // s1.m_B = 100; // 到了 Son1 中 m_B 是保护权限 类外访问不到 } // 保护继承 class Base2 { public: int m_A; protected: int m_B; private: int m_C; }; class Son2 : protected Base2 { public: void func() { m_A = 100; // 父类中公共成员,到子类中变成保护成员 m_B = 100; // 父类中保护成员,到子类中变成保护成员 // m_C = 100; // 父类中私有成员 子类访问不到 } }; void test02() { Son2 s1; // s1.m_A = 1000; // 在 Son2 中 m_A变为保护权限,因此类外访问不到 // s1.m_B = 1000; // 在 Son2 中 m_B变为保护权限,因此类外访问不到 } // 私有继承 class Base3 { public: int m_A; protected: int m_B; private: int m_C; }; class Son3 : private Base3 { public: void func() { m_A = 100; // 父类中公共成员,到子类中变成私有成员 m_B = 100; // 父类中保护成员,到子类中变成私有成员 // m_C = 100; // 父类中私有成员,子类访问不到 } }; void test03() { Son3 s1; // s1.m_A = 1000; // 到 Sno3 中 变成私有成员 类外访问不到 // s1.m_B = 1000; // 到 Sno3 中 变成私有成员 类外访问不到 } class GrandSon3 : public Son3 { public: void func() { // m_A = 1000; // 到了 Son3 中 m_A 变为了私有,即使是儿子,也是访问不到的 // m_B = 1000; // 到了 Son3 中 m_A 变为了私有,即使是儿子,也是访问不到的 } }; int main() { system("pause"); return 0; }
问题:从父类继承过来的成员,哪些属于子类对象中?
示例:
#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; }; // 利用开发人员命令提示工具查看对象模型 // 查看命令 // cl /d1 reportSingleClassLayout类名 "文件名.cpp" void test01() { // 16 // 父类中所有非静态成员属性都会被子类继承下去 // 父类中私有成员属性 是被编译器隐藏了,因此访问不到,但是确实被继承下去了 cout << "sizeof Son = " << sizeof(Son) << endl; } int main() { test01(); system("pause"); return 0; }
利用工具查看:
打开工具窗口后,定位到当前CPP文件的盘符
然后输入: cl /d1 reportSingleClassLayout查看的类名 所属文件名
效果如下图:
结论: 父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后?
示例:
#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() { // Base b; // 继承中的构造和析构顺序如下: // 先构造父类,再构造子类,析构的顺序与构造的顺序相反 Son s; } int main() { test01(); system("pause"); return 0; }
总结:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
示例:
#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(); system("pause"); return 0; }
总结:
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
示例:
#include <iostream> using namespace std; // 继承中的同名静态成员处理方式 class Base { public: // 特点 // 1、编译期间分配内存 // 2、所有对象共享同一份数据 // 3、类内声明,类外初始化 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() { // 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(); // 子类出现和父类同名静态成员函数,也会隐藏父类中的所用同名成员函数 // 如果想访问父类中被隐藏同名成员,必须加作用域 Son::Base::func(100); } int main() { // test01(); test02(); system("pause"); return 0; }
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)
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; }; // 子类 需要继承 Base1 和 Base2 // 语法: class 子类 : 继承方法 父类, 继承方法 父类 ... 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 << "Son sizeof = " << sizeof(Son) << endl; // 当父类中出现同名成员,需要加作用域 cout << "Base1::m_A = " << s.Base1::m_A << endl; cout << "Base2::m_A = " << s.Base2::m_A << endl; } int main() { test01(); system("pause"); return 0; }
总结: 多继承中如果父类中出现了同名情况,子类使用时候要加作用域
菱形继承概念:
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承
典型的菱形继承案例:
菱形继承问题:
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
示例:
#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 = 20; // 当菱形继承,两个父类拥有相同数据,需要加以作用域 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(); system("pause"); return 0; }
结: