面向对象的程序设计
  • 面向对象的程序设计基于三个基本概念:数据抽象,继承和动态绑定.
  • 关于继承和动态绑定有需要注意的几个方面:
    • 1.可以更容易的定义和其他类形似但是不完全相同的新类;
    • 2.可以使用这些彼此相似的类编写程序;
  • OOP表示Object-oriented programming的核心思想包括数据抽象,继承,动态绑定.数据抽象:用于实现类的借口和实现分离;继承:可以定义相似的类型并对其相似关系建模;动态绑定:可以在一定程度上面忽略相似类型的区别,使用统一的方式使用他们的对象;

    • 继承

      • 继承使类之间存在层次关系,层次关系的根部称为基类,其余的称为派生类,其他类直接或者简介从派生类继承而来,通常基类用来定义各个类所公有的成员
        派生类用来定义各自独有的成员.
      • C++语言中,基类将类型相关的函数与派生类不作任何改变直接继承的函数区分对待,如果基类希望派生类定义各自适用的版本,基类会将这些函数声明
        为虚函数;

        class Quote{public:string isbn() const;virtual double net_price(size_t n) const;
        };
        //虚函数表示的含义是这个函数的实现需要交给派生类去实现;
      • 派生类需要通过派生类列表来指出它是从那个或者那些类继承而来的,每个基类前面需要有访问说明符号;

        class Bulk_quote :public Quote { //前面表示的就是派生类列表;public:double net_price(size_t)const override;};
      • C++11新标准允许派生类显示的注明它将使用那个成员函数改写基类的虚函数,在该函数的形参列表后面加上一个override;

    • 动态绑定:在C++语言中,我们使用基类的引用(或者指针)调用一个虚函数时将发生动态绑定;
    • 注意这几点:
      • 派生类在继承基类时,适用public公有继承的方式,就可以吧派生类的对象当做基类的对象来使用;
      • 动态绑定的函数必须是虚函数,在基类里面必须声明成虚函数,然后再派生类里面重新进行声明;
      • 然后在派生类的声明过程中,对象必须是基类的引用或者是指针的方式;
    • 动态绑定又称为运行时绑定;
    • 定义基类和派生类
      • 类和类在定义有继承关系时的一些特性;
      • 在定义基类时,通常都应该定义一个虚析构函数,即使这个类不执行任何实际操作也需要进行定义;通俗点来说,作为继承关系中的根节点的类通常都需要定义一个虚析构函数.
      • 派生类可以继承基类的成员,但是当基类定义的是虚函数时,派生类必须先提供这个函数新的定义,也就是执行覆盖操作;
      • C++里面基类需要定义的成员函数包括:
        • 1.基类希望派生类进行覆盖的函数,通常这类函数需要定义为虚函数,然后需要使用引用或者指针来调用这个虚函数,就会发生动态时绑定;
        • 2.基类希望派生类直接进行使用而不需要进行改变的函数;
      • 任何构造函数之外的非静态函数都可以是虚函数.使用关键字virtual进行虚函数的声明时,只能够出现在类内部的声明语句之前而不能够用于类外部的
        函数定义;
      • 如果在基类里面将一个函数声明为虚函数,那么在派生类里面隐式的也是虚函数.
      • 成员函数在没有被声明为虚函数时,函数的解析过程发生在编译时而不是运行时;
      • 派生类可以继承定义在基类中的成员,但是派生累的成员函数不一定有全访问从基类继承而来的成员;
      • 派生类可以访问公有成员,不能够访问私有成员;
      • 如果派生类希望它的派生类有权访问该成员,但是禁止其他用户访问,可以使用protected访问运算符说明这类的成员;
      • 派生类必须通过派生类列表明确指出它是从那个或者是那些基类继承而来的;
      • 派生类必须将其继承而来的成员函数中需要覆盖的重新进行说明;
      • 访问限定符的作用是用来控制派生类从基类继承而来的成员是否对派生类的用户可见;
      • 如果派生类是公有的,那么基类的公有成员也是派生类借口的组成部分,也可以将公有派生类的对象绑定到基类的引用或者指针上面.
      • 派生类经常但并不是总是覆盖它继承的虚函数,如果派生类没有覆盖其基类中的某个虚函数,这个函数的行为就是类似普通成员的,派生类会直接继承其在基类中的版本;
      • 派生类可以在它要覆盖的函数前使用vitrual关键字,但并不是必须的.
      • C++11新标准允许在派生类显示的注明它使用某个成员函数覆盖了他继承的虚函数,具体的作法是在形参列表后面,或者在const成员函数的const关键字后面或者在引用成员函数的引用限定符后面添加一个关键字override;
      • 派生类对象以及派生类向基类的类型转换
        • 派生类对象包括:派生类自己定义的非静态成员的子对象,以及与该派生类继承的基类对应的子对象,如果有多个基类,那么子对象也有多个,这里说明的都是数据成员;
        • 在一个对象里面,继承基类的部分和派生类自己定义的部分不一定是连续存储的.
        • 派生类对象里面含有与其基类对应的组成部分,所以派生类对象可以当做基类对象来使用,也可以将基类的指针或者饮用绑定到派生类对象中的基类部分上;

          Quote item; //基类对象
          Bulk_quote bulk; //派生类对象;
          Quote *p = &item; //p指向`Quote`对象;
          p = &bulk; //p指向bulk的Quote部分;
          Quote &r = bulk; //r绑定到bulk的Quote部分;
        • 上述转换通常是派生类到基类的类型转换,编译器会隐式的执行这种转换;
        • 这种转换的特性是我们可以把派生类的转换或者派生类对象的转换的引用用在需要基类引用的地方;
        • 这种转换同样可以把派生类对象的指针用在需要基类指针的地方;
        • 在派生类对象中含有与其基类对应的组成部分,这一事实是继承的关键所在.
      • 派生类构造函数:派生类构造函数是不能够直接初始化从基类继承的成员的,派生类必须使用基类的构造函数来初始化它的基类部分;
      • 每个类控制它自己成员的初始化过程;
      • 派生类对象的基类部分与派生类对象自己的数据成员都是在构造函数的初始化阶段执行初始化操作的;
      • 派生类构造函数同样是通过构造函数的初始化列表来将实参传递给基类构造函数的;
      • 派生类对象的基类部分会像数据成员一样执行默认初始化.
      • 如果想使用其他的基类构造函数,我们需要以类名加()内的实参列表的形式为构造函数提供初始值.
      • 首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员;
      • 派生类可以访问基类的公有成员和受保护成员;
      • 每个类负责定义各自的接口.要想与类的对象进行交互必须使用该类的接口;
      • 派生类对象不能够直接初始化基类的成员;在语法上可以在派生类构造函数体内给他的公有或者受保护的基类成员赋值,但是最好不要这样做;
      • 派生类应该遵从基类的接口,并且通过调用基类的构造函数来初始化那些从基类继承而来的成员;
      • 如果基类定义了一个静态成员,则在整个继承体系中之存在该成员的唯一定义.不论从基类中派生出多少个派生类,对于每个静态成员来说都只存在唯一的实例;
      • 派生类的声明与其它类的差别并不大,声明中包含类名,但是并不包含它的派生类列表;
      • 一条声明语句的目的是令程序知晓某个名字的存在以及改名字表示一个怎样的实体,如一个类,一个函数,或者一个变量.
      • 派生类列表以及与定义相关到的所有其他细节都必须与类的主体一起出现;
      • 如果一个类要用作基类,那么这个类必须被定义;这个规定表示的含义是:派生类包含并且使用它从基类继承而来的成员,为了使用这些成员,派生类当然需要知道基类里面包含什么;
      • 一个类是不能够派生它本身的;
      • 一个类可以是基类,同时也可以是一个派生类;
      • 直接基类出现在派生类列表中,而间接基类由派生类通过其直接基类继承而来;
      • 每个类都会直接继承基类的所有成员.对于一个最终的派生类来说,将包含它的直接基类的子对象以及每个简介基类的子对象;
      • C++11可以通过在类名后面添加关键字final来组织任何派生类将这个类作为基类;
      • 通常情况下,如果我们想把引用或者指针绑定到一个对象上面,那么引用或者指针的类型应该于对象的类型一致,或者对象的类型含有一个可接受的
        const类型转换规则;
      • 存在继承关系的类允许我们将基类的指针或者引用绑定到派生类的对象上面;
      • 如果将基类的指针或者引用绑定到派生类对象上面,就无法区别该对象是基类的对象还是派生类的对象.
      • 智能指针支持派生类向基类的类型转换,这意味着我们可以将一个派生类对象的指针存储在一个基类的智能指针内部;
      • 在使用继承关系的类型时,必须将一个变量或者其他表达式的静态类型与该表达式表示对象的动态类型去分开.
      • 表达式的静态类型在编译时是已知的,是变量声明时的类型或表达式生成的类型;动态类型表示的是变量或表达式表示的内存中的对象的类型,动态类型在
        运行时可知;
      • 如果表达式既不是引用也不是指针,那么动态类型永远和静态类型是一致的;
      • 不存在从基类到派生类的隐式转换规则,因为一个基类的对象可能是派生类对象的一部分,也可能不是,所以不存在从基类向派生类的自动类型转换;
      • 派生类向基类的自动转换只能对指针或者引用类型有效,在派生类类型和基类类型之间不存在这样的转换.假设发生转换,实际的过程应该是这样的:
        • 1.因为这些成员接受引用作为参数,派生类向基类的转换允许我们给基类的拷贝/移动操作传递一个派生类的对象;当给基类的构造函数传递一个派生
          类对象时,实际运行的构造函数是基类中定义的那个,这个构造函数只能够处理基类自己的成员;
        • 2.同样的如果我们将一个派生类对象赋值给一个基类对象,实际运行的是基类中的赋值运算符,同样的只能够处理基类自己的成员,这个过程中就会发生切掉;
      • 当我们用一个派生类对象为一个基类对象初始化赋值时,只有该派生类对象中的基类部分会被拷贝,移动或赋值,它的派生类部分就会被切掉;
      • 存在继承关系之间的转换规则:
        • 1.从派生类向基类的类型转换只对指针或引用类型有效果;
        • 2.基类向派生类不存在隐式类型转换;
        • 3.和其他成员一样,派生类向基类的类型转换也可能会由于访问权限受限而变得不可行;
        • 4.继承体系中的大多数类仍然显式或者隐式的定义了拷贝控制成员,因此我们可以将派生类对象,拷贝,移动或赋值给一个基类对象,但是这个操作只能够处理派生类对象的基类部分;
    • 虚函数
      • 必须为每个虚函数提供定义,因为在动态绑定时,无法确定会使用那个虚函数;
      • 对虚函数的调用可能在运行时才会被解析,被调用的函数是与绑定到指针或者引用上的对象的动态类型相匹配的那个;
      • 动态绑定只有当我们通过指针或者引用调用虚函数时才会发生.
      • C++的多态性:多种形式,吧据有继承关系的多个类型成为多态类型;
      • 引用或指针的静态类型与动态类型不同这一事实是C++语言支持多态性的根本所在.
      • 当且仅当通过指针或者引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不一致;
      • 如果在基类中将一个函数声明为虚函数,那么这个函数在所有派生类里面都是虚函数,在派生类里面,可以使用virtual关键字进行说明,但这不是必须的;
      • 一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被他覆盖的基类函数完全一致,同样的虚函数的返回值类型也必须和基类函数类型一致,但是当虚函数返回的是类本身的指针或者引用时无效;
      • 基类中的虚函数在派生类中隐含地也是一个虚函数.当派生类覆盖了某个虚函数时,该函数在积累中的形参必须与派生类中的形参严格匹配;
    • finaloverride说明符:

      • override表示的含义是用来显式的说明这个函数是用来覆盖基类的虚函数的,如果因为派生类的形参列表,返回值类型不匹配等原因无法进行覆盖,使用了override说明符,编译器使可以检查出这个错误的;
      • 如果在不是虚函数的后面添加了override会导致编译器报错;
      • final如果将某个函数设置为final那么之后所有对这个函数的覆盖操作就会引发错误;
      • 上面这两个说明符都应该出现在形参列表(包括const或者引用修饰符)以及尾置返回类型之后;
      • 虚函数是可以使用默认实参的,如果某次函数调用使用了实参,那么该实参值由本次调用的静态类型决定;
      • 如果是通过基类的指针或引用调用函数,则使用基类中定义的默认是餐,即使实际运行的是派生类中的函数版本也是如此.传入派生类函数的将是基类函数
        定义的默认实参,为了避免这种调用不同实参的情况出现:如果虚函数需要使用默认实参,则基类和派生类中定义的默认实参最好是一致;
      • 某些情况下,对虚函数的调用不希望进行动态绑定,使用作用域限定符来强制使用某个虚函数的版本;
        double undiscounted = base->Quote::net_price(42);
      • 通常情况下,值由成员函数(或者友元)中的代码才需要使用作用域限定符来避免虚函数的机制;
      • 什么时候需要回避虚函数机制:
        • 一般情况下是派生类的虚函数调用覆盖的基类的虚函数版本时;
        • 如果一个派生类虚函数需要调用其基类的版本,但是没有使用作用于运算符,则在运行时该调用将被解析为派生类版本自己的调用,会导致无限递归;
      • 纯虚函数的定义:用于表示某个虚函数是没有意义,也是不需要进行定义的的,声明的方法是在函数后面添加=0;
      • 只有虚函数才能被说明为虚函数,并且=0只能够出现在类里面的虚函数声明语句处;
      • 是可以为纯虚函数提供定义的,但是不能够在类的内部进行定义,函数体定义必须在类的外部;
      • 含有未经覆盖或者直接继承的纯虚函数的类是抽象基类.抽象基类负责定义接口,而后续的其他类可以覆盖这个接口.我们不能够直接创建一个抽象基类的
        对象;
      • 存在继承关系的类的初始化过程:

        class Quote{};
        class Disc_quote:public Quote{};
        class Bulk_quote:public Disc_quote{public:Bulk_quote() = default;Bulk_quote(const string& book,double price,size_t qty,double disc):Disc_quote(book,price,qty,disc){}
        };
      • 1.每个子类需要各自控制其对象的初始化过程;

      • 2.Bulk_quote没有自己的数据成员,但是仍然需要提供接受四个参数的构造函数.这个构造函数将实参传递给Disc_quote(直接基类),
        然后Disc_quote的构造函数继续调用Quote的构造函数;
      • 3.Quote的构造函数首先初始化bulkbookNo以及price成员;
      • 4.执行Disc_quote的构造函数并初始化quantitydiscount成员;
      • 5.最后执行Bulk_quote构造函数,这个函数无法执行实际的初始化,因为这个类并没有自己的特殊成员;
      • 重构:负责重新设计类的体系结构以便于将操作或数据从一个类里面移动到另一个类里面;
      • 访问控制和继承
        • 受保护的成员:

          • 1.protected关键字是一个类用来说明那些它希望派生类分享但是不希望其它公有访问使用的成员;
          • 2.和私有成员一样,受保护的成员对于类的用户来说是不可访问的;
          • 3.和公有成员一样,受保护的成员对于派生类的成员和友元来说是可访问的.
          • 4.派生类的成员或友元只能够通过派生类对象来访问基类受保护成员.
          • 5.派生类对于一个基类中受保护的成员没有任何访问权限;
          • 6.派生类的成员和友元只能访问派生类对象中的基类部分受保护成员,对于普通基类对象中的成员不具有任何特殊的访问权限.
        • 某个类对于其继承而来的访问权限受两个因素影响:
          • 1.基类中该成员的访问限定符;
          • 2.派生类中的派生类列表中的访问说明符.
        • 派生类访问说明符的目的是控制派生类用户(包括派生类的派生类在内)对于基类成员的访问权限,也就是说基类的成员在派生类里面使用何种方式能
          不能被访问;
        • 无论派生类使用何种方式继承基类,都是无法访问基类的私有成员的;
      • 派生类向基类的转换是否可访问由使用该转换的代码决定,同时派生类的派生访问限定符也会有影响;
        • 三种情况:

          • 1.当D公有继承B时,用户代码才能够使用派生类向基类的转换;
          • 2.无论D使用何种方式继承B,D的成员函数和友元函数都能使用派生类向基类的转换;派生类向其基类的类型转换对于派生类的成员和友元来说永
            远是可访问的;
          • 如果D继承B的方式是公有的或者是受保护的,那么D的派生类的成员和友元可以使用DB的类型转换,反之,如果是私有的,那么就不能够使用;
          • 类的设计与受保护的成员,类的设计一般来说有三种情况:
            • 1.普通用户,也就是类的使用者,普通用户编写的代码使用类的对象,只能够访问类的公有接口部分;
            • 2.类的实现:负责编写类的成员和友元的代码,类的成员或者友元可以访问类的共有部分,也能够访问类的私有部分;
            • 3.派生类:基类可以将希望派生类能够使用的部分声明成受保护的.普通用户不能够访问受保护的成员,而派生类以及友元仍然不能够访问私
              有成员;
            • 在类的设计过程中,基类的接口成员应该是公有的;基类把它希望派生类能够使用的部分声明为受保护的,派生类可以在实现自己功能的同时
              使用基类的操作和数据;另一类只能够由基类和基类的友元进行访问,这应该声明为私有的;
        • 友元和继承:
          • 友元关系不能够进行传递,友元关系同样不能继承;
          • 不能够继承友元关系,每个类负责控制各自成员的访问权限.
        • 当需要改变个别成员的可访问性时,可以使用using声明来达到这些目的;
        • 通过在类的内部使用using声明语句,我们可以将该类的直接或间接基类中的任何可访问成员标记出来;
        • using声明语句中名字的权限由出现在该语句之前的访问说明符来进行决定;
        • 派生类只能够为它那些可以访问的名字提供using声明;
    • struct以及class关键自定义的类具有不同的默认访问权限说明符;默认派生类运算符也由定义派生类所用的关键字来决定;
    • 默认情况下,使用class关键字定义的派生类是私有继承的,使用struct关键字定义的派生类是公有继承的,这是唯一的差别,别无他用;
    • 派生类的作用域是位于基类的作用域里面的,类的作用域是有这种继承嵌套关系的;
    • 派生类也能重用定义在其直接基类或者间接基类中的名字,这时定义在内层作用域的名字将隐藏外层作用域的名字;
    • 派生类的成员将隐藏同名的基类成员,但是可以作用于运算符来使用隐藏的成员;
    • 除了覆盖继承而来的虚函数之外,派生类最好不要重用其他定义在基类里面的名字;
    • 名字查找和继承:
      • 1.首先确定对象的静态类型,因为调用的是一个成员,所以类型必须是类类型;
      • 2.首先在类对应的类类型里面查找调用的对象,如果查找不到,就在直接基类里面进行查找,直到基类的顶端,如果查找不到,编译器报错;
      • 3.如果找到对象,执行常规的检查:
        • 1.如果对象是虚函数且我们是通过引用或者指针进行的调用,那么编译器在运行时确定运行虚函数的那个版本;
        • 2.反之对象不是虚函数或者我们通过非引用或指针进行的调用,编译器产生一个常规的调用.
      • 声明在内层作用域的函数并不会重载声明在外层作用域的函数,派生类中的函数不会重载其基类中的成员;
      • 如果派生类函数和基类函数重名,哪怕参数列表是不一样的,就会发生函数的隐藏,如果想要访问基类函数,需要使用作用域限定符;
      • 以上就是同名隐藏规则;
      • 成员函数无论是否是虚函数都是可以被重载的;
      • 如果派生类希望所有的重载版本对他来说都是可见的,那么就必须覆盖所有的版本,或者一个也不进行覆盖;
      • 如果仅仅需要覆盖重载集合中的一些而不是全部函数,可以使用using声明的方式,一条基类成员函数的using语句就可以把该函数的所有重载实例添加
        到派生类作用域里面,派生类只需要定义其特有的函数;
    • 构造函数与拷贝控制:
      • 1.虚析构函数:继承关系对基类拷贝最直接的影响是基类通常应该定义一个虚析构函数,这样就可以动态分配继承体系中的对象了;
      • 需要注意的几点:
        • 1.当执行delete一个动态分配的对象的指针时,将执行析构函数,如果该指针指向的是继承体系中的某个类型,就可能会出现指针的静态类型与被删
          除的对象的动态指针不相符的情况,析构的过程就会出现歧义,所以应该将基类的析构函数设置为虚析构函数,这个问题救回被自动解决;
        • 2.虚析构函数的属性将会被继承,只需要保证基类的析构函数是虚函数,就可以保证delete基类指针的操作能够运行合适的析构函数版本;
        • 3.如果基类的析构函数不是虚函数,那么delete一个指向派生类对象的基类指针就会产生未定义的行为;
      • 如果一个类需要析构函数,那么它同样也需要拷贝和赋值操作.基类的虚构函数并不遵守上述的准则:
        • 1.一个基类总是需要析构函数,而且析构函数可以设置为虚函数,析构函数成为虚函数而令内容为空,这样无法推断该基类还需要赋值运算符或者拷贝
          构造函数;
        • 2.如果基类定义了虚析构函数,即使一个类通过=default的形式定义了析构函数,编译器也不会为这个类合成移动操作;
    • 合成拷贝控制与继承:
      • 1.基类或者派生类的合成拷贝控制的行为与其他合成的构造函数,赋值运算符或析构函数类似;
      • 2.基类和派生类对类本身的成员依次进行初始化,赋值或者销毁等操作;
      • 3.合成的成员还负责使用直接基类中对应的操作对一个对象的直接基类部分进行初始化,赋值或者销毁操作;
      • 4.对于派生类的析构函数来说,它除了销毁类自己的成员之外,还在负责销毁派生类的直接基类,直到继承链的顶端;
    • 派生类中删除的拷贝控制与基类的关系:基类或者派生类可以将合成的默认构造函数或者任何一个拷贝控制成员定义为删除的函数,此外,以下方式将可能导致
      已有的派生类的成员被定义为删除的函数:

      • 1.如果基类中的默认构造函数,拷贝构造函数,赋值运算符或者析构函数是被定义为删除的函数或者不可访问,派生类中的成员将会是被删除的,因为编译器
        无法通过基类成员来执行派生类对象基类部分的构造,赋值或者销毁操作;
      • 2.如果在基类中有一个不可访问或者删除掉的析构函数,则派生类中合成的默认构造和拷贝构造函数将是被删除的,因为编译器无法销毁派生类对象的基类
        部分;
      • 3.编译器不会合成一个删除掉的移动操作,当使用=default请求一个移动操作时,如果基类中的对应操作是删除的或者是不可访问的,那么派生类中该函
        数将是被删除的,因为派生类对象的积累部分是不可移动的.
      • 4.如果基类的析构函数是删除的或是不可访问的,那么派生类的移动构造函数也会是被删除的.
      • 5.如果在基类中没有默认,拷贝或移动构造函数,一般情况下派生类也不会定义相关的操作.
      • 6.大多数基类中会定义一个虚析构函数,所以在默认情况下,基类通常不含有合成的移动操作,而且在它的派生类中也没有合成的移动操作;但是如果需要移
        动操作,首先应该在基类中进行定义,同时还必须显示的定义拷贝操作;如果派生类中含有排斥移动的操作,否则它将自动获得合成的移动操作;
      • 7.派生类的赋值运算符也必须为基类部分成员进行赋值;
      • 派生类对象的基类部分是被自动销毁的;
      • 当派生类定义了拷贝或者移动操作时,该操作负责拷贝或移动包括基类部分成员在内的整个对象.
    • 定义派生类的拷贝或者移动构造函数

      class Base {    };
      class D:public Base {public://默认情况下,基类的默认构造函数初始化对象的基类部分//要想使用拷贝或移动构造函数,我们必须在构造函数初始值列表中//显示调用该构造函数;D(const D& d):Base(d){}D(D&& d):Base(move(d)){}
      };
    • 在默认情况下,基类默认构造函数初始化派生类对象的基类部分.如果我们想拷贝或移动基类部分,则必须在派生类的构造函数初始值列表中显示使用基类的拷
      贝或移动构造函数;

    • 和拷贝与移动构造函数一样,派生类的赋值运算符也必须显示的为基类部分进行赋值;

      D &D::operator=(const D &rhs){Base::operator=(rhs);return *this;
      }
    • 显示的调用基类赋值运算符,为派生类对象的基类部分赋值,如果正确完成,基类运算符释放掉作则运算对象的基类部分的旧值,然后付给一个新的值;

    • 无论基类的构造函数或赋值运算符是自定义的还是合成的版本,派生类的对应操作都可以使用;
    • 派生类析构函数组织负责销毁由派生类自己分配的资源,对象的销毁顺序和创建顺序相反,派生类析构函数首先执行,然后是基类函数,依次向上进行析构;
    • 在构造一个对象时,需要把对象的类和构造函数的类靠城市同一个;对虚函数的调用绑定也符合这种把对象的类和构造函数的类看成是同一个的要求;对于析构
      函数来说也是一样的道理;上述绑定不但直接对调用析构函数是有效的,间接调用也是有效的;
    • 存在上述约定的可能原因是,如果基类构造函数在调用派生类的虚函数版本时,会访问派生类的成员,但是派生类的成员没有进行初始化,程序就会崩溃;
    • 一个规定:如果构造函数或者析构函数调用了某个虚函数,那么我们应该执行与构造函数或析构函数所属类型相对应的虚函数版本;
    • 继承的构造函数
      • 一个类只初始化它的直接基类,所以一个类也只能继承其直接基类的构造函数;
      • 类是不能继承默认,拷贝和移动构造函数的,如果派生类没有这些函数,编译器可以直接合成;
      • 如果派生类需要继承基类构造函数,需要提供一条基于类名的using声明语句;

        class Bulk_quote : publi Disc_quote{
        public:
        using Disc_quote::Disc_quote;
        double net_price(size_t) const;
        };
      • using语句在构造函数里面的作用是使编译器产生代码,对于基类的每个构造函数,编译器都会生成与之对应的派生类构造函数,如果派生类含有自己的
        成员,那么这些成员会被默认初始化;
      • 继承的构造函数:
        • 1.构造函数的using声明不会改变该构造函数的访问级别,基类的私有构造函数在派生类里面仍然是私有的;
        • 2.如果基类的构造函数具有explicit或者constexpr,继承的构造函数也具有这些属性;
        • 3.当基类的构造函数含有默认的实参时,这些实参并不会被继承;在形参的继承时,假设基类的某个构造函数具有两个形参,并且右边的具有默认实参,
          那么派生类将得到两个构造函数,其中一个接受两个形参,另一个接受一个形参;
        • 4.如果基类包含几个构造函数,大多数情况派生类会继承所有这些构造函数,有两种特殊情况:
          • 1.派生类可以继承一部分构造函数,然后其他的构造函数定义自己的版本,如果派生类定义的构造函数与基类的构造函数具有相同的参数列表,这
            些构造函数不会被继承.定义在派生类中的构造函数将替换继承而来的构造函数;
          • 2.默认,拷贝和移动构造函数不会被继承.这些构造函数按照正常的规则被合成;
          • 3.继承的默认构造函数不会被作为用户定义的构造函数来使用,如果一个类只含有继承的构造函数,机会拥有一个合成的默认构造函数;
    • 容器和继承
      • 当派生类对象被赋值给基类对象时,其中的派生类部分救回被”切掉”,因此容器和存在继承关系的类型无法兼容,所以将对象昂放在容器中是不理想的;
      • 当希望在容器里面存放具有继承关系的对象时,实际上应该存在的是基类的智能指针,指针可以是动态类型,基类类型,或者是派生类类型;

        vector<shared_ptr<Quote>> basket;
        basket.psuh_back(make_shared<Quote>("0-201-82470-1",50));
        basket.push_back(
        make_shared<Bulk_quote>("0-201-54848-8",50,10,.25)
        );
        cout << basket.back()->net_price(15) << endl;
      • 派生类的指针可以转换成基类的指针,所以也可以将派生类的智能指针可以转换成为基类的智能指针;
      • 对于C++ 面向对象的编程来说,一个悖论是无法直接使用对象进行面向对象的编程,必须使用指针和引用;
      • 继承和组合:当一个类公有地继承另一个类时,派生类应当反应与基类是一种(Is A)关系,在良好的类的设计体系中,公有派生类的对象应该可以用在任
        何需要基类对象的地方;
      • 类型之间的另一种关系是有一个has A的关系表示暗含成员;

15-面向对象的程序设计--no相关推荐

  1. [Python3]Python面向对象的程序设计

    [Python3]Python面向对象的程序设计 一.面向对象的程序设计的由来 1.第一阶段:面向机器,1940年以前 最早的程序设计都是采用机器语言来编写的,直接使用二进制码来表示机器能够识别和执行 ...

  2. 面向对象的程序设计-电梯调度系统的设计、优化与测试

    面向对象的程序设计(2019)第二单元总结 I  对问题的初体验 在开始OO之旅前,对OO电梯早有耳闻.这一次终于轮到我自己实现OO电梯了.首先从顶层需求出发对电梯系统进行分析,对象包括电梯.任务和乘 ...

  3. Python学习之路9☞面向对象的程序设计

    Python学习之路9☞面向对象的程序设计 一 面向对象的程序设计的由来 见概述:http://www.cnblogs.com/linhaifeng/articles/6428835.html 二 什 ...

  4. python基础----面向对象的程序设计(五个阶段、对小白的忠告、关于OOP常用术语)、类、对象...

    一.面向对象的软件开发有如下几个阶段                                              1.面向对象分析(object oriented analysis ,O ...

  5. 【Python3之面向对象的程序设计】

    一.面向对象的程序设计的由来 1.第一阶段:面向机器,1940年以前 最早的程序设计都是采用机器语言来编写的,直接使用二进制码来表示机器能够识别和执行的指令和数据. 简单来说,就是直接编写 0 和 1 ...

  6. 十一丶面向对象的程序设计

    阅读目录 一 面向对象的程序设计的由来 二 什么是面向对象的程序设计及为什么要有它 三 类与对象 四 属性查找 五 绑定到对象的方法的特殊之处 六 对象之间的交互 七 练习 八 继承与派生 九 多态与 ...

  7. java面向对象模拟电梯_面向对象的程序设计-电梯调度系统的设计、优化与测试...

    面向对象的程序设计(2019)第二单元总结 I  对问题的初体验 在开始OO之旅前,对OO电梯早有耳闻.这一次终于轮到我自己实现OO电梯了.首先从顶层需求出发对电梯系统进行分析,对象包括电梯.任务和乘 ...

  8. python程序开发的各个阶段_python基础----面向对象的程序设计(五个阶段、对小白的忠告、关于OOP常用术语)、类、对象...

    抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型.抽象不仅包括这种模型的数据属性,还定义了这些数据的接口. 对某种抽象的实现就是对此数据 ...

  9. C++面向对象的程序设计谭浩强 第六章课后题

    以往章节 C++面向对象的程序设计谭浩强 第二章课后题 C++面向对象的程序设计谭浩强 第三章课后题 C++面向对象的程序设计谭浩强 第四章课后题 C++面向对象的程序设计谭浩强 第五章课后题 C++ ...

  10. 笔记整理3----Java语言高级(三)11 综合练习+12 面向对象-static变量 与 代码块+13 面向对象-继承与抽象类+14 面向对象-接口与多态+15 面向对象-包修饰符

    11 综合练习+12 面向对象-static变量 与 代码块+13 面向对象-继承与抽象类+14 面向对象-接口与多态+15 面向对象-包&修饰符 第11天 综合练习 今日内容介绍 综合练习 ...

最新文章

  1. Leetcode 62. Unique Paths
  2. CSS3中背景的四个新的属性
  3. MTK:oemlock介绍
  4. 类的成员函数与内联以及静态成员
  5. Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架(前言)
  6. wordpress页面里可不可以写php,如何在WordPress页面中创建不同的可编辑部分?
  7. 页面三个txt加载联动省市县的代码,类似淘宝的收货地址的布局
  8. LanguageTool性能简单测试分析
  9. 每日一句20200103
  10. TLS1.3---密钥的计算
  11. (已更新)漫画小程序更新修复接口,自动采集资源,漫画源码漫画小程序源码简单即可发布
  12. MS DTC服务无法启动解决方法
  13. 《遥感原理与应用》孙家抦版知识点总结(含简答题)——第四章
  14. 江西银行服务器怎么选择硬件配置
  15. 自然辩证法对计算机科学技术的应用,自然辩证法与计算机科学技术
  16. ks 曲线_Ks密度曲线分布图绘图
  17. 彻底理解confidence interval和credible interval
  18. HiveQL整理总结
  19. MySQL数据库(1)~~一起学习数据库冲冲冲
  20. 胡子决定编程语言运势

热门文章

  1. 【观察】OceanBase 4.0,单机与分布式的新拐点
  2. 最新软件外包网站有哪些?
  3. Halcon矩阵(Matrix)算子详解
  4. Eclipse rap 开发经验总结
  5. 小企业仓库管理软件设计开发
  6. 切图具体需要切什么内容_UI日常-切图切图怎么破?
  7. 传统实业巨头怎么搞产业互联网
  8. linux 下搭建portal服务器搭建,Linux环境下IBM WebSphere Portal v8.5独立服务器安装记录...
  9. h5微信页面在手机微信端和微信web开发者工具中都能正常显示,但是在pc端微信浏览器上打不开(显示空白)
  10. 我们且请仁兄到敝庄 水浒