C++笔记:面向对象编程(Visual)(一)

2014-11-24 11:23:59 · 作者: · 浏览: 0

面向对象编程(Visual)

  • C++中,基类必须指出希望派生类重写哪些函数,定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。
  • C++中,通过基类的引用(或指针)调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。
  • 保留字virtual的目的是启用动态绑定。成员默认为非虚函数,对非虚函数的调用在编译时确定。
  • 为了指明函数为虚函数,在其返回类型前面加上保留字virtual。除了构造函数之外,任意非static成员函数都可以是虚函数
  • 基类通常应将派生类需要重定义的任意函数定义为虚函数。
  • 尽管不是必须这样做,派生类一般会重定义所继承的虚函数。派生类没有重定义某个虚函数,则使用基类中定义
  • 派生类中虚函数的声明必须与基类中的定义方式完全匹配, 但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。
  • 一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实。派生类重定义虚函数时,可以使用virtual保留字,但不是必须这样做。


    virtual与其他成员函数

    • C++中的函数调用默认不使用动态绑定,触发动态绑定条件:
      • 只有指定为虚函数的成员函数才能进行动态绑定
      • 必须通过基类类型的引用或指针进行函数调用
      • 因为每个派生类对象都包含基类部分,所以可将基类类型的引用绑定到派生类对象的基类部分,也可以用指向基类的指针指向派生类对象。
      • 使用基类类型的引用或指针时,不知道指针或引用所绑定的对象的类型,编译器都将它当作基类类型对象
      • 基类类型引用和指针的关键点在于静态类型(在编译时可知的引用类型或指针类型)和动态类型(指针或引用所绑定的对象的类型这是仅在运行时可知的)可能不同。
      • 非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定
      • 覆盖虚函数机制
        • 使用作用域操作符强制函数调用使用虚函数的特定版本[Code1]
        • 在派生类中虚函数调用基类版本时,必须显式使用作用域操作符,如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归
        • 虚函数与默认实参
          • 像其他任何函数一样,虚函数也可以有默认实参,如果有用在给定调用中的默认实参值,该值将在编译时确定
          • 如果一个调用省略了具有默认值的实参,则所用的值由调用该函数的类型定义,与对象的动态类型无关
          • 通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值
          • 在同一虚函数的基类版本和派生类版本中使用不同的默认实参几乎一定会引起麻烦。

            为什么会希望覆盖虚函数机制
            最常见的理由是为了派生类虚函数调用基类中的版本。在这种情况下,基类版本可以完成继承层次中所有类型的公共任务,而每个派生类型只添加自己的特殊工作。
            例如,可以定义一个具有虚操作的Camera类层次。Camera类中的display函数可以显示所有的公共信息,派生类(如PerspectiveCamera)可能既需要显示公共信息又需要显示自己的独特信息。可以显式调用Camera版本以显示公共信息,而不是在PerspectiveCamera的display实现中复制Camera的操作。 在这种情况下,已经确切知道调用哪个实例,因此,不需要通过虚函数机制。


            虚析构函数

            • 处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。
            • 如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。要保证运行适当的析构函数,基类中的析构函数必须为虚函数。[Code2]
            • 如果层次中根类的析构函数为虚函数,则派生类析构函数也将是虚函数,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。
            • 即使析构函数没有工作要做,继承层次的根类最好也应该定义一个虚析构函数。
            • 在复制控制成员中,只有析构函数应定义为虚函数,构造函数不能定义为虚函数。构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。
            • 将类的赋值操作符设为虚函数很可能会令人混淆,而且不会有什么用处。
              • 虽然可以在基类中将成员函数 operator= 定义为虚函数,但这样做并不影响派生类中使用的赋值操作符。
              • 每个类有自己的赋值操作符,派生类中的赋值操作符有一个与类本身类型相同的形参,该类型必须不同于继承层次中任意其他类的赋值操作符的形参类型。
              • 如果该操作符为虚函数,则每个类都将得到一个虚函数成员,该成员定义了参数为一个基类对象的 operator= 。但是,对派生类而言,这个操作符与赋值操作符是不同的。


                构造函数和析构函数中的虚函数

                • 尽量不要在构造函数和析构函数中调用虚函数,因为构造或析构期间的对象类型对虚函数的绑定有影响。
                  • 基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。
                    • 构造派生类对象时首先运行基类构造函数初始化对象的基类部分。在执行基类构造函数时,对象的派生类部分是未初始化的。实际上,此时对象还不是一个派生类对象。
                    • 撤销派生类对象时,首先撤销它的派生类部分,然后按照与构造顺序的逆序撤销它的基类部分。 在这两种情况下,运行构造函数或析构函数的时候,对象都是不完整的。
                    • 为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。
                    • 如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本


                      虚函数与作用域

                      • 要获得动态绑定,必须通过基类的引用或指针调用虚成员。当我们这样做时,编译器器将在基类中查找函数。假定找到了名字,编译器就检查实参是否与形参匹配。
                      • 虚函数必须在基类和派生类中拥有同一原型。如果基类成员与派生类成员接受的实参不同,就没有办法通过基类类型的引用或指针调用派生类函数。[Code3]
                      • 通过基类调用被屏蔽的虚函数。[Code4]
                      • 名字查找与继承
                        • 首先确定进行函数调用的对象、引用或指针的静态类型。
                        • 在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。 如果不能在类或其相关基类中找到该名字,则调用是错误的。
                        • 一旦找到了该名字,就进行常规类型检查,查看如果给定找到的定义,该函数调用是否合法。
                        • 假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。


                          纯虚函数

                          • 含有(或继承)一个或多个纯虚函数的类是抽象基类。除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象
                          • 纯虚函数为后代类型提供了可以覆盖的接口,但是这个类中的版本决不会调用。重要的是,用户将不能创建对象象。
                          • 纯虚函数的定义:在函数形参表后面写上 = 0,double net_price(std::s