多态即多种形态。当类之间存在层次结构,且类之间是通过继承关联时,就会用到多态。
多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。例如,基类 Shape 被派生为两个类,如下所示:
#include <iostream>
using namespace std;
class Shape
{
protected:
int width;
int height;
public:
Shape(int a = 0, int b = 0)
{
width = a;
height = b;
}
int area()
{
cout << "Parent class area :" << endl;
return 0;
}
};
class Rectangle : public Shape
{
public:
Rectangle(int a = 0, int b = 0) :Shape(a, b) { }
int area()
{
cout << "Rectangle class area :" << endl;
return (width * height);
}
};
class Triangle : public Shape
{
public:
Triangle(int a = 0, int b = 0) :Shape(a, b) { }
int area()
{
cout << "Triangle class area :" << endl;
return (width * height / 2);
}
};
int main()
{
Shape * shape;
Rectangle rec(10, 7);
Triangle tri(10, 5);
shape = &rec; // 存储矩形的地址
shape->area(); // 调用矩形的求面积函数 area
shape = &tri; // 存储三角形的地址
shape->area(); // 调用三角形的求面积函数 area
return 0;
}
编译和执行上面的代码:
Parent class area :
Parent class area :
导致输出为基类版本的原因:调用函数 area() 被编译器设置为基类中的版本,即所谓的静态多态,或静态链接。函数调用在程序执行前就准备好了,有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。
修改程序,在 Shape 类中,area() 的声明前放置关键字 virtual,如下所示:
class Shape
{
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtual int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
此时编译器看的是指针的内容,而不是它的类型。因此,由于Rectangle 和 Triangle 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数,如此,每个子类都有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,多个不同的类都可以带有一个相同名称,但具体实现不同的函数,函数的参数甚至可以是相同的。
虚函数
虚函数是在基类中使用关键字 virtual 声明的函数。对于虚函数而言,父类和子类都有各自的版本,由多态方式调用的时候动态绑定。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数,使得在程序中任意点都可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
虚函数声明如下:
virtual Return_Type Function_Name(Parameter)
虚函数必须实现(定义),如果不实现,编译器将报错,错误提示为:
error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"
补充:析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
纯虚函数
有时想在基类中定义虚函数,以便在派生类中重新定义该函数,使其更好地适用于对象,但在基类中又不能对虚函数给出有意义的实现,此时就会用到纯虚函数。纯虚函数声明如下:
virtual void funtion() = 0;
可以把基类中的虚函数 area() 改写如下:
class Shape
{
protected:
int width;
int height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtual int area() = 0; // pure virtual function
};
= 0 是告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
基类的虚函数或纯虚函数在子类中依旧是虚函数,子类的子类,即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。有时不希望基类的某个函数在子类中被重定义,在 C++11 及以后可以用关键字 final 来避免该函数被再次定义。
class Shape
{
public:
virtual int area()
{
cout << "Parent class area :" << endl;
return 0;
}
};
class Rectangle : public Shape
{
public:
virtual int area() final //area() 在Rectangle的子类中已经不再是虚函数,不能再被子类重定义
{
cout << "Rectangle class area :" << endl;
return (width * height);
}
};
补充:纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。