c++ 如何实现多态

//  File Name : polymorphism_test.cpp
//  Author : keakon
//  Create Date : 2006/5/11
//  Last Edited Date : 2006/5/26
//  通过3次测试,演示了如何实现多态性。

#include 
< iostream >
#include 
< iterator >
#include 
< ostream >
#include 
< string >

using  std::cerr;
using  std::cout;
using  std:: string ;

// 实覆盖
class  A1
{
public:
    A1(
string const& name) : m_Name(name) {cout << getName() << "A1::A1() ";}
    
~A1() {cout << getName() << "A1::~A1() ";}
    
void print() const {cout << getName() << "A1::print() ";}
protected:
    
string const& getName() const {return m_Name;}
private:
    
string m_Name;
}
;

class  A2 :  public  A1
{
public:
    A2(
string const& name) : A1(name) {cout << getName() << "A2::A2() ";}
    
~A2() {cout << getName() << "A2::~A2() ";}
    
void print() const {cout << getName() << "A2::print() ";}
}
;

// 虚覆盖
class  B1
{
public:
    B1(
string const& name) : m_Name(name) {cout << getName() << "B1::B1() ";}
    
virtual ~B1() {cout << getName() << "B1::~B1() ";}
    
virtual void print() const {cout << getName() << "B1::print() ";}
protected:
    
string const& getName() const {return m_Name;}
private:
    
string m_Name;
}
;

class  B2 :  public  B1
{
public:
    B2(
string const& name) : B1(name) {cout << getName() << "B2::B2() ";}
    
virtual ~B2() {cout << getName() << "B2::~B2() ";}
    
virtual void print() const {cout << getName() << "B2::print() ";}
}
;

// 虚覆盖,并使用实析构函数(这是个错误)
class  C1
{
public:
    C1(
string const& name) : m_Name(name) {cout << getName() << "C1::C1() ";}
    
~C1() {cout << getName() << "C1::~C1() ";}
    
virtual void print() const {cout << getName() << "C1::print() ";}
protected:
    
string const& getName() const {return m_Name;}
private:
    
string m_Name;
}
;

class  C2 :  public  C1
{
public:
    C2(
string const& name) : C1(name) {cout << getName() << "C2::C2() ";}
    
~C2() {cout << getName() << "C2::~C2() ";}
    
virtual void print() const {cout << getName() << "C2::print() ";}
}
;

// 分开各部分的输出
void  printLine(unsigned  int  height  =   1 , unsigned  int  length  =   20 ,
                
char  ch  =   ' - ' , std::ostream &   out   =  cout)
{
    
const string LINE(length, ch);

    
for (; height != 0--height)
    
{
    
//在out上输出一行ch
        std::copy(LINE.begin(), LINE.end(), std::ostream_iterator<char>(out));
        
out << ' ';
    }

}


// 测试
int  main()
{
    
try
    
{
    
//实覆盖
        cout << "Real override 1: ";
        
{
            printLine();
            A1 a1(
"a1.");
            printLine();
            A2 a2(
"a2.");
            printLine(
2);

            a1.print();
            printLine();
            a2.print(); 
//调用的是a2.A2::print()
            printLine();
            static_cast
<A1>(a2).print();
            
//上句调用A1::A1(A1&)或A1::A1(A1 const&)生成一个临时的A1类的变量
            
//然后这个临时变量调用A1::print()
            
//在脱离作用域后(该语句结束时)将调用A1::~A1()
            
//其余见注释4
            printLine();
            a2.A1::print(); 
//不同于上句,此处不调用析构函数

            printLine(
2);
        }
 //超出作用域,调用析构函数;下面也按照同样的格式使用花括号

        printLine(
3);

        
//使用指针实现实覆盖,可能会产生错误
        cout << "Real override 2: ";
        
{
            printLine();
            A1
* pa1 = new A1("pa1->");
            printLine();
            A1
* pa2 = new A2("pa2->"); //注意是基类的指针
            printLine();
            A2
* pa3 = new A2("pa3->");
            printLine();
            A2 a4(
"a4.");
            A1
* pa4 = &a4; //注意是基类的指针
            A1& ra2 = *pa2; //注意是基类的引用   
            printLine(2);

            pa1
->print(); //未检查指针,因为出错时new会抛出异常
            printLine();
            pa2
->print(); //调用的是pa2->A1::print()
            printLine();
            pa3
->print(); //调用的是pa3->A2::print()
            printLine();
            pa4
->print(); //调用的是a4.A1::print(),即pa4->A1::print()
            printLine();
            ra2.print();
            printLine(
2); //调用的是pa2->A1::print()

            delete pa1;
            printLine();
            delete pa2; 
//错误,(*pa2).A2::~A2()不会被调用
            printLine();
            delete pa3;
            printLine();
            pa1 
= NULL;
            pa2 
= NULL;
            pa3 
= NULL;
            pa4 
= NULL; //pa4不是new出来的,不用delete
        }


        printLine(
3);

        
//下面不再重复实覆盖中一些相同的测试

        
//虚覆盖
        cout << "Virtual override 1: ";
        
{
            printLine();
            B1 b1(
"b1.");
            printLine();
            B2 b2(
"b2.");
            printLine();

            b1.print();
            printLine();
            b2.print(); 
//调用的是b2.B2::print(),同实函数一样
            printLine();
        }


        printLine(
3);

        cout 
<< "Virtual override 2: ";
        
{
            printLine();
            B1
* pb1 = new B1("pb1->");
            printLine();
            B1
* pb2 = new B2("pb2->"); //注意是基类的指针
            printLine();
            B1
& rb2 = *pb2; //注意是基类的引用
            printLine();

            pb1
->print();
            printLine();
            pb2
->print(); //调用的是pb2->B2::print()
            printLine();
            rb2.print(); 
//调用的是pb2->B2::print()
            printLine();

            delete pb1;
            printLine();
            delete pb2;
            pb1 
= NULL;
            pb2 
= NULL;
        }


        printLine(
3);

        
//虚覆盖,并使用实析构函数(这是个错误)
        cout << "Virtual override 1, using real destruction: ";
        
{
            printLine();
            C1 c1(
"c1.");
            printLine();
            C2 c2(
"c2.");
            printLine(
2);

            c1.print();
            printLine();
            c2.print();
            printLine(
2);
        }


        printLine(
3);

        cout 
<< "Virtual override 2, using real destruction: ";
        
{
            printLine();
            C1
* pc1 = new C1("pc1->");
            printLine();
            C1
* pc2 = new C2("pc2->"); //注意是基类指针
            printLine(2);

            pc1
->print();
            printLine();
            pc2
->print(); //pc2->C2::~C2()不会被调用
            printLine(2);

            delete pc1;
            printLine();
            delete pc2;
            cout 
<< std::endl;
            pc1 
= NULL;
            pc2 
= NULL;
        }


        
return 0;
    }

    
catch (std::bad_alloc&)
    
//如果内存不够,new抛出std::bad_alloc
    
//似乎在VC++下永远不会抛出该异常,但也不会出错;在g++等编译器中则会抛出该异常
    {
        cerr 
<< " No enough memory! ";
        
return 1;
    }


    
catch (...)
    
{
        cerr 
<< " 发现未知异常,也许你人品有问题。";
        
return 2;
    }

}


  结论:

1.通过对象直接调用成员函数时,始终默认使用该对象的类的成员函数(除非用::显示指定类名)。

2.通过指向对象的指针或引用调用成员函数时:
如果该函数是实函数,则调用该指针或引用的类的成员函数;
如果该函数是虚函数,则调用该指针或引用指向的对象的类的成员函数。

3.基类析构函数在此情况下必须为虚函数(此时,其派生类的析构函数也将是虚函数):
用new来创建派生类对象,并且delete时使用的指针为基类的指针。
若为实析构函数,则派生类部分的成员将不会被析构。

4.若未定义复制构造函数,不要将一个派生类对象强制类型转换为基类对象。
因为若未显式定义复制构造函数,则编译器会生成一个默认的复制构造函数。
然而这个默认的复制构造函数将不会做你希望做的其他的事,如:
如果要在构造函数中维护一个类对象的计数器,则这个函数不会导致计数器增加;
如果要在构造函数中new一个对象,则这个函数也不会new该有的对象。
这个默认的复制构造函数进行的是浅拷贝,使用的成员都是原来的对象的。
因此这个临时对象在析构时,delete的是原来的对象new出来的对象;
而原来的对象在析构时将再次delete这个对象;这属于未定义行为,可能引起程序崩溃。
并且,即使在析构函数中,在delete这个对象后,将指向这个对象的指针赋值为NULL也无效;
因为更改的是临时对象的指针,而原对象的指针仍指向那个被delete了的对象。

关于第4点的结论,我实际上是写了另一个测试代码证明的。
不过那个测试代码在最后一次测试时被我改得太乱了,就不给出了。
有兴趣的可以将A1和A2类添加静态的对象计数器、对象指针和复制构造函数,再自己去研究。
我在VC++6.0 SP6上测试时,debug和release模式下运行状态居然不同(前者崩溃,后者看上去正常)。
然而我一直没太多时间好好研究它,问了好多人却没人给出正确回答。
最后我只好自己跟踪反汇编代码了。能得出上述的结论,还得感谢VC++的调试器。

附:原理阐述

c++的编译器是怎么识别这个指针指向派生类的对象的?
例如:
class A {
   int i;
   int j;
   virtual void function(){
       cout<<"this is class a"<<endl;
   }
  
};

class B :public A

   int k;
   void function()
   {
      cout<<"this is class b"<<endl;
    }
};


void main()
{
    A* pa;
    A a;
    pa=&a;
    pa->function();

    B b;
    pa=&b;
    pa->function();
}
类A的对象块的空间大小是和类B的对象块的大小一样吗?

 

类A的对象块的空间大小和类B的对象块的空间大小不一样。就此代码,32位
环境中,A的对象12字节(两个int一个虚表指针),B的对象16字节(三个int一个
虚表指针)。

把&b赋值给pa之前,b的空间已经分配好了16字节。所谓“把&b赋值给pa”,只
不过是把那16字节的首地址保存在pa中,所以把&b赋值给pa之后,pa指向的空间
大小是16字节,而不是12字节。只是因为pa的类型为A*,所以用pa->的形式只可以
访问到b中属于从A继承的部分,而不能访问到B增加的部分。

通过指针访问成员实际上要计算一个该成员在对象中的偏移。然后用指针中的地址
加上这个偏移就访问到了成员。
 
虚函数的实质是每个对象中有一个指针 ,指向一个表,表中依次存放了虚函数的地址。
这个表我们叫他虚表。
 当我们用pa->function();这样的形式调用一个虚函数,编译器实际上隐式做了一些
 转换,先通过虚表指针找到虚表,再在虚表中找到虚函数指针,最后通过虚函数指针
 调用虚函数。

 一个虚函数指针在基类的虚表中的位置和  在派生类的虚表中的位置是相同的。派生类
 改写一个虚函数意味着增加一段函数代码,同时派生类的虚表中对应的虚函数指针更改
 为指向新增加的代码。

 指针的类型只是决定了访问成员时计算偏移的方式和大小,而指针不管指向基类还是派生
 类,调用一个虚函数所需要的偏移是一样的,只是内容不一样,从而导致不同的形为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值