C++ 虚函数 纯虚函数

本文包含三个问题

1.析构函数是否应为虚函数问题?

2.成员函数的虚函数问题?

3.析构函数是否可以为纯虚函数问题?

 

(一)析构函数是否应为虚函数问题

说明:仅在使用父类指针指向子类对象时有区别

  1. 当析构函数非虚函数时,使用父类指针指向子类对象,在析构时将不会调用子类析构函数
  2. 当析构函数是虚函数时,使用分类指针指向子类对象,在析构时会调用子类析构函数,且调用顺序为:先调用子类析构函数,再调用父类析构函数
  3. 所以,为了能够正确的调用对象的析构函数,一般要求具有层次结构的顶级类,定义其析构函数为虚函数。因为在delete一个抽象类指针时候,必须要通过虚函数找到真正的析构函数。

代码详情

class CBase
{
public:
    CBase(){cout << "CBase()";}
    ~CBase(){cout << "~CBase()";}   //析构函数未加virtual时 
};

class CSon:public CBase
{
public:
    CSon(){cout << "CSon()";}
    ~CSon(){cout << "~CSon()";}
};

int main()
{
    //使用1:(正常,可以正常调用子类析构函数)
    CSon   son1;

    //使用2:(正常,可以正常调用子类析构函数)
    CSon   *pbase2 = new CSon;
    delete   pbase2;

    //使用1、2输出都是:
    //CBase()
    //CSon() 
    //~CSon()
    //~CBase()

    //使用3:(非正常,不能正常调用子类析构函数)
    CBase *pbase2 = new CSon;
    delete pbase2;

    //输出是:
    //CBase()
    //CSon()
    //~CBase()

    return 0;
}

 

   (二)成员函数的虚函数问题

说明:仅在使用父类指针指向子类对象时有区别,也是C++实现多态的关键

  1. 当成员函数非虚函数时,使用父类指针指向子类对象,在调用此成员函数时,将调用父类成员函数
  2. 当成员函数是虚函数时,且子类重写了该函数,使用父类指针指向子类对象,在调用此成员函数时,将调用子类成员函数
  3. 父类指针指向父类对象,或子类指针指向子类对象时,不论成员函数是否是virtual,都会调用自己的成员函数,没有影响

代码详情

class CBase
{
public:
    void call(){cout << "*base* call";}   //成员函数未加virtual时 
};

class CSon:public CBase
{
public:
    void call(){cout << "*son* call";}
};

int main()
{
    //使用:
    CSon   *pbase1 = new CSon;  //子类指针指向子类对象,调用子类函数
    CBase  *pbase2 = new CSon;  //父类指针指向子类对象,调用父类函数
    
    pbase1->call();
    pbase2->call();
    
    //输出:
    //*son* call
    //*base* call

    return 0;
}

(三)析构函数是否可以为纯虚函数问题

  • 当我们想把CBase做成抽象类,使其不能直接构造对象,需要在其中定义一个纯虚函数。如果其中没有其他合适的函数,就可以把析构函数定义为纯虚的。
  • 当定义析构函数为纯虚函数时,必须进行定义。这是因为,析构函数、构造函数和其他内部函数不一样,在调用时,编译器需要产生一个调用链。也就是,子类的析构函数里面隐含调用了基类的析构函数。当刚才的代码中,缺少~CBase()的函数体,编译器会报错。
  • 这里面有一个误区,有人认为,virtual f()=0这种纯虚函数语法就是没有定义体的语义。其实,这是不对的。这种语法只是表明这个函数是一个纯虚函数,因此这个类变成了抽象类,不能产生对象。我们完全可以为纯虚函数指定函数体 。通常的纯虚函数不需要函数体,是因为我们一般不会调用抽象类的这个函数,只会调用派生类的对应函数。这样,我们就有了一个纯虚析构函数的函数体。

代码详情

class CBase
{
public:
    CBase(){cout << "CBase()";}
    virtual ~CBase() = 0;    
};

CBase::~CBase() {cout << "~CBase()";} //注:必须写实现,必须在类外

class CSon:public CBase
{
public:
    CSon(){cout << "CSon()";}
    ~CSon(){cout << "~CSon()";}
};

int main()
{
    //使用:
    CBase *pbase2 = new CSon;
    delete pbase2;

    //输出:
    //CBase()
    //CSon()
    //~CSon()
    //~CBase()
    
    return 0;
}

 

最后,总结一下关于虚函数的一些常见问题:

  • 虚函数是动态绑定的,也就是说,使用虚函数的指针和引用能够正确找到实际类的对应函数,而不是执行定义类的函数。这是虚函数的基本功能。
  • 构造函数不能是虚函数。而且,在构造函数中调用虚函数,实际执行的是父类的对应函数,因为自己还没有构造好, 多态是被禁用的。
  • 析构函数可以是虚函数,而且,在一个复杂类结构中,这往往是必须的。
  • 将一个函数定义为纯虚函数,实际上是将这个类定义为抽象类,不能实例化对象。
  • 纯虚函数通常没有定义体,但也完全可以拥有。
  • 析构函数可以是纯虚的,但纯虚析构函数必须有定义体,因为析构函数的调用是在子类中隐含的。
  • 非纯的虚函数必须有定义体,不然是一个错误。
  • 派生类的override虚函数定义必须和父类完全一致。除了一个特例,如果父类中返回值是一个指针或引用,子类override时可以返回这个指针(或引用)的派生。

猜你喜欢

转载自blog.csdn.net/Jecklin_online/article/details/81223415
今日推荐