正在加载……
专注、离线、切勿分心
虚函数的声明和定义:
★ 虚函数的定义很简单,只要在成员函数原型前加一个关键字  virtual  即可。
★ 如果一个基类的成员函数定义为虚函数,那么,它在所有派生类中也保持为虚函数;即使在派生类中省略了virtual关键字,也仍然是虚函数。
★ 派生类要对虚函数进行中可根据需要重定义,重定义的格式有一定的要求:
       ——与基类的虚函数有相同的参数个数;
       ——与基类的虚函数有相同的参数类型;
       ——与基类的虚函数有相同的返回类型。
★ 若虚函数名称相同,参数不同?
虚函数和基类的虚函数不是完全相同,既不构成覆盖,又不构成重载,但是可以编译成功。C++认为派生类中的虚函数是重定义函数,是隐藏的,失去了虚特性。在j基类指针引用或对象调用的过程中始终调用的是基类的虚函数。

对于派生类而言,基类的虚函数被派生类的虚函数隐藏了,如果用派生类的对象直接去调用则只存在派生类的虚函数。对于通过多态(基类指针或引用指向派生类之后)去调用虚函数,则始终是调用的基类的虚函数,此时派生类的虚函数被隐藏了。也就是说:相对基类而言,派生类的虚函数被隐藏,相对派生类而言,基类的虚函数被隐藏只跟调用的对象有关,跟指向的内容没关。

构造函数为什么不能为虚函数?
根据虚函数的性质,如果A的构造函数为虚函数,且B类也给出了构造函数,则应该只执行B类的构造函数,不再执行A类的构造函数。这样A就不能构造了。

为什么需要虚函数:
virtual void base::disp()
{
        cout<<"hello,base"<<endl;
}
通过指针访问disp():
不加virtual时,具体调用哪个版本的disp()只取决于指针本身的类型,和指针所指对象的类型无关。
而加virtual时,具体调用哪个版本的disp()不再取决于指针本身的类型,而是取决于指针所指对象的类型。
#include <iostream>
using namespace std;
class Base
{
        public:
                virtual int func(int x)
                {
                        cout<<"Base::func(int)"<<endl;
                        return x;
                }
};
class Derived : public Base
{
        public:
                int func(int x)  
//虚函数要求严格,必须和基类保持一致,即使在前面没有加上virtual,也是虚函数
                {
                        cout<<"Derived::func(int)"<<endl;
                        return x;
                }
};
void test(Base & base)
{   cout<<"x="<<base.func(5)<<endl;   }
int main()
{
        Base base;
        Derived derived;
        test(base);   //执行的是Base::func
        test(derived);   //执行的是Derived::func
        cout<<endl;
        Base & ref = derived;
        ref.func(7);
        cout<<endl;
        Base * pbase = &derived;
        pbase->func(10);  
//通过基类指针指向派生类对象之后,再去调用虚析构函数,只跟对象有关,而与指针的类型无关
        return 0;
}
//指针访问
#include <iostream>
using namespace std;
class Base
{
        public:
                virtual void display()
                {   cout<<"Base::display()"<<endl;   }
        private:
};
class Child : public Base
{
        public:
                void display()
                {   cout<<"Child::display()"<<endl;   }
};
int main()
{//虚函数发生的条件:
//1、基类的指针或者引用指向派生类对象
//2、派生类覆盖了基类的虚函数
//3、通过基类的指针或引用调用虚函数,发生动态多态
        Base base;
        Base * pb = &base;
        pb->display();
        Child child;
        pb = &child;
        pb->display();
        cout<<"sizeof(Base)"<<sizeof(Base)<<endl;
        cout<<"sizeof(Child)"<<sizeof(Child)<<endl;
        cout<<endl;
        child.display();    //直接去代码区找到display(),然后去执行,没有多态机制
        cout<<endl;
        Child * pc = &child;
        pc->display();    //指针访问,多态
        cout<<endl;
        pc=(Child*)&base;    //一般不会这样做
        pc->display(); 
        pc->Base::display();   //没有多态机制,静态联编
        return 0;
}

总结:形成多态的条件:有继承关系并且派生类中对基类的virtual函数现实了覆盖,利用基类的引用或指针指向派生类对象,再用基类的对象去调用virtual函数的时候会实现多态




虚函数的访问:

对虚函数的访问方式不同,程序具体调用哪个函数可能也会有所不同
1、对象名访问
     ●和普通函数一样,虚函数一样可以通过对象名来调用,此时编译器采用的是静态联编
     ●通过对象名访问虚函数时, 调用哪个类的函数取决于定义对象名的类型。对象类型是基类时,就调用基类的函数;对象类型是子类时,就调用子类的函数。如:
          obj_base.disp();  //调用基类虚函数
          obj_child.disp(); //调用子类虚函数
     ●在子类中还可以使用作用域运算符来指定调用哪个类的函数。如:
          obj_child.base::disp(); //调用基类虚函数
          obj_child.child::disp();//调用子类虚函数

2、指针访问
      ●使用指针访问非虚函数时,编译器根据指针本身的类型决定要调用哪个函数,而不是根据指针指向的对象类型;
      ●使用指针访问虚函数时,编译器根据指针所指对象的类型决定要调用哪个函数(动态联编),而与指针本身的类型无关
      ●使用指针访问是虚函数调用的最主要形式。

3、引用访问
      ●使用引用访问虚函数,与使用指针访问虚函数类似
      ●不同的是,引用一经声明后,引用变量本身无论如何改变,其调用的函数就不会再改变,始终指向其开始定义时的函数。因此在使用上有一定限制,但这在一定程度上提高了代码的安全性,特别体现在函数参数传递等场合中,可以将引用理解成一种“受限制的指针
#include <iostream>
using namespace std;
class Base
{
        public:
                virtual void display()
                {   cout<<"Base::display()"<<endl;   }
                void print()
                {   cout<<"Base::print()"<<endl;   }
};
class Child : public Base
{
        public:
                void display()
                {   cout<<"Child::display()"<<endl;   }
                void print()
                {   cout<<"Child::print()"<<endl;   }
};
int main()
{
        Base base;
        Base * pb = & base;
        pb->print();
        Base & ref1 = base;
        ref1.display();
        cout<<endl;
        Child child;
        pb = &child;
        pb->print();
        pb->display();
        cout<<endl;
        Base & ref2 = child;
        ref2.print();
        ref2.display();
        Base base2;
        ref2=base2;     //引用一经声明,调用的函数就不会在改变,因为地址是不会改变的,虽然值改变了
        ref2.display()
        return 0;
}
#include <iostream>
using namespace std;
class Base
{
        public:
                virtual void display()
                {   cout<<"Base::display()"<<endl;   }
                void print()
                {   cout<<"Base::print()"<<endl;   }
                void callBase1()        //在成员函数中访问,也会发生虚函数机制
                {   this->display();   }        //成员函数访问虚函数,要用this指针
                void callBase2()
                {   Base::display();   }
};
class Child : public Base
{
        public:
                void display()
                {   cout<<"Child::display()"<<endl;   }
                void print()
                {   cout<<"Child::print()"<<endl;   }
                void callChild1()
                {   this->display();   }
                void callChild2()
                {   Base::display();   }
};
int main()
{
        Base base;
        Child child;
        base.callBase1();
        child.callChild1();
        cout<<endl;
        Base * pb = & base;
        pb->callBase1();
        pb->callBase2();
        cout<<endl;
        pb=&child;
        pb->callBase1();
        pb->callBase2();
        return 0;
}

4、类成员函数中访问
      ●在类内的成员函数中访问该类层次中的虚函数,采用动态联编,要使用this指针。

5、在构造函数或析构函数中访问
      ●构造函数和析构函数是特殊的成员函数,在其中访问虚函数时,C++采用静态联编,即在构造函数或析构函数内,即使是使用“this->虚函数名”的形式来调用,编译器仍将其解释为静态联编的“本类名::虚函数名”。即它们所调用的虚函数是自己类中定义的函数如果在自己的类中没有实现该函数,则调用的是基类中的虚函数。但绝不会调用任何在派生类中重定义的虚函数



虚函数表vftable:
     如果类中包含有虚成员函数,在用该类实例化对象时,对象的第一个成员将是一个指向虚函数表(vftable)的指针(vfptr)虚函数表记录运行过程中实际应该调用的虚函数的入口地址




包含虚函数的类的sizeof:
     包含有虚函数的类,在用sizeof求类对象所占用的内存空间的时候,因为此时对象有个指向虚函数表的指针,所以结果应该多四个字节,但是当同一个类中有多个虚函数的时候,也只有4个字节。当另一个类虚基继承时,又会多4个字节。如果派生类中又自己定义了新的虚函数,则又多4个字节。

#include<iostream>
using namespace std;
class A
{
        int i;
        virtual void fun();
        virtual void fun1();
        virtual void fun2();
};
// i 4个字节,vfptr指针8个字节,内存对齐 8+4  对齐 16
class B:virtual public A
{
        virtual void fun();
        virtual void fun1();
};
//8 8 4 = 24
int main()
{
        cout<<"sizeof A="<<sizeof(A)<<endl;
        cout<<"sizeof B="<<sizeof(B)<<endl;
        return 0;
}
//sizeof A=16
//sizeof B=24
#include<iostream>
using namespace std;
class A
{
        int i;
        void f();
        virtual void fun();
        virtual void fun1();
        virtual void fun2();
};
class B:virtual public A
{
        virtual void fun1();
        virtual void fun3();
};
// 虚继承已经有虚函数表了,fun3()特有的,只要
// 加入虚函数表就可以了
int main()
{
        cout<<"sizeof A="<<sizeof(A)<<endl;
        cout<<"sizeof B="<<sizeof(B)<<endl;
        return 0;
}
//sizeof A=16
//sizeof B=24



posted on 2018-07-25 20:31  正在加载……  阅读(233)  评论(0编辑  收藏  举报