本文主要是介绍学习c++的第十二天,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
多态性
多态性
虚函数
虚析构函数
纯虚函数
多态性
多态性
多态性的实现依赖于继承和虚函数。在面向对象编程中,通过基类的指针或引用来调用派生类的成员函数时,可以根据实际的对象类型决定调用哪个类的成员函数,从而实现多态性。
- 在静态联编中,编译器在编译阶段就能确定调用的具体函数,这种情况下多态性的效果并不明显。而在动态联编中,编译器只能确定调用的是基类的成员函数,具体调用哪个派生类的函数则在运行时决定,这就发挥了多态性的优势。
- 动态联编和多态性的典型应用是通过虚函数实现的。虚函数是在基类中声明的带有 virtual 关键字的成员函数,它可以被派生类重写(Override)。当使用基类的指针或引用调用虚函数时,实际调用的是派生类中重写的函数。
下面,我们围绕静态联编,给大家举一个简单例子:
#include <iostream>class Shape {
public:void draw() {std::cout << "绘制一个形状" << std::endl;}
};class Circle : public Shape {
public:void draw() {std::cout << "绘制一个圆形" << std::endl;}
};class Rectangle : public Shape {
public:void draw() {std::cout << "绘制一个矩形" << std::endl;}
};int main() {Shape shape;Circle circle;Rectangle rectangle;// 基类指针指向派生类对象Shape* shapePtr1 = &circle;Shape* shapePtr2 = &rectangle;// 调用基类的成员函数,由于静态联编,在编译时就确定了调用的函数shape.draw(); // 绘制一个形状shapePtr1->draw(); // 绘制一个形状shapePtr2->draw(); // 绘制一个形状return 0;
}
在上述示例中,我们有一个基类 Shape 和两个派生类 Circle 和 Rectangle。基类和派生类都有一个名为 draw 的成员函数。
在 main 函数中,我们创建了一个基类对象 shape 和两个派生类对象 circle 和 rectangle。然后,我们将派生类对象的地址赋给基类指针 shapePtr1 和 shapePtr2。
在调用成员函数时,基类对象 shape 和派生类对象的指针 shapePtr1 和 shapePtr2 都调用了 draw 函数。由于静态联编,在编译时就确定了调用的函数,因此无论是通过基类对象还是派生类对象的指针调用,都会执行基类的 draw 函数,输出 "绘制一个形状"。
这个示例展示了静态联编的特点,即在编译时就确定了调用哪个函数,不会根据对象的实际类型来决定调用哪个函数。
通过多态性,我们可以以一种统一的方式处理不同类型的对象。这样做的好处是,我们可以将对象视为其基类类型,而不需要关注具体是哪个派生类的实例。这使得我们可以设计出更加灵活和可扩展的代码,同时提高了代码的可读性和可维护性。
总结来说,多态性是面向对象编程的重要特性,它通过继承和虚函数机制实现。它允许不同类型的对象对同一个消息做出不同的响应,提高了代码的灵活性和可扩展性。静态联编和动态联编是多态性的两种实现方式,静态联编由于编译时候就已经确定好怎么执行,因此执行起来效率高;而动态联编想必虽然慢一些,但优点是灵活。两者各有千秋,有各自不同的使用场景。
虚函数
虚函数是用于实现动态联编和多态性的一种机制。通过将父类中的成员函数声明为虚函数,可以在子类中重写该函数并根据对象的实际类型选择调用哪个函数,从而实现多态性。
在 C++ 中,将函数声明为虚函数需要在函数名前加上关键字 virtual。当一个函数被声明为虚函数时,它的子类中的同名函数也会自动成为虚函数。当使用一个指向子类对象的基类指针或引用调用虚函数时,程序会在运行时动态地确定调用哪个函数,这就是动态联编。
一般形式如下:
virtual 函数返回值 函数名(形参)
{函数体
}
需要注意的是:
- 虚函数不能是静态成员函数或友元函数:虚函数依赖于对象的实际类型来确定调用哪个函数,而静态成员函数和友元函数不属于特定的对象,因此无法实现动态多态性,所以它们不能被声明为虚函数。
- 内联函数不能是虚函数:内联函数是在编译时插入代码的,而虚函数的调用是在运行时动态决定的。即使虚函数在类的定义中被标记为 inline,编译器仍然会将其视为非内联函数,因为虚函数的调用需要在运行时根据对象的实际类型来确定。
- 构造函数不能是虚函数,析构函数可以是虚函数:构造函数在创建对象时被调用,并负责初始化对象的成员变量等工作。由于在调用构造函数时对象尚未完全构建,因此无法实现动态多态性,所以构造函数不能声明为虚函数。而析构函数在销毁对象时被调用,可以实现动态多态性,通常情况下我们将析构函数声明为虚函数,以确保在删除基类指针时正确调用派生类的析构函数,防止内存泄漏。
以下是一个简单的使用虚函数的示例:
#include <iostream>
using namespace std;class Shape {
public:virtual void draw() {cout << "绘制一个形状" << endl;}
};class Circle : public Shape {
public:void draw() {cout << "绘制一个圆形" << endl;}
};class Rectangle : public Shape {
public:void draw() {cout << "绘制一个矩形" << endl;}
};int main() {Shape shape;Circle circle;Rectangle rectangle;Shape* shapePtr1 = &circle;Shape* shapePtr2 = &rectangle;// 通过基类指针调用虚函数,程序会在运行时动态地确定调用哪个函数shape.draw(); // 绘制一个形状shapePtr1->draw(); // 绘制一个圆形shapePtr2->draw(); // 绘制一个矩形return 0;
}
在上述示例中,我们将 Shape 类的 draw 函数声明为虚函数,并在其派生类 Circle 和 Rectangle 中重写了该函数。在 main 函数中,我们创建了一个基类对象 shape 和两个派生类对象 circle 和 rectangle,并将派生类对象的地址赋给基类指针。
通过基类指针调用虚函数时,程序会在运行时动态地确定调用哪个函数。因此,通过基类对象 shape 调用 draw 函数时,输出 "绘制一个形状";通过派生类对象的指针 shapePtr1 和 shapePtr2 调用 draw 函数时,分别输出 "绘制一个圆形" 和 "绘制一个矩形",实现了多态性。
虚析构函数
在C++中,我们不能将构造函数声明为虚函数,因为构造函数的调用是在实例化对象时发生的,而虚函数的实现是通过虚函数表来实现的。在实例化对象之前,对象还没有被创建,也没有内存空间可供调用虚函数,所以将构造函数声明为虚函数是没有意义的。
然而,析构函数可以被声明为虚函数,并且通常情况下我们将其声明为虚析构函数。通过将析构函数声明为虚函数,可以在删除指向派生类对象的基类指针时,根据实际所指向的对象类型动态绑定调用相应的析构函数,从而实现正确的对象内存释放。
当使用基类指针指向派生类对象时,如果基类的析构函数不是虚函数,那么在删除指针时只会调用基类的析构函数,而不会调用派生类的析构函数。这可能导致派生类对象中动态分配的资源没有得到正确释放,从而造成内存泄漏。
通过声明析构函数为虚函数,我们可以保证在删除基类指针时调用正确的析构函数,确保对象的内存得到正确释放,避免内存泄漏问题。
下面是一个使用虚析构函数的示例:
#include <iostream>
using namespace std;class Base {
public:virtual ~Base() {cout << "调用了基类的析构函数" << endl;}
};class Derived : public Base {
public:~Derived() {cout << "调用了派生类的析构函数" << endl;}
};int main() {Base* ptr = new Derived();delete ptr;return 0;
}
在上述示例中,我们定义了一个基类 Base 和一个派生类 Derived。基类 Base 的析构函数被声明为虚函数(使用 virtual 关键字),而派生类 Derived 的析构函数没有显式声明为虚函数。
在 main 函数中,我们创建了一个派生类对象的指针 ptr,并将其赋给基类指针。接着,我们使用 delete 关键字删除基类指针,这会触发析构函数的调用。
由于基类的析构函数是虚函数,因此在删除指针时,会调用正确的析构函数。输出结果为:
调用了派生类的析构函数
调用了基类的析构函数
可以看到,通过将基类的析构函数声明为虚函数,我们确保了派生类的析构函数也被正确地调用,从而释放了派生类对象中的资源。
纯虚函数
在C++中,纯虚函数是一种在基类中声明但没有实现的虚函数。纯虚函数的声明形式是在函数原型的末尾使用 "= 0" 进行标记。纯虚函数用于定义一个接口,要求派生类必须实现该函数。
virtual 返回值 函数名(形参)=0;
纯虚函数的作用是定义一个基类的接口,这个接口可以被派生类继承并实现。通过在基类中声明纯虚函数,我们可以强制派生类提供相应的实现,以确保派生类具有某些特定的行为或功能。
下面是一个使用纯虚函数的示例:
#include <iostream>
using namespace std;class Shape {
public:virtual double getArea() const = 0; // 纯虚函数
};class Rectangle : public Shape {
private:double length;double width;
public:Rectangle(double l, double w) : length(l), width(w) {}double getArea() const override { // 派生类必须实现纯虚函数return length * width;}
};class Circle : public Shape {
private:double radius;
public:Circle(double r) : radius(r) {}double getArea() const override { // 派生类必须实现纯虚函数return 3.14159 * radius * radius;}
};int main() {Shape* shape1 = new Rectangle(5, 6);Shape* shape2 = new Circle(3);cout << "矩形的面积: " << shape1->getArea() << endl;cout << "圆形的面积: " << shape2->getArea() << endl;delete shape1;delete shape2;return 0;
}
在上述示例中,我们定义了一个基类 Shape,其中声明了一个纯虚函数 getArea()。这个函数没有提供实现,只是要求派生类必须实现它。派生类 Rectangle 和 Circle 都继承自基类 Shape,并实现了 getArea() 函数。
在 main 函数中,我们创建了基类指针 shape1 和 shape2,分别指向派生类对象 Rectangle 和 Circle。通过调用 getArea() 函数,我们可以获得矩形和圆形的面积。
注意到,基类 Shape 是抽象类,因为它包含一个纯虚函数。抽象类不能被实例化,只能用作其他类的基类。而派生类必须实现纯虚函数,否则它们也会成为抽象类。
这篇关于学习c++的第十二天的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!