曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 3662|回复: 3
打印 上一主题 下一主题

虚函数

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
跳转到指定楼层
楼主
发表于 2011-9-3 13:45:40 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
虚函数定义在基类中,且函数前用 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() 函数,如果调用派生类里不是从基类继承而来的函数,那么就是错误的!

程序是在运行时决定调用虚拟函数的哪个定义。进一步的说,这个决定只依赖于基类型指针所指向的对象的类型。

尽管可以用 “对象.函数" 这样的方式来调用虚函数,但这种调用方法忽略了虚函数的多态性。只有使用基类型指针访问虚函数的时候,运行时的多态才得以体现。

虚函数在派生类中的重新定义并不是一种特殊的函数重载形式
首先,重载函数必须在参数的类型或数量上不同,而重新定义的虚函数在参数的类型和数量上必须相同。其次,虚函数的原型与重新定义的形式必须完全相同。如果不同,那么函数只会被认为是简单的重载形式,从而失去了虚函数的特征。另外,在定义虚函数的类中,虚函数必须声明为的成员而不是友元,但虚函数可以被声明为其他类的友元。析构函数可以是虚函数,但构造函数不可以。

在派生类中重新定义虚函数时,也被称为覆盖函数。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
沙发
 楼主| 发表于 2011-9-3 18:26:27 | 只看该作者

一种接口,多种方法,虚函数的简单应用

一种接口,多种方法
基类声明所有派生类的公共接口,而让派生类定义具体的方法来实现这些接口,这就是”一种接口,多种方法“的思想,而虚函数的应用就是这种思想的体现。

示例:
[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); 这么写便行。

小型示例程序并不能体现虚函数的全部功能,通常在大型的系统中,多态才会显示出最大的威力。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
板凳
 楼主| 发表于 2011-9-3 15:18:51 | 只看该作者

虚函数的继承

函数的虚属性可以被继承。在函数被声明为虚函数后,不论经过多少层继承,这个函数将仍然是虚拟函数。

当派生类没有覆盖虚函数(自己没有重新定义虚函数)时,虚函数在基类中的定义被调用,如下示例:
[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

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
地板
 楼主| 发表于 2011-9-3 19:06:00 | 只看该作者

纯虚函数 和 抽象类

在上面的例子中,我们看到基类中的函数 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 ,其义为 ”抽象类型“。这里就有了一个概念 ”抽象类“ ,”抽象类“ 是指:
如果一个类至少含有一个纯虚函数,那么这个类就被称为抽象类。抽象类有一个重要特征:不能定义抽象类型的对象。抽象类只能用来作为其他类的基类,而不能用来声明对象。因为在抽象类中有一个或多个函数没有定义。然而,尽管基类是抽象的,但它仍然可以使用它来声明指针或引用,并藉此支持运行时多态。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-21 14:43 , Processed in 0.084259 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表