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()。所以应该讲参数改为引用或者指针。

注意事项

  1. 构造函数不能是虚函数。派生类的构造函数会自动调用基类的构造函数。
  2. 析构函数应当是虚函数。如果A是基类,B是派生类,B类对象过期时,先调用~B(),释放B类对象的内存,再释放A类对象的内存。
base *p = new derive;
delete p

  如果~base()不是虚函数,那么delete p只释放了base类的内存,而没有释放derive的内存。所以通常给一个基类提供一个虚函数的析构函数,即是它不需要析构函数。

  1. 友元不能是虚函数,因为友元不是类成员
posted @ 2018-04-01 22:37  h_hg  阅读(168)  评论(0编辑  收藏  举报