zoukankan      html  css  js  c++  java
  • 2,虚函数

    C++的学习总是一个漫长的过程。前一篇是关于输入、输出流同步的问题,今天又谈到虚函数,给人一种无序的杂乱感。先不管了,记录下来,好记性不如烂笔头。

    一、什么是虚函数


      在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数,用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。

      这是百度百科的定义,简单明了,我们下面通过实际例子来感受下。

      1,程序a.cpp。我们定义类A,并派生出B,B类重载了A中的foo().主函数中,A *p = new B; p->foo();观察程序输出,判断调用了A还是B的foo().

     1 #include <iostream>
     2 #include <typeinfo>
     3 using namespace std;
     4 
     5 class A {
     6 public:
     7     A(){};
     8     ~A(){};
     9     void foo() {
    10         cout << "This is an A." << endl;
    11     }
    12 };
    13 
    14 class B : public A
    15 {
    16 public:
    17     B(){};
    18     ~B(){};
    19     void foo() {
    20         cout << "This is a B." << endl;
    21     }
    22 
    23 };
    24 
    25 int main() {
    26     A *p = new B;
    27 
    28     cout << " p has type of " << typeid(p).name()<<endl;
    29     cout << " *p has type of " << typeid(*p).name()<<endl;
    30     p->foo();
    31     delete p;
    32     return 0;
    33 }
    View Code

      

      从输出我们可以看到,p->foo()调用的是A的foo().另外,我们还可以通过typeid(p).name()来查询p的类型。typeid方法定义在<typeinfo>中,可以查看源码,里面定义了 class type_info, 这里不在多讲。

      2,程序b.cpp。这里,我们更改a.cpp中A,BD的foo()为虚函数。再看看输出效果如何

      先提前注意下,当声明A中的foo()为虚函数时,其析构函数也要声明为虚函数,否则会产生一下warning: deleting object of polymorphic class type 'A' which has non-virtual destructor might cause undefined behavior [-Wdelete-non-virtual-dtor]|

      编译器为GCC 6.1.0,此warning的深层机制没有深究,还请广大博友赐教了。好,看程序。

     1 #include <iostream>
     2 #include <typeinfo>
     3 using namespace std;
     4 
     5 class A {
     6 public:
     7     A(){};
     8     virtual ~A(){};
     9     virtual void foo() {
    10         cout << "This is an A." << endl;
    11     }
    12 };
    13 
    14 class B : public A
    15 {
    16 public:
    17     B(){};
    18     ~B(){};
    19     virtual void foo() {
    20         cout << "This is a B." << endl;
    21     }
    22 
    23 };
    24 
    25 int main() {
    26     A *p = new B;
    27 
    28     cout << " p has type of " << typeid(p).name()<<endl;
    29     cout << " *p has type of " << typeid(*p).name()<<endl;
    30     p->foo();
    31     delete p;
    32     return 0;
    33 }
    View Code

      注意看输出,

      

      我们看到,p还是A类型的指针,但是*p却是class B类型。然后p->foo()调用的是B的foo().这就是RTTI机制(Runing Type Information),它提供了运行时确定对象类型的方法。

      总结下,当基类指针指向一个子类对象,通过这个指针调用子类和基类同名成员函数的时候,基类声明为虚函数(子类同名函数不是虚函数也可以)就会调用子类的这个函数,不声明就会调用基类的。

      这里,引入一个问题,如果我们把析构函数写成如下形式:

    virtual ~A(){
            cout << "~A() is inline function??" << endl;
        };

      请问,~A()是内联函数吗?因为我们都知道,类中定义的函数默认为inline函数,那这里的~A()呢??

      虚函数与内联函数的联系,见下文三、虚函数可不可以是内联函数。

    二、虚函数的用处


      大家都知道league of legends吧,假如我们所有的英雄都是基于父类Legeng的,有个纯虚函数q_attack() = 0;然后它派生出的子类,如FrostArcher,ProdigalExplorer,LooseCannon都实现了自己独特的q攻击函数q_attack()。当他们三个开团时,他们不停的用自己的攻击方式去攻击敌人(好怪异,开团只用q)。这个时候,我们就可以定义一个父类指针数组legend,把这三个英雄的指针封装起来,然后循环的legend[i]->q_attack(), 三个legend就不停的攻击敌人了。

      示意代码如下:

     1 int main() {
     2     Legend *legend[3];
     3     legend[0] = new FrostArcher;
     4     legend[1] = new ProdigaExplorer;
     5     legeng[2] = new LooseCannon;
     6 
     7     for (int i = 0; i < 3; ++i) {
     8         legend[i]->q_attack();
     9     }
    10 
    11     return 0;
    12 }

    三、虚函数可不可以是内联函数


      内联函数是指用inline关键字修饰的函数。在类定义中的函数被默认为内联函数。

      内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理。

      上述定义来自百度百科,感觉还是比较清晰的。

      1,程序c.cpp。我们在b.cpp的基础上,为foo()方法添加inline关键字,观察程序输出。

     1 #include <iostream>
     2 #include <typeinfo>
     3 using namespace std;
     4 
     5 class A {
     6 public:
     7     A(){};
     8     virtual ~A(){
     9         cout << "~A() is inline function??" << endl;
    10     };
    11     inline virtual void foo() {
    12         cout << "This is an A." << endl;
    13     }
    14 };
    15 
    16 class B : public A
    17 {
    18 public:
    19     B(){};
    20     ~B(){};
    21     inline void foo() {
    22         cout << "This is a B." << endl;
    23     }
    24 
    25 };
    26 
    27 int main() {
    28     A *p = new B;
    29 
    30     cout << " p has type of " << typeid(p).name()<<endl;
    31     cout << " *p has type of " << typeid(*p).name()<<endl;
    32     p->foo();
    33     delete p;
    34     return 0;
    35 }
    View Code

      输出如下,

      

      我们看到,与b.cpp相比,程序输出一模一样。说明,即使虚函数声明为inline,仍然实现动态绑定。

      所以,结论是虚函数不可能是内联函数。

     

    四、简单谈谈const(与虚函数无联系,临时想到)


      virtual,inline都是修饰函数的,现在突然想到const,也总结下吧。如果我们在foo()后面添加const ,有什么意义呢?那就是把foo()修饰为const,foo()函数体内不能对成员数据作任何改动。如果声明类的一个const 实例,那么它只能调用const修饰的函数。

      1,程序d.cpp。我们在c.cpp的情况下,修改B的foo()为foo() const, 并添加非cosnt函数foo2(). 当 const B cb;我们观察下cb.foo(), 和 cb.foo2()哪个会执行?

     1 #include <iostream>
     2 #include <typeinfo>
     3 using namespace std;
     4 
     5 class A {
     6 public:
     7     A(){};
     8     virtual ~A(){
     9         cout << "~A() is inline function??" << endl;
    10     };
    11     inline virtual void foo() {
    12         cout << "This is an A." << endl;
    13     }
    14 };
    15 
    16 class B : public A
    17 {
    18 public:
    19     B(){};
    20     ~B(){};
    21     inline void foo() const{
    22         cout << "This is a B." << endl;
    23     }
    24 
    25     void foo2() {
    26         cout << "foo2() is not const." <<endl;
    27     }
    28 
    29 };
    30 
    31 int main() {
    32     A *p = new B;
    33     const B cb;
    34     cout << " p has type of " << typeid(p).name()<<endl;
    35     cout << " *p has type of " << typeid(*p).name()<<endl;
    36     p->foo();
    37 
    38     cb.foo();
    39     //cb.foo2();//error: passing 'const B' as 'this' argument discards qualifiers [-fpermissive]|
    40     delete p;
    41     return 0;
    42 }
    View Code

      经实验,cb.foo2()会出现error,见程序注释。

      总结:

      * const对象只能调用const成员函数。   
          * const对象的值不能被修改,在const成员函数中修改const对象数据成员的值是语法错误 。
          * 在const函数中调用非const成员函数是语法错误。

      最后,贴出调用cb.foo()的运行结果

    好了,虚函数简单写在这。另外,关于纯虚函数以及type_info的内容还有很多,可以参见http://blog.csdn.net/hackbuteer1/article/details/7558868和http://www.jb51.net/article/55968.htm,写的比较详细。

  • 相关阅读:
    hdu1915
    2014年9月28日 18:35:01
    洛谷—— P1122 最大子树和
    洛谷——P1103 书本整理
    洛谷—— P2049 魔术棋子
    UVA_1575
    洛谷—— P2424 约数和
    中文乱码问题
    JSP标签
    include指令
  • 原文地址:https://www.cnblogs.com/letgo/p/5724259.html
Copyright © 2011-2022 走看看