虚函数和模板的用途区别
模板是范型编程的基础,所谓范型编程就是用独立与任何特定类型的方式编写代码
所以简单地说,类是对象的抽象,而模板又是类的抽象,也就用模板能定义出具体类
再理解深刻点
在c++里,常说的多态一般分为两种:
一种是运行时的多态,也就是虚函数体现的多态
另一种是编译时的多态,也就是范型编程的多态,体现在参数的多态
在作用上是为了提高编程效率,其实用其他技术也能达到同样的效果
另外贴一篇讲虚函数的存在意义的知乎回答,写的实在太清楚了:
链接:https://www.zhihu.com/question/23971699/answer/84332610
虚函数主要是用来实现多态和多重继承的
没有虚函数理论上也可以实现多态,但是太麻烦了,没有虚函数清晰
主要是在多态上使用。
多态就是一个指针指向子类对象,那么他调用的函数就是子类的对象的。
这就实现了什么?
实现了一个函数会根据传入参数的不同有不同的功能。
函数有多个状态。 就是多态
void fuck (Animal *a);
如果没有多态 那么 这个函数 只能fuck Animal
想要fuck多种 就要用重载,写多个同名函数 参数不同
但是有了多态 他可以fuck所有Animal的子类 比如牛 羊 耗子 什么的
根据传进来的参数不同,fuck不同的动物。
这样这个函数就是fuckAnimalYouWant 函数了
这就多态了
如果现在你想fuck狗
要做的只是:
1.创建狗类继承Animal
2.传入到fuck函数中
此时 并不需要该代码 即使传入到fuck函数中 也可以是通过配置文件这种形式改变传入参数,并没有改变代码
如果没有多态呢?
1.创建狗对象继承Animal
2.在类中增加一个fuck函数 参数是Dog
这就改变代码了。
违反了设计模式开闭原则:对扩展开放,对修改关闭
实现多态的方式是什么
子类有和父类 同名同参数(重写) 的函数
所以 对于多态的状态来说,一定会有两个同名同参数函数,分别定义在子类中和父类中
那么当用指针调用一个函数的时候,究竟是调用子类的还是父类的:
对于一个类成员函数的调用:
Animal *a = new Dog()
a->bark()
表面上看来是在一个对象内部去调用函数,好像是这个函数是存在于对象内部的感觉。。
但是实际上不是
实际上所有的函数都存放在单独的一个代码区,而函数对象里面只有成员变量。
也就是说
a->bark()
实际上被编译器变成了
Animal::bark(a) 把对象a传进去 才告诉了编译器具体是哪个对象调用的这个函数
但是对于多态来说:
Animal::bark() 有
Dog::bark()也一定有
究竟选哪个函数执行呢?
此时还没运行,还是在编译阶段,也就是内存中什么Dog啊 Animal 的对象还没有存在。
所以只能根据a这个指针的类型 来决定使用哪个。也就是 Animal::bark()
所以
无论a指向什么子类,他调用的bark函数 都是Animal版本的父类版本的bark函数。
所以
现在没有办法达到多态的效果
这个在编译时决定函数是哪个类的函数的方式就叫做静态联编
a->bark()
现在希望调用的是Dog::bark(a)
调用的是Dog类的
所以就不能使用静态联编,静态联编在编译的时候决定函数是哪个。
为什么知道调用的是Dog::bark(a) 而不是 Cat::bark(a)呢?
因为a指向的对象是一个Dog 而不是一个Cat
所以必须在a指向的对象创建出来后才能决定究竟调用哪个函数
这就是动态联编
怎么把静态联编改成动态联编呢??
编译器在静态联编时做了以下转换
a->bark ---> Animal::bark(a)
当bark函数被设置为虚函数时,就不会进行那个转换了,而是转化为
a->bark ----》 (a->vtpl[1])(a)
先通过a指针找到对象,再找到对象中的虚表,再在虚表里面找到该调用的那个函数的函数指针
因为必须要在a指向的对象里面找,所以 必须等到a被创建出来,所以必须是运行时
所以这就是动态联编 运行时决定调用哪个函数
而这个vtpl就是虚表指针,
当类中出现一个virtual指针时编译器就自动在类的成员变量里加入一个指针成员。
这个指针成员指向这个类的虚表指针。
当子类重写父类方法时 同时也是把继承自父类的虚表中对应函数的索引的函数指针从父类函数改成了自己的函数。。
这就造成了子类和父类对象里面的虚表内容不同。
所以动态联编时 去子类 或者父类里面找虚表,
调用的函数不同。
就形成了多态的效果
这个过程其实自己写逻辑也能写出来。
但是官方作出了virtual就是为了释放这个工作量
当你写了virtual这个关键字 ,以上的创建虚表 继承 等一系列工作就都自动执行了。
但是降低了执行效率。