美文网首页
C++中继承与多态

C++中继承与多态

作者: nethanhan | 来源:发表于2017-10-14 10:55 被阅读0次

    父子间的同名冲突

    首先来看一段代码:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Parent
    {
    public:
        int mi;
    };
    
    class Child : public Parent
    {
    public:
        int mi;
    };
    
    int main()
    {
        Child c;
        
        //这里的mi是Parent中的还是Child中的呢?
        c.mi = 100;
        
        return 0;
    }
    

    编译通过,说明子类可以定义和父类相同的同名成员。

    • 子类可以定义父类中的同名成员
    • 子类中的成员将隐藏父类中的同名成员
    • 父类中的同名成员依然存在于子类中
    • 通过作用域分辨符( : : )访问父类中的同名成员
    • 访问父类中的同名成员
    Child c;
    
    //子类中的mi
    c.mi = 100;
    
    //父类中的mi
    c.Parent::mi = 1000;
    

    再来看一个例子:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    //定义一个命名空间A
    namespace A
    {
        int g_i = 0;
    }
    //定义一个命名空间B
    namespace B
    {
        int g_i = 1;
    }
    
    class Parent
    {
    public:
        int mi;
        
        Parent()
        {
            cout << "Parent() : " << "&mi = " << &mi << endl;
        }
    };
    
    class Child : public Parent
    {
    public:
        int mi;
        
        Child()
        {
            cout << "Child() : " << "&mi = " << &mi << endl;
        }
    };
    
    int main()
    {
        Child c;
        
        //向子类的mi成员赋值100
        c.mi = 100;    
        //通过作用域向父类的mi成员赋值1000
        c.Parent::mi = 1000;
        
        //打印子类中mi的地址
        cout << "&c.mi = " << &c.mi << endl;
        //打印子类中mi的内容
        cout << "c.mi = " << c.mi << endl;
        
        //打印父类中mi的地址
        cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
        //打印父类中mi的内容
        cout << "c.Parent::mi = " << c.Parent::mi << endl;
        
        return 0;
    }
    

    输出结果为:

    Parent() : &mi = 0x7fff57f57a90
    Child() : &mi = 0x7fff57f57a94
    &c.mi = 0x7fff57f57a94
    c.mi = 100
    &c.Parent::mi = 0x7fff57f57a90
    c.Parent::mi = 1000
    
    • 类中的成员函数可以进行重载
      • 重载函数的本质为多个不同的函数
      • 函数名和参数列表是唯一的标识
      • 函数重载必须发生在同一个作用域中
      • 所以父子之间的同名成员不构成重载

    比如像这样:

    class Parent
    {
    public:
        int mi;
        
        void add(int v)
        {
            mi += v;
        }
        
        void add(int a, int b)
        {
            mi += (a + b);
        }
    };
    
    class Child : public Parent
    {
    public:
        int mi;
        
        void add(int v)
        {
            mi += v;
        }
        
        void add(int a, int b)
        {
            mi += (a + b);
        }
        
        void add(int x, int y, int z)
        {
            mi += (x + y + z);
        }
    };
    

    代码 Parent类 和 Child类 中有同名的函数add ,但是两个类之间不构成重载,只有Parent类中多个add函数构成重载。

    • 子类中的函数将隐藏父类的同名函数
    • 子类无法重载父类中的成员函数
    • 使用作用域分辨符访问父类中的同名函数
    • 子类可以定义父类中完全相同的成员函数

    父子间的赋值兼容

    • 子类对象可以当作父类对象使用(兼容性)
      • 子类对象可以直接赋值给父类对象
      • 子类对象可以直接初始化父类对象
      • 父类指针可以直接指向子类对象
      • 父类引用可以直接引用子类对象

    举个例子:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Parent
    {
    public:
        int mi;
        
        void add(int i)
        {
            mi += i;
        }
        
        void add(int a, int b)
        {
            mi += (a + b);
        }
    };
    
    class Child : public Parent
    {
    public:
        int mv;
        
        void add(int x, int y, int z)
        {
            mv += (x + y + z);
        }
    };
    
    int main()
    {
        Parent p;
        Child c;
        //子类对象可以直接赋值给父类对象
        p = c;
        //子类对象可以直接初始化父类对象
        Parent p1(c);
        //父类引用可以直接引用子类对象
        Parent& rp = c;
        //父类指针可以直接指向子类对象
        Parent* pp = &c;
    
        return 0;
    }
    

    在main函数中进行上述几条的操作都没有出现编译出错。现在进行这样操作:

    rp.mi = 100;
    rp.add(5);        
    rp.add(10, 10);  
    

    发现可以编译通过,并没有出现同名覆盖的问题。但是如果这样操作:

    pp->mv = 1000;
    pp->add(1, 10, 100);
    

    运行以后就会报错,报错信息如下:

    48-1.cpp:51:10: error: no member named 'mv' in 'Parent'
         pp->mv = 1000;
         ~~  ^
    48-1.cpp:52:10: error: no matching member function for call to 'add'
         pp->add(1, 10, 100);
         ~~~~^~~
    48-1.cpp:16:10: note: candidate function not viable: requires 2 arguments, but 3
          were provided
        void add(int a, int b)
             ^
    48-1.cpp:11:10: note: candidate function not viable: requires single argument
          'i', but 3 arguments were provided
        void add(int i)
             ^
    2 errors generated.
    

    信息提示没有找到带有3个参数的add函数。为什么呢?

    • 当使用父类指针(引用)指向子类对象时
      • 子类对象退化为父类对象
      • 只能访问父类中定义的成员
      • 可以直接访问被子类覆盖的同名成员

    特殊的同名函数

    • 子类中可以冲定义父类中已经存在的成员函数
    • 这种冲定义发生在继承中,叫做函数重写
    • 函数重写是同名覆盖的一种特殊情况

    例如:

    class Parent
    {
        public:
            void print()
            {
                cout << "I'm Parent." << endl;
            }
    };
    
    //函数重写
    
    class Child : public Parent
    {
        public:
            void print()
            {
                cout << "I'm Child" << endl;
            }
    };
    

    假如函数重写和赋值兼容同时出现呢? 就像这样:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Parent
    {
    public:
        int mi;
        
        void add(int i)
        {
            mi += i;
        }
        
        void add(int a, int b)
        {
            mi += (a + b);
        }
        
        void print()
        {
            cout << "I'm Parent." << endl;
        }
    };
    
    class Child : public Parent
    {
    public:
        int mv;
        
        void add(int x, int y, int z)
        {
            mv += (x + y + z);
        }
        
        void print()
        {
            cout << "I'm Child." << endl;
        }
    };
    
    void how_to_print(Parent* p)
    {
        p->print();
    }
    
    int main()
    {
        Parent p;
        Child c;
        
        how_to_print(&p);    // Expected to print: I'm Parent.
        how_to_print(&c);    // Expected to print: I'm Child.
        
        return 0;
    }
    

    预期输出是 I'm Parent.I'm Child. 。但是实际输出:

    I'm Parent.
    I'm Parent.
    
    • 问题分析
      • 编译期间,编译器只能根据指针的类型判断所指向的对象
      • 根据赋值兼容,编译器认为父类指针指向的是父类对象
      • 因此,编译结果只可能是调用父类中定义的同名函数

    在编译 void how_to_print(Parent* p) 这个函数时,编译器不可能知道指针p究竟指向了什么,但是编译器没有理由报错。于是,编译器认为最安全的做法是调用父类的print函数,因为父类和子类肯定都有相同的print函数。

    多态的概念和意义

    • 函数重写回顾
      • 父类中被重写的函数依然会继承给子类
      • 子类中重写的函数将覆盖父类中的函数
      • 通过作用域分辨符( : : )可以访问到父类中的函数

    就像这样:

    Child c;
    Parent* p = &c;
    
    c.Parent::print();  //从父类中继承
    c.print();          //从子类中重写
    
    p->print();         //父类中定义
    

    虽然程序逻辑是这样,但并不是我们所期望的。面向对象中期望的行为:

    • 根据 实际的对象类型 判断如何调用重写函数
    • 父类指针(引用) 指向
      • 父类对象 则调用 父类 中定义的函数
      • 子类对象 则调用 子类 中定义的重写函数

    这里就引出了面向对象中的 多态 的概念:

    • 根据实际的 对象类型决定函数调用 的具体目标
    • 同样的 调用语句 在实际运行时有 多种不同的表现形态

    例如:

    p->print();
    

    p指向父类对象时,会执行

    void print()
    {
        cout << "I'm Parent" << end;
    }
    

    p指向子类对象时,会执行

    void print()
    {
        cout << "I'm Child" << endl;
    }
    
    • C++语言直接支持多态的概念
      • 通过使用 virtual 关键字对多态进行支持
      • virtual 声明的函数被重写后具有多态特性
      • virtual 声明的函数叫做虚函数

    举个例子:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Parent
    {
    public:
        //用 virtual 关键字修饰,则具有多态特性
        virtual void print()
        {
            cout << "I'm Parent." << endl;
        }
    };
    
    class Child : public Parent
    {
    public:
        void print()
        {
            cout << "I'm Child." << endl;
        }
    };
    
    void how_to_print(Parent* p)
    {
        // 展现多态的行为
        p->print();     
    }
    
    int main()
    {
        Parent p;
        Child c;
        
        how_to_print(&p);    // Expected to print: I'm Parent.
        how_to_print(&c);    // Expected to print: I'm Child.
        
        return 0;
    }
    

    执行结果为:

    I'm Parent.
    I'm Child.
    
    • 多态的意义
      • 在程序运行过程中展现出动态的特性
      • 函数重写必须多态实现,否则没有意义
      • 多态是面向对象组件化程序设计的基础特性

    静态联编和动态联编

    • 理论中的概念
      • 静态联编
        • 在程序的编译期间就能确定具体的函数调用。 如:函数重载
      • 动态联编
        • 在程序实际运行后才能确定具体的函数调用。如:函数重写

    举个例子:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Parent
    {
    public:
        //函数重载  并且用 virtual 关键字修饰
        virtual void func()
        {
            cout << "void func()" << endl;
        }
        //函数重载  并且用 virtual 关键字修饰
        virtual void func(int i)
        {
            cout << "void func(int i) : " << i << endl;
        }
        //函数重载  并且用 virtual 关键字修饰
        virtual void func(int i, int j)
        {
            cout << "void func(int i, int j) : " << "(" << i << ", " << j << ")" << endl;
        }
    };
    
    class Child : public Parent
    {
    public:
        //函数重载
        void func(int i, int j)
        {
            cout << "void func(int i, int j) : " << i + j << endl;
        }
        //函数重载
        void func(int i, int j, int k)
        {
            cout << "void func(int i, int j, int k) : " << i + j + k << endl;
        }
    };
    
    void run(Parent* p)
    {
        p->func(1, 2);     // 展现多态的特性
                           // 动态联编
    }
    
    int main()
    {
        Parent p;
        
        p.func();         // 静态联编
        p.func(1);        // 静态联编
        p.func(1, 2);     // 静态联编
        
        cout << endl;
        
        Child c;
        
        c.func(1, 2);     // 静态联编
        
        cout << endl;
        
        run(&p);
        run(&c);
        
        return 0;
    }
    

    运行结果为:

    void func()
    void func(int i) : 1
    void func(int i, int j) : (1, 2)
    
    void func(int i, int j) : 3
    
    void func(int i, int j) : (1, 2)
    void func(int i, int j) : 3
    

    小结

    • 子类可以定义父类的 同名成员 ,定义时子类中的成员将 隐藏 父类中的 同名成员
    • 子类和父类 中的函数 不能构成重载关系
    • 使用 作用域分辨符 可以访问父类中的 同名成员
    • 子类对象 可以当做 父类对象 使用
    • 父类指针 可以正确的指向 子类对象
    • 父类引用 可以正确的代表 子类对象
    • 子类中可以重写父类中的 成员函数
    • 函数重写只可能发生在 父类与子类 之间
    • 多态是根据 实际对象的类型 确定调用的 具体函数
    • virtual关键字 是C++中支持 多态 的唯一方式
    • 被重写的 虚函数 可表现出多态的特性

    相关文章

      网友评论

          本文标题:C++中继承与多态

          本文链接:https://www.haomeiwen.com/subject/ujsxuxtx.html