Part 1. 虚函数
关于C++继承,我们先来看一小段代码。Base表示基类,Derived表示Base的继承类。
#include<iostream>
using namespace std;
class Base{//基类
public:
Base(int a, int b):base_a(a),base_b(b) {};
~Base() { cout << "Base destructor!" << endl; };
void print(){
cout << base_a << " " << base_b << endl;
}
protected://protected权限,继承类可访问
int base_a, base_b;
};
class Derived : public Base{//继承类
public:
Derived(int a, int b, int c):Base(a, b), derived_c(c) {};
~Derived() { cout << "Derived destructor!" << endl; };
void print(){
cout << base_a << " " << base_b <<" "<< derived_c << endl;
}
private:
int derived_c;
};
int main(){
Derived *d = new Derived(1, 2, 3);//继承类的指针操作继承类的成员
d->print();//调用继承类函数
delete d;//先释放继承类的资源,再释放基类的资源
Base *p = new Derived(1, 2, 3);//基类指针指向继承类
p->print();//调用基类函数
delete p;//只释放基类资源,未释放继承类资源
return 0;
}
运行结果为,
注意到,
Base *p = new Derived(1, 2, 3);//基类指针指向继承类
p->print();//调用基类函数
delete p;//只释放基类资源,未释放继承类资源
即便基类指针指向的是继承类的对象,但实际调用的是仍然是基类的成员函数和析构函数,特别地,在析构函数调用时,继承类析构函数未被调用,这样易造成内存泄漏!这是因为,当基类指针被用来指向继承类对象时,会进行指针类型转换,将继承类对象的指针先转换为基类的指针,所以基类指针指向的是继承类对象中的基类部分,这种方法是无法通过基类指针去调用派生类对象中的成员函数的。
解决方法是,通过使用虚函数:当把基类的某个成员函数声明为虚函数后,允许在其继承类中对该函数进行重新定义,赋予它新的功能,并且可以通过指向基类的指针指向不同的继承类,从而调用其中的同名函数。这样看来,虚函数可以实现动态调用的效果。
由此,我们调整代码,
virtual ~Base() { cout << "Base destructor!" << endl; };
virtual void print(){
cout << base_a << " " << base_b << endl;
}
运行结果为,
可以看出,当基类指针指向继承类的对象时,如果使用虚函数,可以使得调用的函数为继承类中重新定义的方法。当基类有多个继承类时,可以通过这种方法方便地实现不同继承类方法的动态绑定!
part 2. 纯虚函数
纯虚函数没有实现体,基本形式为, virtual void function_name()=0; 如,
class Figure{
public:
...
virtual void draw() = 0;
virtual void rotate(double) = 0;
...
};
此时,Figure类就变成了抽象类,抽象类不能实例化一个对象,但可以有指针,具体的实现由其继承类来实现。当基类方法不好确定或将来的行为多种多样时,而此行为又是必须的,就可以在基类的设计中,引入虚函数的概念。
这里列举一个完整的例子,
#include <math.h>
#include <iostream>
using namespace std;
class Point
{
private:
double x;
double y;
public:
Point(double i, double j) : x(i), y(j) { }
void print() const{ cout << "(" << x << ", " << y << ")"; }
};
class Figure//基类
{
private:
Point center;
public:
Figure(double i = 0, double j = 0) : center(i, j) { }
//注意:这里函数是返回引用,而不是副本(非引用);但如果返回值是函数内部变量时,则无法使用返回引用,否则当函数调用完成时,临时变量也会被撤销,程序报错。
Point& location(){ return center;}
void move(Point p)
{
center = p;
draw();
}
//虚函数
virtual void draw() = 0;
virtual void rotate(double) = 0;
};
class Circle : public Figure//Figure 的继承类
{
private:
double radius;
public:
Circle(double i = 0, double j = 0, double r = 0) : Figure(i, j), radius(r) { }
void draw()
{
cout << "A circle with center ";
location().print();
cout << " and radius " << radius << endl;
}
void rotate(double){ cout << "no effect.\n";}
};
class Square : public Figure//Figure的另一个继承类
{
private:
double side;
double angle;
public:
Square(double i = 0, double j = 0, double d = 0, double a = 0) : Figure(i, j), side(d), angle(a) { }
void draw()
{
cout << "A square with center ";
location().print();
cout << " side length " << side << ".\n"
<< "The angle between one side and the X-axis is " << angle << endl;
}
void rotate(double a)
{
angle += a;
cout << "The angle between one side and the X-axis is " << angle << endl;
}
void vertices()
{
cout << "The vertices of the square are:\n";
}
};
int main()
{
Circle c(1, 2, 3);
Square s(4, 5, 6);
Figure *f = &c, &g = s;
f->draw();
f->move(Point(2, 2));
g.draw();
g.rotate(1);
s.vertices();//这里无法使用指针类型g调用方法vertices,这是因为该方法是继承类中所拥有的函数
return 0;
}
运行结果为,
基类Figure类中的虚函数draw()和rotate(double)方法没有方法体,具体的实现是在继承类Square类和Circle类中实现的。