总结
当您创建一个静态对象并调用其函数之一时,您将获得该类的函数版本。编译器在编译时就知道对象是什么类。
当你创建一个对象的指针或引用并调用一个非虚函数时,编译器仍然假定它在编译时知道类型。如果引用实际上是一个子类,您可能会得到该函数的超类版本。
当你创建一个对象的指针或引用并调用一个虚函数时,编译器会在运行时查找对象的实际类型,并调用特定于它的函数版本.一个非常重要的例子是,如果你想通过基类指针销毁一个对象,析构函数必须在基类中是虚的,否则你将不会调用正确版本的析构函数。通常,这意味着子类分配的任何动态内存都不会被释放。
示例
也许考虑一下虚拟课程的实际用途会有所帮助。添加关键字不仅仅是为了它自己!让我们稍微改变一下这个例子,来处理圆形、椭圆和形状。
这里有很多样板,但请记住:椭圆是距两个焦点平均距离的点的集合,圆是焦点相同的椭圆。
#include <cmath>
#include <cstdlib>
#include <iostream>
using std::cout;
struct point {
double x;
double y;
point(void) : x(0.0), y(0.0) {}
point( const point& p ) : x(p.x), y(p.y) {}
point( double a, double b ) : x(a), y(b) {}
};
double dist( const point& p, const point& q )
// Cartesian distance.
{
const double dx = p.x - q.x;
const double dy = p.y - q.y;
return sqrt( dx*dx + dy*dy );
}
std::ostream& operator<< ( std::ostream& os, const point& p )
// Prints a point in the form "(1.4,2)".
{
return os << '(' << p.x << ',' << p.y << ')';
}
class shape
{
public:
virtual bool is_inside( const point& p ) const = 0; // Pure virtual.
protected:
// Derived classes need to be able to call the default constructor, but no
// actual objects of this class may be created.
shape() { cout << "Created some kind of shape.\n"; }
// Destructors of any superclass that might get extended should be virtual
// in any real-world case, or any future subclass with a nontrivial
// destructor will break:
virtual ~shape() {}
};
// We can provide a default implementation for a pure virtual function!
bool shape::is_inside( const point& _ ) const
{
cout << "By default, we'll say not inside.\n";
return false;
}
class ellipse : public shape {
public:
ellipse( const point& p1, const point& p2, double avg_dist )
: f1(p1), f2(p2), d(avg_dist)
{
cout << "Ellipse created with focuses " << f1 << " and " << f2
<< " and average distance " << d << " from them.\n";
}
bool is_inside( const point& p ) const
{
const double d1 = dist( p, f1 ), d2 = dist( p, f2 );
const bool inside = d1+d2 <= d*2;
cout << p << " has distance " << d1 << " from " << f1 << " and "
<< d2 << " from " << f2 << ", whose average is "
<< (inside ? "less than " : "not less than ") << d << ".\n";
return inside;
}
protected: // Not part of the public interface, but circle needs these.
point f1; // The first focus. For a circle, this is the center.
point f2; // The other focus. For a circle, this is the center, as well.
double d; // The average distance to both focuses. The radius of a circle.
};
class circle : public ellipse {
public:
circle( const point& center, double r )
: ellipse( center, center, r )
{
cout << "Created a circle with center " << center << " and radius " << r
<< ".\n";
}
// Override the generic ellipse boundary test with a more efficient version:
bool is_inside( const point& p ) const
{
const double d1 = dist(p, f1);
const bool inside = d1 <= d;
cout << p << " has distance " << d1 << " from " << f1 << ", which is "
<< (inside ? "less than " : "not less than ") << d << ".\n";
return inside;
}
};
int main(void)
{
const circle c = circle( point(1,1), 1 );
const shape& s = c;
// These call circle::is_inside():
c.is_inside(point(0,0));
s.is_inside(point(0.5, 1.5));
dynamic_cast<const ellipse&>(s).is_inside(point(0.5,0.5));
// Call with static binding:
static_cast<const ellipse>(c).is_inside(point(0,0));
// Explicitly call the base class function statically:
c.ellipse::is_inside(point(0.5,0.5));
// Explicitly call the ellipse version dynamically:
dynamic_cast<const ellipse&>(s).ellipse::is_inside(point(0.5,0.5));
return EXIT_SUCCESS;
}
这个的输出是:
Created some kind of shape.
Ellipse created with focuses (1,1) and (1,1) and average distance 1 from them.
Created a circle with center (1,1) and radius 1.
(0,0) has distance 1.41421 from (1,1), which is not less than 1.
(0.5,1.5) has distance 0.707107 from (1,1), which is less than 1.
(0.5,0.5) has distance 0.707107 from (1,1), which is less than 1.
(0,0) has distance 1.41421 from (1,1) and 1.41421 from (1,1), whose average is not less than 1.
(0.5,0.5) has distance 0.707107 from (1,1) and 0.707107 from (1,1), whose average is less than 1.
(0.5,0.5) has distance 0.707107 from (1,1) and 0.707107 from (1,1), whose average is less than 1.
您可能需要考虑一下如何将其扩展到三个维度,从笛卡尔坐标更改为极坐标,添加一些其他类型的形状(例如三角形),或者将椭圆的内部表示更改为中心和轴。
附注:
设计者经常认为,因为圆可以由一个点和一个距离定义,而一个椭圆可以由两个点和一个距离定义,ellipse 类应该是 circle 的子类,它添加了一个成员第二个重点。这是一个错误,不仅仅是出于象牙塔的数学学究。
如果您对椭圆采用任何算法(例如通过使用圆锥曲线方程快速计算其与直线的交点)并在圆上运行它,它仍然可以工作。在这里,我们使用圆只有一个焦点的知识,以两倍的速度查找一个点是否在圆内的示例。
但这不能反过来!如果ellipse 继承自circle 而你尝试draw_fancy_circle(ellipse(f1, f2, d)); 你会得到一个比你想画的椭圆更大的圆。因为椭圆违反了 circle 类的约定,所以任何假设圆是真正圆的代码都会默默地出错,直到你重新编写它们。这违背了编写一堆适用于任何类型对象的代码并重用它的意义。
square 类与rectangle 之间的关系的含义留给读者作为练习。