zoukankan      html  css  js  c++  java
  • 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

     

  • 相关阅读:
    SharePoint 2013 安装.NET Framework 3.5 报错
    SharePoint 2016 配置工作流环境
    SharePoint 2016 站点注册工作流服务报错
    Work Management Service application in SharePoint 2016
    SharePoint 2016 安装 Cumulative Update for Service Bus 1.0 (KB2799752)报错
    SharePoint 2016 工作流报错“没有适用于此应用程序的地址”
    SharePoint 2016 工作流报错“未安装应用程序管理共享服务代理”
    SharePoint JavaScript API in application pages
    SharePoint 2016 每天预热脚本介绍
    SharePoint 无法删除搜索服务应用程序
  • 原文地址:https://www.cnblogs.com/haoyul/p/7287681.html
Copyright © 2011-2022 走看看