【C++】C++中的虚函数与纯虚函数

C++中的虚函数

  先来看一下实际的场景,就很容易明白为什么要引入虚函数的概念。假设我们有一个基类Base,Base中有一个方法eat;有一个派生类Derived从基类继承来,并且覆盖(Override)了基类的eat;继承表明ISA(“是一个”)的关系,现在我们有一个基类的指针(引用)绑定到派生类对象(因为派生类对象是基类的一个特例,我们当然可以用基类指针指向派生类对象),当我们调用pBase->eat()的时候,我们希望调用的是Derived类的eat,而实际上调用的是Base类的eat,测试代码如下:

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 using namespace std;
 5
 6 class Base
 7 {
 8 public:
 9     Base(){}
10
11     void eat()
12     {
13         cout << "I am base eat()" << endl;
14     }
15 private:
16
17 };
18
19 class Derived:public Base
20 {
21 public:
22     Derived(){}
23
24     void eat()
25     {
26         cout << "I am derived eat()" << endl;
27     }
28 };
29
30 int main()
31 {
32     Base* pBase;
33     Base base;
34     Derived* pDerived;
35     Derived derived;
36
37     // 如果基类声明为普通函数,基类指针不管指向基类对象还是派生类对象,基类指针都调用基类函数
38     pBase = &derived;
39     pBase->eat();
40     pBase = &base;
41     pBase->eat();
42
43
44     return 0;
45 }

  运行结果如下:

  我们将基类指针分别绑定到派生类对象和基类对象,运行结果显示我们始终调用的是基类的函数,这个时候调用哪个函数是由指针的静态类型决定的,因为pBase为Base的指针,所以不管指向基类对象还是派生类对象,都会调用由指针静态类型所决定的函数

为了能够实现,当基类指针指向派生类对象的时候,自动调用派生类对象自己实现的函数,C++引入了虚函数的概念。可以声明基类函数为虚函数,派生类对象覆盖了整个虚函数之后,根据派生类对象的类别自动调用派生类自己实现的函数。还是上面的例子,这次我们将基类的eat函数声明为虚函数(virtual)

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 using namespace std;
 5
 6 class Base
 7 {
 8 public:
 9     Base(){}
10
11     // 基类声明为虚函数
12     virtual void eat()
13     {
14         cout << "I am base eat()" << endl;
15     }
16 private:
17
18 };
19
20 class Derived:public Base
21 {
22 public:
23     Derived(){}
24
25     void eat()
26     {
27         cout << "I am derived eat()" << endl;
28     }
29 };
30
31 int main()
32 {
33     Base* pBase;
34     Base base;
35     Derived* pDerived;
36     Derived derived;
37
38     // 基类指针指向派生类时,调用派生类的eat函数;指向基类时调用基类的eat函数
39     pBase = &derived;
40     pBase->eat();
41     pBase = &base;
42     pBase->eat();
43
44
45     return 0;
46 }

  运行结果如下:

  从运行结果可以看到,基类指针在绑定基类对象时调用了基类的eat函数,在绑定派生类对象时调用了派生类的eat函数,具体调用哪个函数是运行时决定的。

小结:如果没有虚函数,无论基类指针指向的实际对象是什么,都会调用基类定义的函数,无法实现多态行为。为了实现多态行为,C++引入了虚函数。具体的方式是在基类(需要实现多态)的成员函数声明之前加上virtual关键字,派生类对象覆盖override该虚函数,然后将基类的指针(或者引用)的指针绑定到派生类对象或基类对象上,编译器在运行时确定指针所指向的具体对象,并自动调用具体对象的成员函数。

纯虚函数

前面讨论了我们C++通过引入虚函数来实现多态的特性。然而有时会出现这样的场景,基类的某些虚函数对于基类本身是没有意义的,它只是为了让派生类去继承这个函数并override自己的实现。比如:我们有一个平面图形Figure类,所有的平面图像都有面积Area函数来计算平面图形的面积,然而直接定义一个Figure对象却没有指明具体的类别,并且去调用Area函数是没有意义的。我们并不希望用户这样做,但是在仅仅引入虚函数的情况下,我们却没有办法避免用户这么做。代码实例如下:

 1 #include <iostream>
 2 using namespace std;
 3
 4 class Figure
 5 {
 6 public:
 7     Figure(){}
 8     virtual double Area()
 9     {
10         return 0.0;
11     }
12 };
13
14 class Rect:public Figure
15 {
16 public:
17     Rect(double w = 10.0, double h = 10.0):Figure(), width_(w), height_(h){}
18     double Area()
19     {
20         return width_ * height_;
21     }
22 private:
23     double width_;
24     double height_;
25 };
26
27 class Circle:public Figure
28 {
29 public:
30     Circle(double r = 10.0):Figure(), radius_(r){}
31     double Area()
32     {
33         return 3.14 * radius_ * radius_;
34     }
35 private:
36     double radius_;
37 };
38
39 int main()
40 {
41     // 这样的定义和调用并没有任何意义
42     // 我们并不清楚用户这样做要达到什么的行为,我们宁愿用户根本不能定义Figure类
43     Figure figure;
44     figure.Area();
45
46     return 0;
47 }

为了避免用户定义Figure类并调用Area函数,C++引入了纯虚函数概念, 通过将Area函数声明为纯虚函数(pure virtual function),来避免创建Figure类对象,纯虚函数的声明是在虚函数后面加上=0,代码如下:

 1 #include <iostream>
 2 using namespace std;
 3
 4 class Figure
 5 {
 6 public:
 7     Figure(){}
 8
 9     // 声明Area函数为纯虚函数
10     virtual double Area() = 0;
11 };
12
13 class Rect:public Figure
14 {
15 public:
16     Rect(double w = 10.0, double h = 10.0):Figure(), width_(w), height_(h){}
17     double Area()
18     {
19         cout << "Rect Area: " << width_ * height_ << endl;
20         return width_ * height_;
21     }
22 private:
23     double width_;
24     double height_;
25 };
26
27 class Circle:public Figure
28 {
29 public:
30     Circle(double r = 10.0):Figure(), radius_(r){}
31     double Area()
32     {
33         cout << "Circle Area: " << 3.14 * radius_ * radius_ << endl;
34         return 3.14 * radius_ * radius_;
35     }
36 private:
37     double radius_;
38 };
39
40 int main()
41 {
42     // 创建包含纯虚函数的类对象会出现编译错误
43     // Figure figure;
44     // figure.Area();
45
46     // 再来体会一下多态特性
47     Figure* pFigure;
48     Rect rect;
49     Circle circle;
50     pFigure = &rect;
51     pFigure->Area();
52     pFigure = &circle;
53     pFigure->Area();
54
55     return 0;
56 }

  运行结果如下:

如果定义Figure类对象figure会出现编译错误,编译显示不能定义抽象类实例对象。C++将包含一个或者多个虚函数的类称为抽象类,用户不能定义抽象类的具体对象,派生类必须override其继承的每一个纯虚函数,否则派生类也为抽象类,不能直接使用。注意纯虚函数通常情况下只有声明,并没有定义,留作派生类override。但是纯虚函数有定义也是可以的,表明基类希望派生类继承某些共性行为。

C++虚函数与纯虚函数总结如下:

【1】C++通过引入虚函数来实现多态行为。虚函数的声明方式如下:  virtual returnType FunctionName(Parameters List) ,派生类可以override基类的虚函数实现自己的行为,当基类指针或引用绑定到派生类对象时,在运行时确定指针指向的对象具体类型,并自动调用相应类型的函数实现多态行为。

【2】析构函数应该是虚函数,这是因为假设虚构函数不是虚函数,如果基类指针指向派生类对象,如果没有虚函数提供多态机制,那么基类指针只是调用基类对应的析构函数,并没有析构派生类对象的派生类部分,因而出现资源并没有完全释放的情况;反之,如果基类的虚构函数是虚函数,则基类指针多态的调用派生类的析构函数,最后调用基类的析构函数,从而完成完全的析构过程。

【2】C++引入纯虚函数来规范派生类的行为,实际上等同于告诉派生类 “你必须提供这些纯虚函数的具体实现,我并不关心你具体是怎么实现的”。纯虚函数的声明方式如下: virtual returnType FunctionName(Parameter List) = 0; 。

【3】定义了一个以上纯虚函数的类称为抽象类,抽象类不能定义具体的实例对象,但是可以定义抽象类的指针或引用。抽象类的派生类必须override其继承的所有纯虚函数,否则派生类也成为抽象类,不能直接使用。在具体的工程实践过程中,抽象类一般指生命纯虚函数,但是并不定义纯虚函数,而是由其派生类来override这些纯虚函数,这样做到了派生类只是继承了抽象类的接口,但是并不继承抽象类的实现(因为根本就没有实现)。

参考文献

[1] Wiki:Virtual function

[2] Hackbuteer1的专栏:虚函数与纯虚函数的区别

[3] 类别(class)继承的一些特性

时间: 2024-10-27 12:47:55

【C++】C++中的虚函数与纯虚函数的相关文章

C++中虚函数和纯虚函数的作用与区别-详解

虚函数为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的此函数! 纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数! 虚函数 引入原因:为了方便使用多态特性,我们常常需要在基类中定义虚函数. class Cman { public: virtual void Eat(){--}; void Move(); private: }; class CChild : public CMan { public: virtual void

C++中虚函数和纯虚函数的总结

虚函数与纯虚函数 在他们的子类中都可以被重写.它们的区别是:(1)纯虚函数只有定义,没有实现:而虚函数既有定义,也有实现的代码. 纯虚函数一般没有代码实现部分,如virtual void print() = 0; 而一般虚函数必须要有代码的实现部分,否则会出现函数未定义的错误.virtual void print(){ printf("This is virtual function\n"); }(2)包含纯虚函数的类不能定义其对象,而包含虚函数的则可以. 定义为虚函数是为了允许用基类

继承中的虚函数、纯虚函数、普通函数

一.虚函数 被virtual关键字修饰的类成员函数就是虚函数.虚函数的作用就是实现运行时的多态性,将接口与实现分离.简单理解就是相同函数有着不同的实现,但因个体差异而采用不同的策略. 基类中提供虚函数的实现,为派生类提供默认的函数实现.派生类可以重写基类的虚函数以实现派生类的特殊化.如下: class Base{ public: virtual void foo() { cout<<"Base::foo() is called"<<endl; } }; clas

虚函数和纯虚函数的作用与区别

http://blog.csdn.net/xwpc702/article/details/8670025 虚函数为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的此函数!纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数!虚函数引入原因:为了方便使用多态特性,我们常常需要在基类中定义虚函数.class Cman{public:virtual void Eat(){……};void Move();private:};class C

C++ 虚函数与纯虚函数 浅析

[摘要] 本文首先简述虚函数与纯虚函数的定义,然后分析比较两者的区别与联系(DWS). [正文] 1)虚函数与纯虚函数有什么区别? 虚函数,不代表函数为不被实现的函数,为了允许用基类的指针来调用子类的这个函数:允许被其子类重新定义的成员函数. 纯虚函数,才代表函数没有被实现,为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数. 2)虚就虚在所谓"推迟联编"或者"动态联编"上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的.

虚函数和纯虚函数

虚函数为了重写和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的此函数! 纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数! 1.动态绑定 在执行期间(非编译期)判断所引用对象的实际类型,根据实际类型(动态类型)调用相应的方法. 动态绑定灵活性相对静态绑定来说要高,因为它在运行之前可以进行选择性的绑定,但同时,动态绑定的执行效率要低些,因为绑定对象还要进行编译(现在编译期一般都会多次编译). 触发动态绑定的条件:(1)只有指定为虚函数

c++虚函数,纯虚函数

1.虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类,而只含有虚函数的类不能被称为抽象类. 2.虚函数可以被直接使用,也可以被子类重载以后,以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类有声明而没有定义. 3.虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用. 4.虚函数和纯虚函数通常存在于抽象基类之中,被继承的子类重载,目的是提供一个统一的接口. 5.虚函数的定义形式:virtual{};纯虚函数的定义形式:virtual  { }

C++ 虚函数 、纯虚函数、接口的实用方法和意义

也许之前我很少写代码,更很少写面向对象的代码,即使有写多半也很容易写回到面向过程的老路上去.在写面向过程的代码的时候,根本不管什么函数重载和覆盖,想到要什么功能就变得法子的换个函数名字,心里想想:反正函数重载本质也就是入栈了两个不同的函数. 回过头来讲,让我了解标题这三个概念的实际用处,还是在于我这第四次重写毕业论文的代码,将它改写成面向对象的时候,才理解的.在面向对象设计的过程中, 类是从抽象逐渐具体起来的,父类可以是非常非常抽象的东西,而最终实例化的子类就非常具体了.在这个继承的过程中,不断

转 理解虚基类、虚函数与纯虚函数的概念

原文地址:理解虚基类.虚函数与纯虚函数的概念 引言 一直以来都没有写过一篇关于概念性的文章,因为我觉得这些概念性的东西书本上都有并且说的也很详细写来也无用,今天突发奇想想写 一写,下面就和大家讨论一下虚基类.虚函数与纯虚函数,一看名字就让人很容易觉得混乱.不过不要紧待看完本文后你就会理解了. 正文 虚基类        在说明其作用前先看一段代码 class A { public:     int iValue; }; class B:public A { public:     void bP