C++ 深入理解模板实现多态思想
创始人
2024-05-22 18:39:24
0

文章目录

  • 前言
  • 一、模板与多态基础
    • 1.模板
    • 2.多态
  • 二、模板实现多态
  • 三、实际应用


前言

对C/C++学习感兴趣的可以看看这篇文章噢:C/C++教程

最近有时间,便用WTL写了一个兼具群聊单聊以及传输文件的聊天软件,过几天应该就能更新到 C/C++教程系列 中了

所以在这里提前讲解一下WTL中的一个非常重要的概念:模板实现多态

一、模板与多态基础

再进一步了解如何用模板来实现多态前,我们还是来看一看这两个概念的基础理解

1.模板

首先是模板,其主要用途在于让我们程序员少写代码

比如像下面两个函数类似的一系列函数:

int add(int a, int b) {return a + b;
}
double add(double a, double b) {return a + b;
}

就可以用模板简写为:

template
T add(T a, T b) {return a + b;
}

使用的方式如下:

	add(1,3);add(1.1, 3.4);add('s','a');

但由于C++编译器可以自动推断参数类型,所以中间的是可以省略的

这里要注意一个非常重要的问题,虽然我们只写了一个模板函数,但实际上并不止有一个函数

比如这里我们用了三种类型的add函数,那么编译器就会为我们分别生成三个函数

也就是说,这是编译器根据我们写的模板。帮我们自动生成的函数

进一步来说,模板是完全给编译器看的,并不会参与到最终的可执行文件中

上面这一点便是模板的精髓!

为了更加直观的理解,我们来看一下最终生成的三个函数的内存地址:

#include
template
T add(T a, T b) {return a + b;
}int main() {printf("%p\n", add);printf("%p\n", add);printf("%p\n", add);
}

这样我们就能实际的看到最终确实是生成了三个函数,因为三个函数的地址完全不同,分别就代表着三个版本的add函数
在这里插入图片描述
总结来说就是,模板并没有减少最终的代码量,它仅仅只是减少了我们程序员需要写的代码量

并且这个过程是在编译期间就完成了的,这一点很重要!

之所以要用模板来实现多态,就是看重了它是在编译期间就完成的,而不会去影响最终的可执行文件的执行时间、大小

2.多态

然后便是多态了,多态是类中一个很重要的概念,其主要用途就是使得函数接口统一化

比如下面这段代码:

#include  
using namespace std;
class A {
public:virtual void area() {cout << "这是基类A" << endl;}
};
class B : public A {
public:void area(){cout << "这是子类B" << endl;}
};
class C : public A {
public:void area(){cout << "这是子类C" << endl;}
};
// 程序的主函数
int main()
{A* a;a = new B();a->area();a = new C();a->area();return 0;
}

逻辑并不复杂,就是B,C两个类都继承于A

并且在基类中我们用到了关键字virtual 定义area为虚函数,还在两个子类里面都分别重写了这个函数

因为BC类都继承于A类,所以我们可以用A类指针来接收BC对象

从占用内存上考虑,子类是继承父类的,所以子类所占用的内存量肯定大于或等于父类占用内存,那么子类申请一块内存,赋值给父类的指针,父类就不可能会内存访问越界,而反过来,如果用子类指针存储父类对象,由于子类访问的内存大于等于父类,就可能造成内存访问越界,因此一般禁止这样使用

此时我们发现,我们只用了一个A调用同一个函数area,却可以完成两个类的调用!

在这里插入图片描述

所以很多时候,当我们使用别人的提供给我们的类时,只要知道了它的父类有哪些函数,那么其子类就必然有对应的函数

这可以极大方便类的管理、升级以及使用

虽然它的好处很多,但同样也有坏处,那就是它是动态绑定函数的,依靠了一个叫做虚函数表的东西,导致其内存占用更大,运行时间更长

比如上面的代码我们就可以在调试窗口中看到其虚函数表:

在这里插入图片描述

就是这个名为 _vfptr的变量名称,他就是指向虚函数表的函数指针,而虚函数表中就存有我们的虚函数

父类指针想要正确使用子类重写的函数,就必须要在这个虚函数表中进行遍历查询对应的函数地址

所以一旦你的类中有虚函数,那么你的类就肯定会多出一个指针大小的内存用于存储虚函数表的地址,并且最终生成的可执行文件也会变大很多字节

这取决于你的虚函数个数,每多一个虚函数,那么虚函数表就需要多一个指针大小的内存来存储

如果依旧不太懂的,可以自行在浏览器中搜索一下,有很多优秀的文章对此有解释

总结来说就是:使用传统类的多态特性,会导致程序效率变低,最终生成的可执行文件体积变大

原因就是它生成了虚函数表、虚函数指针,在程序运行过程中执行查询函数的操作

MFC就是因为大量使用的这种多态,公共控件都继承于基本窗口类,一般都有数十上百个虚函数,所以这就导致即使你什么都没干,一个MFC程序都至少有数兆大小,并且运行效率还较低

二、模板实现多态

了解了上面所说的两个基本概念的优缺点之后,现在我们就可以来到如何使用模板来实现多态了

因为模板就是编译期间就完成的操作,如果让模板来实现多态,那么就不存在运行期间去遍历虚函数表来找对应的函数,也不需要开辟一个虚函数表来存储虚函数地址

既能节约内存,又能提高程序运行效率,是不是非常的完美!

下面我们就来看一看模板实现多态的基本流程

#include  
using namespace std;
template
class A {
public:void Show() {T* p=static_cast(this);p->area();}void area() {cout << "这是基类A" << endl;}
};
class B : public A {
public:void area(){cout << "这是子类B" << endl;}
};
class C : public A {
public:void area(){cout << "这是子类C" << endl;}
};
// 程序的主函数
int main()
{B b;b.Show();C c;c.Show();return 0;
}

这里同样是B,C两个类都继承自A类,但不同点就在于A类带了一个模板变量

所以B,C类在继承A的时候,就需要将自己这个类型传递进去

此时三个类都写了area函数,但只有基类写了show方法对吧

但由于B,C类都是继承自A类,所以它们其实也已经含有了show方法

然后便是最重要的一步,在基类的show方法中,我将this指针转化为T类型指针

static_cast与强制转化基本等价,唯一很大一点的区别就是,强制转换可以任意使用,比如B没有继承自A类,强制转换仍然可以将两者指针进行转换,而static_cast无法转换两个毫不相干的东西,这样就保证了传入的类型是继承自基类的,否则编译会直接报错

此时这里的p指针

T* p=static_cast(this);

实际就转化为了调用者的指针,以B举例子:

B b;
b.Show(); //调用Show方法后,完成了指针的转换,指代的B,那么B调用area函数,也就是调用自己重写的area函数

如果现在再多出一个子类D继承于A,但里面什么都没有:

class D : A{
}

那么当你使用D时:

D d;
d.Show(); //将指针转换为D类型,由于D类型没有重写area方法,所以将调用继承下来的基类area方法

同样是一个show函数,能够却能根据情况选择出不同的函数调用,而且还是在编译期间就完成了的

这便是模板实现多态的基本原理

在这里插入图片描述

三、实际应用

由于上面说的都是实现原理,例子比较奇怪,下面我们来直接看一看ATL中的代码:

WTL是基于ATL之上开发的,而ATL库则是vs开发环境中自带,WTL库需要自己去下载

可以输入以下代码

#include
class MyWindow :public CWindowImpl 
{};

然后右键速览CWindowImpl类,接着在跳出的文件中搜索static_cast:

在这里插入图片描述
就能看到很多像上图这样的调用

  1. 先将this指针还原为子类
  2. 然后再调用对应的函数
  3. 如果子类重写了这个函数,那么就调用子类的函数,否则就调用父类的函数

当然这并不完全如此,比如上图中的那一出,是将其转化为子类后,传给某个函数进行处理

不过总体逻辑是一致的:在父类中操作子类,以实现静态多态的目的

相关内容

热门资讯

团员民主评议总结 团员民主评议总结2篇  总结是事后对某一阶段的学习或工作情况作加以回顾检查并分析评价的书面材料,它可...
高二数学的知识点总结 高二数学的知识点总结  数学是我们学习中非常重要的一门课程,数学与我们的生活密切相关, 所以我们一定...
挖掘机销售人员的个人工作总结 挖掘机销售人员的个人工作总结  时间乘着年轮循序往前,一段时间的工作已经结束了,回顾过去的工作,倍感...
依法行政工作总结 依法行政工作总结(通用5篇)  时间总在不经意间匆匆溜走,我们的工作又告一段落了,回顾这段时间的工作...
教育技能实训总结范文通用14... 教育技能实训总结范文 第一篇通过这次的实训使我们学到了很多,同时也让我们意识到我们要学的更多。从程序...
双创工作总结共40篇 双创工作总结 第一篇省里要来检查“双创”工作,我们单位的领导也开会通知我们要打扫好自我科室的卫生,唉...
换届选举工作总结范文 换届选举工作总结范文  工作总结的定义  工作总结(Job Summary/Work Summary...
新教材网络培训总结 新教材网络培训总结  新教材网络培训是很重要的,下面unjs小编整理了新教材网络培训总结,欢迎阅读!...
病区工作总结通用8篇 病区工作总结 第一篇新型冠状病毒感染的肺炎疫情牵动着每个人的心。“疫情就是命令,防控就是责任”,在抗...
测量员的工作总结 测量员的工作总结范文  时间不知不觉,我们后知后觉,辛苦的工作已经告一段落了,回想起这段时间的工作,...
晋升助理工程师个人见习期工作... 晋升助理工程师个人见习期工作总结范文  总结是把一定阶段内的有关情况分析研究,做出有指导性的经验方法...
保安年终总结 精选保安年终总结三篇  总结在一个时期、一个年度、一个阶段对学习和工作生活等情况加以回顾和分析的一种...
计算机组装总结 计算机组装总结(通用8篇)  总结是事后对某一阶段的学习或工作情况作加以回顾检查并分析评价的书面材料...
教研工作总结63篇 教研工作总结 第一篇本学期,我们科学教研组以《科学(3——6年级)课程标准》为指导,以培养学生科学素...
初中数学二次函数知识点总结(...   V.二次函数与一元二次方程  特别地,二次函数(以下称函数)y=ax^2+bx+c,  当y=0...
开学第一周国旗下讲话稿 开学第一周国旗下讲话稿(精选49篇)  在社会一步步向前发展的今天,越来越多人会去使用讲话稿,讲话稿...
污水处理厂个人年终工作总结 污水处理厂个人年终工作总结污水处理厂个人年终工作总结1  秋去冬来,不知不觉20xx年已接近尾声,新...
医生专业技术工作述评 医生专业技术工作述评  医生专业技术工作述评  一、自然状况  xxx,男,xxx年出生于安徽马鞍山...
公司上半年工作总结 精选公司上半年工作总结范文锦集八篇  总结就是把一个时段的学习、工作或其完成情况进行一次全面系统的总...