C++虚函数
我们都知道基类的指针和引用可以在不进行显示转换指向派生类,如下,反之不可。
clas base{};
class derive:public base{};
derive d;
base &pt = &d;
base &pr = &d;
那么它会调用哪个函数?
非虚函数版
#include <iostream>
using namespace std;
class base
{
public:
void f(){cout << "base" << endl;}
};
class derive:public base
{};
int main()
{
base b;
derive d;
b.f();//base
d.f();//base,继承void base::f()
base &pr = d;
pr.f();//base
return 0;
}
如果派生类也有个相同的函数,那么基类的同名函数会被覆盖。
#include <iostream>
using namespace std;
class base
{
public:
void f(){cout << "base" << endl;}
};
class derive:public base
{
public:
void f(){cout << "derive" << endl;}//覆盖掉void base::f()
};
int main()
{
base b;
derive d;
b.f();//base
d.f();//derive
base &pb = d;
pb.f();//base
derive &pd = d;
pd.f();//derive
return 0;
}
上面的例子可以知道函数的版本是根据指针或引用的类型来选择的,而不是指向的对象的类型。
如果想调用base的f(),可以通过d.A::f();
和pd.A::f();
进行调用。关于这个讨论,可以参考基类和派生类之间的同名函数,存在重载吗?
虚函数(virtual fuction)
如果函数为虚函数,通过指针或引用调用此函数,函数的版本是根据指向的对象类型,而不是指针或引用的类型。
#include <iostream>
using namespace std;
class base
{
public:
virtual void f(){cout << "base" << endl;}
};
class derive:public base
{};
int main()
{
base b;
derive d;
b.f();//base
d.f();//base
base &pb = b;
pb.f();//base
derive &pd = d;
pd.f();//base
return 0;
}
由于derive没有定义void f()
,所以继承void base::f()
。
#include <iostream>
using namespace std;
class base
{
public:
virtual void f(){cout << "base" << endl;}
};
class derive:public base
{
public:
void f(){cout << "derive" << endl;}
};
int main()
{
base b;
derive d;
b.f();//base
d.f();//derive
base &pb = b;
pb.f();//base
derive &pd = d;
pd.f();//derive
return 0;
}
虽然在基类的函数加上virtual后,派生类的相应函数就自动为虚函数,但是我们一般还是在派生类相应的函数加上virtual。一方面是为了让自己知道这是一个虚函数,另一方面是为了一脉继承,也就是说让derive的派生类的相应函数也为虚函数。
重新定义
上面的说法,通俗地讲就是‘子类重载父类的方法’,这种作法是不存在。
#include <iostream>
using namespace std;
class base
{
public:
virtual void f(){cout << "base" << endl;}
};
class derive:public base
{
public:
virtual void f(int n){cout << "derive" << endl;}//将隐藏基类所有名为f的函数,即void base::f()
};
int main()
{
derive d;
d.f();//提示参数过少
return 0;
}
这告诉我们:①如果派生类重新定义虚函数,应确保与原来的原型一样,但如果返回值是基类的指针或者引用,则可以修改为派生类的指针或者引用,这种特性叫做返回类型协变(covariance of return type)。②如果基类的函数被重载,应该在派生类定义所有基类版本,避免基类函数被覆盖。
class base
{
public:
virtual void f();
virtual void f(int);
};
class derive
{
public:
virtual void f();
virtual void f(int);
};
在第一份代码中,如果想使用d.f()
,只需如下修改。
class derive:public base
{
public:
using base::f;
virtual void f(int n){cout << "derive" << endl;}//将隐藏基类所有名为f的函数,即void base::f()
};
易错点
#include <iostream>
using namespace std;
class base
{
public:
base()
{
cout << "creat" << endl;
}
virtual void f(){cout << "base" << endl;}
};
class derive:public base
{
public:
void f(){cout << "derive" << endl;}
};
void fv(base t)
{
t.f();
}
int main()
{
derive d;
fv(d);//base
return 0;
}
上述fv的参数是传值,会导致讲d的base部分复制给t(也就是调用base::base()),进而调用base::f()
。所以应该讲参数改为引用或者指针。
注意事项
- 构造函数不能是虚函数。派生类的构造函数会自动调用基类的构造函数。
- 析构函数应当是虚函数。如果A是基类,B是派生类,B类对象过期时,先调用
~B()
,释放B类对象的内存,再释放A类对象的内存。
base *p = new derive;
delete p
如果~base()
不是虚函数,那么delete p
只释放了base类的内存,而没有释放derive的内存。所以通常给一个基类提供一个虚函数的析构函数,即是它不需要析构函数。
- 友元不能是虚函数,因为友元不是类成员