zoukankan      html  css  js  c++  java
  • (转)虚函数和纯虚函数区别

      在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念。因为它充分体现 了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。比如在微软的MFC类库中,你会发现很多函数都有virtual关键字,也就是说, 它们都是虚函数。难怪有人甚至称虚函数是C++语言的精髓。 

    那么,什么是虚函数呢,我们先来看看微软的解释: 

    虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。 

                                                                  ——摘自MSDN 

    这个定义说得不是很明白。MSDN中还给出了一个例子,但是它的例子也并不能很好的说明问题。我们自己编写这样一个例子: 

    #include "stdio.h" 
    #include "conio.h"

    class Parent
    {
    public:
    char data[20];
    void Function1();
    virtual void Function2(); // 这里声明Function2是虚函数
    }parent;

    void Parent::Function1()
    {
    printf("This is parent,function1\n");
    }

    void Parent::Function2()
    {
    printf("This is parent,function2\n");
    }

    class Child: public Parent
    {
    void Function1();
    void Function2();

    } child;

    void Child::Function1()
    {
    printf("This is child,function1\n");
    }

    void Child::Function2()
    {
    printf("This is child,function2\n");
    }

    int main(int argc, char* argv[])
    {
    Parent *p; // 定义一个基类指针

    if ( _getch()=='c' ) // 如果输入一个小写字母c
    p=&child; // 指向继承类对象
    else
    p=&parent; // 否则指向基类对象

    p->Function1(); // 这里在编译时会直接给出Parent::Function1()的 入口地址。
    p->Function2(); // 注意这里,执行的是哪一个Function2?

    return 0;
    }

    用任意版本的Visual C++或Borland C++编译并运行,输入一个小写字母c,得到下面的结果:

    This is parent,function1 
    This is child,function2 

      为什么会有第一行的结果呢?因为我们是用一个Parent类的指针调用函数Fuction1(),虽然实际上这个指针指向的是Child类的对象,但编译器 无法知道这一事实(直到运行的时候,程序才可以根据用户的输入判断出指针指向的对象),它只能按照调用Parent类的函数来理解并编译,所以我们看到了 第一行的结果。

      那么第二行的结果又是怎么回事呢?我们注意到,Function2()函数在基类中被virtual关键字修饰,也就是 说,它是一个虚函数。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。如果我们在运行上面的程序时任意输入 一个非c的字符,结果如下: 

    This is parent,function1 
    This is parent,function2 

      请注意看第二行,它的结果出现了变化。程序中仅仅调用了一个Function2()函数,却可以根据用户的输入自动决定到底调用基类中的Function2 还是继承类中的Function2,这就是虚函数的作用。我们知道,在MFC中,很多类都是需要你继承的,它们的成员函数很多都要重载,比如编写MFC应 用程序最常用的CView::OnDraw(CDC*)函数,就必须重载使用。把它定义为虚函数(实际上,在MFC中OnDraw不仅是虚函数,还是纯虚 函数),可以保证时刻调用的是用户自己编写的OnDraw。虚函数的重要用途在这里可见一斑。 
    ----------------------------------------------------------- 
    再看下面的 
    派生类的大小问题C++中虚函数和纯虚函数的概念,差别和分别存在的原因 

    首先:强调一个概念 

    定义一个函数为虚函数,不代表函数为不被实现的函数,定义它为虚函数是为了允许用基类的指针来调用子类的这个函数 
    定义一个函数为纯虚函数,才代表函数没有被实现,定义他是为了实现一个接口起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
     

    对继承的影响: 
    普通的类(没有虚函数,纯虚函数)就可以被继承,而且工作的相当好 

    关于这个问题有以下疑问: 
    纯虚函数难道就是为了实现接口?接口存在的意义? 
    我实在弄不懂,我干嘛要预先定义好?未来的事情本难料,就等有一天我的类中需要使用某个函数,在添加一个函数不就可以? 

    关于实例化一个类: 
    有纯虚函数的类是不可能生成类对象的,如果没有纯虚函数则可以。比如: 

    class CA 
    {
    public:
    virtual void fun() = 0; // 说明fun函数为纯虚函数
    virtual void fun1();
    };

    class CB
    {
    public:
    virtual void fun();
    virtual void fun1();
    };

    // CA,CB类的实现
    ...

    void main()
    {
    CA a; // 不允许,因为类CA中有纯虚函数
    CB b; // 可以,因为类CB中没有纯虚函数

    ...
    }

    --------------------------------------------------------------- 

    虚函数在多态中间的使用: 
    多态一般就是通过指向基类的指针来实现的。 
    dog mydogwangwang; 
    mydogwangwang.born(); 
    一定是返回“dog” 
    那么 
    horse myhorsepipi; 
    myhorsepipi.born(); 
    一定是返回“horse” 

    也是多态呀? 
    ///////////////////////////////////////////////// 
    有一点你必须明白,就是用父类的指针在运行时刻来调用子类: 
    例如,有个函数是这样的: 

    void animal::fun1(animal *maybedog_maybehorse) 
    {
    maybedog_maybehorse->born();
    }

      参数maybedog_maybehorse在编译时刻并不知道传进来的是dog类还是horse类,所以就把它设定为animal类,具体到运行时决定了才决定用那个函数。 
    也就是说用父类指针通过虚函数来决定运行时刻到底是谁而指向谁的函数。 

    //用虚函数 
    #include <iostream.h>

    class animal
    {
    public:
    animal();
    ~animal();
    void fun1(animal *maybedog_maybehorse);
    virtual void born();
    };

    void animal::fun1(animal *maybedog_maybehorse)
    {
    maybedog_maybehorse->born();
    }

    animal::animal()
    {
    }
    animal::~animal()
    {
    }
    void animal::born()
    {
    cout<< "animal";
    }
    class dog: public animal
    {
    public:
    dog();
    ~dog();
    virtual void born();
    };
    dog::dog()
    {
    }
    dog::~dog()
    {
    }
    void dog::born()
    {
    cout<<"dog";
    }

    class horse:public animal
    {
    public:
    horse();
    ~horse();
    virtual void born();
    };

    horse::horse()
    {
    }
    horse::~horse()
    {
    }
    void horse::born()
    {
    cout<<"horse";
    }

    void main()
    {
    animal a;
    dog b;
    horse c;
    a.fun1(&c);
    }
    //output: horse

    //不用虚函数
    #include <iostream.h>

    class animal
    {
    public:
    animal();
    ~animal();
    void fun1(animal *maybedog_maybehorse);
    void born();
    };

    void animal::fun1(animal *maybedog_maybehorse)
    {
    maybedog_maybehorse->born();
    }
    animal::animal()
    {
    }
    animal::~animal()
    {
    }
    void animal::born()
    {
    cout<< "animal";
    }
    class dog: public animal
    {
    public:
    dog();
    ~dog();
    void born();
    };

    dog::dog()
    {
    }
    dog::~dog()
    {
    }
    void dog::born()
    {
    cout<<"dog";
    }
    class horse:public animal
    {
    public:
    horse();
    ~horse();
    void born();
    };
    horse::horse()
    {
    }
    horse::~horse()
    {
    }
    void horse::born()
    {
    cout<<"horse";
    }

    void main()
    {
    animal a;
    dog b;
    horse c;
    a.fun1(&c);
    }
    //output: animal

    --------------------------------------------------------------- 

    有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。 
    --------------------------------------------------------------- 

    定义纯虚函数就是为了让基类不可实例化化, 
    因为实例化这样的抽象数据结构本身并没有意义. 
    或者给出实现也没有意义 
    实际上我个人认为纯虚函数的引入,是出于两个目的:

    1.为了安全.因为避免任何需要明确但是因为不小心而导致的未知的结果. 提醒子类去做应做的实现. 
    2.为了效率,不是程序执行的效率,而是为了编码的效率.

  • 相关阅读:
    【caffe】epoch,[batch_size],iteration的含义
    OAuth2.0学习(1-6)授权方式3-密码模式(Resource Owner Password Credentials Grant)
    OAuth2.0学习(1-5)授权方式2-简化模式(implicit grant type)
    OAuth2.0学习(1-4)授权方式1-授权码模式(authorization code)
    OAuth2.0学习(1-3)OAuth2.0的参与者和流程
    OAuth2.0学习(1-1)OAuth2.0是什么?
    nodejs(1-1)
    HTTP协议扫盲(一)HTTP协议的基本概念和通讯原理
    MySql入门(2-2)创建数据库
    SpringCloud的注解:EnableEurekaClient vs EnableDiscoveryClient
  • 原文地址:https://www.cnblogs.com/stoneJin/p/2229598.html
Copyright © 2011-2022 走看看