莱州网站建设费用,wordpress 电子商务主题,手机免费在线搭建网站,企业建站服务退役军人继承
继承是面向对象三大特性之一 定义类时#xff0c;下级别的成员除了拥有上一级的共性#xff0c;还有自己的特性#xff0c;就可以考虑使用继承的技术#xff0c;减少代码的重复
继承的基本语法
语法#xff1a;class 子类 : 继承方式 父类
子类也被成为派生类父类…继承
继承是面向对象三大特性之一 定义类时下级别的成员除了拥有上一级的共性还有自己的特性就可以考虑使用继承的技术减少代码的重复
继承的基本语法
语法class 子类 : 继承方式 父类
子类也被成为派生类父类也被称为基类
class A
{
public:string name;
};
class B :public A
{
public:int age;
};
int main()
{B b;b.name 张三;b.age 10;cout b.name b.age endl;return 0;
}继承方式
继承方式一共有三种
公共继承 访问权限不变 保护继承 除私有内容外都变为保护权限 私有继承 除私有内容外都变为私有权限
父类中的私有内容三种继承方法都无法访问
class A
{
public:int a;
protected:int b;
private:int c;
};
class B :public A//公共继承
{};
class C :protected A//保护继承
{};
class D :private A//私有继承
{};继承中的对象模型
父类中所有非静态成员属性都会被子类继承下去 父类中私有的成员属性是被编译器给隐藏了因此是访问不到但是确实被继承下去了 利用开发人员命令提示工具查看对象模型
跳转盘符盘符:跳转文件路径cd 具体路径下查看命名dir报告单个类的布局cl /d1 reportSingleClassLayout类名 文件名
文件名可按Tap建自动补齐
class A
{
public:int a;
protected:int b;
private:int c;
};
class B :public A//公共继承
{int c;
};class B size(16):---0 | --- (base class A)0 | | a4 | | b8 | | c| ---
12 | c---继承中构造和析构顺序
先调用父类构造函数再调用子类构造函数析构顺序与构造相反
继承同名成员处理方式
子类对象可以直接访问到子类中的同名成员 子类对象加作用域可以访问到父类同名成员 当子类与父类拥有同名的成员函数子类会隐藏父类中所有同名成员函数加作用域可以访问到父类中同名函数
class A
{
public:void test(){cout A endl;}
};
class B :public A//公共继承
{
public:void test(){cout B endl;}
};
int main()
{B b;b.test();b.A::test();return 0;
}继承同名静态成员处理方式
静态成员跟非静态成员出现同名处理方法一致只不过有两种处理方法
通过对象.通过类名::
class A
{
public:static string a;
};
class B :public A//公共继承
{
public:static string a;
};
//类内声明类外初始化
string B::a B;
string B::A::a A;
int main()
{//通过对象访问B b;cout b.a endl;cout b.A::a endl;//通过类名访问cout B::a endl;//第一个::表示通过类名方式访问第二个::代表访问父类作用域下cout B::A::a endl;return 0;
}多继承语法
C允许一个类继承多个类 语法class 子类 : 继承方式 父类1 , 继承方式 父类2... 多继承可能会引发父类中有同名成员出现需要加作用域区分 C实际开发中不建议用多继承
class A
{
public:int a;
};
class B
{
public:int a;
};
class C :public A, public B
{
};
int main()
{C c;c.A::a 10;c.B::a 20;cout c.A::a endl;cout c.B::a endl;return 0;
}菱形继承
菱形继承概念
两个派生类继承同一个基类又有某个类同时继承这两个派生类这种继承被称为菱形继承也被称为钻石继承
典型的菱形继承问题 菱形继承问题子类继承两份相同的数据导致资源浪费以及毫无意义
羊继承了动物的数据驼也继承了动物的数据当羊驼使用数据时就会产生二义性羊驼继承自动物的数据继承了两份只需要一份就可以
利用虚继承解决菱形继承的问题
继承之前加上关键字virtual变为虚继承公共的父类被称为虚基类
class A
{
public:int a;
};
//A为虚基类
class B :virtual public A{};
class C :virtual public A{};
class D:public B,public C{};
int main()
{D d;d.a 10;cout d.a endl;return 0;
}vbptr虚基类指针
v-virtualb-baseptr-pointer
虚基类指针指向vbtable虚基类表
实际继承了两个指针通过偏移量找到那份唯一的数据
class D size(24):---0 | --- (base class B)0 | | {vbptr}| | alignment member (size4)| ---8 | --- (base class C)8 | | {vbptr}| | alignment member (size4)| ---| alignment member (size4)------ (virtual base A)
16 | a---D::$vbtableB:0 | 01 | 16 (Dd(B0)A)D::$vbtableC:0 | 01 | 8 (Dd(C0)A)
vbi: class offset o.vbptr o.vbte fVtorDispA 16 0 4 0多态
多态的基本语法
多态分为两类
静态多态函数重载和运算符重载属于静态多态复用函数名动态多态派生类和虚函数实现运行时多态
静态多态和动态多态区别
静态多态的函数地址早绑定编译阶段确定函数地址动态多态的函数地址晚绑定运行阶段确定函数地址
动态多态满足条件
有继承关系子类要重写父类的虚函数使用父类的指针或引用执行子类对象
重写不同于函数重载
函数返回值类型、函数名、参数列表完全相同
C中父子之间的类型转换不需要做强制类型转换父类的指针或引用可以直接指向子类对象
class A
{
public://虚函数virtual void test(){cout A endl;}
};
class B :public A
{
public:void test(){cout B endl;}
};
void test(A a)
{a.test();
}
int main()
{B b;test(b);//A a b;return 0;
}多态的原理剖析
class A size(8):---0 | {vfptr}---A::$vftable:| A_meta| 00 | A::test
class B size(8):---0 | --- (base class A)0 | | {vfptr}| ------B::$vftable:| B_meta| 00 | B::testvfptr虚函数表指针
v-virtualf-functionptr-pointer指向vftable虚函数表 表内记录虚函数地址A::test
当子类重写父类虚函数子类中的虚函数表内部会替换成子类的虚函数地址
多态案例-计算器类
案例描述分别利用普通写法和多态技术设计实现两个操作数进行运算的计算器类。 多态的优点
代码组织结构清晰可读性强利于前期和后期的扩展和维护
在真实开发中提倡开闭原则
对拓展进行开放对修改进行关闭
#include iostream
using namespace std;
//实现计算器抽象类
class Abstract
{
public:virtual int result(){return 0;}int m_num1;int m_num2;
};
//实现计算器加法类
class add :public Abstract
{
public:virtual int result(){return m_num1 m_num2;}
};
//实现减法类
class subtraction :public Abstract
{
public:virtual int result(){return m_num1 - m_num2;}
};
int main()
{//父类指针指向子类对象Abstract* a new add;a-m_num1 10;a-m_num2 10;cout a-result() endl;//用完记得销毁delete a;//a不需要再定义和初始化a new subtraction;a-m_num1 10;a-m_num2 10;cout a-result() endl;return 0;
}纯虚函数和抽象类
在多态中通常父类中的虚函数实现是毫无意义的主要都是调用子类重写的内容 因此可以将虚函数改为纯虚函数 纯虚函数语法virtual 返回值类型 函数名(参数列表) 0; 当类中有了纯虚函数这个类也称为抽象类特点
无法实例化对象子类必须重写抽象类中的纯虚函数否则也属于抽象类
抽象类特点
无法实例化对象子类必须重写抽象类中的纯虚函数否则也属于抽象类
#include iostream
using namespace std;
class AbstractDrinking
{
public:virtual void Boil() 0;//煮水virtual void Brew() 0;//冲泡virtual void PourInCup() 0;//倒入杯中virtual void PutSomeThing() 0;//添加佐料void MakeDrink()//制作饮品{Boil();Brew();PourInCup();PutSomeThing();}};
class Coffee :public AbstractDrinking
{virtual void Boil(){cout 煮矿泉水 endl;}virtual void Brew(){cout 冲泡咖啡 endl;}virtual void PourInCup(){cout 倒入杯中 endl;}virtual void PutSomeThing(){cout 添加牛奶 endl;}
};
class Tea :public AbstractDrinking
{virtual void Boil(){cout 煮矿泉水 endl;}virtual void Brew(){cout 冲泡茶叶 endl;}virtual void PourInCup(){cout 倒入杯中 endl;}virtual void PutSomeThing(){cout 添加枸杞 endl;}
};
void dowork(AbstractDrinking *a)
{a-MakeDrink();delete a;
}
int main()
{AbstractDrinking* a new Tea;dowork(a);return 0;
}虚析构和纯虚析构
多态使用时如果子类中有属性开辟到堆区那么父类指针在释放时无法调用到子类的析构代码 解决方法将父类中的析构函数改为虚析构或者纯虚析构 虚析构和纯虚析构共性
可以解决父类指针释放子类对象都需要有具体的函数实现
虚析构和纯虚析构区别
如果是纯虚析构该类属于抽象类无法实例化对象
虚析构语法virtual ~类名() {}; 纯虚析构语法virtual ~类名() 0; 总结
虚析构和纯虚析构是用来解决通过父类指针释放子类对象如果子类中没有堆区数据可以不写为虚析构和纯虚析构拥有纯虚析构的类也属于抽象类
#include iostream
using namespace std;
class A
{
public:A(){m_name new string(A);}virtual void speak(){cout *m_name endl;}//利用虚析构可以解决父类指针释放子类对象时不干净的问题virtual ~A(){if (m_name ! NULL){cout A析构 endl;delete m_name;m_name NULL;}}string* m_name;
};
class B :public A
{
public:B(){m_name new string(B);}virtual ~B(){if (m_name ! NULL){cout B析构 endl;delete m_name;m_name NULL;}}
};
int main()
{A* a new B;//先构造父类再构造子类a-speak();//父类指针在析构时不会调用子类中析构函数导致子类如果有堆区属性会导致内存泄露delete a;return 0;
}#include iostream
using namespace std;
class A
{
public:A(){m_name new string(A);}virtual void speak(){cout *m_name endl;}//纯虚析构声明//有了纯虚析构后这个类也属于抽象类无法实例化对象virtual ~A() 0;string* m_name;
};
class B :public A
{
public:B(){m_name new string(B);}virtual ~B(){if (m_name ! NULL){cout B析构 endl;delete m_name;m_name NULL;}}
};
//纯虚析构实现
A::~A()
{cout A纯虚析构 endl;
}
int main()
{A* a new B;//先构造父类再构造子类a-speak();//父类指针在析构时不会调用子类中析构函数导致子类如果有堆区属性会导致内存泄露delete a;return 0;
}多态案例-电脑组装
案例描述
电脑主要组成部件为CPU、显卡、内存条将每个零件封装出抽象基类并且提供不同的厂商生产不同的零件创建电脑类提供让电脑工作的函数并且调用每个函数工作的接口测试时组装三台不同的电脑进行工作
#include iostream
using namespace std;
//抽象不同零件类
//抽象CPU类
class CPU
{
public://抽象的计算函数virtual void calculate() 0;
};
//抽象显卡类
class VideoCard
{
public://抽象的显示函数virtual void dispaly() 0;
};
//抽象内存条类
class Memory
{
public://抽象的存储函数virtual void storage() 0;
};
//电脑类
class Computer
{
public:Computer(CPU* cpu, VideoCard* vc, Memory* mem){m_cpu cpu;m_vc vc;m_mem mem;}//提供析构函数释放电脑的三个零件~Computer(){if (m_cpu ! NULL){delete m_cpu;m_cpu NULL;}if (m_vc ! NULL){delete m_cpu;m_vc NULL;}if (m_mem ! NULL){delete m_cpu;m_mem NULL;}}//提供工作函数void work(){//让零件工作起来调用接口m_cpu-calculate();m_vc-dispaly();m_mem-storage();}
private:CPU* m_cpu;//CPU的零件指针VideoCard* m_vc;//显卡的零件指针Memory* m_mem;//内存条的零件指针
};
//具体厂商Intel
class IntelCPU :public CPU
{
public:virtual void calculate(){cout Intel的CPU开始计算了 endl;}
};
class IntelVideoCard :public VideoCard
{
public:virtual void dispaly(){cout Intel的显卡开始显示了 endl;}
};
class IntelMemory :public Memory
{
public:virtual void storage(){cout Intel的内存开始存储了 endl;}
};
//具体厂商Lenovo
class LenovoCPU :public CPU
{
public:virtual void calculate(){cout Lenovo的CPU开始计算了 endl;}
};
class LenovoVideoCard :public VideoCard
{
public:virtual void dispaly(){cout Lenovo的显卡开始显示了 endl;}
};
class LenovoMemory :public Memory
{
public:virtual void storage(){cout Lenovo的内存开始存储了 endl;}
};
int main()
{//电脑零件CPU* intelcpu new IntelCPU;VideoCard* intervc new IntelVideoCard;Memory* intermem new IntelMemory;//创建第一台电脑Computer* computer1 new Computer(intelcpu, intervc, intermem);computer1-work();delete computer1;//创建第二台电脑Computer* computer2 new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);computer2-work();delete computer2;//创建第三台电脑Computer* computer3 new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory);computer3-work();delete computer3;return 0;
}