网站建设实施流程,多功能产品设计,2019做网站必须做可信网站吗,网站开发外包价格一.虚函数 在基类的函数前加上virtual关键字#xff0c;在派生类中重写该函数#xff0c;运行时将会根据所指对象的实际类型来调用相应的函数#xff0c;如果对象类型是派生类#xff0c;就调用派生类的函数#xff0c;如果对象类型是基类#xff0c;就调用基类的函数。 …一.虚函数 在基类的函数前加上virtual关键字在派生类中重写该函数运行时将会根据所指对象的实际类型来调用相应的函数如果对象类型是派生类就调用派生类的函数如果对象类型是基类就调用基类的函数。 一虚表和虚基表指针
要理解这个问题我们要引出虚表和虚基表
虚表虚函数表的缩写类中含有 virtual 关键字修饰的方法时编译器会自动生成虚表它是在编译器确定的
虚表指针在含有虚函数的类实例化对象时对象地址的前四个字节存储的指向虚表的指针它是在构造函数中被初始化的 二纯虚函数
纯虚函数Pure Virtual Function是C中的一个特殊类型的虚拟函数它在基类中声明但没有定义。纯虚函数的声明使用virtual关键字并在函数声明的末尾添加 0来表示它是一个纯虚函数。子类派生类必须提供纯虚函数的实际实现否则子类也会被标记为抽象类无法创建对象。
class Shape {
public:// 声明纯虚函数virtual void draw() 0;// 普通成员函数void displayInfo() {// 这里可以包含一些通用的代码std::cout This is a shape. std::endl;}
};class Circle : public Shape {
public:// 子类必须提供纯虚函数的实现void draw() override {std::cout Drawing a circle. std::endl;}
};class Square : public Shape {
public:// 子类必须提供纯虚函数的实现void draw() override {std::cout Drawing a square. std::endl;}
};int main() {Circle circle;Square square;circle.displayInfo(); // 调用基类函数circle.draw(); // 调用派生类函数square.displayInfo(); // 调用基类函数square.draw(); // 调用派生类函数return 0;
}
在上述示例中Shape类包含一个纯虚函数draw()因此Shape类本身是一个抽象类不能创建它的对象。然后Circle和Square类都继承自Shape类并必须提供对draw()的实际实现。这种机制允许多态性Polymorphism的实现允许不同的派生类以不同的方式实现相同的虚拟函数。 二.多态的实现
根据上图举例分析
#includeiostream
#includevector
using namespace std;
class A {
public:virtual void prints() {cout A::prints endl;}A() {cout A:构造函数 endl;}
};
class B:public A {
public:virtual void prints() {cout B::prints endl;}B() {cout B:构造函数 endl;}
};
class C :public A {
public:};
int main() {A *b new B();b-prints();b new C();b-prints();return 0;
} 子类B重写了基类A的虚函数子类C并没有重写从结果分析依然是体现了多态性 下面阐述实现多态的过程
1.编译器在发现基类中含有虚函数时会自动为每个含有虚函数的类生成一份虚表该表是一个一维数组虚表里保存了虚函数的入口地址
2.编译器会在每个对象的前四个字节中保存一个虚表指针即vptr指向对象所属类的虚表
3.所谓的合适时机在派生类定义对象时程序运行会自动调用构造函数在构造函数中创建虚表并对虚表指针进行初始化。在构造子类对象时会先调用父类的构造函数此时编译器只“看到了”父类并为父类对象初始化虚表指针令它指向父类的虚表当调用子类的构造函数时为子类对象初始化虚表指针令它指向子类的虚表
4.当派生类对基类的虚函数没有重写时派生类的虚表指针指向的是基类的虚表当派生类对基类的虚函数重写时派生类的虚表指针指向的是自身的虚表当派生类中有自己的虚函数时在自己的虚表中将此虚函数地址添加在后面。
所以指向派生类的基类指针在运行时就可以根据派生类对虚函数的重写情况动态的进行调用从而实现多态性。 三.为什么析构函数一般写成虚函数 由于类的多态性通常通过父类指针或引用来操作子类对象。因为多套允许我们以统一的方式处理不同的派生类对象并且在运行时确定要调用的方法。 如果析构函数不被声明为虚函数则编译器实施静态绑定在删除基类指针时只会调用基类的析构函数而不调用派生类析构函数这样会造成派生类析构不完全造成内存泄漏。 这种行为是为了确保资源的正确释放。由于我们只知道父类的类型编译器无法确定指针指向的是哪个子类对象因此只能调用父类的析构函数来释放资源。
没有虚析构
#includeiostream
#includevector
using namespace std;
class A {
public:virtual void prints() {cout A::prints endl;}A() {cout A:构造函数 endl;}virtual ~A() {cout A:析构函数 endl;}
};
class B:public A {
public:virtual void prints() {cout B::prints endl;}B() {cout B:构造函数 endl;}~B() {cout B:析构函数 endl;}
};
int main() {A *b new B();b-prints();delete b;b NULL;return 0;
} 虚析构
#includeiostream
#includevector
using namespace std;
class A {
public:virtual void prints() {cout A::prints endl;}A() {cout A:构造函数 endl;}virtual ~A() {cout A:析构函数 endl;}
};
class B:public A {
public:virtual void prints() {cout B::prints endl;}B() {cout B:构造函数 endl;}~B() {cout B:析构函数 endl;}
};
int main() {A *b new B();b-prints();delete b;b NULL;return 0;
} 分析可以看到析构函数是先从子类析构再到父类析构