18,类和对象(继承)

继承是面向对象三大特性之一

有些类与类之间存咋特殊的关系,例如下图

 我们发现,定义这些类是,下级别的成员除了拥有上一级的共性,还有自己的特性

这个时候我们就可以考虑利用继承的技术,减少重复的代码

18.1继承的基本语法

很多网站都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同,利用普通写法和继承的写法实现网页中的内容,是两种完全不同的风格。

继承的好处:减少重复代码

语法:class  子类:继承方式   父类

如下的class  Java:public   BasePage

子类也称为派生类

父类也称为基类

普通实现

#include<iostream>
using namespace std;
//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;}
};
void test01()
{cout << " Java下载视频页面如下:" << endl;Java ja;ja.header();ja.footer();ja.left();ja.content();
}
void test02()
{cout << "--------------------------------" << endl;cout << " Python下载视频页面如下:" << endl;Python py;py.header();py.footer();py.left();py.content();
}
void test03()
{cout << "--------------------------------" << endl;cout << " C++下载视频页面如下:" << endl;Cpp c;c.header();c.footer();c.left();c.content();
}
int main()
{test01();test02();test03();system("pause");return 0;
}

继承实现

#include<iostream>
using namespace std;
//继承实现页面
//公共页面类
class BasePage
{
public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){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;}};//Cpp页面
class Cpp :public BasePage{public:void content(){cout << "Cpp学科视频" << endl;}};void test01(){cout << " Java下载视频页面如下:" << endl;Java ja;ja.header();ja.footer();ja.left();ja.content();}void test02(){cout << "--------------------------------" << endl;cout << " Python下载视频页面如下:" << endl;Python py;py.header();py.footer();py.left();py.content();}void test03(){cout << "--------------------------------" << endl;cout << " C++下载视频页面如下:" << endl;Cpp c;c.header();c.footer();c.left();c.content();}
int main()
{test01();test02();test03();system("pause");return 0;
}

总结:继承的好处:可以减少重复代码

class A:public B;

A类称为子类或派生类

B类称为父类或基类

派生类中的成员,包含两大做部分:

一类是基类继承过来的,一类是自己增加的成员

从基类继承过过来的表现其共性,而新增的成员体现了其个性

18.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 = 10;//父类的保护权限成员 到子类中依然是保护权限//m_C = 10;//报错,父类的私有权限成员 到子类访问不到}
};
void test01()
{Son1 s1;s1.m_A = 100;//s1.m_B = 100;报错,不可访问//s1.m_C = 100;报错,不可访问
}
//保护继承
class Son2 :protected Base1
{
protected:void func(){m_A = 10;//父类的公共权限成员 到子类中变为保护权限m_B = 10;//父类的保护权限成员 到子类中依然是保护权限//m_C = 10;//报错,父类的私有权限成员 到子类访问不到}
};
void test02()
{Son2 s2;//s2.m_A = 1000;//在Son2中m_A变为保护权限,因此类外访问不到//s2.m_B = 1000;//在Son2中m_B还是保护权限,因此类外访问不到//s2.m_C = 1000;//在Son2中m_C还是私有权限,因此访问不到
}
//私有继承
class Son3 :private Base1
{
private:void func(){m_A = 10;//父类的公共权限成员 到子类中变为私有权限m_B = 10;//父类的保护权限成员 到子类中变为私有权限//m_C = 10;//报错,父类的私有权限成员 到子类访问不到}
};
void test03()
{Son3 s3;//s3.m_A = 10000;在Son3中m_A变为私有权限,因此访问不到//s3.m_B = 10000;在Son3中m_B变为私有权限,因此访问不到//s3.m_C = 10000;在Son3中m_C还是私有权限,因此访问不到
}
class GrandSon3 :public Son3//是Son3的子类,而Son3中都变为私有权限了,所以都访问不到
{
public:void func(){//m_A = 100;//到了Son3中m_A变为私有,及使是子类,也访问不到//m_B = 100;//m_C = 100;}
};
int main()
{test01();test02();test03();system("pause");return 0;
}

 18.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 Son=" << sizeof(Son) << endl;//输出结果为16,因为里面包含四个intcout << "sizeof Base=" << sizeof(Base) << endl; // 输出结果为12,因为里面包含三个int
}
int main()
{test01();system("pause");return 0;
}

利用开发人员命令提示工具查看对象模型
跳转盘符  F:
跳转文件路径 cd 具体路径下
查看命名
c1 /d1 reportSingleClassLayout类名 文件名

结论:父类中所有非静态成员属性都会被子类继承下去

父类中私有成员属性是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了

18.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()
{//Base b;Son s;//输出结果:Base构造函数//Son构造函数//Son析构函数//Base析构函数//继承中的构造和析构顺序如下://先构造父类,再构造子类,西沟的顺序与构造的顺序相反
}
int main()
{test01();system("pause");return 0;
}

总结:继承中,先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

18.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 p;cout << "m_A=" << p.m_A << endl;//输出结果:200cout << "Son下m_A=" << p.Base::m_A << endl;//输出结果:100
}
//同名成员函数处理
void test02()
{Son p;p.func();//直接调用 调用是子类中的同名成员p.Base::func();//加一个作用域就可以调用父类的了//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数//如果想访问到父类中被隐藏的同名成员函数,需要加作用域p.Base::func(100);
}
int main()
{test01();test02();system("pause");return 0;
}

总结:子类对象可以直接访问到子类中同名成员

子类对象加作用域可以访问到父类同名成员

当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

18.6继承同名静态成员处理方式

问题:继承中同名的静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一致

访问子类同名成员 直接访问即可

访问父类同名成员 直接加作用域

#include<iostream>
using namespace std;
//继承中的同名静态成员处理方式
class Base
{
public:static int m_A;static void func(){cout << "Base-static voic func()" << endl;}static void func(int a){cout << "Base-static voic func(int a)" << endl;}
};
int Base::m_A = 100;
class Son:public Base
{
public:static int m_A;static void func(){cout << "Son-static voic func()" << endl;}
};
int Son::m_A = 200;
//同名静态成员属性
void test01()
{//通过对象访问cout << "通过对象访问" << endl;Son s;cout << "Son 下m_A=" << s.m_A << endl;cout << "Base下m_A=" << s.Base::m_A << endl;//通过类名访问cout << "通过类名访问" << endl;cout << "Son 下m_A=" << Son::m_A << endl;cout << "Base下m_A=" << Son::Base::m_A << endl;//第一个::代表通过类名方式访问 第二个::代表访问父类作用域下
}
//同名静态成员函数
void test02()
{Son s;//通过对象访问cout << "通过对象访问" << endl;s.func();s.Base::func();s.Base::func(100);//通过类名访问cout << "通过类名访问" << endl;Son::func();Son::Base::func();//子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数//如果像访问父类中被隐藏同名成员,需要加作用域Son::Base::func(100);
}
int main()
{test01();test02();system("pause");return 0;
}

总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和通过类名)

18.7静态成员和非静态成员区别(回顾)

  1. 静态成员:

    • 静态成员是类中的共享成员,它们与类本身关联,而不是与类的对象实例相关联。
    • 静态成员在类的所有对象之间是共享的,只有一个副本存在于内存中。
    • 可以使用类名或对象来访问静态成员。
    • 静态成员可以在类的定义外部初始化,也可以在类的内部进行初始化。
    • 静态数据成员在程序运行期间一直存在,直到程序结束。
    • 静态成员函数不能直接访问非静态成员变量和非静态成员函数,只能访问其他静态成员。
  2. 非静态成员:

    • 非静态成员是对象的一部分,每个对象都有自己的一份非静态成员。
    • 非静态成员变量在每个对象中都有独立的副本,存储在各自对象的内存空间中。
    • 非静态成员只能通过对象来访问。
    • 非静态数据成员在对象创建时被分配内存,在对象销毁时被释放。
    • 非静态成员函数可以直接访问非静态成员变量和其他非静态成员函数。

总结:

  • 静态成员是一种类成员,与类本身相关联,而非静态成员是对象成员,与类的对象实例相关联。
  • 静态成员在内存中只有一个副本,而非静态成员在每个对象中都有独立的副本。
  • 静态成员可以通过类名或对象来访问,而非静态成员只能通过对象来访问。
  • 静态成员函数只能访问其他静态成员,而非静态成员函数可以访问非静态成员和其他非静态成员函数。

18.8多继承语法

C++允许一个类继承多个类

语法:class 子类 :继承方式  父类1,继承方式  父类2...

多继承可能会引发父类中有同名成员出现,需要加作用域区分

C++实际开发中不建议用多继承

#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 Base2
{
public:Base2(){m_A = 200;}void func(){cout << "Base2-func()调用" << endl;}void func(int a){cout << "Base2-func(int a)调用" << endl;}int m_A;
};
class Son :public Base,public Base2
{
public:Son(){m_A = 300;}void func(){cout << "Son-func()调用" << endl;}int m_A;
};
void test01()
{Son p;cout << "m_A=" << p.m_A << endl;//输出结果:300//当父类中出现同名成员,需要加作用域区分cout << "Base下m_A=" << p.Base::m_A << endl;//输出结果:100cout << "Base2下m_A=" << p.Base2::m_A << endl;//输出结果:200
}
void test02()
{Son p;p.func();p.Base::func();p.Base2::func();p.Base::func(100);p.Base2::func(100);
}
int main()
{test01();test02();system("pause");return 0;
}

总结:多继承中如果父类出现了同名情况,子类使用时候要加作用域

18.9菱形继承

菱形继承概念:

两个派生类继承同一个基类

又有某个类同时继承着两个派生类

这种继承被称为菱形继承,或者钻石继承

典型的菱形继承案例:

菱形继承问题:

羊继承了动物的数据,驼同样继承了动物的数据 ,当羊驼使用数据时,就会产生二义性

羊驼继承自动物的数据继承了两份,我们应该清楚,这份数据我们只需要一份就可以

#include<iostream>
using namespace std;
//动物类
class Animal
{
public:int m_Age;
};
//利用虚继承可以解决菱形继承的问题virtual
//继承之前加上关键字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;//输出结果:虚继承前18虚继承后28cout << "st.Tuom_Age=" << st.Tuo::m_Age << endl;//输出结果:虚继承前28虚继承后28cout << "st.m_Age=" << st.m_Age << endl;//虚继承后不会出现不明确的情况,因为这份数据只有一个了//这份数据我们知道只有一份就可以,菱形继承导致数据有两份,资源浪费
}
int main()
{test01();system("pause");return 0;
}

 左边是虚继承后的                                              右边是菱形继承的

vbptr中v-virtual(虚)       b-base(基类)      ptr-pointer(指针)

vbptr(虚基类指针)会指向vbtable(虚基类表)

 Sheep通过指针+8后也能找到m_Age (继承的是指针)

Tuo+4偏移量也能找到m_Age (继承的是指针)

m_Age 只有一份(虚继承解决了菱形继承中有两份数据(羊,驼)使羊驼不知道拿哪个数据的问题)

总结:

菱形继承是什么:一个父类下面有两个子类,两个子类下面有一个共同的孙子类

class Animal
{
public:int m_Age;
};
class Sheep :public Animal {};
class Tuo :public Animal {};
class SheepTuo :public Sheep, public Tuo {};

解决方法:加上virtual使其变成虚继承

class Animal
{
public:int m_Age;
};
class Sheep :virtual public Animal {};
class Tuo :virtual public Animal {};
class SheepTuo :public Sheep, public Tuo {};

菱形继承导致的主要问题是带来两份相同的数据,导致资源浪费以及毫无意义

利用虚继承可以解决菱形继承问题