zoukankan      html  css  js  c++  java
  • 继承(二)

    1. 父子间的同名冲突

    同名变量冲突

    • 子类可以定义父类中的同名成员变量
    • 父类中的同名成员变量被隐藏,但仍然存在于子类中
    • 父类中的同名成员变量需要通过作用域分辨符(::)访问
    Child c;
    c.mi = 100;           //访问子类中的mi
    c.Parent::mi = 1000;  //访问父类中的mi
    
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    namespace A
    {
    int g_i = 0;
    }
    
    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;
    
        c.mi = 100;
        c.Parent::mi = 1000;
    
        cout << endl;
        cout << "&c.mi = " << &c.mi << endl;
        cout << "c.mi = " << c.mi << endl;
        cout << endl;
    
        cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
        cout << "c.Parent::mi = " << c.Parent::mi << endl;
    
        return 0;
    }
    

    同名函数冲突

    • 子类中的成员函数将隐藏父类的同名成员函数(只需要同名即可,对参数列表和返回值类型无要求
    • 使用作用域分辨符访问父类中的同名成员函数
    • 子类可以定义与父类完全相同的成员函数(包括函数名,参数列表、返回值类型)
    • 子类无法重载父类中的成员函数
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    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);
        }
    };
    
    int main()
    {
        Child c;
    
        c.mi = 100;
        c.Parent::mi = 1000;
    
        cout << "c.mi = " << c.mi << endl;
        cout << "c.Parent::mi = " << c.Parent::mi << endl;
    
        c.add(1);
        c.add(2, 3);
        c.add(4, 5, 6);
    
        c.Parent::add(1);
        c.Parent::add(2, 3);
    
        cout << "c.mi = " << c.mi << endl;
        cout << "c.Parent::mi = " << c.Parent::mi << endl;
    
        return 0;
    }
    

    2. 同名冲突引发的问题

    父子间的赋值兼容

    父子间的赋值兼容,指的是子类对象可以当作父类对象使用,具体表现在两个方面

    • 子类对象可以直接初始化父类对象,或者给父类对象赋值
    • 父类指针(引用)可以直接指向(引用)子类对象
    • 实际上仅仅指向(引用)了子类对象中的父类部分
    • 通过父类指针(引用),只能访问父类中的成员变量和成员函数
    • 子类对象本身不受影响,依然可以访问自身的成员
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Parent
    {
    public:
        int mi;
    
        Parent(int i = 0)
        {
            mi = i;
        }
    
        void add(int i)
        {
            mi += i;
        }
    
        void add(int a, int b)
        {
            mi += (a + b);
        }
    };
    
    class Child : public Parent
    {
    public:
        int mv;
    
        Child(int v = 0)
        {
            mv = v;
        }
    
        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;
    
        rp.mi = 100;
        rp.add(5);
        rp.add(10, 10);
    
        cout << "p.mi = " << p.mi << endl;
        cout << "c.mv = " << c.mv << endl;
        cout << "rp.mi = " << rp.mi << endl;
        cout << "pp->mi = " << pp->mi << endl;
        cout << endl;
    
        /*编译不过,只能访问父类中的成员*/
        // pp->mv = 1000;
        // pp->add(1, 10, 100);
    
        /*父类指针或引用指向子类对象,对子类对象本身没有影响*/
        c.mv = 1000;
        c.add(1, 1, 1);
    
        cout << "p.mi = " << p.mi << endl;
        cout << "c.mv = " << c.mv << endl;
        cout << "rp.mi = " << rp.mi << endl;
        cout << "pp->mi = " << pp->mi << endl;
    
        return 0;
    }
    

    函数重写

    • 子类可以重定义父类中已经存在的成员函数
    • 这种重定义发生在继承中,叫做函数重写
    • 函数重写是同名覆盖的一种特殊情况
    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:
        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: I'm Parent
        how_to_print(&c);  // Expected: I'm Child
    
        return 0;
    }
    

    第34-35行注释部分展示了我们期望的结果,但实际运行结果和期望不符,原因在于

    • 编译期间,编译器只能根据指针(引用)的类型判断所指向(引用)的对象
    • 根据赋值兼容,编译器认为父类指针(引用)指向(引用)的是父类对象
    • 因此,编译结果只可能是调用父类中定义的同名函数

    编译器的处理方法是合理的,但不是我们期望的,这就是同名冲突引发的问题,要想解决这个问题,需要用到多态的知识。

    3. 继承中的强制类型转换

    我们之前讲过了C++的四种强制类型转换,那么在继承中如何正确地使用强制类型转换呢?

    • dynamic_cast是与继承相关的类型转换关键字
    • dynamic_cast要求相关的类中必须有虚函数
    • dynamic_cast用于有直接或者间接继承关系的类指针(引用)之间
    • 指针:转换成功,得到目标类型的指针;转换失败,得到一个空指针
    • 引用:转换成功,得到目标类型的引用;转换失败,得到一个异常操作信息
    • 编译器会检查dynamic_cast的使用是否正确
    • 类型转换的结果只可能在运行阶段才能得到
    /*
    ** 继承中的dynamic_cast关键字
    */
    
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Base
    {
    public:
        Base()
        {
            cout << "Base::Base()" << endl;
        }
    
        /*
         * dynamic_cast要求类中必须有虚函数,因此将父类的析构函数定义为虚函数
        */
        virtual ~Base()
        {
            cout << "Base::~Base()" << endl;
        }
    };
    
    class Derived : public Base
    {
    
    };
    
    int main()
    {
        Base *p0 = new Derived;
        Base *p1 = new Base;
        Base &p2 = *p1;
    
        Derived *pd0 = dynamic_cast<Derived *>(p0); //dynamic_cast转换指针成功,得到目标类型指针
        Derived *pd1 = dynamic_cast<Derived *>(p1); //不能用子类指针指向一个父类对象,dynamic_cast转换指针失败,得到的pd1为NULL
    
        cout << "pd0 = " << pd0 << endl;
        cout << "pd1 = " << pd1 << endl;
    
        Derived &pd2 = dynamic_cast<Derived &>(p2); //dynamic_cast转换引用失败,运行时抛出异常
    
        delete p0;
        delete p1;
    
        return 0;
    }
    

  • 相关阅读:
    一个屌丝程序猿的人生(一百零二)
    一个屌丝程序猿的人生(一百零一)
    一个屌丝程序猿的人生(一百)
    一个屌丝程序猿的人生(九十九)
    JavaEE开发之SpringMVC中的静态资源映射及服务器推送技术
    JavaEE开发之SpringMVC中的自定义拦截器及异常处理
    JavaEE开发之SpringMVC中的路由配置及参数传递详解
    JavaEE开发使用Maven管理的SpringMVC工程
    JavaEE开发之Spring中的条件注解、组合注解与元注解
    JavaEE开发之Spring中的多线程编程以及任务定时器详解
  • 原文地址:https://www.cnblogs.com/songhe364826110/p/11601383.html
Copyright © 2011-2022 走看看