C++虚函数&虚析构函数&纯虚函数的使用说明和理解

一、为什么要使用虚函数

请看下列代码:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Student{
public:
    Student(int n, string na, float s):num(n),name(na),score(s){}
    void display( );
    //virtual void display( );//此时基类的成员函数被定义为虚函数!!!!
protected:
    int num;
    string name;
    float score;
};

class Graduate:public Student
{
public:
    Graduate(int n, string na, float s, float p):Student(n,na,s),pay(p){}
    void display( );
private:
    float pay;
};

void Student::display() 
{
    cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\n\n";
}

void Graduate::display() 
{
    cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\npay="<<pay<<endl;
}

int main( )
{
    Student stud1(1001,"Li",87.5);
    Graduate grad1(2001,"Wang",98.5,563.5);
    Student *pt = &stud1;//注意:此处pt由基类定义,指向基类对象的指针
    pt->display();
    pt = &grad1;
    pt->display();

    Graduate *pt1 = &grad1;//注意:此处定义pt1指向派生类对象
    pt1->display();

    return 0;
}

运行结果如下:

从以上例子可看出,由于定义了pt为指向基类Student的指针,因此该指针只能访问基类成员,而不能访问派生类成员。

但是当基类的成员函数display被定义为虚函数呢?

cpp 复制代码
virtual void display( );

运行结果如下:

由上可知,当基类的成员函数被定义为虚函数时,即便指针指向基类对象,但是当赋值派生类给当前指针时,仍可以调用派生类的成员函数!

二、虚函数的使用方法

在基类中使用virtual声明成员函数为虚函数:

类内声明: virtual [类型] 函数名([参数表列])

在类外定义虚函数时,不再加virtual;

使用虚函数,可获得如下效果:

1.派生类根据需要可以重新定义函数体(函数覆盖),使用虚函数提高了程序的可扩充性;

2.成员函数被声明为虚函数后,其派生类中函数覆盖的同名函数自动成为虚函数;

3.若虚函数在派生类中未重定义,则派生类简单地继承其直接基类的虚函数;

4.指向基类的指针,当指向派生类对象时,则可以调用派生类的方法。

再举一个例子:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Circle
{
public:
    Circle(double r=0):radius(r) { }
    double area ( ) const
    //virtual double area ( ) const//定义为虚函数!!!
    {
        return 3.14159*radius*radius;//圆形面积公式s = πr²
    }
protected:
    double radius;
};

class Cylinder:public Circle
{
public:
    Cylinder (double r=0,double h=0):Circle(r),height(h) {}
    double area() const
    {
        return 2*Circle::area( )+2*3.14159*Circle::radius*height;
        //圆柱面积公式:2个底面积+1个侧面积,其中侧面积 = 2πrh
    };
protected:
    double height;
};

int main( )
{
    Circle c1(5.2);
    Cylinder cy1(5.2, 10);
    Circle *pc=&c1;
    cout<<pc->area()<<endl;
    pc = &cy1;
    cout<<pc->area()<<endl;
    return 0;
}

由于只定义了一个指向基类Circle对象的指针pc(未定义指向派生类对象的指针),因此

基类成员函数"double area ( ) const"不定义为虚函数时的运算结果:

定义为虚函数时的运算结果:

三、静态关联和动态关联

通过关联,把一个标识符 和一个存储地址联系起来;即把一个函数名与一个类对象捆绑在一起。

静态关联: 函数重载和通过对象名调用的(虚)函数,在编译阶段即可确定其调用的(虚)函数属于哪一个类;

动态关联: 通过基类指针虚函数 ,在代码运行阶段才确定关联关系;动态关联提供动态的多态性,即运行阶段的多态性。

四、何时使用虚函数

(一)何时使用

首先,如果一个类要作为基类,其成员函数可以定义为虚函数;如果不是基类,省点事吧

其次,看成员函数在类被继承后有无可能被更改功能。如果希望更改其功能的,一般应该在基类中将其声明为虚函数,如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不声明为虚函数。

再次,应考虑对成员函数的调用是通过对象名 还是通过基类指针或引用去访问。如果是通过基类指针或引用去访问的,则应当声明为虚函数。

有时,在定义虚函数时,并不定义其函数体,即函数体是空的,只等着被继承。它的作用只是定义了一个虚函数名,具体功能留给派生类去添加------纯虚函数

(二)关于虚函数的进一步理解

虚函数只能是 类的成员函数,而不能将类外的普通函数声明为虚函数。

虚函数的作用是允许在派生类中对基类的虚函数重新定义(函数覆盖),只能用于类的继承层次结构中。

排他性:一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义非virtual,但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数。

使用虚函数系统要有少量的空间开销:当一个类带有虚函数时,编译系统会为该类构造一个虚函数表(一个指针数组),用于存放每个虚函数的入口地址。

五、虚析构函数

(一)虚析构函数举例

当派生类的对象从内存中撤销时,一般先调用 派生类的析构函数,然后再调用基类的析构函数。问题由此而来:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Point{
public:
    Point() {}
    ~Point()
    //virtual ~Point()//    定义基类的析构函数为虚函数时
    {
        cout<<"executing ~Point destructor OK"<<endl;
    }
};

class Circle:public Point
{
public:
    Circle(){}
    ~Circle( )
    {
        cout<<"executing ~Circle destructor OK"<<endl;
    }
};

int main( )
{
    Point *p=new Circle;
    delete p;
    return 0;
}

运行结果:

发现只执行了基类Point的析构函数,派生类Circle的析构函数未执行!

即:用new运算符建立了派生类对象,并且由一个基类的指针变量指向该派生类对象 ,用delete运算符撤销对象时,系统只执行基类的析构函数,而不执行派生类的析构函数------派生类对象析构中要求的工作将被忽略。

而如果基类的析构函数定义为虚函数时,即"virtual ~Point()",执行结果如下:

由此可见,如果将基类的析构函数声明为虚函 数时,由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不相同。

(二)虚析构函数用法

当基类的析构函数为虚函数时,无论指针指的是同一类族中的哪一个类对象,系统会采用动态关联 ,自动调用相应的析构函数,对该对象进行清理工作:先调用了派生类的析构函数,再调用了基类的析构函数,符合人们的愿望;

最好把基类的析构函数声明为虚函数,这将使所有派生类的析构函数自动成为虚函数;

虚析构函数是很重要的技巧:专业人员一般都习惯声明虚析构函数 ,即使基类并不需要析构函数,也显式地定义一个函数体为空的虚析构函数,以保证撤销动态分配空间时能正确的处理

构造函数不能声明为虚函数。

六、纯虚函数

虚函数,有时并不是基类本身的要求,而是考虑到派生类的需要,在基类中预留了一个函数名

在设计类的层次结构过程中,常常有的基类的成员函数并无意义,具体功能由派生类决定

纯虚函数是只有函数的名字而不具备函数的功能 ,不能被调用的虚成员函数。

(一)纯虚函数的定义

纯虚函数的定义格式:

virtual 函数类型 函数名 (参数表列) =0

cpp 复制代码
virtual float area()=0; //纯虚函数
virtual float area() const =0; //纯虚常函数

//注意区别
virtual float area() const {return 0}; //虚函数

①纯虚函数没有函数体

②最后面的"=0" 只起形式上的作用,告诉编译系统"这是纯虚函数";

③这是一个声明语句,最后应有分号

如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍

然为纯虚函数。

(二)纯虚函数的应用

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Animal
{
public:
    virtual void cry() = 0; //定义基类中的纯虚函数
};

class Mouse : public Animal
{
public:
    void cry()
    {
        cout<<"zhi zhi zhi"<<endl;
    }
};

class Dog : public Animal
{
public:
    virtual void cry()
    {
        cout<<"wang wang wang"<<endl;
    }
};

class Cat : public Animal
{
public:
    virtual void cry()
    {
        cout<<"miao miao miao"<<endl;
    }
};

int main( )
{
    Animal *p;    //定义p为指向基类Animal的指针
    //p = new Animal();
    //p->cry();
    Mouse m1;
    p=&m1;
    p->cry();
    Cat c1;
    p=&c1;
    p->cry();
    Dog d1;
    p=&d1;
    p->cry();
    return 0;
}

运行结果:

由上可知:

纯虚函数功能的实现,只能在派生类中

 有纯虚函数的类,丧失了定义对象的能力

 有纯虚函数的类,是专门来当基类的!

相关推荐
小wanga5 小时前
C++知识
java·开发语言·c++
深思慎考5 小时前
LinuxC++项目开发日志——高并发内存池(1-定长内存池)
linux·c++
木心爱编程5 小时前
C++容器内存布局与性能优化指南
开发语言·c++·性能优化
咔咔咔的5 小时前
3446. 按对角线进行矩阵排序
c++
芒果敲代码6 小时前
什么是交叉编译?
c++
Qiang_san7 小时前
C++11新特性 | 欢迎来到现代C++的世界!
开发语言·c++
要做朋鱼燕7 小时前
【C++】迭代器详解与失效机制
开发语言·c++·算法
曼巴UE58 小时前
UE5 C++ 第三方动态库的使用
开发语言·c++
C语言小火车8 小时前
【C++八股文】数据结构篇
数据结构·数据库·c++·c++八股文