C++面向对象 多态&虚函数的一个例子
在给人讲题时使用的,对Runoob C++ 多态的扩展。1
例子
先看基类和继承类的定义:基类Shape
提供了方法area()
和display_area()
用来求图形面积和显示图形面积,子类Rectangle
和Circle
分别重写了这两个方法,按照长方形和圆形的性质重新定义。
class Shape{
public:
double area(){
return 0;
}
void display_area(){
cout << "Shape Area = " << area() << endl;
}
};
class Rectangle : public Shape{
private:
int width;
int length;
public:
Rectangle(int a, int b){
width = a;
length = b;
}
double area(){
return width * length;
}
void display_area(){
cout << "Rectangle Area = " << area() << endl;
}
};
class Circle : public Shape{
private:
int rad;
public:
Circle(int a){
rad = a;
}
double area(){
return 3.14 * rad * rad;
}
void display_area(){
cout << "Circle Area = " << area() << endl;
}
};
继承
简单继承,不涉及多态的主函数:分别实例化Rectangle
和Circle
对象,调用函数输出其面积。
int main(){
Rectangle r1(3, 4);
Circle c1(5);
// point to rectangle
Rectangle* r_ptr = &r1;
r_ptr->display_area();
// point to circle
Circle* c_ptr = &c1;
c_ptr->display_area();
}
输出结果:
Rectangle Area = 12
Circle Area = 78.5
静态多态&缺陷
主函数通过一个Shape
指针分别指向实例化的Rectangle
和Circle
对象,实现多态。这里没用到virtual.
int main(){
Rectangle r1(3, 4);
Circle c1(5);
Shape* s_ptr;
// point to rectangle
s_ptr = &r1;
s_ptr->display_area();
// point to circle
s_ptr = &c1;
s_ptr->display_area();
}
执行程序输出:
Shape Area = 0
Shape Area = 0
导致错误输出的原因是早绑定,并不太懂,可以参考2。总之,编译时因为s_ptr
是Shape*
类型,所以默认调用Shape :: display_area()
和Shape :: area()
,而这并不是我们想要的。
动态多态
把Shape :: area()
声明为虚函数:
class Shape{
public:
virtual double area(){
return 0;
}
void display_area(){
cout << "Shape Area = " << area() << endl;
}
};
重新执行上一节的主函数,结果为:
Shape Area = 12
Shape Area = 78.5
解析:第一次s_ptr->display_area()
时,因为早绑定,首先执行Shape :: display_area()
;在display_area()
函数体调用area()
时,因为area()
声明为虚函数,所以优先寻找子类的area()
实现,所以此处的area()
执行的是Rectangle :: area()
。第二次同理。(上面说的“执行”指的是编译时绑定)
把Shape :: display_area()
声明为虚函数:
class Shape{
public:
double area(){
return 0;
}
virtual void display_area(){
cout << "Shape Area = " << area() << endl;
}
};
重新执行上一节的主函数,结果为:
Rectangle Area = 12
Circle Area = 78.5
是想要的结果。
解析:第一次s_ptr->display_area()
时,因为display_area()
声明为虚函数,所以首先执行Rectangle :: display_area()
,进而执行Rectangle :: area()
,跟Shape :: area()
没有关系。第二次同理。(上面说的“执行”指的是编译时绑定)
两个都虚?同理分析。
纯虚函数
在正常应用中,我们通常不希望程序调用Shape :: area()
,并输出Shape Area = 0
的结果。因此,Shape :: area()
就不该有定义,且尝试调用Shape :: area()
的行为应该报错。纯虚函数就是为这种情况服务的:把Shape :: area()
定义为“没有”,只能从子类找area()
的定义。
class Shape{
public:
virtual double area() = 0;
virtual void display_area() = 0;
};
纯虚函数要求:
- 子类必须重写(重新按照接口要求实现)父类中定义的纯虚函数。如果不这么做会报错:cannot declare variable ‘r1’ to be of abstract type ‘Shape’ because the following virtual functions are pure within ‘Shape’: virtual void area() = 0;
- 包含纯虚函数的父类不能被实例化,报和上面一样的错。
这里的细节我还没搞清楚,比如为什么会报这个错。这不重要。
含有纯虚函数的基类的使用方法:定义一个基类类型的指针,指向派生类,调用派生类中已经重写过的方法。
注意:基类定义纯虚函数和基类无定义,子类新定义的区别在于,前者是“定义为没有”,后者是“没有定义”。