欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++ 虚函数 纯虚函数

程序员文章站 2023-12-31 22:23:28
...

本文包含三个问题

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时可以返回这个指针(或引用)的派生。

上一篇:

下一篇: