• EC++学习笔记(六) 继承和面向对象设计


    条款32:确定你的 public 继承塑模出 is-a 关系

    public inheritance 意味着 is-a 关系
    class Derived 以 public 形式继承 class Base, 则每一个 Derived对象同时也是一个 Base对象,反之不成立
    可以施行于Base class 对象身上的每件事情,也同样可以施行于Derived class 对象

    class Person{...};
    class Student : public Person{...};

    每一个Student都是Person,反之,每个Person不一定是Student
    正因为具有 is-a 关系,指向Base类型的引用可以引用Base类型对象,也可以引用Derived类型对象(多态基础)

    条款33:通过组合塑模出 has-a 关系
    class Address{...};
    class PhoneNumber{...};
    class Person{
    public:
        ...
    private:
        std::string         name;
        Address             address;
        PhoneNumber         phoneNumber;
    };

    string对象、Address对象、PhoneNumber对象组合塑模出Person对象
    Person class 示范 has-a 关系:每一个Person都 has-a string name、has-a Address、 has-a PhoneNumber

    条款34:避免遮掩继承而来的名称

    内层作用域的名称会遮蔽外围作用域的名称

    int x;            //global变量
    void func() {
        double x;    //local变量
    }

    class Base {
    private:
        int x;
    public:
        virtual void mf1() = 0;
        virtual void mf2();
        void mf3();
    };
    
    class Derived : public Base {
    public:
        virtual void mf1();
        void mf4();
    };

    如果 Derived 中mf4实现如下:

    void Derived::mf4() {
        mf2();
        ...
    }

    名称查找规则(作用域由小到大): Derived mf4 ==> class Derived ==> class Base ==> namespace ==> global

    class Base {
    private:
        int x;
    public:
        virtual void mf1() = 0;
        virtual void mf1(int);
        virtual void mf2();
        void mf3();
        void mf3(double);
    };
    
    class Derived :public Base {
    public:
        virtual void mf1();
        void mf3();
        void mf4();
    };

    class Base 中 mf1() 和 mf3() 被 Derived 中 mf1() 和 mf3() 遮蔽

    Derived d;
    int x;
    d.mf1();    //没问题,调用 Derived::mf1
    d.mf1(x);     //错误!因为 Derived::mf1 遮蔽了 Base::mf1
    d.mf2();    //没问题,调用 Base::mf2
    d.mf3();    //没问题,调用 Derived::mf3
    d.mf3(x);    //错误!因为 Derived::mf3 遮蔽了 Base::mf3

    如果继承 class Base 并加上重载函数,又希望重新定义其中一部分,那么必须为那些原本会被遮蔽的每个名称都引入一个 using 声明,强制声明作用域

    class Base {
    private:
        int x;
    public:
        virtual void mf1() = 0;
        virtual void mf1(int);
        virtual void mf2();
        virtual void mf3();
        virtual void mf3(double);
    };
    
    class Derived : public Base{
    public:
        using Base::mf1;
        using Base::mf2;
        virtual void mf1();
        void mf3();
        void mf4();
    };

    Derived d;
    int x;
    d.mf1();    //没问题,调用 Derived::mf1
    d.mf1(x);     //没问题,调用 Base::mf1
    d.mf2();    //没问题,调用 Base::mf2
    d.mf3();    //没问题,调用 Derived::mf3
    d.mf3(x);    //没问题,调用 Base::mf3

    为了让被 base class 遮蔽的名称重见天日,可使用 using 声明式

    条款35:区分接口继承和实现继承
    class Shape {
    public:
        virtual void draw() const = 0;
        virtual void error(const std::string& msg);
        int objectID() const;
    };
    class Rectangle : public Shape {...};
    class Elllipse  : public Shape {...};

    成员函数的接口总是被继承,因为 public 继承意味着 is-a,所以对base class 为真的任何事情一定也对其 derived class 为真

    1.pure virtual member function:
    pure virtual 函数有两个最突出的特性:
    a.必须被任何"继承了它们"class 重新声明
    b.它们在 抽象基类里面通常没有定义

    声明一个 pure virtual 函数是为了让 derived class 只继承 函数接口

    Shape* ps = new Shape;        //错误!抽象基类不能实例化
    Shape* ps1 = new Rectangle;    //基类指针指向派生类对象
    ps1->draw();                //多态:调用 Rectangle::draw
    
    Shape* ps2 = new Elllipse;    //基类指针指向派生类对象
    ps2->draw();                //多态:调用 Elllipse::draw
    
    ps1->Shape::draw();            //调用 Shape::draw
    ps2->Shape::draw();            //调用 Shape::draw
    2.简朴的 non-pure virtual member function:
    声明简朴的 non-pure virtual 函数是为了让 derived class 继承 函数接口 和 函数缺省实现

    a.函数为 virtual 函数,表明 继承base class 的函数接口
    b.函数为 non-pure 函数,表明 base class 可以为该函数提供一个缺省实现,并且让 derived class 继承该函数缺省实现
    Shape::error 声明式(non-pure virtual function)是告诉 derived class 的设计者"你必须提供一个 error 函数,但如果你不想自己写一个,可以使用

    Shape class 提供的缺省版本"
    基类中的所有 non-pure virtual 函数都必须提供一个 缺省实现(可以为空函数体),否则编译错误

    3. non-virtual member function:
    声明 non-virtual 函数是为了让 derived class 继承 函数接口 和 函数强制性实现(注意区别于 函数缺省实现)

    实际上 non-virtual 函数表现出 不变性(所有 derived class 都不能重定义该函数,并且都共享 base class 的同一份强制性函数实现)

    总结:
    设计一个 class 的API函数原型时,必须谨慎选择:
    (1)pure virtual member function 继承base class函数接口(derived class 必须重定义该函数)
    (2)non-pure virtual member function 继承base class函数接口和函数缺省实现(derived class 没有重定义该函数时,会执行继承而来的缺省实现)
    (3)non-virtual member function 继承base class函数接口和函数强制性实现(derived class 不能重定义该函数,都共享继承而来的强制性实现)
    条款36:考虑 virtual 函数以外的其他选择

    设计某个方法时,可以考虑 virtual 函数以外的选择方案,这里主要采用 std::function 结合 std::bind 替换 virtual 函数

    条款37:绝不重定义继承而来的 non-virtual 函数
    条款38:绝不重定义继承而来的 缺省参数值
    class Base {
    public:
        virtual void func() const = 0;
    };
    
    class Derived : public Base {
    public:
        virtual void func() const;
    };
    Base* pBase = new Base;
    Base* pDerived = new Derived;

    (1)对象的静态类型:对象在程序中被声明时所采用的类型
    pBase声明为 Base*,所以 pBase 静态类型为 Base*(不论真正指向的对象类型)
    pDerived声明为 Base*,所以 pDerived 静态类型为 Base*(不论真正指向的对象类型)

    (2)对象的动态类型:目前真正所指对象的类型
    pBase真正指向的对象类型为 Base*,所以 pBase 动态类型为 Base*
    pDerived真正指向的对象类型为 Derived*,所以 pDerived 动态类型为 Derived*

    virtual 函数系动态绑定而来,最终调用哪一份函数实现代码,取决于发出调用的那个对象的动态类型

    virtual 函数是动态绑定, 缺省参数值是静态绑定,重定义缺省参数值会导致致命的语义错误.

    条款39:尽量慎用 private 继承(采用组合)

    Base class 里的所有成员通过 private 继承之后, 在 Derived class 中都会变成 private 属性

    条款40:禁用 多重继承 和 virtual 继承

    采用组合的替代方案, 多重继承和 virtual 继承会导致语义歧义和非常大的复杂度

    注:Google c++ 编程规范:所有继承都必须是 public,否则采用组合

  • 相关阅读:
    IOS之Block的应用-textFeild的回调应用
    KVC与KVO的不同
    git
    perl读取excel
    Linux用户管理
    Linux软件包的管理
    linux系统学习(二)
    linux系统学习(一)
    js模版渲染
    Discuz核心函数的解析
  • 原文地址:https://www.cnblogs.com/wwwjieo0/p/3443728.html
Copyright © 2020-2023  润新知