`
wangyanlong0107
  • 浏览: 480498 次
  • 性别: Icon_minigender_1
  • 来自: 沈阳
社区版块
存档分类
最新评论

【转】 多态与虚函数

    博客分类:
  • c++
 
阅读更多

多态与虚函数

                
                
1.多态性
多态的概念:
l         在面向对象的概念中,多态性是指不同对象收到相同消息时,根据对象类不同产生不同的动作。
 
C++允许程序员发送相同消息到不同的相关对象,而由对象决定如何完成该动作,并且支持软件选择实现决策的时间。其中,运行时的多态是面向对象的程序设计语言所独有的。有人还认为,只有与动态联编相结合的多态才是真正的面向对象的多态。
 
多态的特点:
l          提供了把接口与实现分开的另一种方法;
l          提高了代码的组织性可读性,更重要的是它使软件的可扩充性有了充分的提高;
l         是OOP的一个重要特征,提供了丰富的逻辑关系清晰的描述对象方法的手段,提高了软件功能和版本进化的设计维护能力。
 
C++支持两种多态性:
l          编译时多态:程序运行前发生的事件——函数重载、运算符重载——静态绑定
l          运行时多态:程序运行时发生的事件——虚函数机制          ——动态绑定
            
2.函数绑定(function call binding
绑定:将函数调用与函数体连接起来叫做绑定。
预绑定(early binding)绑定在程序运行之前进行(由编译器和连接器执行),也叫静态联编
后绑定(late binding)编译器在编译时未确定要调用的函数,必须根据程序运行所产生的信息来通知调用哪一个函数,也叫动态联编
C语言就只有一种绑定方式就是预绑定。
           
预绑定与后绑定的区别:
预绑定:
l          意味着绑定基于的信息都是静态的,是编译和连接时就可以确定的;
l         编译系统根据指针(或引用)本身的类型,而不是它所指向的对象的类型来进行绑定;
l         预绑定的实体包括一般函数、重载函数、非虚成员函数和非虚友元函数。
后绑定:
l          在运行时,根据对象类型的不同来选择合适的函数调用,这些类型信息在编译时是不可知的,故只能用后绑定解决这一问题;
l          拥有虚函数的类对象中,必然包含着相应的类型信息,否则动态绑定不可能实现;
l          编译时的多态的实现,取决于程序的静态信息是否足够为相同的程序实体确定不同的标识符。这里的程序实体,是指程序代码中的各种名称和代码段;
l         要实现运行时的多态,进行动态联编,就必须使用虚函数。
               
编译时的多态,表现在以下几方面:
l          对于在一个类中说明的重载,编译系统根据重载函数的参数个数、类型以及顺序的差别,来分别调用相应的函数;
l          对于在基类和派生类中重载函数,即使所带参数完全相同,由于它们属于不同的类,在编译时可以根据对象名前缀来加以区别;或者使用“类名::前缀,也可以指示编译器分辨出应该调用哪个类的成员函数;
l          调用编译时绑定的函数,优点是高效率,因为编译系统可以在运行前对代码进行优化;缺点是缺少灵活性,不能满足程序的可扩充性要求。
                     
                      

例1:编写基类、派生类、子派生类,在基类中定义虚函数,并全部实现,定义每个类的对象,观察每种对象的大小及其内存分配规律。

           
代码如下:
           
/************************************************************************
* 虚函数彻底研究
***********************************************************************
*/

#include 
<IOSTREAM.H>
//基类
class CBase
{
    
int x;
public:
    CBase(
int n) {x=n;}
    
~CBase(){}
    
virtual void SetX(int n) {x=n;}
    
virtual int GetX() {return x;}
}
;

//派生类
class CDerive : public CBase
{
    
int x;
public:
    CDerive(
int n1,int n2):CBase(n1)
    
{
        x
=n2;
    }

    
~CDerive() {}
    
void SetX(int n) {x=n;}
    
int GetX() {return x;}
}
;

//子派生类
class CSubDerive : public CDerive
{
    
int x;
public:
    CSubDerive(
int n1,int n2,int n3):CDerive(n1,n2)
    
{
        x
=n3;
    }

    
~CSubDerive() {}
    
void SetX(int n) {x=n;}
    
int GetX() {return x;}
}
;
void main()
{
    cout
<<"CBase size = "<<sizeof(CBase)<<endl;    
    cout
<<"CDerive size = "<<sizeof(CDerive)<<endl;
    cout
<<"CSubDerive size = "<<sizeof(CSubDerive)<<endl;

    CBase obj1(
1);    
    CDerive obj2(
2,3);    
    CSubDerive obj3(
4,5,6);

    CBase 
*pObj1=&obj1;    
    CDerive 
*pObj2=&obj2;    
    CSubDerive 
*pObj3=&obj3;

    CBase 
*pObj[]={pObj1,pObj2,pObj3};
}
               
1. 运行结果:
              
               
2. 对象的地址及内容:
           
           
             
       
           可以看出,子派生类的前4个字节(低地址)是虚函数表的地址,接下来的4个字节是从基类继承来的x,然后是由派生类继承来的x,最后才是该类的x(高地址);派生类的对象空间也如此。
           
3.  基类指针指向三个对象时,指针的内容:
             


要虚函数发挥作用,必须用基类的指针(或引用)指向派生类的对象,并用指针(或引用)调用虚函数。也就是说,只有用地址才能体现运行多态性。因为不论是指向基类还是指向派生类的指针(引用),大小都是一样的,这样才能用基类指针指向派生类对象。这时,指针提供的信息是不完全的,在编译阶段不知道应该调用虚函数的哪个版本。而如果用对象调用虚函数,由于类型已经确定了,因此编译系统很可能采用预绑定;

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics