现在的位置: 首页 > 综合 > 正文

C++与C#对比学习:多态,虚函数

2013年09月02日 ⁄ 综合 ⁄ 共 3382字 ⁄ 字号 评论关闭

    地球人都知道面向对象的三个主要特征是封装,继承,多态.前面两个概念顾名思义,比较容易理解,封装就是所有的东东都给封装到一个个的class中,并通过public,private等访问修饰符提供一些权限控制.继承嘛就是子类能拥有父类的非私有的成员变量和函数,就像你能继承你老子的财产一样啊.多态光看名字可不好理解,可没变态那么通俗易懂啊.其实前面说的三个特性还有另外的版本,比如:数据抽象,继承,动态绑定.说法不一样但表示的意思完全一样.

    多态可以简单的理解为有同一种东西有多种形态,比如雨,雪,冰都是水的不同形态.在代码里体现就是调用相同的函数但函数实际上执行的是不同的操作.而实现这种功能的最主要手段是虚函数,当然不一定非得是它,用接口也一样.只不过大多数时候还是用虚函数实现多态多一点.

 

C#虚函数和多态

虚函数就是在普通的函数前面加个关键字virtual就行了.如果虚函数没用来实现多态的话完全可以当作一般的函数用,没啥区别

下面举个例子,有父亲Father,有子类Son,Daughter

class Father

{

      public virtual void ShowMsg()      //这就是虚函数

      {

          Console.WriteLine("This is father.");

      }

}

class Son :Father

{

/*此处加个override到后面就能实现多态,多态就是virtual和override的搭配使用*/

      public override void ShowMsg()          

 {

          Console.WriteLine("This is Son.");

      }

}

class Daughter :Father

{

/*此处没用override而是用new,其实也可以去掉new,因为默认就是当作添加个new在那,当然其实还可以用virtual,这样如果谁继承了此类还可以通过配合override来实现多态*/

public new void ShowMsg()                                                      

{

Console.WriteLine("This is Daughter.");

}

}

测试函数

Public void ShowYourName(Father fa)

{

     fa.ShowMsg();

}

 

static void Main(string[] args)

        {

         Fahter father = new Father();

         Father son = new Son();

         Father daughter = new Daughter();

         ShowYourName(father );   //结果this is father

         ShowYourName(son);       //结果this is son

         ShowYourName(daughter );  /*结果this is father.上面的两个函数是体现了多态,但Daughter类中没用override去重写函数,所以不能实现多态.*/

}

由上面可知当调用相同的函数ShowYourName()时执行了不同的操作.这就是多态.其实觉得还是用动态绑定这术语更容易理解点,就是函数的参数类型fa只有在运行时才真正确定它的类型.因为子类is a 特殊的父类,所以可以先用父类来代替子类的类型.不过C#里面没指针的概念,不容易解释清楚,等会举C++的例子时用指针解释更容易明白.

如果要不用虚函数而是用接口或抽象来也一样可以实现多态.

比如public abstract class Father

{

   public abstract void ShowMsg();

}

public interface Father

{

  void ShowMsg();

}

只不过如果是继承抽象函数必须用override来重写,不能用new.继承接口的话直接像普通函数一样重新定义下就行,只不过注意修饰符必须是public.

另外这两者自然不能直接Father fa = new Father()这样实例化.其他操作就都一样了.也一样实现多态了.

 

 

C++虚函数与多态

假如有父类Father,有子类Son,Duaghter.

class Father

{

public :

int age;

virtual void ShowMsg(){cout<<"This is father.";}        //virtual必须有

};

class Son :public Father

{

public :

virtual void ShowMsg(){cout<<"This is Son.";}        //virtual关键字可有可无

};

class Daughter :public Father

{

public :

void ShowMsg(){cout<<"This is Daughter."<<endl;}

};

void ShowYourName(Father* fa)

{

fa->ShowMsg();

}

int _tmain(int argc, _TCHAR* argv[])

{

Father fa;

Son son;

Daughter da;

Father* pFa;

pFa = &fa;

ShowYourName(pFa); //结果this is father

pFa = &son;

ShowYourName(pFa); //结果this is son

pFa = &da;

ShowYourName(pFa); //结果this is daughter

return 0;

}

 

貌似C++里面没有像C#一样可以用new关键字来隐藏子类的函数.不过有个操作可以实现类似的功能.

假如这样Father *pFa;  Daughter da;

pFa = &((Father)da);

ShowYourName(pFa);    //这样结果就会为this is father,和上面的示例不一样,上面示例是this is daughter.

 

C++虚函数实现多态的原理

看了示例后还有点云里雾里的.虚函数怎么就莫名其妙的实现了多态呢?

这就要说到虚拟函数表(vtable)

我们想下当创建Father,Son,Daughter这三个类时,在内存中是怎么表示的呢.

类Father,在内存中这样表示的呢.你用sizeof(fa)查看下发现结果为8,其中4个字节是用来装int型数据,还有4个字节是装着一个指针vptr,如果64位上指针应该是8个字节了啊.

vptr指向一个虚拟函数表,此表中又装着另外一个指针(*ShowMsg)(),它指向Father::ShowMsg().(这就是真正保存函数的地方,(如果不是虚函数而是一般的函数也保存在这个区域,只不过不是通过指针来找).

类Son继承了Father,所以会把Father内存中的东东拷贝过来,所以sizeof一下也是8.其中4字节保存int类型.4字节保存指针vptr,同样指向虚拟函数表,但这里会做这样一个操作.如果发现Son中有重写了虚拟函数,就会更新此表,否则不更新.由于Son重写了虚拟函数,所以表中的指针(*ShowMsg)()指向的内容变了,指向了Son::ShowMsg()

而类Daughter跟Son的操作完全一样.

 

这样当pFa = &da;表示指针指向类Daughter的内存,由于虚函数列表中对应的函数是Daughter::ShowMsg().所以就明白了为啥pFa->ShowMsg();调用的是类Daughter中的这函数了.但假如Daughter没有重写虚函数,整个类Daughter就为空,只是继承Father,这样的话它的虚函数表中所指的函数就是Father::ShowMsg()了

 

现在虚函数是怎么实现多态的是弄明白了,那刚才上面说的那个与C#中有new关键字隐藏子类函数的操作是咋回事呢?

Father *pFa; Daughter da;

pFa = &((Father)da); //注意这个地方(Father)da 进行类型转换时实际上是完全拷贝了类Father内存中的内容.所以虚函数列表指向的函数就是Father::ShowMsg()了

ShowYourName(pFa);

抱歉!评论已关闭.