虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
虚函数
是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到基类中定义的函数。我们想要的是在程序运行时可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态多态,或动态链接,或后期绑定。
如果基类area() 没有加关键字virtual,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接。 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。
纯虚函数
您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。包含纯虚函数的类叫做抽象类。
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape(int a = 0, int b = 0)
{
width = a;
height = b;
cout << "Parent class area : " << width << endl;
}
// 纯虚函数,没有函数主体,有返回值
virtual int B() = 0;
// 纯虚函数,没有函数主体,没有返回值
virtual void A() = 0;
// 虚函数
virtual int area()
{
cout << "Parent class area : " << 0 << endl;
return 0;
}
};
class Rectangle : public Shape {
public:
Rectangle(int a = 0, int b = 0) :Shape(a, b) { }
void A()
{
cout << "Rectangle class area : " << endl;
}
int B()
{
cout << "Rectangle class area : " << endl;
return 0;
}
int area()
{
cout << "Rectangle class area : " << (width * height) << endl;
return (width * height);
}
};
class Triangle : public Shape {
public:
Triangle(int a = 0, int b = 0) :Shape(a, b) { }
int area()
{
cout << "Triangle class area : " << endl;
return (width * height / 2);
}
};
// 程序的主函数
int main()
{
Shape *shape;
Rectangle rec(10, 7);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
system("pause");
return 0;
}
纯虚函数
- 纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
- 虚函数声明如下:virtual ReturnType FunctionName(Parameter)虚函数必须实现(哪怕是仅仅添加一对大括号),如果不实现,编译器将报错。
- 对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
- 实现了纯虚函数的子类,该纯虚函数在子类中就变成了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
- 虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
- 在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
- 友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
- 析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。
虚函数和纯虚函数的区别
- virtual修饰的虚函数必须有实现(哪怕是仅仅添加一对大括号),纯虚函数是没有函数主体,virtual void funtion1()=0来表示。
- virtual修饰的虚函数可以被子类重写,也可以不重写,而纯虚函必须被子类重写。
- 无法创建包含纯虚函数的抽象类的实例,只能被继承无法实例化。
虚函数的底层实现原理 VTABLE
- 编译器对每个包含虚函数的类创建一个虚函数表VTABLE,表中每一项指向一个虚函数的地址,即VTABLE表可以看成一个函数指针的数组,每个虚函数的入口地址就是这个数组的一个元素。
- 每个派生类的VTABLE继承了它各个基类的VTABLE,如果基类VTABLE中包含某一项(虚函数的入口地址),则其派生类的VTABLE中也将包含同样的一项,但是两项的值可能不同。如果派生类中重载了该项对应的虚函数,则派生类VTABLE的该项指向重载后的虚函数,如果派生类中没有对该项对应的虚函数进行重新定义,则使用基类的这个虚函数地址。
- 在创建含有虚函数的类的对象的时候,编译器会在每个对象的内存布局中增加一个vptr指针项,该指针指向本类的VTABLE。在通过指向基类对象的指针调用一个虚函数时,编译器生成的代码是先获取所指对象的vtb1指针,然后调用vptr所指向类的VTABLE中的具体虚函数的入口地址。
- 当基类中没有定义虚函数时,其长度=数据成员长度;派生类长度=自身数据成员长度+基类继承的数据成员长度;
- 当基类中定义虚函数后,其长度=数据成员长度+虚函数表的地址长度;派生类长度=自身数据成员长度+基类继承的数据成员长度+虚函数表的地址长度。
- 包含一个虚函数和几个虚函数的类的长度增量为0。含有虚函数的类只是增加了一个指针用于存储虚函数表的首地址。
- 派生类与基类同名的虚函数在VTABLE中有相同的索引号(或序号)。
final关键字
父类的虚函数或纯虚函数在子类中依然是虚函数。有时我们并不希望父类的某个函数在子类中被重写,在 C++11 及以后可以用关键字 final 来避免该函数再次被重写。
#include<iostream>
using namespace std;
class Base
{
public:
virtual void func()
{
cout<<"This is Base"<<endl;
}
};
class _Base:public Base
{
public:
void func() final//正确,func在Base中是虚函数
{
cout<<"This is _Base"<<endl;
}
};
class __Base:public _Base
{
/* public://不正确,func在_Base中已经不再是虚函数,不能再被重写
void func()
{
cout<<"This is __Base"<<endl;
}*/
};
int main()
{
_Base a;
__Base b;
Base* ptr=&a;
ptr->func();
ptr=&b;
_Base* ptr2=&b; ptr->func();
ptr2->func();
}
如果不希望一个类被继承,也可以使用 final 关键字。
格式如下:
class Class_name final
{
};
标签:函数,int,多态性,派生类,C++,Base,纯虚,基类 From: https://blog.51cto.com/u_6871414/5896991