• C++学习笔记----4.4 继承情况下的类作用域嵌套


    引言

    在继承情况下,派生类的作用域嵌套在基类作用域中:如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义。

    正是这种类作用域的层次嵌套使我们能够直接访问基类的成员,就好像这些成员是派生类成员一样:

     

    1
    2
    3
    Bulk_item bulk;
     
    cout << bulk.book() << endl;

    名字book的使用将这样确定[先派生->后基类]:

    1)bulk是Bulk_item类对象,在Bulk_item类中查找,找不到名字book。

    2)因为从Item_base派生Bulk_item,所以接着在Item_base类中查找,找到名字book,则引用成功的确定了。



    一、名字查找在编译时发生

    对象、引用或指针的静态类型决定了对象能够完成的行为。甚至当静态类型和动态类型可能不同的时候,就像使用基类类型的引用或指针时可能会发生的,静态类型仍然决定着可以使用什么成员:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Disc_item : public Item_base
    {
    public:
        std::pair<size_t,double> discount_policy() const
        {
            return std::make_pair(quantity,discount);
        }
     
        //other member as before...
    };
    </size_t,double>

    只能通过Disc_item类型或Disc_item派生类型的对象、指针或引用访问discount_policy():

     

    1
    2
    3
    4
    5
    6
    Bulk_item bulk;
    Bulk_item *bulkP = &bulk;
    Item_base *itemP = &bulk;
     
    bulkP -> discount_policy(); //OK
    itemP -> discount_policy(); //Error

    通过itemP访问是错误的,因为基类类型的指针(引用或对象)只能访问对象的基类部分,而不能访问派生类部分,而在基类中又没有定义discount_policy()成员。

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    //P498 习题15.21/22
    class Item_base
    {
    public:
        Item_base(const std::string &book = "",
                  double sales_price = 0.0):
            isbn(book),price(sales_price) {}
     
        std::string book() const
        {
            return isbn;
        }
     
        //只是返回总价格,不进行打折
        virtual double net_price(std::size_t n) const
        {
            return n * price;
        }
     
        virtual ~Item_base() {}
     
    private:
        std::string isbn;
     
    protected:
        double price;
    };
     
    class Disc_item : public Item_base
    {
    public:
        Disc_item(const std::string &book = "",
                  double sales_price = 0.0,
                  std::size_t qty = 0,
                  double disc_rate = 0.0):
            Item_base(book,sales_price),quantity(qty),discount(disc_rate) {}
     
        //将函数设置为纯虚函数,以防止用户创建Disc_item对象
        double net_price(size_t) const = 0;
     
        std::pair<size_t,double> discount_policy() const
        {
            return std::make_pair(quantity,discount);
        }
     
    protected:
        std::size_t quantity;   //可实行折扣的数量
        double discount;        //折扣率
    };
     
    //批量购买折扣类
    class Bulk_item : public Disc_item
    {
    public:
        Bulk_item(const std::string &book = "",
                  double sales_price = 0.0,
                  std::size_t qty = 0,
                  double disc_rate = 0.0):
            Disc_item(book,sales_price,qty,disc_rate) {}
     
        double net_price(std::size_t cnt) const
        {
            if (cnt >= quantity)
            {
                return cnt * (1 - discount) * price;
            }
            else
            {
                return cnt * price;
            }
        }
    };
     
    //有限折扣类
    class Lds_item : public Disc_item
    {
    public:
        Lds_item(const std::string &book = "",
                 double sales_price = 0.0,
                 std::size_t qty = 0,
                 double disc_rate = 0.0):
            Disc_item(book,sales_price,qty,disc_rate) {}
     
        double net_price(std::size_t cnt) const
        {
            if (cnt <= quantity)
            {
                return cnt * (1 - discount) * price;
            }
            else
            {
                return price * (cnt - quantity * discount);
            }
        }
    };
    </size_t,double>

    二、名字冲突与继承

    与基类成员同名的派生类成员将屏蔽对基类成员的直接访问:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class Base
    {
    public:
        Base():mem(0){}
     
    protected:
        int mem;
    };
     
    class Derived : public Base
    {
    public:
        Derived(int i):mem(i){}
        int get_mem() const
        {
            return mem; //Derived::mem
        }
     
    private:
        int mem;    //将会屏蔽Base::mem
    };

    get_mem中对mem的引用被确定为Derive中的名字:

     

    1
    2
    Derived d(43);
    cout << d.get_mem() << endl;    //output 43

    可以使用作用域操作符访问被屏蔽的成员:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Derived : public Base
    {
    public:
        int get_mem() const
        {
            return Base::mem; //Derived::mem
        }
        //As before
    };
     
    //测试
        Derived d(43);
        cout << d.get_mem() << endl;    //output 0

    作用域操作符指示编译器在Base中查找mem成员。

    【最佳实践】

    设计派生类时,只要可能,最好避免与基类成员的名字冲突

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    //P499 习题15.23
    class Base
    {
    public:
        void foo(int);
     
    protected:
        int bar;
        double foo_bar;
    };
     
    class Derived : public Base
    {
    public:
        void foo(string);
        bool bar(Base *pb);
        void foobar();
     
    protected:
        string bar;
    };
     
    void Derived::foobar()
    {
        bar = "1024";
    }
     
    bool Derived::bar(Derived *pb)
    {
        return foo_bar == pb -> foo_bar;
    }
     
    int main()
    {
        Derived d;
        d.foo("1024");
    }
    /*说明:可能是g++编译器对类型检查比较严格,这个程序在g++编译器上死活编译不过,
    *因为在Derivd中的string bar处编译器提示说:与前面的声明冲突了!
    *的确,在Derivd中,bar既有数据成员又有成员函数!!!
    */

    三、作用域与成员函数

    在基类和派生类中使用同一名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同,基类成员也会被屏蔽:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    struct Base
    {
        int memfuc();
    };
     
    struct Derived : Base
    {
        int memfuc(int);
    };
     
    int main()
    {
        Derived d;
        Base b;
     
        b.memfuc();         //调用Base::memfuc()
        d.memfuc(10);       //调用Derived::memfuc()
        d.memfuc();         //Error
        d.Base::memfuc();//调用Base::memfuc()
    }

    Derived中的memfuc声明隐藏了Base中的声明。在确定下面一条语句时:

     

    1
    d.memfuc();

    编译器查找名字memfuc,并在Derived类中找到。一旦找到了名字,编译器要就不再继续查找了。

    【小心地雷】

    局部作用域中声明的函数不会重载全局作用域中定义的函数,同样,派生类中定义的函数也不会重载基类中定义的成员。通过派生类对象调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类中根本没有定义该函数时,才考虑基类函数。如:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct Base
    {
        int memfuc();
    };
     
    struct Derived : Base
    {
        int memfuc(int);
    };
     
    Derived d;
    d.memfuc(); //Error

    如果将Derived中的intmemfuc(int)注释掉,则:

     

    1
    d.memfuc(); //OK

    重载函数

    像其他任意函数一样,成员函数(无论虚还是非虚)也可以重载。派生类可以重定义所继承的0个或多个版本。

    [注意] 如果派生类重定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    struct Derived : Base
    {
        int memfuc();
        int memfuc(int);
        double memfuc(double);
    };
     
    int main()
    {
        Derived d;
        d.memfuc();     //Derived::memfuc()
        d.memfuc(10);   //Derived::memfuc(int)
    }

    如果派生类想通过自身类型使用所有的重载版本,则派生类必须要么重定义所有重载版本,要么一个也不重定义。

    有时类需要仅仅重定义一个重载版本,并且想要继承其他版本的含义,在这种情况下,派生类不用重定义所继承的每一个基类版本,它可以为重载成员提供using声明。一个using声明只能指定一个名字,不能指定形参表,因此:using声明会将该函数的所有重载实例加到派生类的作用域。将所有名字加入作用域之后,派生类只需要重定义本类型确实必须定义的那些函数,对其他版本可以使用继承的定义。

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    struct Base
    {
        int memfuc();
        int memfuc(int);
        int memfuc(double);
    };
     
    struct Derived : Base
    {
        using Base::memfuc;
        int memfuc();   //重定义
    };
     
    int main()
    {
        Derived d;
        d.memfuc();     //Derived::memfuc()
        d.memfuc(10);   //Base::memfuc(int)
    }

    四、虚函数与作用域

    虚函数:如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class Base
    {
    public:
        virtual int fcn();
    };
     
    class D1 : public Base
    {
    public:
        //该fcn屏蔽了Base类中的虚函数fun
        int fcn(int);
        /**此时有两个名为 fcn 的函数:
        *类从 Base 继承的一个名为 fcn 的虚函数
        *类定义的名为 fcn 的非虚成员函数,该函数接受一个 int 形参
        */
    };
     
    class D2 : public D1
    {
    public:
        /**重定义了它继承的两个函数:
        *1.重定义了 Base 中定义的 fcn 的原始版本
        *2.重定义了 D1 中定义的非虚版本。
        */
        int fcn();
        int fcn(int);
    };



    通过基类调用被屏蔽的虚函数

    通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Base bobj;
    D1 d1obj;
    D2 d2obj;
     
    Base *bp1 = &bobj,*bp2 = &d1obj,*bp3 = &d2obj;
     
    bp1 -> fcn();    //调用Base::fcn()
    bp2 -> fcn();    //调用Base::fcn()
    bp3 -> fcn();    //调用D2::fcn()

    【关键概念:名字查找与继承】

    理解 C++中继承层次的关键在于理解如何确定函数调用。确定函数调用遵循以下四个步骤:

    1)首先确定进行函数调用的对象、引用或指针的静态类型

    2)在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。如果不能在类或其相关基类中找到该名字,则调用是错误的。

    3)一旦找到了该名字,就进行常规类型检查,查看如果给定找到的定义,该函数调用是否合法。

    4)假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //P502 习题15.24
        Bulk_item bulk;
        Item_base item(bulk);
        Item_base *p = &bulk;
     
        /**由于net_price为虚函数
        *对虚函数而言,只能通过指针或引用进行动态绑定
        *而通过对象调用虚函数,所调用到的总是该对象所属类型中定义的函数
        */
        p -> net_price(10);  //调用Bulk_item版本的net_price
        item.net_price(10); //调用Item_base版本的net_price

     

  • 相关阅读:
    【USACO10JAN】Cheese Towers S 奶酪塔 (背包dp)
    【SDOI2015】排序(dfs+结论)
    【NOI2014】购票(树形dp+树剖+斜率优化)
    【BZOJ3329】Xorequ(数位dp+矩阵快速幂)
    [NOI 2012] 骑行川藏
    BZOJ
    [学习笔记] 上下界网络流
    [八省联考 2018] 劈配
    P4313 文理分科
    [SDOI 2015] 序列统计
  • 原文地址:https://www.cnblogs.com/haoyul/p/7287681.html
Copyright © 2020-2023  润新知