(P35)虚函数与多态:纯虚函数 ,抽象类 ,多态 ,虚析构函数

    科技2026-03-10  7

    文章目录

    1.纯虚函数2.抽象类3.多态4.虚析构函数

    1.纯虚函数

    虚函数的特征: 基类之指针指向派生类的对象,调用的是派生类的虚函数,若不是虚函数,则调用的还是基类的函数,是依据类型来决定的; 这就可以使得我们可以以一致的观点来看待不同的派生类对象,而不需要关注派生类对虚函数是如何实现的; 该过程是动态绑定的,也就是在运行时刻才确定虚函数实际的入口地址;

    虚函数是实现多态性的前提 (1)需要在基类中定义共同的接口 (2)接口要定义为虚函数

    如果基类的接口没办法实现怎么办? eg:形状类Shape,它是一个抽象类,它的绘制方法是没有办法实现的

    解决办法 将这些接口定义为纯虚函数; 拥有纯虚函数的类就是抽象类; 抽象类中的纯虚函数是不需要实现的; 抽象类不能实例化(不能定义一个对象),因为它并不是一个实际存在的事物;

    在基类中不能给出有意义的虚函数的定义,这时可以把它说明成纯虚函数,把它的定义留给派生类来做

    定义纯虚函数

    class 类名{ virtual 返回值类型 函数名(参数表) = 0; }; 纯虚函数是不需要实现的,它的实现是留给派生类

    2.抽象类

    拥有纯虚函数的类就是抽象类

    作用: (1)抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为 (2)对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类中去实现

    注意 (1)抽象类只能作为基类来使用 (2)不能声明抽象类的对象 (3)构造函数不能是虚函数,析构函数可以是虚函数 因为在构造函数还没有调用之前,对象是没办法构造出来的,既然对象还没产生,则无法知道构造函数实际的入口地址,因为它是虚函数,实施的是动态绑定,它实际的入口地址存放在虚表中,既然对象都没构造出来,也无法通过虚表指针vptr无法找到实际调用的构造函数的入口地址,所以构造函数不能是虚函数

    eg:P35\01.cpp

    #include <iostream> #include <vector> using namespace std; //Shape类不知道如何绘制Draw,所以定义为纯虚函数 //只要有纯虚函数就是抽象类,抽象类是不能定义对象的,它是现实中并不存在的事物 //在基类中不需要实现纯虚函数,如果派生类没有实现纯虚函数,那么该派生类 //也会成为抽象类,则需要派生类的派生类去实现这个纯虚函数 class Shape { public: // virtual void Draw() = 0; void Draw() { cout<<"Shape ..."<<endl; } }; class Circle : public Shape { public: void Draw() { cout<<"Circle::Draw() ..."<<endl; } }; class Square : public Shape { public: void Draw() { cout<<"Square::Draw() ..."<<endl; } }; void DrawAllShapes(const vector<Shape*>& v) { vector<Square*>::const_iterator it; for (it=v.begin(); it!=v.end(); ++it) { //it存放的是Shape*的指针,*it就是Shape*这个指针类型 //基类指针*it指向派生类对象,会调用派生类的虚函数Draw //这里以一致的观点来看待不同的派生类对象 (*it)->draw(); } } int main(void) { // Shape s;//Error, 不能实例化抽象类 vector<Shape*> v; Shape* ps; ps =new Circle; v.push_back(ps); ps = new Square; v.push_back(ps); //将vector中的各个形状绘制出来 DrawAllShapes(v); return 0; } 测试: Draw方法如果不是纯虚的,DrawAllShapes函数它会依据类型*it调用draw方法(*it就是Shape*这个指针类型,),则他会调用基类的draw方法,没办法将不同的派生类对象统一起来看待

    eg:P35\01.cpp #include <iostream> #include <vector> using namespace std; //Shape类不知道如何绘制Draw,所以定义为纯虚函数 //只要有纯虚函数就是抽象类,抽象类是不能定义对象的,它是现实中并不存在的事物 //在基类中不需要实现纯虚函数,如果派生类没有实现纯虚函数,那么该派生类 //也会成为抽象类,则需要派生类的派生类去实现这个纯虚函数 class Shape { public: virtual void Draw() = 0; //一个类作为抽象基类,多态用途的基类,析构函数就应该声明为虚析构函数 virtual ~Shape() { } }; class Circle : public Shape { public: void Draw() { cout<<"Circle::Draw() ..."<<endl; } ~Circle() { cout <<"~Circle ..."<<endl; } }; class Square : public Shape { public: void Draw() { cout<<"Square::Draw() ..."<<endl; } ~Square() { cout <<"~Square ..."<<endl; } }; void DrawAllShapes(const vector<Shape*>& v) { vector<Square*>::const_iterator it; for (it=v.begin(); it!=v.end(); ++it) { //it存放的是Shape*的指针,*it就是Shape*这个指针类型 //基类指针*it指向派生类对象,会调用派生类的虚函数Draw //这里以一致的观点来看待不同的派生类对象 (*it)->draw(); } } void DeleteAllShapes(const std::vector<Shape*>& v) { vector<Square*>::const_iterator it; for (it=v.begin(); it!=v.end(); ++it) { delete(*it);//析构函数是虚函数 //会依据指针所指向的类型来调用析构函数,实际指向的是派生类的对象,会释放派生类对象,会 //调用派生类的析构函数,再调用基类的析构函数,避免内存泄漏 //如果析构函数不是虚的,则派生类的析构函数是无法被调用的 } } int main(void) { // Shape s;//Error, 不能实例化抽象类 vector<Shape*> v; Shape* ps; ps =new Circle; v.push_back(ps); ps = new Square; v.push_back(ps); //将vector中的各个形状绘制出来 DrawAllShapes(v); DeleteAllShapes(v); return 0; } 测试: 抽象类不能用于直接创建对象实例,可以声明为抽象类的指针和引用可使用指向抽象类的指针支持运行时的多态性派生类中必须实现基类中的纯虚函数,否则它仍然被看作一个抽象类

    3.多态

    多态的优点 <1>多态性有助于更好地对程序进行抽象 (1)控制模块(基类模块)能专注于一般性问题的处理 (2)具体的操作交给具体的对象去做 基类不需要关注派生类如何实现虚函数,只需要关注它们支持的是一致的接口即可,以统一的观点看待不同的派生类对象 <2>多态性有助于提高程序的可扩展性 (1)可以把控制模块与被操作的对象分开 (2)可以添加已定义类的新对象,并能管理该对象 (3)可以添加新类(已有类的派生类)的新对象,并能管理该对象

    eg:工厂模式:P35\01.cpp

    #include <iostream> #include <vector> #include <string> using namespace std; //Shape类不知道如何绘制Draw,所以定义为纯虚函数 //只要有纯虚函数就是抽象类,抽象类是不能定义对象的,它是现实中并不存在的事物 //在基类中不需要实现纯虚函数,如果派生类没有实现纯虚函数,那么该派生类 //也会成为抽象类,则需要派生类的派生类去实现这个纯虚函数 class Shape { public: virtual void Draw() = 0; //一个类作为抽象基类,多态用途的基类,析构函数就应该声明为虚析构函数 virtual ~Shape() { } }; class Circle : public Shape { public: void Draw() { cout<<"Circle::Draw() ..."<<endl; } ~Circle() { cout <<"~Circle ..."<<endl; } }; class Square : public Shape { public: void Draw() { cout<<"Square::Draw() ..."<<endl; } ~Square() { cout <<"~Square ..."<<endl; } }; //增加一个类 class Rectangle : public Shape { void Draw() { cout<<"Rectangle::Draw() ..."<<endl; } ~Rectangle() { cout <<"~Rectangle ..."<<endl; } }; void DrawAllShapes(const vector<Shape*>& v) { vector<Square*>::const_iterator it; for (it=v.begin(); it!=v.end(); ++it) { //it存放的是Shape*的指针,*it就是Shape*这个指针类型 //基类指针*it指向派生类对象,会调用派生类的虚函数Draw //这里以一致的观点来看待不同的派生类对象 (*it)->draw(); } } void DeleteAllShapes(const std::vector<Shape*>& v) { vector<Square*>::const_iterator it; for (it=v.begin(); it!=v.end(); ++it) { delete(*it);//析构函数是虚函数 //会依据指针所指向的类型来调用析构函数,实际指向的是派生类的对象,会释放派生类对象,会 //调用派生类的析构函数,再调用基类的析构函数,避免内存泄漏 //如果析构函数不是虚的,则派生类的析构函数是无法被调用的 } } //简单的工厂模式:new Circle;new Square;new Triangle;这些new对象统一在一个地方 //统一类对象的创建 class ShapeFactory { public: static Shape* CreateShape(const string& name) { Shape* ps = 0; if(name == "Circle") { ps = new Circle; } else if(name == "Square") { ps = new Square; } else if(name == "Rectangle") { ps = new Rectangle; } return ps; } }; int main(void) { // Shape s;//Error, 不能实例化抽象类 vector<Shape*> v; //用工厂模式代替下面 // Shape* ps; // ps =new Circle; // v.push_back(ps); // ps = new Square; // v.push_back(ps); // //上面的控制模块都不需要动 // //新增 // ps = new Triangle; // v.push_back(ps); //工厂模式使用如下 Shape *ps; ps = ShapeFactory::CreateShape("Circle"); v.push_back(ps); ps = ShapeFactory::CreateShape("Square"); v.push_back(ps); ps = ShapeFactory::CreateShape("Rectangle"); v.push_back(ps); //将vector中的各个形状绘制出来 DrawAllShapes(v); DeleteAllShapes(v); return 0; }

    4.虚析构函数

    析构函数可以声明为虚函数 (1)delete 基类指针 (2)程序会根据基类指针指向的对象的类型,确定要调用的析构函数 (3)基类的析构函数为虚函数,所有派生类的析构函数都是虚函数

    构造函数不得是虚函数

    如果要操作具有继承关系的类的动态对象,最好使用虚析构函数。特别是在析构函数需要完成一些有意义的操作——eg:释放内存时

    析构函数还可以是纯虚的

    eg:P35\02.cpp

    #include <iostream> using namespace std; //构造函数不能是虚函数 //析构函数可以是虚函数,若一个类有多态中的基类,应该将析构函数声明为虚析构函数 // class Base // { // public: // void Test() = 0;//这是纯虚函数void Test() = 0;,Base类是抽象类 // }; //对于一个没有任何接口的类,如果想要将它定义成抽象类,只能将虚析构函数声明为纯虚的 //通常情况下,在基类中纯虚函数需要实现 //例外是纯虚析构函数要给出实现(给出一个空的实现即可) class Base { public: virtual ~Test() = 0;//拥有纯虚析构函数的类就是抽象类 { } }; class Derived : public Base { }; int main(void) { Derived d; return 0; }
    Processed: 0.011, SQL: 9