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;
}

运行结果:

由上可知:

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

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

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

相关推荐
「QT(C++)开发工程师」41 分钟前
C++语言编程规范-并发
java·linux·c++
1白天的黑夜11 小时前
递归-21.合并两个有序链表-力扣(LeetCode)
c++·leetcode·链表·递归
adny-code1 小时前
[fastgrind] 一个轻量级C++内存监控及可视化开源库
c++·内存·性能分析·高性能计算
郝学胜-神的一滴2 小时前
Linux系统函数link、unlink与dentry的关系及使用注意事项
linux·运维·服务器·开发语言·前端·c++
赵杰伦cpp2 小时前
list的迭代器
开发语言·数据结构·c++·算法·链表·list
老歌老听老掉牙3 小时前
使用 OpenCASCADE 提取布尔运算后平面图形的外轮廓
c++·平面·opencascade
闻缺陷则喜何志丹3 小时前
【动态规划】数位DP的原理、模板(封装类)
c++·算法·动态规划·原理·模板·数位dp
胖咕噜的稞达鸭3 小时前
二叉树搜索树插入,查找,删除,Key/Value二叉搜索树场景应用+源码实现
c语言·数据结构·c++·算法·gitee
进击的大海贼4 小时前
QT-C++ 自定义加工统计通用模块
开发语言·c++·qt
lingran__4 小时前
算法沉淀第四天(Winner)
c++·算法