C++基础---虚函数

1. 虚函数

1.1 虚函数的定义

  • 虚函数:就是在基类的成员函数前加关键字virtual(即被virtual关键字修饰的成员函数),并在一个或多个派生类中被重新定义的成员函数;
  • 虚函数:就是在编译的时候不确定要调用哪个函数,而是动态决定将要调用哪个函数。它的作用就是为了能让这个函数在它的子类里面可以被重载,这样的话,编译器就可以使用后期绑定来达到多态了,也就是用基类的指针来调用子类的这个函数;
  • 虚函数的作用:在于用专业术语来解释就是实现多态性,多态性是将接口与实现进行分离,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略;
  • 虚函数用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};

1.2 定义虚函数的限制

  • 非类的成员函数不能定义为虚函数,类的成员函数中静态成员函数和构造函数也不能定义为虚函数,但可以将析构函数定义为虚函数。
    注:实际上,常常把基类的析构函数定义为虚函数。因为,将基类的析构函数定义为虚函数后,当利用delete删除一个指向派生类定义的对象指针时,系统会调用相应的类的析构函数。而不将析构函数定义为虚函数时,只调用基类的析构函数。
  • 只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。
  • 当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数。
  • 如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现这种同名函数。
    注:虚函数联系到多态,多态联系到继承。被继承的虚函数仍然是虚函数。没了继承,什么都没得谈。

1.3 调用虚函数的方式

  • 下面演示分别用:对象指针引用的方式来调用虚函数。
    (1)利用类的对象调用虚函数,虚函数没有起到运行时的多态作用;
    (2)利用类的对象指针调用虚函数,虚函数起到了运行时的多态作用;
    (3)利用类的对象引用调用虚函数,虚函数起到了运行时的多态作用;
  • 代码示例:

    
    #include <iostream>
    
    using namespace std;
    class father
    {
    public:
        father(){}
        virtual ~father(){}
    public:
        virtual void run() const{cout<<"父亲可以跑万米"<<endl;}
    };
    class son : public father
    {
    public:
        son(){}
        virtual ~son(){}
    public:
        virtual void run() const{cout<<"儿子亲可以跑十万米"<<endl;}
    };
    class daughter : public father
    {
    public:
        daughter(){}
        virtual ~daughter(){}
    public:
        virtual void run() const{cout<<"女儿亲可以跑十万米"<<endl;}
    };
    void one(father one);//该函数原型中声明一个指向father类的对象
    void two(father* two);//该函数原型中声明一个指向father类的指针
    void three(father& three);//该函数原型中声明一个指向father类的引用
    int main()
    {
        father *p=0;
        int choice = 0;
        while(1)
        {
            bool quit = false;
            cout<<"(0)quit(1)father(2)son(3)daughter:";
            cin>>choice;
            switch(choice)
            {
                case 0:
                {
                    quit = true;
                    break;
                }
                case 1 : 
                {
                    p = new father;
                    break;
                }
                case 2:
                {
                    p = new son;
                    break;
                }
                case 3 : 
                {
                    p = new daughter;
                    break;
                }
                default:
                {
                    cout<<"请输入0~3之间的数字"<<endl;
                    break;
                }
            }
            if(true == quit)
            {
                break;
            }
            one(*p);//将对象作为参数传递给函数one()中
            two(p);//将对象的内存地址传递到函数two()中
            three(*p);//将对象的引用传递到函数three()中
        }
        system("pause");
        return 0;
    }
    void one(father one)
    {
        one.run();//函数头中接收一个指向father的对象,用该对象访问run函数
    }
    void two(father* two)
    {
        two->run();//函数头中接收一个指向father的指针,用该指针访问run函数
    }
    void three(father& three)
    {
        three.run();//函数头中接收一个指向father的引用,用引用访问run函数
    }
    =>(0)quit(1)father(2)son(3)daughter:1
      父亲可以跑万米
      父亲可以跑万米
      父亲可以跑万米
      (0)quit(1)father(2)son(3)daughter:2
      父亲可以跑万米
      儿子亲可以跑十万米
      儿子亲可以跑十万米
      (0)quit(1)father(2)son(3)daughter:3
      父亲可以跑万米
      女儿亲可以跑十万米
      女儿亲可以跑十万米
      (0)quit(1)father(2)son(3)daughter:0

    注:
    (1)在继承中,只有当使用指针或引用的方式来调用虚函数时,虚函数才能发挥多态性的作用。
    (2)只有被说明为虚函数的那个成员函数才具有多态性。
    (3)被继承的虚函数仍然是虚函数。

1.4 系统调用虚函数的原理

  • 每个对象创建虚函数时,对象都得记录这个虚函数,因此编译器建立了一个叫做T表的虚函数表
  • 每个对象都有一个指向该表的指针,叫做虚表指针,该指针用来指向虚函数表;
  • 虚函数表也有一个指针指向该对象,当创建派生类对象的基类部分时,该对象的指针就会自动初始化为指向虚函数表的正确部分,当调用派生类对象的构造函数时,这个对象就会添加到虚函数表中去,并且将指针指向该对象的重载函数
  • 当使用指向基类的指针时,根据对象的实际类型,将该对象的指针继续指向正确的函数;

1.5 解除动态联编

  • 在虚函数中使用成员名限定可以强行解除动态联编
  • 代码示例:

    
    #include <iostream>
    
    using namespace std;
    class father
    {
    public:
        father(){}
        virtual ~father(){}
    public:
        virtual void run() const{cout<<"父亲可以跑万米"<<endl;}
    };
    class son : public father
    {
    public:
        son(){}
        virtual ~son(){}
    public:
        virtual void run() const{cout<<"儿子亲可以跑十万米"<<endl;}
    };
    int main()
    {
        son s;
        son *p = &s;
        p->run();
        p->father::run();//成员名限定会强制使用静态联编来调用类father的成员函数
        system("pause");
        return 0;
    }
    =>儿子亲可以跑十万米
      父亲可以跑万米

1.6 虚函数实现多态

  • 代码示例:

    
    #include <iostream>
    
    using namespace std;
    class poser
    {
    public:
        poser(int age):_age(age){}
        virtual ~poser(){}
    public:
        virtual void beat() const{cout<<"一般选手一拳的力量为260磅"<<endl;}
        void jump() const{cout<<"一般选手可以跳1米高"<<endl;}
        void print()const{cout<<"一般选手的年龄:"<<_age<<endl;}
    protected:
        int _age;
    };
    class Ali : public poser
    {
    public:
        Ali(int age):poser(age){}
        virtual ~Ali(){}
    public:
        virtual void beat() const{cout<<"Ali一拳的力量为420磅"<<endl;}
        void jump() const{cout<<"Ali可以跳1.1米高"<<endl;}
        void print()const{cout<<"Ali的年龄:"<<_age<<endl;}
    };
    class Lewis : public poser
    {
    public:
        Lewis(int age):poser(age){}
        virtual ~Lewis(){}
    public:
        virtual void beat() const{cout<<"Lewis一拳的力量为480磅"<<endl;}
        void jump() const{cout<<"Lewis可以跳1.2米高"<<endl;}
        void print()const{cout<<"Lewis的年龄:"<<_age<<endl;}
    };
    class Tyson : public poser
    {
    public:
        Tyson(int age):poser(age){}
        virtual ~Tyson(){}
    public:
        virtual void beat() const{cout<<"Tyson一拳的力量为500磅"<<endl;}
        void jump() const{cout<<"Tyson可以跳1.3米高"<<endl;}
        void print()const{cout<<"Tyson的年龄:"<<_age<<endl;}
    };
    class Holy : public poser
    {
    public:
        Holy(int age):poser(age){}
        virtual ~Holy(){}
    public:
        virtual void beat() const{cout<<"Holy一拳的力量为350磅"<<endl;}
        void jump() const{cout<<"Holy可以跳1.4米高"<<endl;}
        void print()const{cout<<"Holy的年龄:"<<_age<<endl;}
    };
    int main()
    {
        poser *a[5];
        poser *p;
        int choice = 0;
        for(int i=0; i<5; i++)
        {
            cout<<"(1)Ali(2)Lewis(3)Tyson(4)Holy:";
            cin>>choice;
            switch(choice)
            {
                case 1 : 
                {
                    p = new Ali(20);
                    break;
                }
                case 2:
                {
                    p = new Lewis(21);
                    break;
                }
                case 3 : 
                {
                    p = new Tyson(22);
                    break;
                }
                case 4:
                {
                    p = new Holy(23);
                    break;
                }
                default:
                {
                    p = new poser(24);
                    break;
                }
            }
            a[i] = p;
            a[i]->beat();
            a[i]->jump();
            a[i]->print();
        }
        system("pause");
        return 0;
    }
    =>(1)Ali(2)Lewis(3)Tyson(4)Holy1
         Ali一拳的力量为420磅
         一般选手可以跳1米高
         一般选手的年龄:20
      (1)Ali(2)Lewis(3)Tyson(4)Holy2
         Lewis一拳的力量为480磅
         一般选手可以跳1米高
         一般选手的年龄:21
      (1)Ali(2)Lewis(3)Tyson(4)Holy3
         Tyson一拳的力量为500磅
         一般选手可以跳1米高
         一般选手的年龄:22
      (1)Ali(2)Lewis(3)Tyson(4)Holy4
         Holy一拳的力量为350磅
         一般选手可以跳1米高
         一般选手的年龄:23
      (1)Ali(2)Lewis(3)Tyson(4)Holy5
         一般选手一拳的力量为260磅
         一般选手可以跳1米高
         一般选手的年龄:24
    

    注:
    (1)因为在函数beat()前面加了关键字virtual,表示该函数是有多种形态的,即该函数可能被多个对象所拥有,而且功能不一,换句话说,多个对象在调用同一名字的函数时产生的效果也不一样。
    (2)一个函数被说明为虚函数,在派生类中覆盖了该函数,那么该函数也是个虚函数,不过也可以把它说明为虚函数,这样看起来更好懂些。
    (3)由于jump()和print()函数,没有说明为虚函数,因此,调用了父类的对应函数。


参考文献:
[1]《C++全方位学习》范磊——第十三章
[2]《C++程序设计教程(第二版)》钱能——第五章、第六章、第七章
[3]《C++ Primer(第5版)》王刚 杨巨峰——第一章、第六章
[4] 百度搜索关键字:C++函数、虚函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值