曲径通幽论坛
标题: 虚函数 [打印本页]
作者: beyes 时间: 2011-9-3 13:45
标题: 虚函数
虚函数定义在基类中,且函数前用 virtual 关键字声明,它在一个或多个派生类中会被重新定义。这样,每个派生类可以拥有自己的虚函数定义。
定义了虚函数的类被称为多态类。
下面是虚函数的一个简单举例:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;
class base {
public:
virtual void who() { //声明虚函数
cout << "Base\n";
}
};
class first_d : public base {
public:
void who() { //重新定义 first_d 中的 who()
cout << "First derivation\n";
}
};
class second_d : public base {
public:
void who() { //重新定义 first_d 中的 who()
cout << "Second derivation\n";
}
};
int main()
{
base base_obj;
base *p;
first_d first_obj;
second_d second_obj;
p = &base_obj;
p->who(); //访问base中的 who()
p = &first_obj;
p->who(); //访问 first_d 中的 who()
p = &second_obj;
p->who(); //访问 second_d 中的 who()
return 0;
}
运行输出:
$ ./virfunc
Base
First derivation
Second derivation
在基类 base 中,用 virtual 声明了一个函数 who(),而这个函数在 2 个派生类中都有了自己的重新定义。
基类的指针 p 可以指向它的派生类(可参考:基类指针与派生类指针)。这里需要注意,这个指针在这里只能调用 who() 函数,如果调用派生类里不是从基类继承而来的函数,那么就是错误的!
程序是在运行时决定调用虚拟函数的哪个定义。进一步的说,这个决定只依赖于基类型指针所指向的对象的类型。
尽管可以用 “对象.函数" 这样的方式来调用虚函数,但这种调用方法忽略了虚函数的多态性。只有使用基类型指针访问虚函数的时候,运行时的多态才得以体现。
虚函数在派生类中的重新定义并不是一种特殊的函数重载形式:
首先,重载函数必须在参数的类型或数量上不同,而重新定义的虚函数在参数的类型和数量上必须相同。其次,虚函数的原型与重新定义的形式必须完全相同。如果不同,那么函数只会被认为是简单的重载形式,从而失去了虚函数的特征。另外,在定义虚函数的类中,虚函数必须声明为的成员而不是友元,但虚函数可以被声明为其他类的友元。析构函数可以是虚函数,但构造函数不可以。
在派生类中重新定义虚函数时,也被称为覆盖函数。
作者: beyes 时间: 2011-9-3 15:18
标题: 虚函数的继承
函数的虚属性可以被继承。在函数被声明为虚函数后,不论经过多少层继承,这个函数将仍然是虚拟函数。
当派生类没有覆盖虚函数(自己没有重新定义虚函数)时,虚函数在基类中的定义被调用,如下示例:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;
class base {
public:
virtual void who() {
cout << "Base\\n";
}
};
class first_d : public base {
public:
void who() {
cout << "First derivation\\n";
}
};
class second_d : public base { //没有定义 who()
};
int main()
{
base base_obj;
base *p;
first_d first_obj;
second_d second_obj;
p = &base_obj;
p->who(); //访问 base 的 who()
p = &first_obj;
p->who();
p = &second_obj;
p->who();
return 0;
}
运行输出:$ ./virfuncin
Base
First derivation
Base
在上面程序中,派生类 second_d 里并没有重新定义 who(),也就是没有覆盖它,所以当 p 指向 second_obj 时,base 的 who() 被调用了。
需要注意的是,virtual 的继承是分层次的。比如上面,如果 sencond_d 派生于 first_d 而不是 base ,那么在类的层次结构中,first_d 是最近接 second_d 的类,所以它会调用 first_d 中的 who(),如将上面的函数改写为:
[C++] 纯文本查看 复制代码
using namespace std;
class base {
public:
virtual void who() {
cout << "Base\\n";
}
};
class first_d : public base {
public:
void who() {
cout << "First derivation\\n";
}
};
class second_d : public first_d { //没有定义 who()
};
int main()
{
base base_obj;
base *p;
first_d first_obj;
second_d second_obj;
p = &base_obj;
p->who(); //访问 base 的 who()
p = &first_obj;
p->who();
p = &second_obj;
p->who();
return 0;
}
运行输出:$ ./virfuncin2
Base
First derivation
First derivation
作者: beyes 时间: 2011-9-3 18:26
标题: 一种接口,多种方法,虚函数的简单应用
一种接口,多种方法
基类声明所有派生类的公共接口,而让派生类定义具体的方法来实现这些接口,这就是”一种接口,多种方法“的思想,而虚函数的应用就是这种思想的体现。
示例:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;
class figure {
protected:
double x, y;
public:
void set_dim(double i, double j)
{
x = i;
y = j;
}
virtual void show_area()
{
cout << "No area computation defined "; //该类中不定义具体的面积计算方法,
cout << "for this class.\\n"; //这里仅是给个提示
}
};
class triangle : public figure {
public:
void show_area()
{
cout << "Triangle with height ";
cout << x << " and base " << y;
cout << " has an area of ";
cout << x * 0.5 * y << ".\\n";
}
};
class rectangle : public figure {
public:
void show_area()
{
cout << "Rectangle with dimensions ";
cout << x << " x " << y;
cout << " has an area of ";
cout << x * y << endl;
}
};
int main()
{
figure *p; //声明基类型指针
triangle t; //派生类对象
rectangle r;
p = &t;
p->set_dim(10.0, 5.0);
p->show_area();
p = &r;
p->set_dim(10.0, 5.0);
p->show_area();
return 0;
}
运行输出:$ ./onemul
Triangle with height 10 and base 5 has an area of 25.
Rectangle with dimensions 10 x 5 has an area of 50
上面程序中,三角形面积计算类 triangle 和 矩形面积计算类 triangle 使用了同样的接口函数 show_area() ,但它们分别提供了各自的计算面积的方法。在这两种面积的计算方法中,都会使用到 2 个参数:三角形是底和高,矩形是长和宽。但是我们是否可以派生出一个计算圆面积的类呢?而在计算圆面积时,只会用到一个参数就是半径,那么要给 set_dim() 传递 1 个还是 2 个参数呢,答案是 1 个和 2 个都可以。先看使用 2 个参数的情况下:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;
class figure {
protected:
double x, y;
public:
void set_dim(double i, double j)
{
x = i;
y = j;
}
virtual void show_area()
{
cout << "No area computation defined "; //该类中不定义具体的面积计算方法,
cout << "for this class.\\n"; //这里仅是给个提示
}
};
class triangle : public figure {
public:
void show_area()
{
cout << "Triangle with height ";
cout << x << " and base " << y;
cout << " has an area of ";
cout << x * 0.5 * y << ".\\n";
}
};
class rectangle : public figure {
public:
void show_area()
{
cout << "Rectangle with dimensions ";
cout << x << " x " << y;
cout << " has an area of ";
cout << x * y << endl;
}
};
class circle : public figure {
public:
void show_area()
{
cout << "Circle with radius ";
cout << x;
cout << " has an area of ";
cout << 3.14 * x * x << endl;
}
};
int main()
{
figure *p; //声明基类型指针
triangle t; //派生类对象
rectangle r;
circle c;
p = &t;
p->set_dim(10.0, 5.0);
p->show_area();
p = &r;
p->set_dim(10.0, 5.0);
p->show_area();
p = &c;
p->set_dim(9.0, 10);
p->show_area();
return 0;
}
运行输出:$ ./onemul2
Triangle with height 10 and base 5 has an area of 25.
Rectangle with dimensions 10 x 5 has an area of 50
Circle with radius 9 has an area of 254.34
输出结果是正确的。但是在计算圆面积前使用的 set_dim() 函数里的第 2 个参数是没必要的,在这里它是一个哑值,你给它 10 也好,8 也罢,只要是一个数值,就能保证 set_dim() 函数与基类中的原型匹配。但这种方式看起来相当令人不快,它让我们必须记住这种调用形式(我们实际上只希望,在计算圆面积时,只要传递一个半径参数便够了),这也违背了 ”一种接口,多种方法“ 的思想。一个更好的解决办法是给 set_dim() 中的参数 y 赋一个默认值,比如在基类中将 set_dim(double i, double j) 改成 set_dim(double i, double j=0) 即可。这样一来,在 main() 中计算圆面积时再调用到该该函数时,只需要 set_dim(9.0); 这么写便行。
小型示例程序并不能体现虚函数的全部功能,通常在大型的系统中,多态才会显示出最大的威力。
作者: beyes 时间: 2011-9-3 19:06
标题: 纯虚函数 和 抽象类
在上面的例子中,我们看到基类中的函数 base_show() 实际上就只是输出一句提示,并不参与具体的面积计算,它的作用就是一个占位符,等于告诉别人,我就在这里,但别指望我做点什么。
基类中的 base_show() 函数可以这么做,但如果底下那些参与计算三角形面积,矩形面积以及原面积的派生类的 base_show() 函数如果不具体定义一下相应的面积计算方法,那么就没什么意义了。但是,有些时候,我们必须确保我们的派生类确实要明确定义所有必须的虚函数 --- 解决这个问题的方法是使用纯虚函数。
纯虚函数是这样一种函数,它在基类中声明,但没有定义的虚函数。声明纯虚函数的形式如下:
[C++] 纯文本查看 复制代码
virtual type func_name (parameter-list) = 0;
其中,type 是函数的返回类型,func_name 是函数名,=0 就是要把该虚函数指定为纯虚函数。例如下面基类的声明中就声明了一个纯虚函数:
[C++] 纯文本查看 复制代码
class figure {
protected:
double x, y;
public:
void set_dim(double i, double j)
{
x = i;
y = j;
}
virtual void show_area() = 0; // 声明纯虚函数
};
通过把虚函数声明为纯虚函数可以强制在派生类中重新定义虚函数。如果派生类不这么做,那么就会编译器报错!例如基于上面的基类,我们定义了一个没有重新定义虚函数的派生类如:
[C++] 纯文本查看 复制代码
class circle : public figure {
//在这个派生类中我们没有重新定义虚函数
};
当我们编译程序时,会看到类似下面的错误信息:$ g++ purevir.cc -o purevir
purevir.cc: In function ‘int main()’:
purevir.cc:69:9: error: cannot declare variable ‘c’ to be of abstract type ‘circle’
purevir.cc:57:7: note: because the following virtual functions are pure within ‘circle’:
purevir.cc:20:15: note: virtual void figure::show_area()
上面的错误提醒信息中,还看到了一个概念:abstract type ,其义为 ”抽象类型“。这里就有了一个概念 ”抽象类“ ,”抽象类“ 是指:
如果一个类至少含有一个纯虚函数,那么这个类就被称为抽象类。抽象类有一个重要特征:不能定义抽象类型的对象。抽象类只能用来作为其他类的基类,而不能用来声明对象。因为在抽象类中有一个或多个函数没有定义。然而,尽管基类是抽象的,但它仍然可以使用它来声明指针或引用,并藉此支持运行时多态。
欢迎光临 曲径通幽论坛 (http://www.groad.net/bbs/) |
Powered by Discuz! X3.2 |