继承
继承是面向对象三大特性之一
有些类与类之间存在特殊的关系,例如下图中:
我们发现,定义这些类的时候,下级别的成员除了拥有上一级的共性,还有自己的特性。
这时候我们就可以考虑利用继承的技术,减少重复代码量。
1.继承的基本语法
例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同。
普通实现:
#include<iostream>
#include<string>
using namespace std;//普通实现页面//java页面
class Java
{
public:void header(){cout << "首页、登录注册、公开课、注册、..." << endl;}void footer(){cout << "帮助中心、交流合作、站内地图..." << endl;}void left(){cout << "java、python、c++..." << endl;}void contenet(){cout << "java学科视频" << endl;}
};
class Python
{
public:void header(){cout << "首页、登录注册" << endl;}void footer(){cout << "帮助中心、交流合作" << endl;}void left(){cout << "java、python、c++" << endl;}void contenet(){cout << "python学科视频" << endl;}
};
class Cpp
{
public:void header(){cout << "首页、登录注册" << endl;}void footer(){cout << "帮助中心、交流合作" << endl;}void left(){cout << "java、python、c++" << endl;}void contenet(){cout << "c++学科视频" << endl;}
};
void test()
{cout << "java页面" << endl;Java ja;ja.header();ja.footer();ja.left();ja.contenet();cout << endl;cout << "python页面" << endl;python py;py.header();py.footer();py.left();py.contenet();cout << endl;cout << "cpp页面" << endl;Cpp cpp;cpp.header();cpp.footer();cpp.left();cpp.contenet();
}
int main(void)
{test();system("pause");return 0;
}
继承实现:
#include<iostream>
#include<string>
using namespace std;//公共页面
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 contenet(){cout << "java学科视频" << endl;}
};
//Python 页面
class Python : public BasePage
{
public:void contenet(){cout << "python学科视频" << endl;}
};
//Cpp 页面
class Cpp : public BasePage
{
public: void contenet(){cout << "c++学科视频" << endl;}
};void test()
{cout << "java页面" << endl;Java ja;ja.header();ja.footer();ja.left();ja.contenet();cout << endl;cout << "python页面" << endl;python py;py.header();py.footer();py.left();py.contenet();cout << endl;cout << "cpp页面" << endl;Cpp cpp;cpp.header();cpp.footer();cpp.left();cpp.contenet();
}
int main(void)
{test();system("pause");return 0;
}
总结:
继承的好处:减少重复代码
语法:class 子类:继承方式 父类
子类也称派生类
父类也称基类
派生类中的成员,包含量大部分
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过来的表现其共性,而新增加的成员体现其个性。
2.继承方式
继承的语法——class 子类 :继承方式 父类
继承方式一共有三种:
- 公共继承
- 保护继承
- 私有继承
#include<iostream>
using namespace std;//公共继承
class Base1
{
public:int m_A;
protected:int m_B;
private:int m_C;
};
class Son1 :public Base1
{
public:void func(){m_A = 10;//父类中的公共权限成员,到了子类中依然是公共权限m_B = 20;//父类中的保护权限成员,到了子类中依然是保护权限//m_C = 10;父类中的隐私权限成员,子类访问不到}
};
void test01()
{Son1 son1;son1.m_A = 100;//son1.m_B = 100;保护权限的内容到了类外就无法访问了//私有的更不能访问
};//保护继承
class Base2
{
public:int m_A;
protected:int m_B;
private:int m_C;
};
class Son2 :protected Base2
{void func(){m_A = 100;//父类中公共权限的成员,因为是保护继承,到子类中变为保护权限m_B = 100;//父类中保护权限的成员,保护继承后到了子类还是保护权限。//m_C = 100;父类中的私有成员子类访问不到}
};
void test02()
{Son2 son2;//保护权限类外访问不到,所以在son2中m_A也访问不到了
}//私有继承
class Base3
{
public:int m_A;
protected:int m_B;
private:int m_C;
};
class Son3:private Base3
{void func(){m_A = 100;//父类中公共成员,私有继承后,到了子类变为私有成员m_B = 100;//父类中保护成员,私有继承后,到了子类变为私有成员//m_C = 100;父类的私有权限成员仍然访问不到}
};
void test03()
{Son3 son3;//私有成员类外访问不到
}
//验证Son3私有继承后成员是否变成了私有属性
class GrandSon3 :public Son3//孙子类继承儿子
{void func(){//访问不到父类的私有成员//到了Son3中m_A,m_B,m_C全是私有成员,子类无法访问}
};
int main(void)
{system("pause");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()
{//父类中所有的非静态成员属性都会被子类继承下去//父类中私有的成员属性是被编译器给隐藏了,因此访问不到,但是确实被继承下去了cout << "sizeof of son:" << sizeof(Son) << endl;//结果是16 = 12 + 4
}
int main(void)
{test01();system("pause");return 0;
}
利用VS的开发人员命令提示工具查看对象模型
- 打开工具
- 跳转到你cpp文件所在的盘
- cd文件目录下
- 输入命令:cl /d1 reportSingleClassLayout类名 文件名
4.继承中构造和析构的顺序
子类继承父类后,当创建子类时,也会调用父类的构造函数。
问题:父类和子类的构造函数和析构顺序怎么样的呢?
先构造父类,再构造子类
先析构子类,再析构父类
创建子类对象的同时也会创建一个父类对象。
#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 test01()
{Son son;
}
int main(void)
{test01();system("pause");return 0;
}
总结:继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。
5.继承同名成员处理方式
问题:当子类与父类出现同名的成员。如何通过子类对象,访问到子类或父类中同名的数据呢?
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
-
#include<iostream> using namespace std; class Base { public:Base(){m_A = 100;}void func(){cout << "父类同名成员函数调用" << endl;}void func(int a){cout << "父类同名重载成员函数调用" << endl;}int m_A; }; class Son:public Base { public:Son(){m_A = 200;}void func(){cout << "子类同名成员函数调用" << endl;}int m_A; }; //同名成员属性处理方式 void test01() {Son son;cout <<son.m_A<< endl;//如果要通过子类对象访问到父类中的同名成员,需要加作用域。//加作用域的意思是直接在指定作用域内进行名字查找,因为类名就是一个作用域cout <<son.Base::m_A<< endl; } //同名成员函数处理方式 void test02() {Son son1; son1.func();//子son1.Base::func();//父//如果子类中出现和父类同名的成员函数//子类的同名成员会隐藏掉父类中所有同名成员函数//如果想要访问到父类中被隐藏的同名成员函数,需要加作用域son1.Base::func(10); } int main(void) {test02();system("pause");return 0; }
总结:
- 子类对象可以直接访问到子类中同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类同名函数。
6.继承同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上是如何进行访问的呢?
静态成员和非静态成员出现同名,处理方式 一致。
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
#include<iostream>
using namespace std;
class Base
{
public:static void func(){cout << "父类静态成员函数调用" << endl;}static void func(int a){cout << "父类静态成员重载函数调用" << endl;}static int m_A;
};
int Base::m_A = 100;
class Son :public Base
{
public:static void func(){cout << "子类静态成员函数调用" << endl;}static int m_A;
};
int Son::m_A = 200;
//同名静态成员
void test()
{//通过对象访问Son son1;cout << "通过对象访问" << endl;cout << son1.m_A << endl;cout << son1.Base::m_A << endl;//通过类名访问cout << "通过类名访问" << endl;cout << Son::m_A << endl;//第一个::代表通过类名方式访问,第二个::代表访问父类作用域下cout << Son::Base::m_A << endl;
}
//同名静态函数
void test01()
{//通过对象访问Son son2;cout << "通过对象访问" << endl;son2.func();son2.Base::func(); //通过类名访问cout << "通过类名访问" << endl;Son::func();Son::Base::func();//父类同名重载成员函数调用//子类出现和父类同名的静态成员函数,也会隐藏掉父类中所有同名成员函数(重载)//如果想访问父类中被隐藏的同名成员,需要加作用域Son::Base::func(100);
}
int main(void)
{test();cout << "我是分割线------" << endl;test01();system("pause");return 0;
}
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和类名)。
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;
};
//子类需要继承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 son1;cout << sizeof(son1) << endl;//16cout << "第一个父类的m_A:" << son1.Base1::m_A<<endl;cout << "第二个父类的m_A:" << son1.Base2::m_A<<endl;
}
int main(void)
{test01();system("pause");return 0;
}
总结:多继承中如果父类中出现了同名情况,子类使用时要加作用域。
8.菱形继承
菱形继承概念:
两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承称为菱形继承,或者钻石继承。
典型的菱形继承案例
菱形继承问题:
- 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
- 草泥马继承动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
vbptr——虚基类
继承了两个指针,两个指针通过偏移量找到了唯一的数据。
#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 << endl;cout << st.Tuo::m_Age << endl;cout << st.m_Age << endl;//这份数据我们知道,只有一份就可以了,菱形继承导致数据有两份,资源浪费
}
int main(void)
{test01();system("pause");return 0;
}
总结:
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义。
- 利用虚继承可以解决菱形继承问题——virtual