C++ 虚函数 纯虚函数
程序员文章站
2023-12-31 22:23:28
...
本文包含三个问题
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++实现多态的关键
- 当成员函数非虚函数时,使用父类指针指向子类对象,在调用此成员函数时,将调用父类成员函数
- 当成员函数是虚函数时,且子类重写了该函数,使用父类指针指向子类对象,在调用此成员函数时,将调用子类成员函数
- 父类指针指向父类对象,或子类指针指向子类对象时,不论成员函数是否是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时可以返回这个指针(或引用)的派生。