• ### 学习《C++ Primer》- 8


    Part 8: 面向对象(第15章)

    // @author:       gr
    // @date:         2015-01-09
    // @email:        forgerui@gmail.com
    

    一、OOP

    面向对象程序设计的核心思想是数据抽象、继承和动态绑定。
    数据抽象:可以将类的接口与实现分离
    继承:可以定义相似的类型并对其相似关系建模
    动态绑定:在一定程度上忽略相似类型的区别,使用统一的方式使用它们的对象

    二、定义基类或派生类

    1. 基类通常应该定义一个虚析构函数,即使该函数不执行任何实际操作。

    2. virtualoverride关键字只允许在类内使用,类外使用会报错。

    3. 派生类中,C++11标准允许使用override关键字显式地注明它使用某个成员函数覆盖了它继承的虚函数。

    4. C++11中可以使用final关键字防止继承的发生。

       class Last final {
           private: 
               int a;
       };
      
    5. 在覆盖基类的函数时,大意将参数列表写错,没有构成覆盖,将会是两个独立的函数。如果使用override关键字,编译器会报marked override, but does not override的错误,提示有当前的问题。使用override需要保持基类和派生类的函数形式一致,并且基类的函数要声明为virtual

    6. 派生类可以转换到基类,但不存在基类像派生类的转换。从派生类向基类的转换只对指针或引用类型有效。

    三、虚函数

    1. 回避虚函数的机制
      强迫执行虚函数的某个特定版本,使用作用域运算符可以实现这一目的:

       double undiscounted = baseP->Quote::net_price(42);  //使用基类Quote中的net_price函数
      
    2. 多态性
      使用虚函数,通过动态绑定实现,在运行时进行解析。

    四、抽象基类

    抽象类不能创建对象,只能作为接口,其它类继承它。
    含有纯虚函数的类是抽象类。

    五、访问控制与继承

    1. 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中的受保护成员没有任何访问特权。

       class Base{
           protected:
               int prot_mem;
       };
       class Sneaky : public Base{
           friend void clobber(Sneaky&);
           friend void clobber(Base&);
           int j;
       };
      
       //正确:clobber能访问Sneaky对象的private和protected成员
       void clobber(Sneaky& s){s.j = s.prot_mem = 0;}
       //错误:clobber不能访问Base的protected成员
       void clobber(Base& b){b.prot_mem = 0;}
      

      上面的第二个clobber函数因为不是Base的友元,所以无法访问Base的protected成员。

    2. 派生访问说明符的目的是控制派生类用户(包括派生类的用户、派生类的派生类)对于基类成员的访问权限。

       class Base{
           public:
               void pub_mem();
           protected:
               int prot_mem;
           privated:
               int priv_mem;
       };
       class Pub_Derv : public Base{
           int f() {return prot_mem;}
           char g() {return priv_mem;}
       };
       class Priv_Derv : private Base{
           int f1() const {return prot_mem;}  
       };
       Pub_Derv d1;
       Priv_Derv d2;
       d1.pub_mem();       //正确:pub_mem在派生类中是public的
       d2.pub_mem();       //错误:pub_mem在派生类中是private的
      

      这样,private派生访问说明符表明继承的成员是private,用户无法访问这个成员。使用public继承便可以访问。
      class默认使用的是private继承,struct默认使用的public继承。

    3. 友元与继承
      友元关系不能继承。基类的友元在访问派生类成员时不具有特殊性,同样,派生类的友元也不能随意访问基类的成员。

       class Base{
           friend class Pal;           //Pal在访问Base的派生类不具有特殊性
       };
       class Pal{
           int f(Base b){return b.prot_mem;}
       };
      

    六、继承中的类作用域

    在编译时进行名字查找
    作用域从派生类到基类查找成员。
    如果两个成员名字相同,派生类的名字将隐藏基类的名字,但基类中的变量仍然存在。使用作用域运算符可以使用隐藏的名字。

    名字查找优先于类型检查

    struct Base{
        int memfcn();
    };
    struct Derived : Base{
        int memfcn(int);
    }
    Base b;
    Derived d;
    b.memfcn();         //正确,调用Base::memfcn()
    d.memfcn(42);       //正确,调用Derived::memfcn(int)
    d.memfcn();         //错误,调用Derived::memfcn(int),参数类型不一致
    d.Base::memfcn();   //正确,调用Base:memfcn()
    

    从上面可以看出,查找到相同的名字就停止查找,所以基类的函数被隐藏了。之后再进行参数匹配,发现匹配不对,将会报错。

    覆盖重载的函数
    派生类可以覆盖重载函数的0个或多个实例。如果派生类希望所有的重载版本对于它来说都是可见的,那么它就要覆盖所有的版本,或者一个都不覆盖
    有时,只需覆盖一些而非全部函数时,不都不覆盖每个基类中的每个版本。
    解决方案是为重载的成员提供一条using声明语句,使用using可以将子类的所有重载函数放入到当前作用域,这时在添加需要覆盖的函数。

    class Base{
        public:
            //定义三个版本的fcn函数
            virtual void fcn();
            virtual void fcn(int);
            virtual void fcn(double);
    };
    class Derived : private Base{
        public:
            //使用using引入Base中的fcn重载函数
            using Base::fcn;
            //只覆盖int版本
            virtual void fcn(int a);            
    };
    

    七、构造函数与拷贝控制

    1. 基类虚析构函数
    2. 合成拷贝控制与继承
      如果基类的函数是不可访问或者删除的,则派生类的函数也将是被删除的。

    八、容器与继承

    1. 容器中存放基类型,将派生对象转换为基对象,派生部分将被“切掉”。

    2. 解决上面的问题,可以在容器中存放(智能)指针而非对象

       vector<shared_ptr<Quote>> basket;
       basket.push_back(make_shared<Quote>("1233", 50));
       basket.push_back(make_shared<Bulk_Quote>("12", 50, 10, .25));
       cout << basket.back()->net_price() << endl;
  • 相关阅读:
    python排序函数sort()与sorted()区别
    python中lambda的用法
    Python中如何获取类属性的列表
    百度编辑器UEditor源码模式下过滤div/style等html标签
    【Flask】关于Flask的request属性
    python json.dumps() json.dump()的区别
    SQLAlchemy技术文档(中文版)(全)
    Flask中'endpoint'(端点)的理解
    SqlAlchemy个人学习笔记完整汇总-转载
    MySQL数据类型和常用字段属性总结
  • 原文地址:https://www.cnblogs.com/gr-nick/p/4238642.html
Copyright © 2020-2023  润新知