C++进阶学习---多肽
来源:微信公众号「编程学习基地」
目录
- 虚函数
- 覆盖
- 覆盖条件
- 虚函数表
- 多态
- 单台和多态
- 多态理解
- 多肽的好处
- 虚析构
- 纯虚函数
- 抽象类
- 纯抽象类
- 继承多肽示例程序
虚函数
普通成员函数前加关键字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++进阶学习---多肽相关推荐
- 3D视觉从入门到进阶学习路线
01 什么是知识星球? 知识星球是一个高度活跃的社区平台,在这里你可以和相同研究方向的小伙伴一起探讨科研工作难题.交流最新领域进展.分享paper资料.发布高质量的求职就业信息,当然还可以侃侃而谈,吐 ...
- android java服务,Android进阶学习必会:Java Binder中的系统服务
前言 这个知识点是Android进阶学习必须掌握的知识点之一,也是高阶Android架构师经常问到的点.在这里分想给大家,希望对大家的工作和学习有所帮助.喜欢本文的记得点赞关注哦~ 在前面的Andro ...
- leetcode与python进阶学习总结
转自:leetcode与python进阶学习总结 l1是一个链表型,val是其属性,以下句子意义为如果l1不为空则取l1.val否则取0,节省代码空间,干净利落 x= l1.val if l1 els ...
- opencv进阶学习笔记3:像素运算和图像亮度对比度调节
基础版传送门: python3+opencv学习笔记汇总目录(适合基础入门学习) 进阶版目录: python+opencv进阶版学习笔记目录(适合有一定基础) 像素运算 要求两张图大小,以及格式(np ...
- JQuery进阶学习
JQuery进阶学习的内容 1. 动画 2. 遍历 3. 事件绑定 4. 案例 5. 插 一. 动画 三种方式显示和隐 ...
- 单链表进阶学习 三段
单链表进阶学习 三段 从尾到头打印单链表: 思路: 实际意义就是逆序打印单链表: 利用栈的操作,先进后出,实现逆序打印效果. (注:不建议直接对单链表进行反转操作.这样会破坏链表本身的结构,在做题和练 ...
- 单链表进阶学习 二段
单链表进阶学习 二段 单链表的反转 思路: 定义一个新节点,reserveHead=new HeroNode(); 利用辅助变量cur遍历原来的链表,每遍历一个节点就将其取出,放在新建链表的最前端:( ...
- JavaSE进阶学习笔记-目录汇总(待完成)
声明:此博客来自于黑马程序员学习笔记,并非商用,仅仅是为了博主个人日后学习复习用,如有冒犯,请联系qq208820388立即删除博文,最后,来跟我一起喊黑马牛逼黑马牛逼黑马牛逼 JavaSE进阶学习笔 ...
- java上传视频到七牛云_Java进阶学习:将文件上传到七牛云中
Java进阶学习:将文件上传到七牛云中 通过本文,我们将讲述如何利用七牛云官方SDK,将我们的本地文件传输到其存储空间中去. JavaSDK:https://developer.qiniu.com/k ...
- 类进阶学习目标 java 1614957028
类进阶学习目标 java 1614957028
最新文章
- Mac远程连接Windows桌面
- oracle11g怎么显示中文,ORACLE11G中PLSQL中文显示乱码、Linux下sqlplus查询中文乱码
- 虚拟机vs裸金属服务器,裸金属是虚拟机还是物理机
- 电子技术基础数字部分第六版_大部分数字图书馆技术特点与应用分析
- [译]Kinect for Windows SDK开发入门(八):骨骼追踪进阶 上
- WINDOWS蓝色当机画面解读
- 从 SAS 到 NVMe,换个底盘就完儿事了?
- 计算机二级科目有ps吗,计算机二级有ps吗
- 什么时候都要记得:生活愈是往下,嘴角愈要上扬
- 打开OpenProj 出现Your Java Vendor is Oracle Corporation. To run OpenProj, you need the Sun Java......
- 关于半导体器件材料的这些基础知识你都知道?
- 大数据读书——《淘宝技术这十年》读书笔记
- java mybatisplus Error parsing time stamp
- silvaco的石墨烯fet仿真_高灵敏度表面等离子体光纤传感器仿真设计(二)
- 前端,你需要掌握的重点!!
- 如何用js计算是否为闰年
- 如何更改域计算机用户名和密码错误,win7加入域失败:未知的用户名或密码错误 | 绿萝...
- python夜曲编程_夜曲编程——我毕业了!!!
- 服务器所在文件夹路径,服务器上文件夹路径
- Ubuntu订阅电信物联网平台