C++虚函数与多态性

引例

class employee {
public:
    void salary() { }
};
class manager : public employee {
public:
    void salary() { }
};
class programmer : public employee {
public:
    void salary() { }
};
void payroll(employee& re) {
    re.salary();
};
  • payroll()是面向基类进行处理的函数,具有一般性,能够处理任意的employee对象,还有很好的可拓展性。如果想要加上新员工种类,只要再定义一个employee的派生类,不用修改payroll()。
  • 但是本例中假如定义了一个manager对象Jerry,输入payroll(Jerry),编译器只能知道Jerry是employee而不知道是manager。所以调用的是employee中的salary函数,而不是manager中的salary函数。
  • 将函数体和函数调用相联系称为绑定,默认的函数调用绑定方式是静态绑定,又称为早绑定。解决上述问题的方法是使用动态绑定,将绑定推迟到程序运行时,以便根据具体类型绑定函数调用。动态绑定又称晚绑定运行时绑定
  • 利用动态绑定能实现多态性——同样的消息发送给不同的派生类的对象时执行不同的操作。在C++中,为提高效率,只对虚函数实施动态绑定。

虚函数

  • 为了对成员函数进行动态绑定,要在基类中将该成员函数声明为虚函数。虚函数声明语法是在成员函数前加关键字“virtual”
  • 只有非静态成员函数可以声明为虚函数
  • 在派生类中覆盖基类虚函数时要用相同的参数表和返回类型,否则:
    • 若参数表不同,被视为定义了另一个同名函数,在派生类中隐藏基类虚函数,不能再进行多态调用。
    • 若返回值类型不同,会报错。
  • 构造函数不能是虚函数,析构函数可以是虚函数,基类的析构函数如果声明为虚函数,其派生类析构函数即使不加virtual 也是虚函数。(析构函数最好声明为虚函数)
  • 使用虚函数实现多态性的步骤:
    • 在基类中将需要多态调用的成员函数声明为virtual
    • 在派生类中覆盖基类的虚函数,实现各自需要的功能
    • 基类的指针或引用指向派生类对象,调用虚函数。通过对象调用虚函数不会有多态性
  • 基类指针(引用)即使在指向派生类对象时,也只能调用基类接口中出现的成员函数,不能调用派生类中增加的成员函数。
  • 实例
#include <iostream>
using namespace std;
class Base {
public:
   virtual void f() { cout<<"Base::f()"<<endl; }
};
class Derived1 : public Base {
public:
   void f() { cout<<"Derived1::f()"<<endl; }
};
class Derived2 : public Base {
public:
   void f() { cout<<"Derived2::f()"<<endl; }
};
int main()
{
   Base b; Derived1 d1; Derived2 d2;
   b.f(); //Base::f()
   d1.f(); //Derived1::f()
   d2.f(); //Derived2::f()
   b = d1;
   b.f(); //对象调用,没有多态性,Base::f()
   Base *pb = &b;
   pb -> f(); //Base::f()
   pb = &d1; pb -> f(); //Derived1::f()
   pb = &d2; pb -> f(); //Derived2::f()
}
  • 如果定义了一组图形,都实现了计算面积的操作area()。如果想要计算一组图形面积之和,使用虚函数的多态调用就很方便:
class shape {
public:
    virtual double area() const {return 0;}
};
class rectangle : public shape {
public:
    double area() const { return height*width;}
private:
    double height,width;
};
class circle : public shape {
public:
    double area() const {return PI*r*r;}
private:
    double r;
};
int main()
{
    ...
    shape* p[N];
    double total_area = 0;
    ...
    for ( int i = 0; i < N; i++)
        total_area += p[i] -> area();
    ...
};

抽象类

  • C++中用纯虚函数定义抽象操作,包含至少一个纯虚函数的类称为抽象类,抽象类一般只用作其他类的基类,因此也被称为抽象基类。如果一个抽象类中的所有成员函数都是纯虚函数,这个类称为纯抽象类
  • 上述shape类就是一个抽象类,不存在shape的实例,计算面积的函数area()和计算周长的函数perimeter()操作对一个不确定的图形来说是无法实现的。因此上述函数可以用纯虚函数的定义方法定义为纯虚函数,而不用之前的别扭写法。
  • 定义纯虚函数的语法:
    virtual \quad 返回类型 \quad 函数名 \quad (参数表) \quad = 0 ;
  • 当继承一个抽象类时,要在派生类中覆盖纯虚函数,否则派生类会因为继承得到了纯虚函数而成为抽象类。
  • 不能创建抽象类的实例,通过抽象类的指针或引用来实现虚函数的多态调用。
  • 抽象类中可以包含普通成员函数,在普通成员函数中可以调用虚函数,在执行这个虚函数的时候实现多态调用。
  • 实例:改写shape类和Rectangle类
#include <iostream>
using namespace std;
class Shape {
public:
	virtual double area() = 0;
	virtual double perimeter() = 0;
};
class Rectangle : public Shape {
	double width,height;
public:
	Rectangle(double w = 0,double h = 0)
	{ width = w; height = h; }
	double area() { return width*height; }
	double perimeter() { return (width + height) * 2; }
};
int main()
{
	// Shape s; 会报错,不能声明含有纯虚函数的Shape类实例
	Rectangle a(4,5);
	Shape *p = &a; //抽象类指针
	Shape& q = a;  //抽象类引用
	cout<<p->area()<<endl;
	cout<<p->perimeter()<<endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43575267/article/details/86773295