zoukankan      html  css  js  c++  java
  • [搬运]如何在C++中实现多态性

    也没什么好说的,仅仅做了个测试,了解一下为什么会有一些莫名其妙的规定。

    以前学C++时我对这些是一直没弄懂的,但愿对某些人还是有所帮助的~~
    下述源代码在VC++6.0下通过。
    Tab变成只占1格了,将就看看吧=。=或者copy到编辑器中=。=


    // 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()/n";}
     ~A1() {cout << getName() << "A1::~A1()/n";}
     void print() const {cout << getName() << "A1::print()/n";}
    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()/n";}
     ~A2() {cout << getName() << "A2::~A2()/n";}
     void print() const {cout << getName() << "A2::print()/n";}
    };

    //虚覆盖
    class B1
    {
    public:
     B1(string const& name) : m_Name(name) {cout << getName() << "B1::B1()/n";}
     virtual ~B1() {cout << getName() << "B1::~B1()/n";}
     virtual void print() const {cout << getName() << "B1::print()/n";}
    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()/n";}
     virtual ~B2() {cout << getName() << "B2::~B2()/n";}
     virtual void print() const {cout << getName() << "B2::print()/n";}
    };

    //虚覆盖,并使用实析构函数(这是个错误)
    class C1
    {
    public:
     C1(string const& name) : m_Name(name) {cout << getName() << "C1::C1()/n";}
     ~C1() {cout << getName() << "C1::~C1()/n";}
     virtual void print() const {cout << getName() << "C1::print()/n";}
    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()/n";}
     ~C2() {cout << getName() << "C2::~C2()/n";}
     virtual void print() const {cout << getName() << "C2::print()/n";}
    };

    //分开各部分的输出
    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 << '/n';
     }
    }

    //测试
    int main()
    {
     try
     {
      //实覆盖
      cout << "Real override 1:/n";
      {
       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:/n";
      {
       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:/n";
      {
       printLine();
       B1 b1("b1.");
       printLine();
       B2 b2("b2.");
       printLine();
       
       b1.print();
       printLine();
       b2.print(); //调用的是b2.B2::print(),同实函数一样
       printLine();
      }

      printLine(3);
      
      cout << "Virtual override 2:/n";
      {
       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:/n";
      {
       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:/n";
      {
       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 << "/nNo enough memory!/n";
      return 1;
     }

     catch (...)
     {
      cerr << "/n发现未知异常,也许你人品有问题。";
      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++的调试器。
    */

  • 相关阅读:
    springboot+jsp 遇到的坑
    异步复位同步释放
    DDR工作原理(转载)
    FPGA基础 之逻辑单元
    二进制转BCD
    bcd转二进制
    FPGA学习笔记之IIC—EEPROM写和读
    FPGA学习笔记之mif文件生成方法总结
    FPGA_实验小项目:四位运算小计算器
    小小计算器之消零显示模块数码管消零
  • 原文地址:https://www.cnblogs.com/mmzhang/p/4582569.html
Copyright © 2011-2022 走看看