备案 网站语言,thinkphp和wordpress区别,用手机制作动画的软件,阿里巴巴网站优化virtual实现多态
类的多态特性是支持面向对象的语言最主要的特性#xff0c;但是支持类并不能说明就是支持面向对象#xff0c;能够解决多态问题的语言#xff0c;才是真正支持面向对象的开发的语言。 C多态举例#xff1a; #include iostream
using namespace …virtual实现多态
类的多态特性是支持面向对象的语言最主要的特性但是支持类并不能说明就是支持面向对象能够解决多态问题的语言才是真正支持面向对象的开发的语言。 C多态举例 #include iostream
using namespace std; class Vehicle {
public: Vehicle(float speed, int total) : speed(speed), total(total) {} virtual void ShowMember() { // 声明为虚函数 cout Vehicle function ShowMember. endl; }
protected: float speed; int total;
}; class Car : public Vehicle {
public: Car(int aird, float speed, int total) : Vehicle(speed, total), aird(aird) {} void ShowMember() override { // 使用override关键字明确表示这是重写基类的虚函数
//虚函数在派生类中由于继承的关系这里的virtual也可以不加cout Car function ShowMember. endl; }
protected: int aird;
}; void test(Vehicle temp) { temp.ShowMember(); // 这里会根据temp的实际类型调用对应版本的ShowMember函数
} int main() { Vehicle a(120, 4); Car b(180, 110, 4); test(a); // 输出: Vehicle function ShowMember. test(b); // 输出: Car function ShowMember. return 0;
}
代码展示C中多态性的一个基本用法
通过基类指针或引用调用派生类对象的虚函数时将执行派生类中覆盖该函数的版本。
程序在运行时能够根据其类型确定调用哪个重载的成员函数的能力称为多态性. 多态性依赖虚函数的定义在需要解决多态问题的重载成员函数前加上virtual关键字成员函数就变成了虚函数。但是虚函数增加了一些数据存储和执行指令的开销。
用基类指针指向其子类的实例然后通过父类的指针调用实际子类的成员函数。通过基类指针或基类引用做形参当实参传入不同的派生类(或基类)的指针或引用在函数内部触发动态绑定从而来运行时 实现多态的。
这种技术可以让父类的指针有“多种形态”本质上一种泛型技术就是试图使用不变的代码来实现可变的算法。比如模板技术RTTI技术虚函数技术要么是试图做到在编译时决议要么试图做到运行时决议。
虚函数的定义遵循的规则 1.如果虚函数在基类与派生类中出现仅仅是名字相同而形式参数不同或者是返回类型不同那么即使加上了virtual关键字也是不会进行滞后联编的。 2.只有类的成员函数才能说明为虚函数因为虚函数仅适合用与有继承关系的类对象所以普通函数不能说明为虚函数。 3.静态成员函数不能是虚函数,因为静态成员函数的特点是不受限制于某个对象。 4.内联(inline)函数不能是虚函数因为内联函数不能在运行中动态确定位置。即使虚函数在类的内部定义定义但是在编译的时候系统仍然将它看做是非内联的。 5.构造函数不能是虚函数因为构造的时候对象还是一片位定型的空间只有构造完成后对象才是具体类的实例。 6.析构函数可以是虚函数,而且通常声明为虚函数。 虽然使用虚函数会降低效率但是在处理器速度越来越快时将一个类中的所有成员函数都定义成为virtual总是有好处的它除了会增加一些额外的开销是没有其它坏处的对于保证类的封装特性是有好处的。 虚函数表
虚函数是通过一张虚函数表实现。表中是类的虚函数的地址表可以解决继承、覆盖的问题。
有虚函数的类的实例中这个表被分配在了这个实例的内存中所以当用父类的指针来操作一个子类的时候它像一个地图一样指明了实际所应该调用的函数。C的编译器保证虚函数表的指针存在于对象实例中最前面的位置这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。所以可以通过对象实例的地址得到这张虚函数表然后就可以遍历其中函数指针并调用相应的函数。
假设有这样的一个类 class Base {public:virtual void f() { cout Base::f endl; }virtual void g() { cout Base::g endl; }virtual void h() { cout Base::h endl; }
}
按照上面的说法可以通过Base的实例来得到虚函数表。 typedef void(*Fun)(void); Base b;Fun pFun NULL;cout 虚函数表地址 (int*)(b) endl;cout 虚函数表 — 第一个函数地址 (int*)*(int*)(b) endl;// Invoke the first virtual function pFun (Fun)*((int*)*(int*)(b));pFun();
通过这个示例我们可以看到我们可以通过强行把b转成int 取得虚函数表的地址然后再次取址就可以得到第一个虚函数的地址了也就是Base::f()这在上面的程序中得到了验证把int 强制转成了函数指针。通过这个示例我们就可以知道如果要调用Base::g()和Base::h()其代码如下
(Fun)*((int*)*(int*)(b)0); // Base::f()
(Fun)*((int*)*(int*)(b)1); // Base::g()
(Fun)*((int*)*(int*)(b)2); // Base::h()注意在上面这个图中我在虚函数表的最后多加了一个结点这是虚函数表的结束结点就像字符串的结束符“/0”一样其标志了虚函数表的结束。
下面将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。
一般继承无虚函数覆盖
假设有如下所示的一个继承关系 在这个继承关系中子类没有重载任何父类的函数。那么在派生类的实例中其虚函数表如下所示。对于实例Derive d 的虚函数表如下 可以看到
1虚函数按照其声明顺序放于表中。
2父类的虚函数在子类的虚函数前面。一般继承有虚函数覆盖
如果子类中有虚函数重载了父类的虚函数会是一个什么样子假设我们有下面这样的一个继承关系。 为了让大家看到被继承过后的效果在这个类的设计中只覆盖了父类的一个函数f()。那么对于派生类的实例其虚函数表会是下面的一个样子 从表中可以看到下面几点
1覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
2没有被覆盖的函数依旧。
这样我们就可以看到对于下面这样的程序
Base *b new Derive();
b-f();
由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代于是在实际调用发生时是Derive::f()被调用了。这就实现了多态。
多重继承无虚函数覆盖
下面再让我们来看看多重继承中的情况假设有下面这样一个类的继承关系。注意子类并没有覆盖父类的函数。 对于子类实例中的虚函数表是下面这个样子 可以看到
1 每个父类都有自己的虚表。
2 子类的成员函数被放到了第一个父类的表中。所谓的第一个父类是按照声明顺序来判断的1
这样做就是为了解决不同的父类类型的指针指向同一个子类实例而能够调用到实际的函数。
多重继承有虚函数覆盖
下面我们再来看看如果发生虚函数覆盖的情况。
下图中我们在子类中覆盖了父类的f()函数。 下面是对于子类实例中的虚函数表的图 我们可以看见三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样我们就可以任一静态类型的父类来指向子类并调用子类的f()了。如 Derive d;Base1 *b1 d;Base2 *b2 d;Base3 *b3 d;b1-f(); //Derive::f()b2-f(); //Derive::f()b3-f(); //Derive::f()b1-g(); //Base1::g()b2-g(); //Base2::g()b3-g(); //Base3::g()为什么具备多态特性的类的析构函数有必要声明为virtual?
将基类的析构函数声明为virtual那么当通过基类指针删除对象时会首先调用派生类的析构函数然后再调用基类的析构函数。这样就可以确保所有的清理工作都被正确执行避免了资源泄漏。因此如果类被设计为基类并且希望它的派生类能够通过基类指针被正确地删除那么应该将基类的析构函数声明为virtual。 纯虚函数
纯虚函数是一种特殊的虚函数它在基类中声明为 0表明在基类中不提供具体的实现并且要求任何派生类都必须提供该函数的实现。
纯虚函数为派生类提供了一种接口规范确保派生类必须实现特定的功能。
如果一个类包含至少一个纯虚函数那么这个类就是一个抽象基类。抽象基类不能被实例化因为它包含不完整的功能。
派生类必须重写纯虚函数才能被实例化、如果一个类从抽象基类派生并且它没有重写所有的纯虚函数那么这个派生类也仍然是抽象的不能实例化。派生类必须完全遵循抽象基类定义的接口规范。
#include iostream
using namespace std; // 抽象基类
class CVirtual {
public: CVirtual() {} virtual ~CVirtual() { cout CVirtual destruction endl; } // 纯虚函数 virtual void fun() 0;
}; // 派生类
class CDerived : public CVirtual {
public: CDerived() {} ~CDerived() {} // 覆盖纯虚函数 void fun() override { cout CDerived function call endl; }
}; int main() { // CVirtual v; // 错误抽象基类不能被实例化 CDerived d; // 正确派生类提供了纯虚函数的实现 d.fun(); // 调用派生类中的fun()实现 return 0;
}