来源:微信公众号「编程学习基地」

目录

    • 虚函数
      • 覆盖
      • 覆盖条件
    • 虚函数表
    • 多态
      • 单台和多态
      • 多态理解
      • 多肽的好处
    • 虚析构
    • 纯虚函数
    • 抽象类
    • 纯抽象类
  • 继承多肽示例程序
虚函数

普通成员函数前加关键字virtual,称为虚函数

覆盖
  • 子类成员函数和基类的虚函数具有相同函数原型,该成员函数也就是虚函数,无论其是否带有virtual关键字,都对基类虚函数构成覆盖
覆盖条件
  • 函数为成员函数(非静态)
  • 基类使用virtual
  • 原型严格相同

虚函数程序示例:

#include<iostream>
#include<string>
using namespace std;
//基类 Animal
class Animal
{public:Animal(string name) :m_name(name) {  }void say() {cout << m_name << ":$%^&@" << endl;}
protected:string m_name;
};//派生类 Dog
class Dog :public Animal
{public:Dog(string name) :Animal(name) {}void say(){cout << m_name << ":汪汪汪" << endl;}
};int main()
{Animal *a = new Animal("动物");a->say();               //Animal类说 $%^&@delete a;a = new Dog("旺财");     //隐式转换成基类对象a->say();delete a;return 0;
}
打印结果:
动物:$%^&@
旺财:$%^&@

加关键字virtual:

virtual void say()   //函数前加关键字virtual,形成虚函数
{cout << m_name << ":$%^&@" << endl;
}
int main()
{Animal *a = new Animal("动物");a->say();delete a;a = new Dog("旺财");  //ISA   隐式转换成基类对象//a调用的是虚函数,并且形成覆盖,a实际指向Dog类对象 调用Dog::say()/** 基类成员函数前加virtual 该成员函数成为虚函数* 没有virtual 对象调用函数看声明类型  有虚函数并且形成覆盖,对象调用函数看实际内存中类型*/a->say();delete a;return 0;
}
打印结果
动物:$%^&@
旺财:汪汪汪
虚函数表

编译器通过指针或引用调用虚函数,不会立即生成函数调用指令,而是用一段代码代替确定真实类型,找到虚函数表从而找到入口地址,根据入口地址调用函数。

#include<iostream>
using namespace std;
class N {public:void foo() {cout << "N::foo" << endl;}void bar() {cout << "N::bar" << endl;}int m_a;int m_b;
};class A {public:virtual void foo() {cout << "A::foo" << endl;}virtual void bar() {cout << "A::bar" << endl;}int m_a;int m_b;
};class B :public A {public:void foo() {cout << "B::foo" << endl;}void bar() {cout << "B::bar" << endl;}
};int main() {/**   实例化三个对象,在监视里面查看内存*/N n;A a;B b;//offsetof(s,m) 计算m相对s的偏移地址cout << "sizeof(N):" << sizeof(N) << ",m_a:" << offsetof(N, m_a) << ",m_b:" << offsetof(N, m_b) << endl;cout << "sizeof(A):" << sizeof(A) << ",m_a:" << offsetof(A, m_a) << ",m_b:" << offsetof(A, m_b) << endl;cout << "sizeof(B):" << sizeof(B) << ",m_a:" << offsetof(B, m_a) << ",m_b:" << offsetof(B, m_b) << endl;/**   从上面可以得知有虚函数的类,比没有虚函数的类多了一个四字节的东东,那个东东就是一个指针,指向虚函数表*    不管有多少个虚函数,一个对象只有一个指向虚函数表的指针*///自己定义一个虚函数表typedef void(*VFUN) (void *);  // 将void* 重定义为函数指针typedef VFUN* VPTR;            // 重定义指向虚函数表指针的类型VPTR vf_ptr = *(VPTR*)&a;     //拿到对象a的虚函数表指针cout << vf_ptr << "[" << vf_ptr[0] << "," << vf_ptr[1] << "]" << endl;//通过虚函数表调用虚函数vf_ptr[0](&a); //&a相当于创一个this指针过去vf_ptr[1](&a);    //&a相当于创一个this指针过去cin.get();return 0;
}

打印结果

sizeof(N):8,m_a:0,m_b:4
sizeof(A):12,m_a:4,m_b:8
sizeof(B):12,m_a:4,m_b:8
00CB8B34[00CB141A,00CB142E]
A::foo
A::bar

监视里面也可以很好的看到A,B两个类的对象有一个__vfptr的指针,指向两个虚函数,

多态
单台和多态
#include<iostream>
using namespace std;
//单肽,一个函数一个功能
int add(int x, int y)
{return x + y;
}int sub(int x, int y)
{return x - y;
}//利用函数指针实现多态
int calc(int x, int y, int(*pfun)(int, int))
{return pfun(x, y);
}int main()
{cout << calc(2, 3, add) << endl;cout << calc(2, 3, sub) << endl;cin.get();return 0;
}
多态理解
  • 子类提供了对基类虚函数的有效覆盖,通过指向子类对象的基类指针,或者引用子类对象的基类引用,调用该虚函数,实际上调用的将是子类中的覆盖版本,而非基类中的原始版本
  • 一般函数调用是通过调用对象类型决定,而多态这是通过调用者指针或引用的实际目标对象的类型决定
  • 多态只能通过引用或指针表现,且指针更灵活
  • 除构造和析构函数,通过基类中this指针可以满足多态

其实在虚函数里面就已经讲解到多肽了

#include<iostream>
#include<string>
using namespace std;
//基类 Animal
class Animal
{public:Animal(string name) :m_name(name) {  }virtual void say(){cout << m_name << ":$%^&@" << endl;}
protected:string m_name;
};//派生类 Dog
class Dog :public Animal
{public:Dog(string name) :Animal(name) {}void say(){cout << m_name << ":汪汪汪" << endl;}
};int main()
{Animal *a = new Animal("动物");a->say();               //Animal 说 $%^&@delete a;a = new Dog("旺财");     //隐式转换成基类对象a->say();             //旺财 说 汪汪汪delete a;/**  指针a通过指针指向的改变实现多肽*   a指向Animal时说 $%^&@* a指向Dog时说    汪汪汪*/cin.get();return 0;
}
多肽的好处
  • 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性
  • 派生类的功能可以被基类的指针或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。
虚析构

只有虚析构,没有虚构造

class A
{public:A(){cout << "A构造" << endl;}~A(){cout << "A析构" << endl;}
};class B :public A
{public:B(){p = new int(10);cout << "B构造" << endl;}~B(){cout << "B析构" << endl;delete p;}private:int *p;
};int main()
{A* a = new B; //隐式转换为基类对象delete a;cin.get();return 0;
}

不加virtual 修饰~A(),打印结果

A构造
B构造
A析构

内存泄漏了,B类里面的*p指向的堆内存没有释放

解决办法:

class A
{public:A(){cout << "A构造" << endl;}virtual ~A()    //虚析构{cout << "A析构" << endl;}
};

析构前面加上关键字virtual构成虚析构

A构造
B构造
B析构
A析构

注意:

  • 如果父类的析构函数不是虚析构,父类的指针指向子类时,delete掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数
  • 如果父类的析构函数是虚析构,父类的指针指向子类时,delete掉父类的指针,先调动子类的析构函数,再调动父类的析构函数
纯虚函数

形如virtual 返回值 函数名(形参表)=0;的虚函数,称为纯虚函数或抽象方法

class Base
{virtual void fun(int) = 0; //纯虚函数
};
抽象类
  • 至少拥有一个纯虚函数的类成为抽象类
  • 抽象类不能实例化为对象
  • 抽象类子类不对基类中全部纯虚函数提供有效覆盖,子类也是抽象类
class Base
{virtual void foo(int) = 0; //纯虚函数virtual void fun(int) = 0;
};//抽象类//抽象类子类不对基类中全部纯虚函数提供有效覆盖,子类也是抽象类
class B:public Base
{void foo(int) {}
};
  • 不能实例化抽象类
纯抽象类

全部由纯虚函数构成的抽象类成为纯抽象类或接口

继承多肽示例程序

某小型公司,主要有四类员工( Employee ):经理( Manager )、技术人员( Technician )、销售经理( SalesManager )和推销员( SalesMan )。
现在 , 需要存储这些人员的姓名( name )、编号( id )、当月薪水( salary )。计算月薪总额并显示全部信息。人员编号基数为 1000 ,每输入一个人员工信息编号顺序加 1 。
月薪计算办法是:

经理拿固定月薪 8000 元;
技术人员按每小时 100 元领取月薪;

推销员的月薪按该推销员当月销售额的 4% 提成;

销售经理既拿固定月薪也领取销售提成,固定月薪为 5000 元,销售提成为所管辖部门当月销售总额的 5% 。

继承关系图:

#include<string>
#include<iostream>
using namespace std;
class Employee
{protected:string m_name;int m_id;float m_salary;static int Id;
public:Employee() :m_name(""), m_salary(0.0f), m_id(++Id){}virtual void getSalary() = 0;//纯虚函数void show() {cout << "姓名:" << m_name << endl;cout << "员工ID:" << m_id << endl;cout << "工资:" << m_salary << endl << endl;}virtual ~Employee() {}
};  //员工类int Employee::Id = 1000;class Manager : virtual public Employee
{protected:float m_baseSalary;   //基本工资
public:Manager(string name){m_name = name;m_baseSalary = 8000.0f; //经理拿固定月薪 8000 元}void getSalary(){m_salary = m_baseSalary;}~Manager() {}
};  //经理类class Technician : public Employee
{private:int m_hour;
public:Technician(string name, int hour) {m_name = name;m_hour = hour;}void getSalary() {m_salary = m_hour * 70; //技术人员按每小时 100 元领取月薪}~Technician() {}
};  //技术人员class SalesMan : virtual public Employee
{private:float m_Count;  //销售额
public:SalesMan(string name, float Count) {m_name = name;m_Count = Count;m_partCount += m_Count;}void getSalary() {m_salary = m_Count * 0.04;  //推销员的月薪按该推销员当月销售额的 4% 提成}~SalesMan() {}
protected:static float m_partCount; //总销售
};  //推销员
float SalesMan::m_partCount = 0.0f;class SalesManager : public Manager, public SalesMan
{public:SalesManager(string name) :Manager(name), SalesMan(name, 0){m_name = name;m_baseSalary = 5000;}void getSalary(){//销售经理既拿固定月薪也领取销售提成,固定月薪为 5000 元,销售提成为所管辖部门当月销售总额的 5% 。m_salary = m_baseSalary + m_partCount * 0.05;}~SalesManager() {}
};  //销售经理int main()
{Employee* emp[5] = { 0, };emp[0] = new Manager("经理");emp[0]->getSalary();emp[0]->show();emp[1] = new Technician("技术人员", 99);emp[1]->getSalary();emp[1]->show();emp[2] = new SalesMan("销售人员", 60000);emp[2]->getSalary();emp[2]->show();emp[3] = new SalesMan("销售人员", 900000);emp[3]->getSalary();emp[3]->show();emp[4] = new SalesManager("销售经理");emp[4]->getSalary();emp[4]->show();for (int i = 0; i < 5; ++i){delete emp[i];}getchar();return 0;
}

C++进阶学习---多肽相关推荐

  1. 3D视觉从入门到进阶学习路线

    01 什么是知识星球? 知识星球是一个高度活跃的社区平台,在这里你可以和相同研究方向的小伙伴一起探讨科研工作难题.交流最新领域进展.分享paper资料.发布高质量的求职就业信息,当然还可以侃侃而谈,吐 ...

  2. android java服务,Android进阶学习必会:Java Binder中的系统服务

    前言 这个知识点是Android进阶学习必须掌握的知识点之一,也是高阶Android架构师经常问到的点.在这里分想给大家,希望对大家的工作和学习有所帮助.喜欢本文的记得点赞关注哦~ 在前面的Andro ...

  3. leetcode与python进阶学习总结

    转自:leetcode与python进阶学习总结 l1是一个链表型,val是其属性,以下句子意义为如果l1不为空则取l1.val否则取0,节省代码空间,干净利落 x= l1.val if l1 els ...

  4. opencv进阶学习笔记3:像素运算和图像亮度对比度调节

    基础版传送门: python3+opencv学习笔记汇总目录(适合基础入门学习) 进阶版目录: python+opencv进阶版学习笔记目录(适合有一定基础) 像素运算 要求两张图大小,以及格式(np ...

  5. JQuery进阶学习

     JQuery进阶学习的内容         1. 动画         2. 遍历         3. 事件绑定         4. 案例         5. 插 一. 动画 三种方式显示和隐 ...

  6. 单链表进阶学习 三段

    单链表进阶学习 三段 从尾到头打印单链表: 思路: 实际意义就是逆序打印单链表: 利用栈的操作,先进后出,实现逆序打印效果. (注:不建议直接对单链表进行反转操作.这样会破坏链表本身的结构,在做题和练 ...

  7. 单链表进阶学习 二段

    单链表进阶学习 二段 单链表的反转 思路: 定义一个新节点,reserveHead=new HeroNode(); 利用辅助变量cur遍历原来的链表,每遍历一个节点就将其取出,放在新建链表的最前端:( ...

  8. JavaSE进阶学习笔记-目录汇总(待完成)

    声明:此博客来自于黑马程序员学习笔记,并非商用,仅仅是为了博主个人日后学习复习用,如有冒犯,请联系qq208820388立即删除博文,最后,来跟我一起喊黑马牛逼黑马牛逼黑马牛逼 JavaSE进阶学习笔 ...

  9. java上传视频到七牛云_Java进阶学习:将文件上传到七牛云中

    Java进阶学习:将文件上传到七牛云中 通过本文,我们将讲述如何利用七牛云官方SDK,将我们的本地文件传输到其存储空间中去. JavaSDK:https://developer.qiniu.com/k ...

  10. 类进阶学习目标 java 1614957028

    类进阶学习目标 java 1614957028

最新文章

  1. Mac远程连接Windows桌面
  2. oracle11g怎么显示中文,ORACLE11G中PLSQL中文显示乱码、Linux下sqlplus查询中文乱码
  3. 虚拟机vs裸金属服务器,裸金属是虚拟机还是物理机
  4. 电子技术基础数字部分第六版_大部分数字图书馆技术特点与应用分析
  5. [译]Kinect for Windows SDK开发入门(八):骨骼追踪进阶 上
  6. WINDOWS蓝色当机画面解读
  7. 从 SAS 到 NVMe,换个底盘就完儿事了?
  8. 计算机二级科目有ps吗,计算机二级有ps吗
  9. 什么时候都要记得:生活愈是往下,嘴角愈要上扬
  10. 打开OpenProj 出现Your Java Vendor is Oracle Corporation. To run OpenProj, you need the Sun Java......
  11. 关于半导体器件材料的这些基础知识你都知道?
  12. 大数据读书——《淘宝技术这十年》读书笔记
  13. java mybatisplus Error parsing time stamp
  14. silvaco的石墨烯fet仿真_高灵敏度表面等离子体光纤传感器仿真设计(二)
  15. 前端,你需要掌握的重点!!
  16. 如何用js计算是否为闰年
  17. 如何更改域计算机用户名和密码错误,win7加入域失败:未知的用户名或密码错误 | 绿萝...
  18. python夜曲编程_夜曲编程——我毕业了!!!
  19. 服务器所在文件夹路径,服务器上文件夹路径
  20. Ubuntu订阅电信物联网平台

热门文章

  1. 生命倒计时-倒数9117日
  2. 11、TWS和IB中的streaming市场数据
  3. 前端开发常用字体颜色
  4. 四步打造用户「上瘾」的抖音型 App
  5. 【MySQL 实战】03. MySQL 主从复制(一主一从)
  6. Visual Studio2013安装与部署
  7. 【第22例】IPD 体系进阶:综合创新地图
  8. multisim扩大工作区_Multisim介绍:30分钟内学会捕捉、仿真和布局设计
  9. 高仿中国银行ATM系统
  10. 推荐十部关于AI的记录片