C++ 虚函数调用

C++ 虚函数调用,第1张

函数的好处是,你只要有一个基类的指针,就可以根据情况来执行派生类中的函数。

比如你给的程序中,main中只有一个指针, 他是指向基类A的 p;

但是你注意这里, p = &b; p->print();

这里将输出 20; 及bsetb(20)的结果。

而如果print不是虚函数,这里将输出 10, 你可以自己试试;

也就是说,我们用基类类型的指针调用派生类的函数, 那么该函数必须为虚函数(注意,基类中的虚函数将会是派生类中所有相应函数默认为虚函数)。否则, 该指针将调用基类中的相应函数。

在就题解释一下, 如题, 如果A中print不是虚函数,

void main()

{

A a,p;

B b;

aseta(5);//aa = 5

bseta(10); //ba = 5;

bsetb(20); //bb = 20;

p=&a;p->print(); //aprint() 输出5

p=&b;p->print(); //注意,因为print不再是虚函数, 而p是A类型的指针, &b却是B类型的类的地址, 这里,编译器会处理成,p->print()调用的是b的基类中的print(),也就是说,因为指针类型不符,本来被覆盖了的基类print 又被调用了出来(这也算是一种处理机制了,别问为什么。)

}

好了,这就是虚函数, 统一的基类指针,根据其指向对象的类型,相应的选择函数。 如果你不设成虚函数,那就没这功能。

C++中的虚函数和纯虚函数用法

1虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。

2虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)

只有声明而没有定义。

3虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。

4虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。

5虚函数的定义形式:virtual {method body} ;纯虚函数的定义形式:virtual { } = 0; 在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。

6如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。

虚函数的作用就是接口类能够提供协议(即这个接口的功能是什么,比如都是进行绘画,相对来说比较抽象),而各个子类可以给出具体不同的实现(比如长方形绘画时可以画出长方形,而三角形则画出来的就是三角形)。调用虚函数的客户只需要知道虚函数的提供的协议,而不需要关心这个实现这个虚函数的到底是个什么类(因为都是从一个基类继承的)。

当然你可能会想,为什么不能用非虚函数做上面的事情,我们可以实现时指定一个类型,比如有一个成员用来标识形状的类型,而在实现时用这个类型来判定如何进行绘画,简而言之就是if else或者switch语句,如同下面的代码:

class shape {

   int type;

public:

   void draw() {

      switch (type) {

      case rectangle:

          draw_rectangle();

          break;

      case circle:

          draw_circle();

          break;

      }

};

这个问题在于,当你有一个很多shape时,这个switch case将难以管理,因为有时候代码中有些地方就是需要根据类型来做判断,但是此时你会倾向于使用switch case来实现这些代码。久而久之,一旦加个新类型时,就会很难维护,因为要改动很多地方,就容易出错(当然如果改动地方不多,问题不会很大)。

但是如果使用虚函数实现,那么各个子类管理自己的实现,而有需要依据类型来判断的地方,可以通过虚函数来实现,因为每个类可以定制这种行为,对于draw而言,可以

class rectangle : public shape {

public:

   virtual void draw() {

       // draw rectangle;

   }

};

class circle : public shape {

public:

   virtual void draw() {

        // draw circle

   }

};

这样当我们需要加新shape的时候,只需要添加一个新的子类,实现draw(或者其他依赖于类型的函数时),就不用到处去修改那些swtich case语句了,也不容易出错了。

敏捷开发一书中,提到的OCP(开放封闭原则),就可以用虚函数来做到。

using System;

using SystemCollectionsGeneric;

using SystemLinq;

using SystemText;

namespace ConsoleApplication2

{

    class Program

    {

        class S1

        {

            public virtual void Method()

            {

                ConsoleWriteLine("这是S1实例方法");

            }

        }

        class S2:S1

        {

            public virtual void Method()

            {

                ConsoleWriteLine("这是S2实例方法");

            }

        }

        class S3 : S2

        {

            public  virtual void Method()

            {

                ConsoleWriteLine("这是S3实例方法");

            }

        }

        static void Main(string[] args)

        {

            S3 s = new S3();

            // 调用S3Methoed

            sMethod();

            // 调用S2Methoed

            ((S2)s)Method();

            // 调用S1Methoed

            ((S1)s)Method();

        }

    }

}

调用顺序显然是fun1调用D类成员的show函数,fun2和fun3调用B类成员的show函数

原因:全局main函数中,p是上转型对象,使用D类型的成员(谁叫它用了D类的构造函数嘛),而且没有强制转换为B类类型(只是弄了个B类指针而已),因此fun1要调用D类的成员函数。对象b和fun2不用解释,对象d虽然是D类类型的,但在调用fun3函数时,函数参数b被强制转换成B类类型,因此使用B类的成员函数

virtual void print()const;//f2

 virtual void print()    //f1

f1和f2的函数签名是不一样的,circle内的f1类型的print 和getArea都是Two类里面的,circle内的f2类型的函数是f1类型的函数的重载

你是说如果把base::Out改成Out会出错是吧?

因为如果不指明调用基类的虚函数,那么编译器就必须要等派生类同名的重载函数产生后,调用派生名的函数。而要生成派生类的Out函数,必须先生成派生类的实例。而生成派生类的实例,又必须先调用基类的构造函数。基类的构造函数中又调用了Out,而这时候派生类的实例还没有生成,相应的Out函数也还没生成,所以直接调用Out会导致无法链接到有效的派生类的Out函数,所以出错。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/11666458.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-17
下一篇 2023-05-17

发表评论

登录后才能评论

评论列表(0条)

保存